From 1142934237d5e84c5b4c08e15d8ce5843c6293df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Wed, 31 Aug 2022 15:55:50 -0300 Subject: [PATCH 01/15] feat: linear indexer rewards --- config/graph.mainnet.yml | 4 +- contracts/rewards/IRewardsManager.sol | 2 +- contracts/rewards/RewardsManager.sol | 71 ++++++---------- contracts/rewards/RewardsManagerStorage.sol | 9 +- test/rewards/rewards.test.ts | 91 +++++++++------------ 5 files changed, 72 insertions(+), 105 deletions(-) diff --git a/config/graph.mainnet.yml b/config/graph.mainnet.yml index 6eb08b233..0a8474cda 100644 --- a/config/graph.mainnet.yml +++ b/config/graph.mainnet.yml @@ -131,8 +131,8 @@ contracts: init: controller: "${{Controller.address}}" calls: - - fn: "setIssuanceRate" - issuanceRate: "1000000012184945188" # per block increase of total supply, blocks in a year = 365*60*60*24/13 + - fn: "setIssuancePerBlock" + issuancePerBlock: "114155251141552511415" # per block increase of total supply, blocks in a year = 365*60*60*24/12 - fn: "setSubgraphAvailabilityOracle" subgraphAvailabilityOracle: *availabilityOracle - fn: "syncAllContracts" diff --git a/contracts/rewards/IRewardsManager.sol b/contracts/rewards/IRewardsManager.sol index dc17c8ba8..87d3c2455 100644 --- a/contracts/rewards/IRewardsManager.sol +++ b/contracts/rewards/IRewardsManager.sol @@ -15,7 +15,7 @@ interface IRewardsManager { // -- Config -- - function setIssuanceRate(uint256 _issuanceRate) external; + function setIssuancePerBlock(uint256 _issuancePerBlock) external; function setMinimumSubgraphSignal(uint256 _minimumSubgraphSignal) external; diff --git a/contracts/rewards/RewardsManager.sol b/contracts/rewards/RewardsManager.sol index 6d2e78965..42c52cc77 100644 --- a/contracts/rewards/RewardsManager.sol +++ b/contracts/rewards/RewardsManager.sol @@ -28,11 +28,10 @@ import "./IRewardsManager.sol"; * These functions may overestimate the actual rewards due to changes in the total supply * until the actual takeRewards function is called. */ -contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsManager { +contract RewardsManager is RewardsManagerV4Storage, GraphUpgradeable, IRewardsManager { using SafeMath for uint256; - uint256 private constant TOKEN_DECIMALS = 1e18; - uint256 private constant MIN_ISSUANCE_RATE = 1e18; + uint256 private constant FIXED_POINT_SCALING_FACTOR = 1e18; // -- Events -- @@ -76,29 +75,25 @@ contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsMa // -- Config -- /** - * @dev Sets the issuance rate. - * The issuance rate is defined as a percentage increase of the total supply per block. - * This means that it needs to be greater than 1.0, any number under 1.0 is not - * allowed and an issuance rate of 1.0 means no issuance. - * To accommodate a high precision the issuance rate is expressed in wei. - * @param _issuanceRate Issuance rate expressed in wei + * @dev Sets the GRT issuance per block. + * The issuance is defined as a fixed amount of rewards per block in GRT. + * @param _issuancePerBlock Issuance expressed in GRT per block (scaled by 1e18) */ - function setIssuanceRate(uint256 _issuanceRate) external override onlyGovernor { - _setIssuanceRate(_issuanceRate); + function setIssuancePerBlock(uint256 _issuancePerBlock) external override onlyGovernor { + _setIssuancePerBlock(_issuancePerBlock); } /** - * @dev Sets the issuance rate. - * @param _issuanceRate Issuance rate + * @dev Sets the GRT issuance per block. + * The issuance is defined as a fixed amount of rewards per block in GRT. + * @param _issuancePerBlock Issuance expressed in GRT per block (scaled by 1e18) */ - function _setIssuanceRate(uint256 _issuanceRate) private { - require(_issuanceRate >= MIN_ISSUANCE_RATE, "Issuance rate under minimum allowed"); - - // Called since `issuance rate` will change + function _setIssuancePerBlock(uint256 _issuancePerBlock) private { + // Called since `issuance per block` will change updateAccRewardsPerSignal(); - issuanceRate = _issuanceRate; - emit ParameterUpdated("issuanceRate"); + issuancePerBlock = _issuancePerBlock; + emit ParameterUpdated("issuancePerBlock"); } /** @@ -188,18 +183,13 @@ contract RewardsManager is RewardsManagerV3Storage, 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` + * Linear formula: `x = 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` + * x: newly accrued rewards tokens for the period `t` * - * @return newly accrued rewards per signal since last update + * @return newly accrued rewards per signal since last update, scaled by FIXED_POINT_SCALING_FACTOR */ function getNewRewardsPerSignal() public view override returns (uint256) { // Calculate time steps @@ -209,11 +199,6 @@ contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsMa return 0; } - // Zero issuance under a rate of 1.0 - if (issuanceRate <= MIN_ISSUANCE_RATE) { - return 0; - } - // Zero issuance if no signalled tokens IGraphToken graphToken = graphToken(); uint256 signalledTokens = graphToken.balanceOf(address(curation())); @@ -221,16 +206,11 @@ contract RewardsManager is RewardsManagerV3Storage, 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 x = issuancePerBlock.mul(t); // 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 x.mul(FIXED_POINT_SCALING_FACTOR).div(signalledTokens); } /** @@ -262,7 +242,7 @@ contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsMa ? getAccRewardsPerSignal() .sub(subgraph.accRewardsPerSignalSnapshot) .mul(subgraphSignalledTokens) - .div(TOKEN_DECIMALS) + .div(FIXED_POINT_SCALING_FACTOR) : 0; return subgraph.accRewardsForSubgraph.add(newRewards); } @@ -294,9 +274,9 @@ contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsMa return (0, accRewardsForSubgraph); } - uint256 newRewardsPerAllocatedToken = newRewardsForSubgraph.mul(TOKEN_DECIMALS).div( - subgraphAllocatedTokens - ); + uint256 newRewardsPerAllocatedToken = newRewardsForSubgraph + .mul(FIXED_POINT_SCALING_FACTOR) + .div(subgraphAllocatedTokens); return ( subgraph.accRewardsPerAllocatedToken.add(newRewardsPerAllocatedToken), accRewardsForSubgraph @@ -307,14 +287,13 @@ contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsMa /** * @dev Updates the accumulated rewards per signal and save checkpoint block number. - * Must be called before `issuanceRate` or `total signalled GRT` changes + * Must be called before `issuancePerBlock` or `total signalled GRT` changes * Called from the Curation contract on mint() and burn() * @return Accumulated rewards per signal */ function updateAccRewardsPerSignal() public override returns (uint256) { accRewardsPerSignal = getAccRewardsPerSignal(); accRewardsPerSignalLastBlockUpdated = block.number; - tokenSupplySnapshot = graphToken().totalSupply(); return accRewardsPerSignal; } @@ -395,7 +374,7 @@ contract RewardsManager is RewardsManagerV3Storage, GraphUpgradeable, IRewardsMa uint256 _endAccRewardsPerAllocatedToken ) private pure returns (uint256) { uint256 newAccrued = _endAccRewardsPerAllocatedToken.sub(_startAccRewardsPerAllocatedToken); - return newAccrued.mul(_tokens).div(TOKEN_DECIMALS); + return newAccrued.mul(_tokens).div(FIXED_POINT_SCALING_FACTOR); } /** diff --git a/contracts/rewards/RewardsManagerStorage.sol b/contracts/rewards/RewardsManagerStorage.sol index 7626992da..4743c9fc9 100644 --- a/contracts/rewards/RewardsManagerStorage.sol +++ b/contracts/rewards/RewardsManagerStorage.sol @@ -8,7 +8,7 @@ import "../governance/Managed.sol"; contract RewardsManagerV1Storage is Managed { // -- State -- - uint256 public issuanceRate; + uint256 public issuanceRateDeprecated; uint256 public accRewardsPerSignal; uint256 public accRewardsPerSignalLastBlockUpdated; @@ -29,5 +29,10 @@ contract RewardsManagerV2Storage is RewardsManagerV1Storage { contract RewardsManagerV3Storage is RewardsManagerV2Storage { // Snapshot of the total supply of GRT when accRewardsPerSignal was last updated - uint256 public tokenSupplySnapshot; + uint256 public tokenSupplySnapshotDeprecated; +} + +contract RewardsManagerV4Storage is RewardsManagerV3Storage { + // GRT issued for indexer rewards per block + uint256 public issuancePerBlock; } diff --git a/test/rewards/rewards.test.ts b/test/rewards/rewards.test.ts index 23aee0dfa..28d93a4a5 100644 --- a/test/rewards/rewards.test.ts +++ b/test/rewards/rewards.test.ts @@ -30,7 +30,7 @@ const MAX_PPM = 1000000 const { HashZero, WeiPerEther } = constants -const toRound = (n: BigNumber) => formatGRT(n).split('.')[0] +const toRound = (n: BigNumber) => formatGRT(n.add(toGRT('0.5'))).split('.')[0] describe('Rewards', () => { let delegator: Account @@ -62,20 +62,19 @@ describe('Rewards', () => { const metadata = HashZero - const ISSUANCE_RATE_PERIODS = 4 // blocks required to issue 5% rewards - const ISSUANCE_RATE_PER_BLOCK = toBN('1012272234429039270') // % increase every block + const ISSUANCE_RATE_PERIODS = 4 // blocks required to issue 800 GRT rewards + const ISSUANCE_PER_BLOCK = toBN('200000000000000000000') // 200 GRT every block // Core formula that gets accumulated rewards per signal for a period of time - const getRewardsPerSignal = (p: BN, r: BN, t: BN, s: BN): string => { + const getRewardsPerSignal = (k: BN, t: BN, s: BN): string => { if (s.eq(0)) { return '0' } - return p.times(r.pow(t)).minus(p).div(s).toPrecision(18).toString() + return k.times(t).div(s).toPrecision(18).toString() } // Tracks the accumulated rewards as totalSignalled or supply changes across snapshots class RewardsTracker { - totalSupply = BigNumber.from(0) totalSignalled = BigNumber.from(0) lastUpdatedBlock = BigNumber.from(0) accumulated = BigNumber.from(0) @@ -88,7 +87,6 @@ describe('Rewards', () => { async snapshot() { this.accumulated = this.accumulated.add(await this.accrued()) - this.totalSupply = await grt.totalSupply() this.totalSignalled = await grt.balanceOf(curation.address) this.lastUpdatedBlock = await latestBlock() return this @@ -106,8 +104,7 @@ describe('Rewards', () => { async accruedByElapsed(nBlocks: BigNumber | number) { const n = getRewardsPerSignal( - new BN(this.totalSupply.toString()), - new BN(ISSUANCE_RATE_PER_BLOCK.toString()).div(1e18), + new BN(ISSUANCE_PER_BLOCK.toString()), new BN(nBlocks.toString()), new BN(this.totalSignalled.toString()), ) @@ -148,8 +145,8 @@ describe('Rewards', () => { governor.signer, )) as unknown as RewardsManagerMock - // 5% minute rate (4 blocks) - await rewardsManager.connect(governor.signer).setIssuanceRate(ISSUANCE_RATE_PER_BLOCK) + // 200 GRT per block + await rewardsManager.connect(governor.signer).setIssuancePerBlock(ISSUANCE_PER_BLOCK) // Distribute test funds for (const wallet of [indexer1, indexer2, curator1, curator2]) { @@ -168,28 +165,22 @@ describe('Rewards', () => { }) describe('configuration', function () { - describe('issuance rate update', function () { - it('reject set issuance rate if unauthorized', async function () { - const tx = rewardsManager.connect(indexer1.signer).setIssuanceRate(toGRT('1.025')) + describe('issuance per block update', function () { + it('reject set issuance per block if unauthorized', async function () { + const tx = rewardsManager.connect(indexer1.signer).setIssuancePerBlock(toGRT('1.025')) await expect(tx).revertedWith('Caller must be Controller governor') }) - it('reject set issuance rate to less than minimum allowed', async function () { - const newIssuanceRate = toGRT('0.1') // this get a bignumber with 1e17 - const tx = rewardsManager.connect(governor.signer).setIssuanceRate(newIssuanceRate) - await expect(tx).revertedWith('Issuance rate under minimum allowed') - }) - - it('should set issuance rate to minimum allowed', async function () { - const newIssuanceRate = toGRT('1') // this get a bignumber with 1e18 - await rewardsManager.connect(governor.signer).setIssuanceRate(newIssuanceRate) - expect(await rewardsManager.issuanceRate()).eq(newIssuanceRate) + it('should set issuance rate to minimum allowed (0)', async function () { + const newIssuancePerBlock = toGRT('0') + await rewardsManager.connect(governor.signer).setIssuancePerBlock(newIssuancePerBlock) + expect(await rewardsManager.issuancePerBlock()).eq(newIssuancePerBlock) }) it('should set issuance rate', async function () { - const newIssuanceRate = toGRT('1.025') - await rewardsManager.connect(governor.signer).setIssuanceRate(newIssuanceRate) - expect(await rewardsManager.issuanceRate()).eq(newIssuanceRate) + const newIssuancePerBlock = toGRT('100.025') + await rewardsManager.connect(governor.signer).setIssuancePerBlock(newIssuancePerBlock) + expect(await rewardsManager.issuancePerBlock()).eq(newIssuancePerBlock) expect(await rewardsManager.accRewardsPerSignalLastBlockUpdated()).eq(await latestBlock()) }) }) @@ -245,7 +236,7 @@ describe('Rewards', () => { context('issuing rewards', async function () { beforeEach(async function () { // 5% minute rate (4 blocks) - await rewardsManager.connect(governor.signer).setIssuanceRate(ISSUANCE_RATE_PER_BLOCK) + await rewardsManager.connect(governor.signer).setIssuancePerBlock(ISSUANCE_PER_BLOCK) }) describe('getNewRewardsPerSignal', function () { @@ -450,10 +441,8 @@ describe('Rewards', () => { await advanceBlocks(ISSUANCE_RATE_PERIODS) // Prepare expected results - // NOTE: calculated the expected result manually as the above code has 1 off block difference - // replace with a RewardsManagerMock - const expectedSubgraphRewards = toGRT('891695470') - const expectedRewardsAT = toGRT('51571') + const expectedSubgraphRewards = toGRT('1400') // 7 blocks since signaling to when we do getAccRewardsForSubgraph + const expectedRewardsAT = toGRT('0.08') // allocated during 5 blocks: 1000 GRT, divided by 12500 allocated tokens // Update await rewardsManager.onSubgraphAllocationUpdate(subgraphDeploymentID1) @@ -466,7 +455,7 @@ describe('Rewards', () => { const contractRewardsAT = subgraph.accRewardsPerAllocatedToken expect(toRound(expectedSubgraphRewards)).eq(toRound(contractSubgraphRewards)) - expect(toRound(expectedRewardsAT)).eq(toRound(contractRewardsAT)) + expect(toRound(expectedRewardsAT.mul(1000))).eq(toRound(contractRewardsAT.mul(1000))) }) }) @@ -619,13 +608,11 @@ describe('Rewards', () => { const beforeStakingBalance = await grt.balanceOf(staking.address) // All the rewards in this subgraph go to this allocation. - // Rewards per token will be (totalSupply * issuanceRate^nBlocks - totalSupply) / allocatedTokens - // The first snapshot is after allocating, that is 2 blocks after the signal is minted: - // startRewardsPerToken = (10004000000 * 1.01227 ^ 2 - 10004000000) / 12500 = 122945.16 - // The final snapshot is when we close the allocation, that happens 9 blocks later: - // endRewardsPerToken = (10004000000 * 1.01227 ^ 9 - 10004000000) / 12500 = 92861.24 - // Then our expected rewards are (endRewardsPerToken - startRewardsPerToken) * 12500. - const expectedIndexingRewards = toGRT('913715958') + // Rewards per token will be (issuancePerBlock * nBlocks) / allocatedTokens + // The first snapshot is after allocating, that is 2 blocks after the signal is minted. + // The final snapshot is when we close the allocation, that happens 9 blocks after signal is minted. + // So the rewards will be ((issuancePerBlock * 7) / allocatedTokens) * allocatedTokens + const expectedIndexingRewards = toGRT('1400') // Close allocation. At this point rewards should be collected for that indexer const tx = await staking @@ -731,13 +718,11 @@ describe('Rewards', () => { const beforeStakingBalance = await grt.balanceOf(staking.address) // All the rewards in this subgraph go to this allocation. - // Rewards per token will be (totalSupply * issuanceRate^nBlocks - totalSupply) / allocatedTokens - // The first snapshot is after allocating, that is 2 blocks after the signal is minted: - // startRewardsPerToken = (10004000000 * 1.01227 ^ 2 - 10004000000) / 12500 = 122945.16 - // The final snapshot is when we close the allocation, that happens 9 blocks later: - // endRewardsPerToken = (10004000000 * 1.01227 ^ 9 - 10004000000) / 12500 = 92861.24 - // Then our expected rewards are (endRewardsPerToken - startRewardsPerToken) * 12500. - const expectedIndexingRewards = toGRT('913715958') + // Rewards per token will be (issuancePerBlock * nBlocks) / allocatedTokens + // The first snapshot is after allocating, that is 2 blocks after the signal is minted. + // The final snapshot is when we close the allocation, that happens 9 blocks after signal is minted. + // So the rewards will be ((issuancePerBlock * 7) / allocatedTokens) * allocatedTokens + const expectedIndexingRewards = toGRT('1400') // Close allocation. At this point rewards should be collected for that indexer const tx = await staking @@ -805,13 +790,11 @@ describe('Rewards', () => { // Check that rewards are put into delegators pool accordingly // All the rewards in this subgraph go to this allocation. - // Rewards per token will be (totalSupply * issuanceRate^nBlocks - totalSupply) / allocatedTokens - // The first snapshot is after allocating, that is 2 blocks after the signal is minted: - // startRewardsPerToken = (10004000000 * 1.01227 ^ 2 - 10004000000) / 14500 = 8466.995 - // The final snapshot is when we close the allocation, that happens 4 blocks later: - // endRewardsPerToken = (10004000000 * 1.01227 ^ 4 - 10004000000) / 14500 = 34496.55 - // Then our expected rewards are (endRewardsPerToken - startRewardsPerToken) * 14500. - const expectedIndexingRewards = toGRT('377428566.77') + // Rewards per token will be (issuancePerBlock * nBlocks) / allocatedTokens + // The first snapshot is after allocating, that is 1 block after the signal is minted. + // The final snapshot is when we close the allocation, that happens 4 blocks after signal is minted. + // So the rewards will be ((issuancePerBlock * 3) / allocatedTokens) * allocatedTokens + const expectedIndexingRewards = toGRT('600') // Calculate delegators cut const indexerRewards = delegationParams.indexingRewardCut .mul(expectedIndexingRewards) From 5d20c86b3c7cdedd30125ffe687345389e8f2651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Thu, 1 Sep 2022 12:58:55 -0300 Subject: [PATCH 02/15] feat: minting in L1 gateway with allowance --- config/graph.mainnet.yml | 2 + contracts/gateway/L1GraphTokenGateway.sol | 110 ++++++++- test/gateway/l1GraphTokenGateway.test.ts | 270 +++++++++++++++++++++- test/lib/fixtures.ts | 1 + 4 files changed, 379 insertions(+), 4 deletions(-) diff --git a/config/graph.mainnet.yml b/config/graph.mainnet.yml index 0a8474cda..03fe08496 100644 --- a/config/graph.mainnet.yml +++ b/config/graph.mainnet.yml @@ -58,6 +58,8 @@ contracts: calls: - fn: "addMinter" minter: "${{RewardsManager.address}}" + - fn: "addMinter" + minter: "${{L1GraphTokenGateway.address}}" - fn: "renounceMinter" - fn: "transferOwnership" owner: *governor diff --git a/contracts/gateway/L1GraphTokenGateway.sol b/contracts/gateway/L1GraphTokenGateway.sol index e1f34efe5..57e033a8c 100644 --- a/contracts/gateway/L1GraphTokenGateway.sol +++ b/contracts/gateway/L1GraphTokenGateway.sol @@ -33,6 +33,14 @@ contract L1GraphTokenGateway is GraphTokenGateway, L1ArbitrumMessenger { address public escrow; // Addresses for which this mapping is true are allowed to send callhooks in outbound transfers mapping(address => bool) public callhookWhitelist; + // Total amount minted from L2 + uint256 public totalMintedFromL2; + // Accumulated allowance for tokens minted from L2 at lastL2MintAllowanceUpdateBlock + uint256 public accumulatedL2MintAllowanceSnapshot; + // Block at which new L2 allowance starts accumulating + uint256 public lastL2MintAllowanceUpdateBlock; + // New L2 mint allowance per block + uint256 public l2MintAllowancePerBlock; // Emitted when an outbound transfer is initiated, i.e. tokens are deposited from L1 to L2 event DepositInitiated( @@ -64,6 +72,14 @@ contract L1GraphTokenGateway is GraphTokenGateway, L1ArbitrumMessenger { event AddedToCallhookWhitelist(address newWhitelisted); // Emitted when an address is removed from the callhook whitelist event RemovedFromCallhookWhitelist(address notWhitelisted); + // Emitted when the L2 mint allowance per block is updated + event L2MintAllowanceUpdated( + uint256 accumulatedL2MintAllowanceSnapshot, + uint256 l2MintAllowancePerBlock, + uint256 lastL2MintAllowanceUpdateBlock + ); + // Emitted when tokens are minted due to an incoming transfer from L2 + event TokensMintedFromL2(uint256 amount); /** * @dev Allows a function to be called only by the gateway's L2 counterpart. @@ -168,6 +184,56 @@ contract L1GraphTokenGateway is GraphTokenGateway, L1ArbitrumMessenger { emit RemovedFromCallhookWhitelist(_notWhitelisted); } + /** + * @dev Updates the L2 mint allowance per block + * It is meant to be called _after_ the issuancePerBlock is updated in L2. + * The caller should provide the new issuance per block and the block at which it was updated, + * the function will automatically compute the values so that the bridge's mint allowance + * correctly tracks the maximum rewards minted in L2. + * @param _l2IssuancePerBlock New issuancePerBlock that has been set in L2 + * @param _updateBlockNum L1 Block number at which issuancePerBlock was updated in L2 + */ + function updateL2MintAllowance(uint256 _l2IssuancePerBlock, uint256 _updateBlockNum) + external + onlyGovernor + { + require(_updateBlockNum <= block.number, "BLOCK_CANT_BE_FUTURE"); + require(_updateBlockNum > lastL2MintAllowanceUpdateBlock, "BLOCK_MUST_BE_INCREMENTING"); + accumulatedL2MintAllowanceSnapshot = accumulatedL2MintAllowanceAtBlock(_updateBlockNum); + lastL2MintAllowanceUpdateBlock = _updateBlockNum; + l2MintAllowancePerBlock = _l2IssuancePerBlock; + emit L2MintAllowanceUpdated( + accumulatedL2MintAllowanceSnapshot, + l2MintAllowancePerBlock, + lastL2MintAllowanceUpdateBlock + ); + } + + /** + * @dev Manually sets the parameters used to compute the L2 mint allowance + * The use of this function is not recommended, use updateL2MintAllowance instead; + * this one is only meant to be used as a backup recovery if a previous call to + * updateL2MintAllowance was done with incorrect values. + * @param _accumulatedL2MintAllowanceSnapshot Accumulated L2 mint allowance at L1 block _lastL2MintAllowanceUpdateBlock + * @param _l2MintAllowancePerBlock L2 issuance per block since block number _lastL2MintAllowanceUpdateBlock + * @param _lastL2MintAllowanceUpdateBlock L1 Block number at which issuancePerBlock was last updated in L2 + */ + function setL2MintAllowanceParametersManual( + uint256 _accumulatedL2MintAllowanceSnapshot, + uint256 _l2MintAllowancePerBlock, + uint256 _lastL2MintAllowanceUpdateBlock + ) external onlyGovernor { + require(_lastL2MintAllowanceUpdateBlock <= block.number, "BLOCK_CANT_BE_FUTURE"); + accumulatedL2MintAllowanceSnapshot = _accumulatedL2MintAllowanceSnapshot; + l2MintAllowancePerBlock = _l2MintAllowancePerBlock; + lastL2MintAllowanceUpdateBlock = _lastL2MintAllowanceUpdateBlock; + emit L2MintAllowanceUpdated( + accumulatedL2MintAllowanceSnapshot, + l2MintAllowancePerBlock, + lastL2MintAllowanceUpdateBlock + ); + } + /** * @notice Creates and sends a retryable ticket to transfer GRT to L2 using the Arbitrum Inbox. * The tokens are escrowed by the gateway until they are withdrawn back to L1. @@ -271,8 +337,10 @@ contract L1GraphTokenGateway is GraphTokenGateway, L1ArbitrumMessenger { require(_l1Token == address(token), "TOKEN_NOT_GRT"); uint256 escrowBalance = token.balanceOf(escrow); - // If the bridge doesn't have enough tokens, something's very wrong! - require(_amount <= escrowBalance, "BRIDGE_OUT_OF_FUNDS"); + if (_amount > escrowBalance) { + // This will revert if trying to mint more than allowed + _mintFromL2(_amount.sub(escrowBalance)); + } token.transferFrom(escrow, _to, _amount); emit WithdrawalFinalized(_l1Token, _from, _to, 0, _amount); @@ -356,4 +424,42 @@ contract L1GraphTokenGateway is GraphTokenGateway, L1ArbitrumMessenger { } return l2GRT; } + + /** + * @dev Get the accumulated L2 mint allowance at a particular block number + * @param _block Block at which allowance will be computed + * @return The accumulated GRT amount that can be minted from L2 at the specified block + */ + function accumulatedL2MintAllowanceAtBlock(uint256 _block) public view returns (uint256) { + require(_block >= lastL2MintAllowanceUpdateBlock, "INVALID_BLOCK_FOR_MINT_ALLOWANCE"); + return + accumulatedL2MintAllowanceSnapshot.add( + l2MintAllowancePerBlock.mul(_block.sub(lastL2MintAllowanceUpdateBlock)) + ); + } + + /** + * @dev Mint new L1 tokens coming from L2 + * This will check if the amount to mint is within the L2's mint allowance, and revert otherwise. + * The tokens will be sent to the bridge escrow (from where they will then be sent to the destinatary + * of the current inbound transfer). + * @param _amount Number of tokens to mint + */ + function _mintFromL2(uint256 _amount) internal { + // If we're trying to mint more than allowed, something's gone terribly wrong + // (either the L2 issuance is wrong, or the Arbitrum bridge has been compromised) + require(_l2MintAmountAllowed(_amount), "INVALID_L2_MINT_AMOUNT"); + totalMintedFromL2 = totalMintedFromL2.add(_amount); + graphToken().mint(escrow, _amount); + emit TokensMintedFromL2(_amount); + } + + /** + * @dev Check if minting a certain amount of tokens from L2 is within allowance + * @param _amount Number of tokens that would be minted + * @return true if minting those tokens is allowed, or false if it would be over allowance + */ + function _l2MintAmountAllowed(uint256 _amount) internal view returns (bool) { + return (totalMintedFromL2.add(_amount) <= accumulatedL2MintAllowanceAtBlock(block.number)); + } } diff --git a/test/gateway/l1GraphTokenGateway.test.ts b/test/gateway/l1GraphTokenGateway.test.ts index a5a473d86..0ba00745a 100644 --- a/test/gateway/l1GraphTokenGateway.test.ts +++ b/test/gateway/l1GraphTokenGateway.test.ts @@ -16,6 +16,7 @@ import { toGRT, Account, applyL1ToL2Alias, + advanceBlocks, } from '../lib/testHelpers' import { BridgeEscrow } from '../../build/types/BridgeEscrow' @@ -374,6 +375,175 @@ describe('L1GraphTokenGateway', () => { ) }) + describe('updateL2MintAllowance', function () { + it('rejects calls that are not from the governor', async function () { + const tx = l1GraphTokenGateway + .connect(pauseGuardian.address) + .updateL2MintAllowance(toGRT('1'), await latestBlock()) + await expect(tx).revertedWith('Caller must be Controller governor') + }) + it('does not allow using a future block number', async function () { + const issuancePerBlock = toGRT('120') + let issuanceUpdatedAtBlock = (await latestBlock()).add(2) + const tx1 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx1).revertedWith('BLOCK_CANT_BE_FUTURE') + issuanceUpdatedAtBlock = (await latestBlock()).add(1) // This will be block.number in our next tx + const tx2 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx2) + .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') + .withArgs(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) + }) + it('does not allow using a block number lower than or equal to the previous one', async function () { + const issuancePerBlock = toGRT('120') + const issuanceUpdatedAtBlock = await latestBlock() + const tx1 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx1) + .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') + .withArgs(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) + const tx2 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx2).revertedWith('BLOCK_MUST_BE_INCREMENTING') + const tx3 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock.sub(1)) + await expect(tx3).revertedWith('BLOCK_MUST_BE_INCREMENTING') + const tx4 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock.add(1)) + await expect(tx4) + .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') + .withArgs(issuancePerBlock, issuancePerBlock, issuanceUpdatedAtBlock.add(1)) + }) + it('updates the snapshot and issuance to follow a new linear function, accumulating up to the specified block', async function () { + const issuancePerBlock = toGRT('120') + const issuanceUpdatedAtBlock = (await latestBlock()).sub(2) + const tx1 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx1) + .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') + .withArgs(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) + // Now the mint allowance should be issuancePerBlock * 3 + await expect( + await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), + ).to.eq(issuancePerBlock.mul(3)) + await expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq(0) + await expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(issuancePerBlock) + await expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( + issuanceUpdatedAtBlock, + ) + + await advanceBlocks(10) + + const newIssuancePerBlock = toGRT('200') + const newIssuanceUpdatedAtBlock = (await latestBlock()).sub(1) + + const expectedAccumulatedSnapshot = issuancePerBlock.mul( + newIssuanceUpdatedAtBlock.sub(issuanceUpdatedAtBlock), + ) + const tx2 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(newIssuancePerBlock, newIssuanceUpdatedAtBlock) + await expect(tx2) + .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') + .withArgs(expectedAccumulatedSnapshot, newIssuancePerBlock, newIssuanceUpdatedAtBlock) + + await expect( + await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), + ).to.eq(expectedAccumulatedSnapshot.add(newIssuancePerBlock.mul(2))) + await expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq( + expectedAccumulatedSnapshot, + ) + await expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(newIssuancePerBlock) + await expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( + newIssuanceUpdatedAtBlock, + ) + }) + }) + describe('setL2MintAllowanceParametersManual', function () { + it('rejects calls that are not from the governor', async function () { + const tx = l1GraphTokenGateway + .connect(pauseGuardian.address) + .setL2MintAllowanceParametersManual(toGRT('0'), toGRT('1'), await latestBlock()) + await expect(tx).revertedWith('Caller must be Controller governor') + }) + it('does not allow using a future block number', async function () { + const issuancePerBlock = toGRT('120') + let issuanceUpdatedAtBlock = (await latestBlock()).add(2) + const tx1 = l1GraphTokenGateway + .connect(governor.signer) + .setL2MintAllowanceParametersManual(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx1).revertedWith('BLOCK_CANT_BE_FUTURE') + issuanceUpdatedAtBlock = (await latestBlock()).add(1) // This will be block.number in our next tx + const tx2 = l1GraphTokenGateway + .connect(governor.signer) + .setL2MintAllowanceParametersManual(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx2) + .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') + .withArgs(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) + }) + it('updates the snapshot and issuance to follow a new linear function, manually setting the snapshot value', async function () { + const issuancePerBlock = toGRT('120') + const issuanceUpdatedAtBlock = (await latestBlock()).sub(2) + const snapshotValue = toGRT('10') + const tx1 = l1GraphTokenGateway + .connect(governor.signer) + .setL2MintAllowanceParametersManual( + snapshotValue, + issuancePerBlock, + issuanceUpdatedAtBlock, + ) + await expect(tx1) + .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') + .withArgs(snapshotValue, issuancePerBlock, issuanceUpdatedAtBlock) + // Now the mint allowance should be 10 + issuancePerBlock * 3 + await expect( + await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), + ).to.eq(snapshotValue.add(issuancePerBlock.mul(3))) + await expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq( + snapshotValue, + ) + await expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(issuancePerBlock) + await expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( + issuanceUpdatedAtBlock, + ) + + await advanceBlocks(10) + + const newIssuancePerBlock = toGRT('200') + const newIssuanceUpdatedAtBlock = (await latestBlock()).sub(1) + const newSnapshotValue = toGRT('10') + + const tx2 = l1GraphTokenGateway + .connect(governor.signer) + .setL2MintAllowanceParametersManual( + newSnapshotValue, + newIssuancePerBlock, + newIssuanceUpdatedAtBlock, + ) + await expect(tx2) + .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') + .withArgs(newSnapshotValue, newIssuancePerBlock, newIssuanceUpdatedAtBlock) + + await expect( + await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), + ).to.eq(newSnapshotValue.add(newIssuancePerBlock.mul(2))) + await expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq( + newSnapshotValue, + ) + await expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(newIssuancePerBlock) + await expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( + newIssuanceUpdatedAtBlock, + ) + }) + }) describe('calculateL2TokenAddress', function () { it('returns the L2 token address', async function () { expect(await l1GraphTokenGateway.calculateL2TokenAddress(grt.address)).eq(mockL2GRT.address) @@ -519,7 +689,7 @@ describe('L1GraphTokenGateway', () => { ) await expect(tx).revertedWith('ONLY_COUNTERPART_GATEWAY') }) - it('reverts if the gateway does not have tokens', async function () { + it('reverts if the gateway does not have tokens or allowance', async function () { // This scenario should never really happen, but we still // test that the gateway reverts in this case const encodedCalldata = l1GraphTokenGateway.interface.encodeFunctionData( @@ -549,7 +719,7 @@ describe('L1GraphTokenGateway', () => { toBN('0'), encodedCalldata, ) - await expect(tx).revertedWith('BRIDGE_OUT_OF_FUNDS') + await expect(tx).revertedWith('INVALID_L2_MINT_AMOUNT') }) it('reverts if the gateway is revoked from escrow', async function () { await grt.connect(tokenSender.signer).approve(l1GraphTokenGateway.address, toGRT('10')) @@ -625,6 +795,102 @@ describe('L1GraphTokenGateway', () => { await expect(escrowBalance).eq(toGRT('2')) await expect(senderBalance).eq(toGRT('998')) }) + it('mints tokens up to the L2 mint allowance', async function () { + await grt.connect(tokenSender.signer).approve(l1GraphTokenGateway.address, toGRT('10')) + await testValidOutboundTransfer(tokenSender.signer, defaultData, emptyCallHookData) + + // Start accruing L2 mint allowance at 2 GRT per block + await l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(toGRT('2'), await latestBlock()) + await advanceBlocks(2) + // Now it's been three blocks since the lastL2MintAllowanceUpdateBlock, so + // there should be 8 GRT allowed to be minted from L2 in the next block. + + // At this point, the gateway holds 10 GRT in escrow + const encodedCalldata = l1GraphTokenGateway.interface.encodeFunctionData( + 'finalizeInboundTransfer', + [ + grt.address, + l2Receiver.address, + tokenSender.address, + toGRT('18'), + utils.defaultAbiCoder.encode(['uint256', 'bytes'], [0, []]), + ], + ) + // The real outbox would require a proof, which would + // validate that the tx was initiated by the L2 gateway but our mock + // just executes unconditionally + const tx = outboxMock + .connect(tokenSender.signer) + .executeTransaction( + toBN('0'), + [], + toBN('0'), + mockL2Gateway.address, + l1GraphTokenGateway.address, + toBN('1337'), + await latestBlock(), + toBN('133701337'), + toBN('0'), + encodedCalldata, + ) + await expect(tx) + .emit(l1GraphTokenGateway, 'WithdrawalFinalized') + .withArgs(grt.address, l2Receiver.address, tokenSender.address, toBN('0'), toGRT('18')) + await expect(tx).emit(l1GraphTokenGateway, 'TokensMintedFromL2').withArgs(toGRT('8')) + await expect(await l1GraphTokenGateway.totalMintedFromL2()).to.eq(toGRT('8')) + await expect( + await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), + ).to.eq(toGRT('8')) + + const escrowBalance = await grt.balanceOf(bridgeEscrow.address) + const senderBalance = await grt.balanceOf(tokenSender.address) + await expect(escrowBalance).eq(toGRT('0')) + await expect(senderBalance).eq(toGRT('1008')) + }) + it('reverts if the amount to mint is over the allowance', async function () { + await grt.connect(tokenSender.signer).approve(l1GraphTokenGateway.address, toGRT('10')) + await testValidOutboundTransfer(tokenSender.signer, defaultData, emptyCallHookData) + + // Start accruing L2 mint allowance at 2 GRT per block + await l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(toGRT('2'), await latestBlock()) + await advanceBlocks(2) + // Now it's been three blocks since the lastL2MintAllowanceUpdateBlock, so + // there should be 8 GRT allowed to be minted from L2 in the next block. + + // At this point, the gateway holds 10 GRT in escrow + const encodedCalldata = l1GraphTokenGateway.interface.encodeFunctionData( + 'finalizeInboundTransfer', + [ + grt.address, + l2Receiver.address, + tokenSender.address, + toGRT('18.001'), + utils.defaultAbiCoder.encode(['uint256', 'bytes'], [0, []]), + ], + ) + // The real outbox would require a proof, which would + // validate that the tx was initiated by the L2 gateway but our mock + // just executes unconditionally + const tx = outboxMock + .connect(tokenSender.signer) + .executeTransaction( + toBN('0'), + [], + toBN('0'), + mockL2Gateway.address, + l1GraphTokenGateway.address, + toBN('1337'), + await latestBlock(), + toBN('133701337'), + toBN('0'), + encodedCalldata, + ) + await expect(tx).revertedWith('INVALID_L2_MINT_AMOUNT') + }) }) }) }) diff --git a/test/lib/fixtures.ts b/test/lib/fixtures.ts index 8375a86a4..ed6002a2d 100644 --- a/test/lib/fixtures.ts +++ b/test/lib/fixtures.ts @@ -155,6 +155,7 @@ export class NetworkFixture { } else { await l1GraphTokenGateway.connect(deployer).syncAllContracts() await bridgeEscrow.connect(deployer).syncAllContracts() + await grt.connect(deployer).addMinter(l1GraphTokenGateway.address) } await staking.connect(deployer).setSlasher(slasherAddress, true) From cc4a2dde188fe3256c6128163cc9f6fe031d3606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Fri, 2 Sep 2022 16:38:41 -0300 Subject: [PATCH 03/15] test: avoid issues with epoch boundary in allocation tests --- test/staking/allocation.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/staking/allocation.test.ts b/test/staking/allocation.test.ts index 4d6a9150c..473ecb451 100644 --- a/test/staking/allocation.test.ts +++ b/test/staking/allocation.test.ts @@ -582,6 +582,9 @@ describe('Staking:Allocation', () => { for (const tokensToAllocate of [toBN(100), toBN(0)]) { context(`> with ${tokensToAllocate} allocated tokens`, async function () { beforeEach(async function () { + // Advance to next epoch to avoid creating the allocation + // right at the epoch boundary, which would mess up the tests. + await advanceToNextEpoch(epochManager) await allocate(tokensToAllocate) }) From 51cc8cdc9009bbfbe82c0f99affd334a62895859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Mon, 5 Sep 2022 13:52:12 -0300 Subject: [PATCH 04/15] chore: add comment on setIssuancePerBlock to remind of L1 allowance change --- contracts/rewards/RewardsManager.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/rewards/RewardsManager.sol b/contracts/rewards/RewardsManager.sol index 42c52cc77..7cb652cd5 100644 --- a/contracts/rewards/RewardsManager.sol +++ b/contracts/rewards/RewardsManager.sol @@ -77,6 +77,9 @@ contract RewardsManager is RewardsManagerV4Storage, GraphUpgradeable, IRewardsMa /** * @dev Sets the GRT issuance per block. * The issuance is defined as a fixed amount of rewards per block in GRT. + * Whenever this function is called in layer 2, the updateL2MintAllowance function + * _must_ be called on the L1GraphTokenGateway in L1, to ensure the bridge can mint the + * right amount of tokens. * @param _issuancePerBlock Issuance expressed in GRT per block (scaled by 1e18) */ function setIssuancePerBlock(uint256 _issuancePerBlock) external override onlyGovernor { From 06b9774f11ba22b3838bb8a86d037850894a01a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Wed, 7 Sep 2022 14:28:56 -0300 Subject: [PATCH 05/15] fix(RewardsManager): skip calculations if issuance is zero --- contracts/rewards/RewardsManager.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/rewards/RewardsManager.sol b/contracts/rewards/RewardsManager.sol index 7cb652cd5..35ff6f571 100644 --- a/contracts/rewards/RewardsManager.sol +++ b/contracts/rewards/RewardsManager.sol @@ -201,6 +201,10 @@ contract RewardsManager is RewardsManagerV4Storage, GraphUpgradeable, IRewardsMa if (t == 0) { return 0; } + // ...or if issuance is zero + if (issuancePerBlock == 0) { + return 0; + } // Zero issuance if no signalled tokens IGraphToken graphToken = graphToken(); From bbe2cffe4016515e807d756ef561255bc0b10003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Wed, 7 Sep 2022 14:32:29 -0300 Subject: [PATCH 06/15] fix(RewardsManagerStorage): make deprecated variables private --- contracts/rewards/RewardsManagerStorage.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/rewards/RewardsManagerStorage.sol b/contracts/rewards/RewardsManagerStorage.sol index 4743c9fc9..821d93edb 100644 --- a/contracts/rewards/RewardsManagerStorage.sol +++ b/contracts/rewards/RewardsManagerStorage.sol @@ -8,7 +8,7 @@ import "../governance/Managed.sol"; contract RewardsManagerV1Storage is Managed { // -- State -- - uint256 public issuanceRateDeprecated; + uint256 private issuanceRateDeprecated; uint256 public accRewardsPerSignal; uint256 public accRewardsPerSignalLastBlockUpdated; @@ -29,7 +29,7 @@ contract RewardsManagerV2Storage is RewardsManagerV1Storage { contract RewardsManagerV3Storage is RewardsManagerV2Storage { // Snapshot of the total supply of GRT when accRewardsPerSignal was last updated - uint256 public tokenSupplySnapshotDeprecated; + uint256 private tokenSupplySnapshotDeprecated; } contract RewardsManagerV4Storage is RewardsManagerV3Storage { From b77f50196586b7240bcb96b4cad18fd44bb7c089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Wed, 7 Sep 2022 14:35:27 -0300 Subject: [PATCH 07/15] fix(cli): replace issuanceRate getter/setter with one for issuancePerBlock --- cli/commands/protocol/get.ts | 2 +- cli/commands/protocol/set.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/commands/protocol/get.ts b/cli/commands/protocol/get.ts index 439b539b4..801e468a7 100644 --- a/cli/commands/protocol/get.ts +++ b/cli/commands/protocol/get.ts @@ -34,7 +34,7 @@ export const gettersList = { 'epochs-length': { contract: 'EpochManager', name: 'epochLength' }, 'epochs-current': { contract: 'EpochManager', name: 'currentEpoch' }, // Rewards - 'rewards-issuance-rate': { contract: 'RewardsManager', name: 'issuanceRate' }, + 'rewards-issuance-per-block': { contract: 'RewardsManager', name: 'issuancePerBlock' }, 'subgraph-availability-oracle': { contract: 'RewardsManager', name: 'subgraphAvailabilityOracle', diff --git a/cli/commands/protocol/set.ts b/cli/commands/protocol/set.ts index 966d07897..a7850601a 100644 --- a/cli/commands/protocol/set.ts +++ b/cli/commands/protocol/set.ts @@ -39,7 +39,7 @@ export const settersList = { // Epochs 'epochs-length': { contract: 'EpochManager', name: 'setEpochLength' }, // Rewards - 'rewards-issuance-rate': { contract: 'RewardsManager', name: 'setIssuanceRate' }, + 'rewards-issuance-per-block': { contract: 'RewardsManager', name: 'setIssuancePerBlock' }, 'subgraph-availability-oracle': { contract: 'RewardsManager', name: 'setSubgraphAvailabilityOracle', From 51243715b7d87d544836aeab5a0808e89b97f3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Wed, 7 Sep 2022 14:48:58 -0300 Subject: [PATCH 08/15] fix(e2e): issuanceRate replaced by issuancePerBlock --- config/graph.localhost.yml | 4 ++-- e2e/deployment/config/l1/rewardsManager.test.ts | 6 +++--- e2e/deployment/config/l2/rewardsManager.test.ts | 6 +++--- tasks/deployment/config.ts | 2 +- test/lib/deployment.ts | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config/graph.localhost.yml b/config/graph.localhost.yml index 4a90dfd20..9f5b25590 100644 --- a/config/graph.localhost.yml +++ b/config/graph.localhost.yml @@ -131,8 +131,8 @@ contracts: init: controller: "${{Controller.address}}" calls: - - fn: "setIssuanceRate" - issuanceRate: "1000000012184945188" # per block increase of total supply, blocks in a year = 365*60*60*24/13 + - fn: "setIssuancePerBlock" + issuancePerBlock: "114155251141552511415" # per block increase of total supply, blocks in a year = 365*60*60*24/12 - fn: "setSubgraphAvailabilityOracle" subgraphAvailabilityOracle: *availabilityOracle - fn: "syncAllContracts" diff --git a/e2e/deployment/config/l1/rewardsManager.test.ts b/e2e/deployment/config/l1/rewardsManager.test.ts index 4cc990161..8f1d3a48c 100644 --- a/e2e/deployment/config/l1/rewardsManager.test.ts +++ b/e2e/deployment/config/l1/rewardsManager.test.ts @@ -10,8 +10,8 @@ describe('[L1] RewardsManager configuration', () => { if (GraphChain.isL2(graph.chainId)) this.skip() }) - it('issuanceRate should match "issuanceRate" in the config file', async function () { - const value = await RewardsManager.issuanceRate() - expect(value).eq('1000000012184945188') // hardcoded as it's set with a function call rather than init parameter + it('issuancePerBlock should match "issuancePerBlock" in the config file', async function () { + const value = await RewardsManager.issuancePerBlock() + expect(value).eq('114155251141552511415') // hardcoded as it's set with a function call rather than init parameter }) }) diff --git a/e2e/deployment/config/l2/rewardsManager.test.ts b/e2e/deployment/config/l2/rewardsManager.test.ts index 29ad83c5f..37b34da5d 100644 --- a/e2e/deployment/config/l2/rewardsManager.test.ts +++ b/e2e/deployment/config/l2/rewardsManager.test.ts @@ -10,8 +10,8 @@ describe('[L2] RewardsManager configuration', () => { if (GraphChain.isL1(graph.chainId)) this.skip() }) - it('issuanceRate should be zero', async function () { - const value = await RewardsManager.issuanceRate() - expect(value).eq('0') + it('issuancePerBlock should be zero', async function () { + const value = await RewardsManager.issuancePerBlock() + expect(value).eq('0') // hardcoded as it's set with a function call rather than init parameter }) }) diff --git a/tasks/deployment/config.ts b/tasks/deployment/config.ts index 123afed53..f3992e652 100644 --- a/tasks/deployment/config.ts +++ b/tasks/deployment/config.ts @@ -67,7 +67,7 @@ const staking: Contract = { const rewardsManager: Contract = { name: 'RewardsManager', - initParams: [{ name: 'issuanceRate', type: 'BigNumber' }], + initParams: [{ name: 'issuancePerBlock', type: 'BigNumber' }], } const contractList: Contract[] = [epochManager, curation, disputeManager, staking, rewardsManager] diff --git a/test/lib/deployment.ts b/test/lib/deployment.ts index a6afe9dbb..e07b1e490 100644 --- a/test/lib/deployment.ts +++ b/test/lib/deployment.ts @@ -56,7 +56,7 @@ export const defaults = { initialSupply: toGRT('10000000000'), // 10 billion }, rewards: { - issuanceRate: toGRT('1.000000023206889619'), // 5% annual rate + issuancePerBlock: toGRT('114.155251141552511415'), // 300M GRT/year dripInterval: toBN('50400'), // 1 week in blocks (post-Merge) }, } From 4f2262defd1d960bb9bf6812b2cfb3b784c9454d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Wed, 7 Sep 2022 14:51:34 -0300 Subject: [PATCH 09/15] chore(L1GraphTokenGateway): rename _block parameter to _blockNum for consistency --- contracts/gateway/L1GraphTokenGateway.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/gateway/L1GraphTokenGateway.sol b/contracts/gateway/L1GraphTokenGateway.sol index 57e033a8c..eb490b35e 100644 --- a/contracts/gateway/L1GraphTokenGateway.sol +++ b/contracts/gateway/L1GraphTokenGateway.sol @@ -427,14 +427,14 @@ contract L1GraphTokenGateway is GraphTokenGateway, L1ArbitrumMessenger { /** * @dev Get the accumulated L2 mint allowance at a particular block number - * @param _block Block at which allowance will be computed + * @param _blockNum Block at which allowance will be computed * @return The accumulated GRT amount that can be minted from L2 at the specified block */ - function accumulatedL2MintAllowanceAtBlock(uint256 _block) public view returns (uint256) { - require(_block >= lastL2MintAllowanceUpdateBlock, "INVALID_BLOCK_FOR_MINT_ALLOWANCE"); + function accumulatedL2MintAllowanceAtBlock(uint256 _blockNum) public view returns (uint256) { + require(_blockNum >= lastL2MintAllowanceUpdateBlock, "INVALID_BLOCK_FOR_MINT_ALLOWANCE"); return accumulatedL2MintAllowanceSnapshot.add( - l2MintAllowancePerBlock.mul(_block.sub(lastL2MintAllowanceUpdateBlock)) + l2MintAllowancePerBlock.mul(_blockNum.sub(lastL2MintAllowanceUpdateBlock)) ); } From 63517863c314fd81853b12973b0afc04a1fa4d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Wed, 7 Sep 2022 18:08:39 -0300 Subject: [PATCH 10/15] test(L1GraphTokenGateway): remove redundant awaits and combine two event assertions --- test/gateway/l1GraphTokenGateway.test.ts | 61 ++++++++++++------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/test/gateway/l1GraphTokenGateway.test.ts b/test/gateway/l1GraphTokenGateway.test.ts index 0ba00745a..f991b7f2a 100644 --- a/test/gateway/l1GraphTokenGateway.test.ts +++ b/test/gateway/l1GraphTokenGateway.test.ts @@ -241,10 +241,10 @@ describe('L1GraphTokenGateway', () => { it('can be paused and unpaused by the governor', async function () { let tx = l1GraphTokenGateway.connect(governor.signer).setPaused(false) await expect(tx).emit(l1GraphTokenGateway, 'PauseChanged').withArgs(false) - await expect(await l1GraphTokenGateway.paused()).eq(false) + expect(await l1GraphTokenGateway.paused()).eq(false) tx = l1GraphTokenGateway.connect(governor.signer).setPaused(true) await expect(tx).emit(l1GraphTokenGateway, 'PauseChanged').withArgs(true) - await expect(await l1GraphTokenGateway.paused()).eq(true) + expect(await l1GraphTokenGateway.paused()).eq(true) }) describe('setPauseGuardian', function () { it('cannot be called by someone other than governor', async function () { @@ -265,10 +265,10 @@ describe('L1GraphTokenGateway', () => { await l1GraphTokenGateway.connect(governor.signer).setPauseGuardian(pauseGuardian.address) let tx = l1GraphTokenGateway.connect(pauseGuardian.signer).setPaused(false) await expect(tx).emit(l1GraphTokenGateway, 'PauseChanged').withArgs(false) - await expect(await l1GraphTokenGateway.paused()).eq(false) + expect(await l1GraphTokenGateway.paused()).eq(false) tx = l1GraphTokenGateway.connect(pauseGuardian.signer).setPaused(true) await expect(tx).emit(l1GraphTokenGateway, 'PauseChanged').withArgs(true) - await expect(await l1GraphTokenGateway.paused()).eq(true) + expect(await l1GraphTokenGateway.paused()).eq(true) }) }) }) @@ -361,8 +361,8 @@ describe('L1GraphTokenGateway', () => { ) const escrowBalance = await grt.balanceOf(bridgeEscrow.address) const senderBalance = await grt.balanceOf(tokenSender.address) - await expect(escrowBalance).eq(toGRT('10')) - await expect(senderBalance).eq(toGRT('990')) + expect(escrowBalance).eq(toGRT('10')) + expect(senderBalance).eq(toGRT('990')) } before(async function () { await fixture.configureL1Bridge( @@ -431,12 +431,12 @@ describe('L1GraphTokenGateway', () => { .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') .withArgs(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) // Now the mint allowance should be issuancePerBlock * 3 - await expect( + expect( await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), ).to.eq(issuancePerBlock.mul(3)) - await expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq(0) - await expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(issuancePerBlock) - await expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( + expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq(0) + expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(issuancePerBlock) + expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( issuanceUpdatedAtBlock, ) @@ -455,14 +455,14 @@ describe('L1GraphTokenGateway', () => { .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') .withArgs(expectedAccumulatedSnapshot, newIssuancePerBlock, newIssuanceUpdatedAtBlock) - await expect( + expect( await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), ).to.eq(expectedAccumulatedSnapshot.add(newIssuancePerBlock.mul(2))) - await expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq( + expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq( expectedAccumulatedSnapshot, ) - await expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(newIssuancePerBlock) - await expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( + expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(newIssuancePerBlock) + expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( newIssuanceUpdatedAtBlock, ) }) @@ -504,14 +504,12 @@ describe('L1GraphTokenGateway', () => { .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') .withArgs(snapshotValue, issuancePerBlock, issuanceUpdatedAtBlock) // Now the mint allowance should be 10 + issuancePerBlock * 3 - await expect( + expect( await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), ).to.eq(snapshotValue.add(issuancePerBlock.mul(3))) - await expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq( - snapshotValue, - ) - await expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(issuancePerBlock) - await expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( + expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq(snapshotValue) + expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(issuancePerBlock) + expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( issuanceUpdatedAtBlock, ) @@ -532,14 +530,14 @@ describe('L1GraphTokenGateway', () => { .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') .withArgs(newSnapshotValue, newIssuancePerBlock, newIssuanceUpdatedAtBlock) - await expect( + expect( await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), ).to.eq(newSnapshotValue.add(newIssuancePerBlock.mul(2))) - await expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq( + expect(await l1GraphTokenGateway.accumulatedL2MintAllowanceSnapshot()).to.eq( newSnapshotValue, ) - await expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(newIssuancePerBlock) - await expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( + expect(await l1GraphTokenGateway.l2MintAllowancePerBlock()).to.eq(newIssuancePerBlock) + expect(await l1GraphTokenGateway.lastL2MintAllowanceUpdateBlock()).to.eq( newIssuanceUpdatedAtBlock, ) }) @@ -792,8 +790,8 @@ describe('L1GraphTokenGateway', () => { .withArgs(grt.address, l2Receiver.address, tokenSender.address, toBN('0'), toGRT('8')) const escrowBalance = await grt.balanceOf(bridgeEscrow.address) const senderBalance = await grt.balanceOf(tokenSender.address) - await expect(escrowBalance).eq(toGRT('2')) - await expect(senderBalance).eq(toGRT('998')) + expect(escrowBalance).eq(toGRT('2')) + expect(senderBalance).eq(toGRT('998')) }) it('mints tokens up to the L2 mint allowance', async function () { await grt.connect(tokenSender.signer).approve(l1GraphTokenGateway.address, toGRT('10')) @@ -838,16 +836,17 @@ describe('L1GraphTokenGateway', () => { await expect(tx) .emit(l1GraphTokenGateway, 'WithdrawalFinalized') .withArgs(grt.address, l2Receiver.address, tokenSender.address, toBN('0'), toGRT('18')) - await expect(tx).emit(l1GraphTokenGateway, 'TokensMintedFromL2').withArgs(toGRT('8')) - await expect(await l1GraphTokenGateway.totalMintedFromL2()).to.eq(toGRT('8')) - await expect( + .emit(l1GraphTokenGateway, 'TokensMintedFromL2') + .withArgs(toGRT('8')) + expect(await l1GraphTokenGateway.totalMintedFromL2()).to.eq(toGRT('8')) + expect( await l1GraphTokenGateway.accumulatedL2MintAllowanceAtBlock(await latestBlock()), ).to.eq(toGRT('8')) const escrowBalance = await grt.balanceOf(bridgeEscrow.address) const senderBalance = await grt.balanceOf(tokenSender.address) - await expect(escrowBalance).eq(toGRT('0')) - await expect(senderBalance).eq(toGRT('1008')) + expect(escrowBalance).eq(toGRT('0')) + expect(senderBalance).eq(toGRT('1008')) }) it('reverts if the amount to mint is over the allowance', async function () { await grt.connect(tokenSender.signer).approve(l1GraphTokenGateway.address, toGRT('10')) From 263355d3b17395bba207a32c2efb53bda5bb3cc9 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Mon, 26 Sep 2022 21:35:48 -0300 Subject: [PATCH 11/15] fix: add RewardsManager as minter in L2 --- config/graph.arbitrum-goerli.yml | 2 ++ config/graph.arbitrum-localhost.yml | 2 ++ config/graph.arbitrum-one.yml | 2 ++ e2e/deployment/config/l2/l2GraphToken.test.ts | 4 ++-- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/config/graph.arbitrum-goerli.yml b/config/graph.arbitrum-goerli.yml index fa1a66ad3..68a656a31 100644 --- a/config/graph.arbitrum-goerli.yml +++ b/config/graph.arbitrum-goerli.yml @@ -57,6 +57,8 @@ contracts: init: owner: "${{Env.deployer}}" calls: + - fn: "addMinter" + minter: "${{RewardsManager.address}}" - fn: "renounceMinter" - fn: "transferOwnership" owner: *governor diff --git a/config/graph.arbitrum-localhost.yml b/config/graph.arbitrum-localhost.yml index 1061044be..c58aabf44 100644 --- a/config/graph.arbitrum-localhost.yml +++ b/config/graph.arbitrum-localhost.yml @@ -57,6 +57,8 @@ contracts: init: owner: "${{Env.deployer}}" calls: + - fn: "addMinter" + minter: "${{RewardsManager.address}}" - fn: "renounceMinter" - fn: "transferOwnership" owner: *governor diff --git a/config/graph.arbitrum-one.yml b/config/graph.arbitrum-one.yml index e68d67521..9588d5f32 100644 --- a/config/graph.arbitrum-one.yml +++ b/config/graph.arbitrum-one.yml @@ -57,6 +57,8 @@ contracts: init: owner: "${{Env.deployer}}" calls: + - fn: "addMinter" + minter: "${{RewardsManager.address}}" - fn: "renounceMinter" - fn: "transferOwnership" owner: *governor diff --git a/e2e/deployment/config/l2/l2GraphToken.test.ts b/e2e/deployment/config/l2/l2GraphToken.test.ts index 7b87253e0..e486405ed 100644 --- a/e2e/deployment/config/l2/l2GraphToken.test.ts +++ b/e2e/deployment/config/l2/l2GraphToken.test.ts @@ -36,9 +36,9 @@ describe('[L2] L2GraphToken', () => { await expect(tx).revertedWith('Only Governor can call') }) - it('RewardsManager should not be minter (for now)', async function () { + it('RewardsManager should be minter', async function () { const rewardsMgrIsMinter = await L2GraphToken.isMinter(RewardsManager.address) - expect(rewardsMgrIsMinter).eq(false) + expect(rewardsMgrIsMinter).eq(true) }) }) }) From dbdda2f563c3cc65491f2f3125e93fdbe2a44384 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Fri, 21 Oct 2022 12:10:33 -0300 Subject: [PATCH 12/15] fix: use strict < for block in L2 mint allowance (OZ N-02) --- contracts/gateway/L1GraphTokenGateway.sol | 4 ++-- test/gateway/l1GraphTokenGateway.test.ts | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/contracts/gateway/L1GraphTokenGateway.sol b/contracts/gateway/L1GraphTokenGateway.sol index eb490b35e..bd2c7036f 100644 --- a/contracts/gateway/L1GraphTokenGateway.sol +++ b/contracts/gateway/L1GraphTokenGateway.sol @@ -197,7 +197,7 @@ contract L1GraphTokenGateway is GraphTokenGateway, L1ArbitrumMessenger { external onlyGovernor { - require(_updateBlockNum <= block.number, "BLOCK_CANT_BE_FUTURE"); + require(_updateBlockNum < block.number, "BLOCK_MUST_BE_PAST"); require(_updateBlockNum > lastL2MintAllowanceUpdateBlock, "BLOCK_MUST_BE_INCREMENTING"); accumulatedL2MintAllowanceSnapshot = accumulatedL2MintAllowanceAtBlock(_updateBlockNum); lastL2MintAllowanceUpdateBlock = _updateBlockNum; @@ -223,7 +223,7 @@ contract L1GraphTokenGateway is GraphTokenGateway, L1ArbitrumMessenger { uint256 _l2MintAllowancePerBlock, uint256 _lastL2MintAllowanceUpdateBlock ) external onlyGovernor { - require(_lastL2MintAllowanceUpdateBlock <= block.number, "BLOCK_CANT_BE_FUTURE"); + require(_lastL2MintAllowanceUpdateBlock < block.number, "BLOCK_MUST_BE_PAST"); accumulatedL2MintAllowanceSnapshot = _accumulatedL2MintAllowanceSnapshot; l2MintAllowancePerBlock = _l2MintAllowancePerBlock; lastL2MintAllowanceUpdateBlock = _lastL2MintAllowanceUpdateBlock; diff --git a/test/gateway/l1GraphTokenGateway.test.ts b/test/gateway/l1GraphTokenGateway.test.ts index f991b7f2a..1578d83b7 100644 --- a/test/gateway/l1GraphTokenGateway.test.ts +++ b/test/gateway/l1GraphTokenGateway.test.ts @@ -382,18 +382,23 @@ describe('L1GraphTokenGateway', () => { .updateL2MintAllowance(toGRT('1'), await latestBlock()) await expect(tx).revertedWith('Caller must be Controller governor') }) - it('does not allow using a future block number', async function () { + it('does not allow using a future or current block number', async function () { const issuancePerBlock = toGRT('120') let issuanceUpdatedAtBlock = (await latestBlock()).add(2) const tx1 = l1GraphTokenGateway .connect(governor.signer) .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock) - await expect(tx1).revertedWith('BLOCK_CANT_BE_FUTURE') + await expect(tx1).revertedWith('BLOCK_MUST_BE_PAST') issuanceUpdatedAtBlock = (await latestBlock()).add(1) // This will be block.number in our next tx const tx2 = l1GraphTokenGateway .connect(governor.signer) .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock) - await expect(tx2) + await expect(tx2).revertedWith('BLOCK_MUST_BE_PAST') + issuanceUpdatedAtBlock = await latestBlock() // This will be block.number-1 in our next tx + const tx3 = l1GraphTokenGateway + .connect(governor.signer) + .updateL2MintAllowance(issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx3) .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') .withArgs(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) }) @@ -474,18 +479,23 @@ describe('L1GraphTokenGateway', () => { .setL2MintAllowanceParametersManual(toGRT('0'), toGRT('1'), await latestBlock()) await expect(tx).revertedWith('Caller must be Controller governor') }) - it('does not allow using a future block number', async function () { + it('does not allow using a future or current block number', async function () { const issuancePerBlock = toGRT('120') let issuanceUpdatedAtBlock = (await latestBlock()).add(2) const tx1 = l1GraphTokenGateway .connect(governor.signer) .setL2MintAllowanceParametersManual(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) - await expect(tx1).revertedWith('BLOCK_CANT_BE_FUTURE') + await expect(tx1).revertedWith('BLOCK_MUST_BE_PAST') issuanceUpdatedAtBlock = (await latestBlock()).add(1) // This will be block.number in our next tx const tx2 = l1GraphTokenGateway .connect(governor.signer) .setL2MintAllowanceParametersManual(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) - await expect(tx2) + await expect(tx2).revertedWith('BLOCK_MUST_BE_PAST') + issuanceUpdatedAtBlock = await latestBlock() // This will be block.number-1 in our next tx + const tx3 = l1GraphTokenGateway + .connect(governor.signer) + .setL2MintAllowanceParametersManual(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) + await expect(tx3) .emit(l1GraphTokenGateway, 'L2MintAllowanceUpdated') .withArgs(toGRT('0'), issuancePerBlock, issuanceUpdatedAtBlock) }) From 18452596702d20883407650f4a42681da7e3f026 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Fri, 21 Oct 2022 12:15:41 -0300 Subject: [PATCH 13/15] fix: remove the deprecated pow from RewardsManager (OZ N-03) --- .solhintignore | 1 - contracts/rewards/RewardsManager.sol | 63 ------------------------ contracts/tests/RewardsManagerMock.sol | 68 -------------------------- test/rewards/rewards.test.ts | 19 ------- 4 files changed, 151 deletions(-) delete mode 100644 contracts/tests/RewardsManagerMock.sol diff --git a/.solhintignore b/.solhintignore index 24a21bb44..b36dffeb7 100644 --- a/.solhintignore +++ b/.solhintignore @@ -4,6 +4,5 @@ node_modules ./contracts/discovery/erc1056 ./contracts/rewards/RewardsManager.sol ./contracts/staking/libs/LibFixedMath.sol -./contracts/tests/RewardsManagerMock.sol ./contracts/tests/ens ./contracts/tests/testnet/GSRManager.sol diff --git a/contracts/rewards/RewardsManager.sol b/contracts/rewards/RewardsManager.sol index 35ff6f571..e259d50b8 100644 --- a/contracts/rewards/RewardsManager.sol +++ b/contracts/rewards/RewardsManager.sol @@ -424,67 +424,4 @@ contract RewardsManager is RewardsManagerV4Storage, GraphUpgradeable, IRewardsMa return rewards; } - - /** - * @dev Raises x to the power of n with scaling factor of base. - * Based on: https://github.com/makerdao/dss/blob/master/src/pot.sol#L81 - * @param x Base of the exponentiation - * @param n Exponent - * @param base Scaling factor - * @return z Exponential of n with base x - */ - function _pow( - uint256 x, - uint256 n, - uint256 base - ) private pure returns (uint256 z) { - 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) - } - } - } - } - } } diff --git a/contracts/tests/RewardsManagerMock.sol b/contracts/tests/RewardsManagerMock.sol deleted file mode 100644 index cbd57b2d3..000000000 --- a/contracts/tests/RewardsManagerMock.sol +++ /dev/null @@ -1,68 +0,0 @@ -pragma solidity ^0.7.6; -pragma abicoder v2; - -// Mock contract used for testing rewards -contract RewardsManagerMock { - /** - * @dev Raises x to the power of n with scaling factor of base. - * Based on: https://github.com/makerdao/dss/blob/master/src/pot.sol#L81 - * @param x Base of the exponentiation - * @param n Exponent - * @param base Scaling factor - * @return z Exponential of n with base x - */ - function pow( - uint256 x, - uint256 n, - uint256 base - ) public pure returns (uint256 z) { - 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) - } - } - } - } - } -} diff --git a/test/rewards/rewards.test.ts b/test/rewards/rewards.test.ts index 28d93a4a5..49e597745 100644 --- a/test/rewards/rewards.test.ts +++ b/test/rewards/rewards.test.ts @@ -9,7 +9,6 @@ import { Curation } from '../../build/types/Curation' import { EpochManager } from '../../build/types/EpochManager' import { GraphToken } from '../../build/types/GraphToken' import { RewardsManager } from '../../build/types/RewardsManager' -import { RewardsManagerMock } from '../../build/types/RewardsManagerMock' import { Staking } from '../../build/types/Staking' import { @@ -48,7 +47,6 @@ describe('Rewards', () => { let epochManager: EpochManager let staking: Staking let rewardsManager: RewardsManager - let rewardsManagerMock: RewardsManagerMock // Derive some channel keys for each indexer used to sign attestations const channelKey1 = deriveChannelKey() @@ -140,11 +138,6 @@ describe('Rewards', () => { governor.signer, )) - rewardsManagerMock = (await deployContract( - 'RewardsManagerMock', - governor.signer, - )) as unknown as RewardsManagerMock - // 200 GRT per block await rewardsManager.connect(governor.signer).setIssuancePerBlock(ISSUANCE_PER_BLOCK) @@ -831,18 +824,6 @@ describe('Rewards', () => { }) }) - describe('pow', function () { - it('exponentiation works under normal boundaries (annual rate from 1% to 700%, 90 days period)', async function () { - const baseRatio = toGRT('0.000000004641377923') // 1% annual rate - const timePeriods = (60 * 60 * 24 * 10) / 15 // 90 days in blocks - for (let i = 0; i < 50; i = i + 4) { - const r = baseRatio.mul(i * 4).add(toGRT('1')) - const h = await rewardsManagerMock.pow(r, timePeriods, toGRT('1')) - console.log('\tr:', formatGRT(r), '=> c:', formatGRT(h)) - } - }) - }) - describe('edge scenarios', function () { it('close allocation on a subgraph that no longer have signal', async function () { // Update total signalled From 69e542d61dc28a9009498d1ce21e9acbf4e356cf Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Fri, 23 Dec 2022 12:16:44 -0300 Subject: [PATCH 14/15] fix: set issuancePerBlock on goerli config --- config/graph.goerli.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/graph.goerli.yml b/config/graph.goerli.yml index af19d28ed..36edf25ea 100644 --- a/config/graph.goerli.yml +++ b/config/graph.goerli.yml @@ -131,8 +131,8 @@ contracts: init: controller: "${{Controller.address}}" calls: - - fn: "setIssuanceRate" - issuanceRate: "1000000011247641700" # per block increase of total supply, blocks in a year = 365*60*60*24/13 + - fn: "setIssuancePerBlock" + issuancePerBlock: "114155251141552511415" # per block increase of total supply, blocks in a year = 365*60*60*24/12 - fn: "setSubgraphAvailabilityOracle" subgraphAvailabilityOracle: *availabilityOracle - fn: "syncAllContracts" From dc40106f1dbc35bef9d3da98c488ba0027226b34 Mon Sep 17 00:00:00 2001 From: Pablo Carranza Velez Date: Wed, 15 Mar 2023 11:16:34 -0300 Subject: [PATCH 15/15] fix(config): set L1 gateway as minter on localhost and goerli --- config/graph.goerli.yml | 2 ++ config/graph.localhost.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/config/graph.goerli.yml b/config/graph.goerli.yml index 36edf25ea..8668c4938 100644 --- a/config/graph.goerli.yml +++ b/config/graph.goerli.yml @@ -58,6 +58,8 @@ contracts: calls: - fn: "addMinter" minter: "${{RewardsManager.address}}" + - fn: "addMinter" + minter: "${{L1GraphTokenGateway.address}}" - fn: "renounceMinter" - fn: "transferOwnership" owner: *governor diff --git a/config/graph.localhost.yml b/config/graph.localhost.yml index 9f5b25590..ce99ecabd 100644 --- a/config/graph.localhost.yml +++ b/config/graph.localhost.yml @@ -58,6 +58,8 @@ contracts: calls: - fn: "addMinter" minter: "${{RewardsManager.address}}" + - fn: "addMinter" + minter: "${{L1GraphTokenGateway.address}}" - fn: "renounceMinter" - fn: "transferOwnership" owner: *governor