From 6984ede153f35333809ff866a0bd4e5522c0660d Mon Sep 17 00:00:00 2001 From: Victor Elias Date: Thu, 20 Jul 2023 20:19:23 -0300 Subject: [PATCH 1/6] bonding: Implement treasury contribution --- contracts/bonding/BondingManager.sol | 67 +++++++++++++++++++++++++-- contracts/bonding/IBondingManager.sol | 1 + 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/contracts/bonding/BondingManager.sol b/contracts/bonding/BondingManager.sol index 5dd05937..7ea3abf9 100644 --- a/contracts/bonding/BondingManager.sol +++ b/contracts/bonding/BondingManager.sol @@ -94,6 +94,11 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { // in the pool are locked into the active set for round N + 1 SortedDoublyLL.Data private transcoderPool; + // The % of newly minted rewards to be routed to the treasury. Represented as a PreciseMathUtils percPoint value. + uint256 public treasuryRewardCutRate; + // If the balance of the treasury in LPT is above this value, automatic treasury contributions will halt. + uint256 public treasuryBalanceCeiling; + // Check if sender is TicketBroker modifier onlyTicketBroker() { _onlyTicketBroker(); @@ -145,6 +150,30 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { emit ParameterUpdate("unbondingPeriod"); } + /** + * @notice Set treasury reward cut rate. Only callable by Controller owner + * @param _cutRate Percentage of newly minted rewards to route to the treasury. Must be a valid PreciseMathUtils + * percentage (<100% specified with 27-digits precision). + */ + function setTreasuryRewardCutRate(uint256 _cutRate) external onlyControllerOwner { + require(PreciseMathUtils.validPerc(_cutRate), "_cutRate is invalid precise percentage"); + + treasuryRewardCutRate = _cutRate; + + emit ParameterUpdate("treasuryRewardCutRate"); + } + + /** + * @notice Set treasury balance ceiling. Only callable by Controller owner + * @param _ceiling Balance at which treasury reward contributions should halt. Specified in LPT fractional units + * (18-digit precision). + */ + function setTreasuryBalanceCeiling(uint256 _ceiling) external onlyControllerOwner { + treasuryBalanceCeiling = _ceiling; + + emit ParameterUpdate("treasuryBalanceCeiling"); + } + /** * @notice Set maximum number of active transcoders. Only callable by Controller owner * @param _numActiveTranscoders Number of active transcoders @@ -307,6 +336,11 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { totalStake, currentRoundTotalActiveStake ); + + // Deduct what would have been the treasury rewards + uint256 treasuryRewards = MathUtils.percOf(rewards, treasuryRewardCutRate); + rewards = rewards.sub(treasuryRewards); + uint256 transcoderCommissionRewards = MathUtils.percOf(rewards, earningsPool.transcoderRewardCut); uint256 delegatorsRewards = rewards.sub(transcoderCommissionRewards); @@ -841,16 +875,39 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { // Create reward based on active transcoder's stake relative to the total active stake // rewardTokens = (current mintable tokens for the round * active transcoder stake) / total active stake - uint256 rewardTokens = minter().createReward(earningsPool.totalStake, currentRoundTotalActiveStake); + IMinter mtr = minter(); + uint256 totalRewardTokens = mtr.createReward(earningsPool.totalStake, currentRoundTotalActiveStake); + + uint256 treasuryRewards = PreciseMathUtils.percOf(totalRewardTokens, treasuryRewardCutRate); + if (treasuryRewards > 0) { + address trsy = treasury(); + uint256 treasuryBalance = livepeerToken().balanceOf(trsy); + + uint256 maxTreasuryRewards = treasuryBalanceCeiling > treasuryBalance + ? treasuryBalanceCeiling - treasuryBalance + : 0; + if (treasuryRewards > maxTreasuryRewards) { + treasuryRewards = maxTreasuryRewards; + // halt treasury contributions until the cut rate param is updated again + treasuryRewardCutRate = 0; + } + + if (treasuryRewards > 0) { + mtr.trustedTransferTokens(trsy, treasuryRewards); + emit TreasuryReward(msg.sender, trsy, treasuryRewards); + } + } + + uint256 transcoderRewards = totalRewardTokens.sub(treasuryRewards); - updateTranscoderWithRewards(msg.sender, rewardTokens, currentRound, _newPosPrev, _newPosNext); + updateTranscoderWithRewards(msg.sender, transcoderRewards, currentRound, _newPosPrev, _newPosNext); // Set last round that transcoder called reward t.lastRewardRound = currentRound; checkpointBondingState(msg.sender, delegators[msg.sender], t); - emit Reward(msg.sender, rewardTokens); + emit Reward(msg.sender, transcoderRewards); } /** @@ -1553,6 +1610,10 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { return IRoundsManager(controller.getContract(keccak256("RoundsManager"))); } + function treasury() internal view returns (address payable) { + return payable(controller.getContract(keccak256("Treasury"))); + } + function bondingCheckpoints() internal view returns (IBondingCheckpoints) { return IBondingCheckpoints(controller.getContract(keccak256("BondingCheckpoints"))); } diff --git a/contracts/bonding/IBondingManager.sol b/contracts/bonding/IBondingManager.sol index 3981e236..8b35619c 100644 --- a/contracts/bonding/IBondingManager.sol +++ b/contracts/bonding/IBondingManager.sol @@ -11,6 +11,7 @@ interface IBondingManager { event TranscoderDeactivated(address indexed transcoder, uint256 deactivationRound); event TranscoderSlashed(address indexed transcoder, address finder, uint256 penalty, uint256 finderReward); event Reward(address indexed transcoder, uint256 amount); + event TreasuryReward(address indexed transcoder, address treasury, uint256 amount); event Bond( address indexed newDelegate, address indexed oldDelegate, From 9aad9e2acdd932535cfc8da2b70c7398f8a81a8c Mon Sep 17 00:00:00 2001 From: Victor Elias Date: Thu, 20 Jul 2023 22:01:37 -0300 Subject: [PATCH 2/6] test/bonding: Add tests for treasury contribution --- contracts/test/mocks/MinterMock.sol | 5 + test/unit/BondingManager.js | 247 ++++++++++++++++++++++++++++ test/unit/helpers/Fixture.js | 1 + 3 files changed, 253 insertions(+) diff --git a/contracts/test/mocks/MinterMock.sol b/contracts/test/mocks/MinterMock.sol index 13c1d5f1..dabd0215 100644 --- a/contracts/test/mocks/MinterMock.sol +++ b/contracts/test/mocks/MinterMock.sol @@ -5,8 +5,13 @@ import "./GenericMock.sol"; contract MinterMock is GenericMock { event TrustedWithdrawETH(address to, uint256 amount); + event TrustedTransferTokens(address to, uint256 amount); function trustedWithdrawETH(address _to, uint256 _amount) external { emit TrustedWithdrawETH(_to, _amount); } + + function trustedTransferTokens(address _to, uint256 _amount) external { + emit TrustedTransferTokens(_to, _amount); + } } diff --git a/test/unit/BondingManager.js b/test/unit/BondingManager.js index 662021a6..17f321ee 100644 --- a/test/unit/BondingManager.js +++ b/test/unit/BondingManager.js @@ -138,6 +138,68 @@ describe("BondingManager", () => { }) }) + describe("setTreasuryRewardCutRate", () => { + const FIFTY_PCT = math.precise.percPoints(BigNumber.from(50), 100) + + it("should start as zero", async () => { + assert.equal( + await bondingManager.treasuryRewardCutRate(), + 0, + "initial treasuryRewardCutRate not zero" + ) + }) + + it("should fail if caller is not Controller owner", async () => { + await expect( + bondingManager + .connect(signers[2]) + .setTreasuryRewardCutRate(FIFTY_PCT) + ).to.be.revertedWith("caller must be Controller owner") + }) + + it("should set treasuryRewardCutRate", async () => { + await bondingManager.setTreasuryRewardCutRate(FIFTY_PCT) + + const newValue = await bondingManager.treasuryRewardCutRate() + assert.equal( + newValue.toString(), + FIFTY_PCT.toString(), + "wrong treasuryRewardCutRate" + ) + }) + }) + + describe("setTreasuryBalanceCeiling", () => { + const HUNDRED_LPT = ethers.utils.parseEther("100") + + it("should start as zero", async () => { + assert.equal( + await bondingManager.treasuryBalanceCeiling(), + 0, + "initial treasuryBalanceCeiling not zero" + ) + }) + + it("should fail if caller is not Controller owner", async () => { + await expect( + bondingManager + .connect(signers[2]) + .setTreasuryBalanceCeiling(HUNDRED_LPT) + ).to.be.revertedWith("caller must be Controller owner") + }) + + it("should set treasuryBalanceCeiling", async () => { + await bondingManager.setTreasuryBalanceCeiling(HUNDRED_LPT) + + const newValue = await bondingManager.treasuryBalanceCeiling() + assert.equal( + newValue.toString(), + HUNDRED_LPT.toString(), + "wrong treasuryBalanceCeiling" + ) + }) + }) + describe("transcoder", () => { const currentRound = 100 beforeEach(async () => { @@ -4648,6 +4710,191 @@ describe("BondingManager", () => { .to.emit(bondingManager, "Reward") .withArgs(transcoder.address, 1000) }) + + describe("treasury contribution", () => { + const TREASURY_CUT = math.precise.percPoints( + BigNumber.from(631), + 10000 + ) // 6.31% + + beforeEach(async () => { + await fixture.token.setMockUint256( + functionSig("balanceOf(address)"), + 0 + ) + + await bondingManager.setTreasuryRewardCutRate(TREASURY_CUT) + await bondingManager.setTreasuryBalanceCeiling(1000) + }) + + it("should update caller with rewards after treasury contribution", async () => { + const startDelegatedAmount = ( + await bondingManager.getDelegator(transcoder.address) + )[3] + const startTotalStake = + await bondingManager.transcoderTotalStake( + transcoder.address + ) + const startNextTotalStake = + await bondingManager.nextRoundTotalActiveStake() + await bondingManager.connect(transcoder).reward() + + const endDelegatedAmount = ( + await bondingManager.getDelegator(transcoder.address) + )[3] + const endTotalStake = await bondingManager.transcoderTotalStake( + transcoder.address + ) + const endNextTotalStake = + await bondingManager.nextRoundTotalActiveStake() + + const earningsPool = + await bondingManager.getTranscoderEarningsPoolForRound( + transcoder.address, + currentRound + 1 + ) + + const expRewardFactor = constants.PERC_DIVISOR_PRECISE.add( + math.precise.percPoints( + BigNumber.from(469), // (1000 - 6.31% = 937) - 50% = 469 (cuts are calculated first and subtracted) + BigNumber.from(1000) + ) + ) + assert.equal( + earningsPool.cumulativeRewardFactor.toString(), + expRewardFactor.toString(), + "should update cumulativeRewardFactor in earningsPool" + ) + + assert.equal( + endDelegatedAmount.sub(startDelegatedAmount), + 937, + "should update delegatedAmount with rewards after treasury cut" + ) + assert.equal( + endTotalStake.sub(startTotalStake), + 937, + "should update transcoder's total stake in the pool with rewards after treasury cut" + ) + assert.equal( + endNextTotalStake.sub(startNextTotalStake), + 937, + "should update next total stake with rewards after treasury cut" + ) + }) + + it("should transfer tokens to the treasury", async () => { + const tx = await bondingManager.connect(transcoder).reward() + + await expect(tx) + .to.emit(fixture.minter, "TrustedTransferTokens") + .withArgs(fixture.treasury.address, 63) + }) + + it("should emit TreasuryReward event", async () => { + const tx = await bondingManager.connect(transcoder).reward() + + await expect(tx) + .to.emit(bondingManager, "TreasuryReward") + .withArgs(transcoder.address, fixture.treasury.address, 63) + }) + + describe("ceiling behavior", () => { + beforeEach(async () => { + await fixture.token.setMockUint256( + functionSig("balanceOf(address)"), + 990 + ) + }) + + it("should limit contribution to ceiling", async () => { + const tx = await bondingManager.connect(transcoder).reward() + + await expect(tx) + .to.emit(fixture.minter, "TrustedTransferTokens") + .withArgs(fixture.treasury.address, 10) // 1000 - 990 + }) + + it("should clear treasuryRewardCutRate param", async () => { + await bondingManager.connect(transcoder).reward() + + const cutRate = await bondingManager.treasuryRewardCutRate() + assert.equal(cutRate.toNumber(), 0, "cut rate not cleared") + }) + + describe("when at limit", () => { + beforeEach(async () => { + await fixture.token.setMockUint256( + functionSig("balanceOf(address)"), + 1000 + ) + }) + + it("should not mint any treasury rewards", async () => { + const tx = await bondingManager + .connect(transcoder) + .reward() + + await expect(tx).not.to.emit( + fixture.minter, + "TrustedTransferTokens" + ) + await expect(tx).not.to.emit( + bondingManager, + "TreasuryReward" + ) + }) + + it("should also clear treasuryRewardCutRate param", async () => { + await bondingManager.connect(transcoder).reward() + + const cutRate = + await bondingManager.treasuryRewardCutRate() + assert.equal( + cutRate.toNumber(), + 0, + "cut rate not cleared" + ) + }) + }) + + describe("when above limit", () => { + beforeEach(async () => { + await fixture.token.setMockUint256( + functionSig("balanceOf(address)"), + 1500 + ) + }) + + it("should not mint any treasury rewards", async () => { + const tx = await bondingManager + .connect(transcoder) + .reward() + + await expect(tx).not.to.emit( + fixture.minter, + "TrustedTransferTokens" + ) + await expect(tx).not.to.emit( + bondingManager, + "TreasuryReward" + ) + }) + + it("should also clear treasuryRewardCutRate param", async () => { + await bondingManager.connect(transcoder).reward() + + const cutRate = + await bondingManager.treasuryRewardCutRate() + assert.equal( + cutRate.toNumber(), + 0, + "cut rate not cleared" + ) + }) + }) + }) + }) }) describe("updateTranscoderWithFees", () => { diff --git a/test/unit/helpers/Fixture.js b/test/unit/helpers/Fixture.js index 6f4eaa9c..fdcf6c1c 100644 --- a/test/unit/helpers/Fixture.js +++ b/test/unit/helpers/Fixture.js @@ -29,6 +29,7 @@ export default class Fixture { this.token = await this.deployAndRegister(GenericMock, "LivepeerToken") this.minter = await this.deployAndRegister(MinterMock, "Minter") + this.treasury = await this.deployAndRegister(GenericMock, "Treasury") this.bondingManager = await this.deployAndRegister( BondingManagerMock, "BondingManager" From cd6d1470237254828b998e561cf337615ff5986f Mon Sep 17 00:00:00 2001 From: Victor Elias Date: Thu, 20 Jul 2023 22:21:04 -0300 Subject: [PATCH 3/6] bonding: Update reward cut logic to match LIP It is a little less exact (might overmint on the last reward call), but the simpler logic might be just worth it. --- contracts/bonding/BondingManager.sol | 28 ++++++++----------- test/unit/BondingManager.js | 41 +++++++++++++++++----------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/contracts/bonding/BondingManager.sol b/contracts/bonding/BondingManager.sol index 7ea3abf9..ccc93130 100644 --- a/contracts/bonding/BondingManager.sol +++ b/contracts/bonding/BondingManager.sol @@ -873,29 +873,25 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { earningsPool.setStake(t.earningsPoolPerRound[lastUpdateRound].totalStake); } + if (treasuryBalanceCeiling > 0) { + uint256 treasuryBalance = livepeerToken().balanceOf(treasury()); + if (treasuryBalance >= treasuryBalanceCeiling) { + // halt treasury contributions until the cut rate param is updated again + treasuryRewardCutRate = 0; + } + } + // Create reward based on active transcoder's stake relative to the total active stake // rewardTokens = (current mintable tokens for the round * active transcoder stake) / total active stake IMinter mtr = minter(); uint256 totalRewardTokens = mtr.createReward(earningsPool.totalStake, currentRoundTotalActiveStake); - uint256 treasuryRewards = PreciseMathUtils.percOf(totalRewardTokens, treasuryRewardCutRate); if (treasuryRewards > 0) { - address trsy = treasury(); - uint256 treasuryBalance = livepeerToken().balanceOf(trsy); - - uint256 maxTreasuryRewards = treasuryBalanceCeiling > treasuryBalance - ? treasuryBalanceCeiling - treasuryBalance - : 0; - if (treasuryRewards > maxTreasuryRewards) { - treasuryRewards = maxTreasuryRewards; - // halt treasury contributions until the cut rate param is updated again - treasuryRewardCutRate = 0; - } + address trsry = treasury(); - if (treasuryRewards > 0) { - mtr.trustedTransferTokens(trsy, treasuryRewards); - emit TreasuryReward(msg.sender, trsy, treasuryRewards); - } + mtr.trustedTransferTokens(trsry, treasuryRewards); + + emit TreasuryReward(msg.sender, trsry, treasuryRewards); } uint256 transcoderRewards = totalRewardTokens.sub(treasuryRewards); diff --git a/test/unit/BondingManager.js b/test/unit/BondingManager.js index 17f321ee..85d2f2a2 100644 --- a/test/unit/BondingManager.js +++ b/test/unit/BondingManager.js @@ -4800,26 +4800,35 @@ describe("BondingManager", () => { }) describe("ceiling behavior", () => { - beforeEach(async () => { - await fixture.token.setMockUint256( - functionSig("balanceOf(address)"), - 990 - ) - }) + describe("under the limit", () => { + beforeEach(async () => { + await fixture.token.setMockUint256( + functionSig("balanceOf(address)"), + 990 + ) + }) - it("should limit contribution to ceiling", async () => { - const tx = await bondingManager.connect(transcoder).reward() + it("should contribute normally", async () => { + const tx = await bondingManager + .connect(transcoder) + .reward() - await expect(tx) - .to.emit(fixture.minter, "TrustedTransferTokens") - .withArgs(fixture.treasury.address, 10) // 1000 - 990 - }) + await expect(tx) + .to.emit(fixture.minter, "TrustedTransferTokens") + .withArgs(fixture.treasury.address, 63) + }) - it("should clear treasuryRewardCutRate param", async () => { - await bondingManager.connect(transcoder).reward() + it("should not clear treasuryRewardCutRate param", async () => { + await bondingManager.connect(transcoder).reward() - const cutRate = await bondingManager.treasuryRewardCutRate() - assert.equal(cutRate.toNumber(), 0, "cut rate not cleared") + const cutRate = + await bondingManager.treasuryRewardCutRate() + assert.equal( + cutRate.toString(), + TREASURY_CUT.toString(), + "cut rate updated" + ) + }) }) describe("when at limit", () => { From 27f8930daf12497ccd3eb4017a51a2f56381aa36 Mon Sep 17 00:00:00 2001 From: Victor Elias Date: Thu, 17 Aug 2023 22:46:43 -0300 Subject: [PATCH 4/6] bonding: Avoid returning payable treasury address --- contracts/bonding/BondingManager.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/bonding/BondingManager.sol b/contracts/bonding/BondingManager.sol index 016c6541..17d15b7e 100644 --- a/contracts/bonding/BondingManager.sol +++ b/contracts/bonding/BondingManager.sol @@ -1611,8 +1611,8 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { return IRoundsManager(controller.getContract(keccak256("RoundsManager"))); } - function treasury() internal view returns (address payable) { - return payable(controller.getContract(keccak256("Treasury"))); + function treasury() internal view returns (address) { + return controller.getContract(keccak256("Treasury")); } function bondingVotes() internal view returns (IBondingVotes) { From ead1edb572319df53dbfa06c3466edb4ca8c0575 Mon Sep 17 00:00:00 2001 From: Victor Elias Date: Thu, 17 Aug 2023 23:22:34 -0300 Subject: [PATCH 5/6] bonding: Update treasury cut rate only on the next round --- contracts/bonding/BondingManager.sol | 21 ++- test/unit/BondingManager.js | 196 ++++++++++++++++++--------- 2 files changed, 149 insertions(+), 68 deletions(-) diff --git a/contracts/bonding/BondingManager.sol b/contracts/bonding/BondingManager.sol index 17d15b7e..b9f22e15 100644 --- a/contracts/bonding/BondingManager.sol +++ b/contracts/bonding/BondingManager.sol @@ -96,6 +96,9 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { // The % of newly minted rewards to be routed to the treasury. Represented as a PreciseMathUtils percPoint value. uint256 public treasuryRewardCutRate; + // The value for `treasuryRewardCutRate` to be set on the next round initialization. + uint256 public nextRoundTreasuryRewardCutRate; + // If the balance of the treasury in LPT is above this value, automatic treasury contributions will halt. uint256 public treasuryBalanceCeiling; @@ -161,11 +164,15 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { * percentage (<100% specified with 27-digits precision). */ function setTreasuryRewardCutRate(uint256 _cutRate) external onlyControllerOwner { + _setTreasuryRewardCutRate(_cutRate); + } + + function _setTreasuryRewardCutRate(uint256 _cutRate) internal { require(PreciseMathUtils.validPerc(_cutRate), "_cutRate is invalid precise percentage"); - treasuryRewardCutRate = _cutRate; + nextRoundTreasuryRewardCutRate = _cutRate; - emit ParameterUpdate("treasuryRewardCutRate"); + emit ParameterUpdate("nextRoundTreasuryRewardCutRate"); } /** @@ -453,6 +460,12 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { function setCurrentRoundTotalActiveStake() external onlyRoundsManager { currentRoundTotalActiveStake = nextRoundTotalActiveStake; + if (nextRoundTreasuryRewardCutRate != treasuryRewardCutRate) { + treasuryRewardCutRate = nextRoundTreasuryRewardCutRate; + // The treasury cut rate changes in a delayed fashion so we want to emit the parameter update event here + emit ParameterUpdate("treasuryRewardCutRate"); + } + bondingVotes().checkpointTotalActiveStake(currentRoundTotalActiveStake, roundsManager().currentRound()); } @@ -877,9 +890,9 @@ contract BondingManager is ManagerProxyTarget, IBondingManager { if (treasuryBalanceCeiling > 0) { uint256 treasuryBalance = livepeerToken().balanceOf(treasury()); - if (treasuryBalance >= treasuryBalanceCeiling) { + if (treasuryBalance >= treasuryBalanceCeiling && nextRoundTreasuryRewardCutRate > 0) { // halt treasury contributions until the cut rate param is updated again - treasuryRewardCutRate = 0; + _setTreasuryRewardCutRate(0); } } diff --git a/test/unit/BondingManager.js b/test/unit/BondingManager.js index d8ffadea..8398f853 100644 --- a/test/unit/BondingManager.js +++ b/test/unit/BondingManager.js @@ -141,6 +141,17 @@ describe("BondingManager", () => { describe("setTreasuryRewardCutRate", () => { const FIFTY_PCT = math.precise.percPoints(BigNumber.from(50), 100) + let currentRound + + beforeEach(async () => { + currentRound = 100 + + await fixture.roundsManager.setMockUint256( + functionSig("currentRound()"), + currentRound + ) + }) + it("should start as zero", async () => { assert.equal( await bondingManager.treasuryRewardCutRate(), @@ -157,15 +168,50 @@ describe("BondingManager", () => { ).to.be.revertedWith("caller must be Controller owner") }) - it("should set treasuryRewardCutRate", async () => { + it("should set only nextRoundTreasuryRewardCutRate", async () => { + const tx = await bondingManager.setTreasuryRewardCutRate(FIFTY_PCT) + await expect(tx) + .to.emit(bondingManager, "ParameterUpdate") + .withArgs("nextRoundTreasuryRewardCutRate") + + assert.equal( + await bondingManager.nextRoundTreasuryRewardCutRate(), + FIFTY_PCT.toString(), + "wrong nextRoundTreasuryRewardCutRate" + ) + assert.equal( + await bondingManager.treasuryRewardCutRate(), + 0, + "wrong treasuryRewardCutRate" + ) + }) + + it("should set treasuryRewardCutRate on the next round", async () => { await bondingManager.setTreasuryRewardCutRate(FIFTY_PCT) - const newValue = await bondingManager.treasuryRewardCutRate() + await fixture.roundsManager.setMockUint256( + functionSig("currentRound()"), + currentRound + 1 + ) + const tx = await fixture.roundsManager.execute( + bondingManager.address, + functionSig("setCurrentRoundTotalActiveStake()") + ) + await expect(tx) + .to.emit(bondingManager, "ParameterUpdate") + .withArgs("treasuryRewardCutRate") + assert.equal( - newValue.toString(), + await bondingManager.treasuryRewardCutRate(), FIFTY_PCT.toString(), "wrong treasuryRewardCutRate" ) + // sanity check that this hasn't changed either + assert.equal( + await bondingManager.nextRoundTreasuryRewardCutRate(), + FIFTY_PCT.toString(), + "wrong nextRoundTreasuryRewardCutRate" + ) }) }) @@ -4697,6 +4743,16 @@ describe("BondingManager", () => { await bondingManager.setTreasuryRewardCutRate(TREASURY_CUT) await bondingManager.setTreasuryBalanceCeiling(1000) + + // treasury cut rate update only takes place on the next round + await fixture.roundsManager.setMockUint256( + functionSig("currentRound()"), + currentRound + 1 + ) + await fixture.roundsManager.execute( + bondingManager.address, + functionSig("setCurrentRoundTotalActiveStake()") + ) }) it("should update caller with rewards after treasury contribution", async () => { @@ -4803,77 +4859,89 @@ describe("BondingManager", () => { }) }) - describe("when at limit", () => { - beforeEach(async () => { - await fixture.token.setMockUint256( - functionSig("balanceOf(address)"), - 1000 - ) - }) + const atCeilingTest = (title, balance) => { + describe(title, () => { + beforeEach(async () => { + await fixture.token.setMockUint256( + functionSig("balanceOf(address)"), + balance + ) + }) - it("should not mint any treasury rewards", async () => { - const tx = await bondingManager - .connect(transcoder) - .reward() + it("should zero the nextRoundTreasuryRewardCutRate", async () => { + const tx = await bondingManager + .connect(transcoder) + .reward() - await expect(tx).not.to.emit( - fixture.minter, - "TrustedTransferTokens" - ) - await expect(tx).not.to.emit( - bondingManager, - "TreasuryReward" - ) - }) + // it should still send treasury rewards + await expect(tx).to.emit( + fixture.minter, + "TrustedTransferTokens" + ) + await expect(tx).to.emit( + bondingManager, + "TreasuryReward" + ) - it("should also clear treasuryRewardCutRate param", async () => { - await bondingManager.connect(transcoder).reward() + await expect(tx) + .to.emit(bondingManager, "ParameterUpdate") + .withArgs("nextRoundTreasuryRewardCutRate") + assert.equal( + await bondingManager.nextRoundTreasuryRewardCutRate(), + 0 + ) + }) - const cutRate = - await bondingManager.treasuryRewardCutRate() - assert.equal( - cutRate.toNumber(), - 0, - "cut rate not cleared" - ) - }) - }) + it("should not mint any treasury rewards in the next round", async () => { + await bondingManager.connect(transcoder).reward() - describe("when above limit", () => { - beforeEach(async () => { - await fixture.token.setMockUint256( - functionSig("balanceOf(address)"), - 1500 - ) - }) + await fixture.roundsManager.setMockUint256( + functionSig("currentRound()"), + currentRound + 2 + ) + await fixture.roundsManager.execute( + bondingManager.address, + functionSig("setCurrentRoundTotalActiveStake()") + ) - it("should not mint any treasury rewards", async () => { - const tx = await bondingManager - .connect(transcoder) - .reward() + const tx = await bondingManager + .connect(transcoder) + .reward() + await expect(tx).not.to.emit( + fixture.minter, + "TrustedTransferTokens" + ) + await expect(tx).not.to.emit( + bondingManager, + "TreasuryReward" + ) + }) - await expect(tx).not.to.emit( - fixture.minter, - "TrustedTransferTokens" - ) - await expect(tx).not.to.emit( - bondingManager, - "TreasuryReward" - ) - }) + it("should also clear treasuryRewardCutRate param in the next round", async () => { + await bondingManager.connect(transcoder).reward() - it("should also clear treasuryRewardCutRate param", async () => { - await bondingManager.connect(transcoder).reward() + await fixture.roundsManager.setMockUint256( + functionSig("currentRound()"), + currentRound + 2 + ) + await fixture.roundsManager.execute( + bondingManager.address, + functionSig("setCurrentRoundTotalActiveStake()") + ) - const cutRate = - await bondingManager.treasuryRewardCutRate() - assert.equal( - cutRate.toNumber(), - 0, - "cut rate not cleared" - ) + const cutRate = + await bondingManager.treasuryRewardCutRate() + assert.equal( + cutRate.toNumber(), + 0, + "cut rate not cleared" + ) + }) }) - }) + } + + atCeilingTest("when at limit", 1000) + atCeilingTest("when above limit", 1500) }) }) }) From 391e444df603813d84164825eabe00b83d42a9a0 Mon Sep 17 00:00:00 2001 From: Victor Elias Date: Fri, 18 Aug 2023 14:08:49 -0300 Subject: [PATCH 6/6] test: Fix TicketBroker tests flakiness! --- test/unit/TicketBroker.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/unit/TicketBroker.js b/test/unit/TicketBroker.js index ea1f1130..24192b19 100644 --- a/test/unit/TicketBroker.js +++ b/test/unit/TicketBroker.js @@ -664,8 +664,11 @@ describe("TicketBroker", () => { expect(endSenderInfo.sender.deposit).to.be.equal(deposit) expect(endSenderInfo.reserve.fundsRemaining).to.be.equal(reserve) - expect(tx).to.changeEtherBalance(funder, -(deposit + reserve)) - expect(tx).to.changeEtherBalance(fixture.minter, deposit + reserve) + await expect(tx).to.changeEtherBalance(funder, -(deposit + reserve)) + await expect(tx).to.changeEtherBalance( + fixture.minter, + deposit + reserve + ) }) })