diff --git a/src/test/BondingVotesFeeLessVotesFix.sol b/src/test/BondingVotesFeeLessVotesFix.sol new file mode 100644 index 00000000..56e13341 --- /dev/null +++ b/src/test/BondingVotesFeeLessVotesFix.sol @@ -0,0 +1,58 @@ +pragma solidity ^0.8.9; + +import "ds-test/test.sol"; +import "./base/GovernorBaseTest.sol"; +import "contracts/Controller.sol"; +import "contracts/bonding/BondingVotes.sol"; +import "contracts/bonding/BondingManager.sol"; +import "./interfaces/ICheatCodes.sol"; + +// forge test --match-contract BondingVotesFeeLessVotesFix --fork-url https://arbitrum-mainnet.infura.io/v3/$INFURA_KEY -vvv --fork-block-number 140314540 +contract BondingVotesFeeLessVotesFix is GovernorBaseTest { + bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); + + BondingManager public constant BONDING_MANAGER = BondingManager(0x35Bcf3c30594191d53231E4FF333E8A770453e40); + IBondingVotes public constant BONDING_VOTES = IBondingVotes(0x0B9C254837E72Ebe9Fe04960C43B69782E68169A); + + bytes32 public constant BONDING_VOTES_TARGET_ID = keccak256("BondingVotesTarget"); + + BondingVotes public newBondingVotesTarget; + + address public DELEGATOR = 0xdB18A9353139880d73616e4972a855d66C9B69f0; + + function setUp() public { + newBondingVotesTarget = new BondingVotes(address(CONTROLLER)); + } + + function doUpgrade() internal { + (, gitCommitHash) = CONTROLLER.getContractInfo(BONDING_VOTES_TARGET_ID); + + stageAndExecuteOne( + address(CONTROLLER), + 0, + abi.encodeWithSelector( + CONTROLLER.setContractInfo.selector, + BONDING_VOTES_TARGET_ID, + address(newBondingVotesTarget), + gitCommitHash + ) + ); + + // Check that new BondingVotesTarget is registered + (address infoAddr, bytes20 infoGitCommitHash) = fetchContractInfo(BONDING_VOTES_TARGET_ID); + assertEq(infoAddr, address(newBondingVotesTarget)); + assertEq(infoGitCommitHash, gitCommitHash); + } + + function testBeforeUpgrade() public { + CHEATS.expectRevert(arithmeticError); + BONDING_VOTES.getVotes(DELEGATOR); + } + + function testAfterUpgrade() public { + doUpgrade(); + + uint256 votes = BONDING_VOTES.getVotes(DELEGATOR); + assertTrue(votes > 0); + } +} diff --git a/test/integration/BondingVotes.js b/test/integration/BondingVotes.js index 3026ebc0..1ff6d61e 100644 --- a/test/integration/BondingVotes.js +++ b/test/integration/BondingVotes.js @@ -1,5 +1,7 @@ import RPC from "../../utils/rpc" import setupIntegrationTest from "../helpers/setupIntegrationTest" +import {createWinningTicket, getTicketHash} from "../helpers/ticket" +import signMsg from "../helpers/signMsg" import chai, {assert} from "chai" import {ethers} from "hardhat" @@ -18,10 +20,12 @@ describe("BondingVotes", () => { let bondingVotes let bondingManager let roundsManager - let roundLength + let ticketBroker let token let minter + let roundLength + const PERC_DIVISOR = 1000000 const PERC_MULTIPLIER = PERC_DIVISOR / 100 @@ -59,6 +63,11 @@ describe("BondingVotes", () => { ) roundLength = (await roundsManager.roundLength()).toNumber() + ticketBroker = await ethers.getContractAt( + "TicketBroker", + fixture.TicketBroker.address + ) + const controller = await ethers.getContractAt( "Controller", fixture.Controller.address @@ -87,6 +96,9 @@ describe("BondingVotes", () => { const nextRound = async (rounds = 1) => { await roundsManager.mineBlocks(rounds * roundLength) + await roundsManager.setBlockHash( + ethers.utils.solidityKeccak256(["string"], ["bar"]) + ) await roundsManager.initializeRound() const currRound = (await roundsManager.currentRound()).toNumber() mintableTokens[currRound] = await minter.currentMintableTokens() @@ -922,5 +934,128 @@ describe("BondingVotes", () => { } }) }) + + describe("transcoder with uninitialized fee factor", () => { + let broadcaster + + before(async () => { + broadcaster = signers[2] + + // Round R + + // delegator bonds to transcoder + await bond(delegator, lptAmount(1), transcoder) + + // broadcaster sets up deposit/reserve + const deposit = ethers.utils.parseEther("1") + await ticketBroker + .connect(broadcaster) + .fundDeposit({value: deposit}) + + const reserve = ethers.utils.parseEther("1") + await ticketBroker + .connect(broadcaster) + .fundReserve({value: reserve}) + + // Round R+1 + await nextRound() + + // Now redeem a ticket for the transcoder + const creationRound = await roundsManager.currentRound() + const creationRoundBlockHash = + await roundsManager.blockHashForRound(creationRound) + console.log(creationRoundBlockHash) + const auxData = ethers.utils.solidityPack( + ["uint256", "bytes32"], + [creationRound, creationRoundBlockHash] + ) + const recipientRand = 5 + const faceValue = 1000 + const ticket = createWinningTicket( + transcoder.address, + broadcaster.address, + recipientRand, + faceValue, + auxData + ) + const senderSig = await signMsg( + getTicketHash(ticket), + broadcaster.address + ) + + await ticketBroker + .connect(transcoder) + .redeemWinningTicket(ticket, senderSig, recipientRand) + }) + + it("should be in round currentRound+1", async () => { + const curr = await roundsManager.currentRound() + expect(curr).to.equal(currentRound + 1) + }) + + it("should have a non-zero cumulativeFeeFactor for transcoder", async () => { + const earningsPool = + await bondingManager.getTranscoderEarningsPoolForRound( + transcoder.address, + currentRound + 1 + ) + expect(earningsPool.cumulativeFeeFactor).to.not.equal(0) + }) + + it("should be able to calculate votes on the current round", async () => { + await expectStakeAt( + delegator, + currentRound + 1, + lptAmount(1), + transcoder.address + ) + await expectStakeAt( + delegator, + currentRound + 2, + lptAmount(1), + transcoder.address + ) + }) + + describe("in a round with no cumulativeFeeFactor initialized", async () => { + before(async () => { + // R+2 + await nextRound() + + await bondingManager.connect(transcoder).reward() + }) + + it("should not have initialized cumulativeFeeFactor", async () => { + const earningsPool = + await bondingManager.getTranscoderEarningsPoolForRound( + transcoder.address, + currentRound + 2 + ) + expect(earningsPool.cumulativeFeeFactor).to.equal(0) + expect(earningsPool.cumulativeRewardFactor).not.to.equal(0) + }) + + it("should be able to calculate votes on next round", async () => { + await expectStakeAt( + delegator, + currentRound + 1, + lptAmount(1), + transcoder.address + ) + await expectStakeAt( + delegator, + currentRound + 2, + lptAmount(1), + transcoder.address + ) + await expectStakeAt( + delegator, + currentRound + 3, + "1234396725000000000", // 1 LPT + rewards + transcoder.address + ) + }) + }) + }) }) }) diff --git a/test/unit/BondingVotes.js b/test/unit/BondingVotes.js index 94907b82..69380fd0 100644 --- a/test/unit/BondingVotes.js +++ b/test/unit/BondingVotes.js @@ -962,7 +962,8 @@ describe("BondingVotes", () => { const setEarningPoolRewardFactor = async ( address, round, - factor + rewardFactor, + feeFactor = 0 ) => { await fixture.bondingManager.setMockTranscoderEarningsPoolForRound( address, @@ -970,8 +971,8 @@ describe("BondingVotes", () => { 0, 0, 0, - factor, - 0 + rewardFactor, + feeFactor ) } @@ -1152,6 +1153,43 @@ describe("BondingVotes", () => { ) }) + it("should not fail if there's no cumulativeFeeFactor on the lastRewardRound", async () => { + await checkpointDelegator({ + startRound: currentRound - 9, + bondedAmount: 1000, + delegateAddress: transcoder.address, + lastClaimRound: currentRound - 10 + }) + await setEarningPoolRewardFactor( + transcoder.address, + currentRound - 10, + PERC_DIVISOR, + PERC_DIVISOR.mul(11).div(10) // 1.1 fee factor + ) + + await checkpointTranscoder({ + account: transcoder.address, + startRound: currentRound - 1, + lastRewardRound: currentRound - 2 + }) + await setEarningPoolRewardFactor( + transcoder.address, + currentRound - 2, + PERC_DIVISOR, + 0 // uninitialized fee factor + ) + + assert.deepEqual( + await bondingVotes + .getVotesAndDelegateAtRoundStart( + delegator.address, + currentRound + ) + .then(t => t.map(v => v.toString())), + ["1000", transcoder.address] + ) + }) + it("should return the bonded amount with accrued pending rewards since lastClaimRound", async () => { await checkpointDelegator({ startRound: currentRound - 9,