Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bonding: Skip fee calculation for voting power #633

Merged
merged 4 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions contracts/bonding/BondingVotes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -485,13 +485,7 @@ contract BondingVotes is ManagerProxyTarget, IBondingVotes {
return bond.bondedAmount;
}

(uint256 stakeWithRewards, ) = EarningsPoolLIP36.delegatorCumulativeStakeAndFees(
startPool,
endPool,
bond.bondedAmount,
0
);
return stakeWithRewards;
return EarningsPoolLIP36.delegatorCumulativeStake(startPool, endPool, bond.bondedAmount);
}

/**
Expand Down
63 changes: 52 additions & 11 deletions contracts/bonding/libraries/EarningsPoolLIP36.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ library EarningsPoolLIP36 {

/**
* @notice Calculates a delegator's cumulative stake and fees using the LIP-36 earnings claiming algorithm.
* @dev This internally calls {delegatorCumulativeStake} and {delegatorCumulativeFees} to calculate stake and fees.
* @param _startPool The earning pool from the start round for the start cumulative factors. Normally this is the
* earning pool from the {Delegator-lastclaimRound}+1 round, as the round where `bondedAmount` was measured.
* earning pool from the {Delegator-lastClaimRound} round, as the round where `_stake` was measured.
* @param _endPool The earning pool from the end round for the end cumulative factors
* @param _stake The delegator initial stake before including earned rewards. Normally the {Delegator-bondedAmount}
* @param _stake The delegator stake at the start round, before earned rewards. Normally {Delegator-bondedAmount}.
* @param _fees The delegator's initial fees before including earned fees
* @return cStake , cFees where cStake is the delegator's cumulative stake including earned rewards and cFees is the
* delegator's cumulative fees including earned fees
Expand All @@ -74,6 +75,23 @@ library EarningsPoolLIP36 {
uint256 _stake,
uint256 _fees
) internal pure returns (uint256 cStake, uint256 cFees) {
cStake = delegatorCumulativeStake(_startPool, _endPool, _stake);
cFees = delegatorCumulativeFees(_startPool, _endPool, _stake, _fees);
}

/**
* @notice Calculates a delegator's cumulative stake using the LIP-36 earnings claiming algorithm.
* @param _startPool The earning pool from the start round for the start cumulative factors. Normally this is the
* earning pool from the {Delegator-lastClaimRound} round, as the round where `_stake` was measured.
* @param _endPool The earning pool from the end round for the end cumulative factors.
* @param _stake The delegator stake at the start round, before earned rewards. Normally {Delegator-bondedAmount}.
* @return The delegator's cumulative stake including earned rewards.
*/
function delegatorCumulativeStake(
EarningsPool.Data memory _startPool,
EarningsPool.Data memory _endPool,
uint256 _stake
) internal pure returns (uint256) {
// If the start cumulativeRewardFactor is 0 set the default value to PreciseMathUtils.percPoints(1, 1)
if (_startPool.cumulativeRewardFactor == 0) {
_startPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
Expand All @@ -84,16 +102,39 @@ library EarningsPoolLIP36 {
_endPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
}

cFees = _fees.add(
PreciseMathUtils.percOf(
_stake,
_endPool.cumulativeFeeFactor.sub(_startPool.cumulativeFeeFactor),
_startPool.cumulativeRewardFactor
)
);
return PreciseMathUtils.percOf(_stake, _endPool.cumulativeRewardFactor, _startPool.cumulativeRewardFactor);
}

/**
* @notice Calculates a delegator's cumulative fees using the LIP-36 earnings claiming algorithm.
* @param _startPool The earning pool from the start round for the start cumulative factors. Normally this is the
* earning pool from the {Delegator-lastClaimRound} round, as the round where `_stake` was measured.
* @param _endPool The earning pool from the end round for the end cumulative factors.
* @param _stake The delegator stake at the start round, before earned rewards. Normally {Delegator-bondedAmount}.
* @param _fees The delegator's initial fees before including earned fees.
* @return The delegator's cumulative fees including earned fees.
*/
function delegatorCumulativeFees(
EarningsPool.Data memory _startPool,
EarningsPool.Data memory _endPool,
uint256 _stake,
uint256 _fees
) internal pure returns (uint256) {
// If the start cumulativeRewardFactor is 0 set the default value to PreciseMathUtils.percPoints(1, 1)
if (_startPool.cumulativeRewardFactor == 0) {
_startPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
}

cStake = PreciseMathUtils.percOf(_stake, _endPool.cumulativeRewardFactor, _startPool.cumulativeRewardFactor);
// If the end cumulativeRewardFactor is 0 set the default value to PreciseMathUtils.percPoints(1, 1)
if (_endPool.cumulativeRewardFactor == 0) {
_endPool.cumulativeRewardFactor = PreciseMathUtils.percPoints(1, 1);
}

return (cStake, cFees);
uint256 earnedFees = PreciseMathUtils.percOf(
_stake,
_endPool.cumulativeFeeFactor.sub(_startPool.cumulativeFeeFactor),
_startPool.cumulativeRewardFactor
);
return _fees.add(earnedFees);
}
}
33 changes: 33 additions & 0 deletions deploy/deploy_bonding_votes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {HardhatRuntimeEnvironment} from "hardhat/types"
import {DeployFunction} from "hardhat-deploy/types"

import ContractDeployer from "../utils/deployer"

const PROD_NETWORKS = ["mainnet", "arbitrumMainnet"]

const isProdNetwork = (name: string): boolean => {
return PROD_NETWORKS.indexOf(name) > -1
}

const func: DeployFunction = async function(hre: HardhatRuntimeEnvironment) {
const {deployments, getNamedAccounts} = hre // Get the deployments and getNamedAccounts which are provided by hardhat-deploy

const {deployer} = await getNamedAccounts() // Fetch named accounts from hardhat.config.ts

const contractDeployer = new ContractDeployer(deployer, deployments)
const controller = await contractDeployer.fetchDeployedController()

const deploy = isProdNetwork(hre.network.name) ?
contractDeployer.deploy.bind(contractDeployer) :
contractDeployer.deployAndRegister.bind(contractDeployer)

await deploy({
contract: "BondingVotes",
name: "BondingVotesTarget",
args: [controller.address],
proxy: false // deploying only the target
})
}

func.tags = ["BONDING_VOTES"]
export default func
58 changes: 58 additions & 0 deletions src/test/BondingVotesFeeLessVotesFix.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
137 changes: 136 additions & 1 deletion test/integration/BondingVotes.js
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -922,5 +934,128 @@ describe("BondingVotes", () => {
}
})
})

describe("transcoder with uninitialized fee factor", () => {
let broadcaster

before(async () => {
broadcaster = signers[2]

// Round R

// 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)
victorges marked this conversation as resolved.
Show resolved Hide resolved
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)

// delegator bonds to transcoder
await bond(delegator, lptAmount(1), transcoder)
})

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,
0,
constants.AddressZero
)
await expectStakeAt(
delegator,
currentRound + 2,
lptAmount(1),
transcoder.address
)
})

describe("in a round with cumulativeFeeFactor not 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,
0,
constants.AddressZero
)
await expectStakeAt(
delegator,
currentRound + 2,
lptAmount(1),
transcoder.address
)
await expectStakeAt(
delegator,
currentRound + 3,
"1234396725000000000", // 1 LPT + rewards
transcoder.address
)
})
})
})
})
})
Loading