Skip to content

Commit 3ae6136

Browse files
nikeshnazarethLeoPatOZggonzalez94
authored
Use two decimals for delayed scale factor (#114)
Co-authored-by: Leo <[email protected]> Co-authored-by: Gustavo Gonzalez <[email protected]>
1 parent 0ee6812 commit 3ae6136

File tree

4 files changed

+62
-37
lines changed

4 files changed

+62
-37
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ env:
1010

1111
jobs:
1212
check:
13-
strategy:
14-
fail-fast: true
15-
1613
name: Foundry project
1714
runs-on: ubuntu-latest
1815
steps:
@@ -23,7 +20,7 @@ jobs:
2320
- name: Install Foundry
2421
uses: foundry-rs/foundry-toolchain@v1
2522
with:
26-
version: nightly
23+
version: stable
2724

2825
- name: Show Forge version
2926
run: |

src/libs/LibPercentage.sol

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.28;
3+
4+
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
5+
6+
library LibPercentage {
7+
using SafeCast for uint256;
8+
9+
// COMMON PRECISION AMOUNTS (https://muens.io/solidity-percentages)
10+
uint256 constant BASIS_POINTS = 10_000;
11+
uint256 constant PERCENT = 100;
12+
13+
/// @dev Calculates the percentage of a given value scaling by `precision` to limit rounding loss
14+
/// @param value The number to scale
15+
/// @param percentage The percentage expressed in `precision` units.
16+
/// @param precision The precision of `percentage` (e.g. percentage 5000 with BASIS_POINTS precision is 50%).
17+
/// @return _ The scaled value
18+
function scaleBy(uint256 value, uint16 percentage, uint256 precision) internal pure returns (uint96) {
19+
return (value * percentage / precision).toUint96();
20+
}
21+
22+
/// @dev Calculates the percentage (represented in basis points) of a given value
23+
/// @param value The number to scale
24+
/// @param percentage The percentage expressed in basis points
25+
/// @return _ The scaled value
26+
function scaleByBPS(uint256 value, uint16 percentage) internal pure returns (uint96) {
27+
return scaleBy(value, percentage, BASIS_POINTS);
28+
}
29+
30+
/// @dev Calculates the percentage of a given value
31+
/// @param value The number to scale
32+
/// @param percentage The percentage to single-percentage precision (e.g. percentage 50 is 50%)
33+
/// @return _ The scaled value
34+
function scaleByPercentage(uint256 value, uint16 percentage) internal pure returns (uint96) {
35+
return scaleBy(value, percentage, BASIS_POINTS);
36+
}
37+
}

src/protocol/BaseProverManager.sol

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.28;
33

4+
import {LibPercentage} from "../libs/LibPercentage.sol";
45
import {ICheckpointTracker} from "./ICheckpointTracker.sol";
56
import {IProposerFees} from "./IProposerFees.sol";
67
import {IProverManager} from "./IProverManager.sol";
78
import {IPublicationFeed} from "./IPublicationFeed.sol";
9+
810
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
911

1012
abstract contract BaseProverManager is IProposerFees, IProverManager {
1113
using SafeCast for uint256;
14+
using LibPercentage for uint96;
1215

1316
struct Period {
1417
// SLOT 1
@@ -17,7 +20,7 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
1720
// SLOT 2
1821
// the fee that the prover is willing to charge for proving each publication
1922
uint96 fee;
20-
// the percentage (in bps) of the fee that is charged for delayed publications.
23+
// the percentage (with two decimals precision) of the fee that is charged for delayed publications.
2124
uint16 delayedFeePercentage;
2225
// the timestamp of the end of the period. Default to zero while the period is open.
2326
uint40 end;
@@ -94,7 +97,7 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
9497
// Deduct fee from proposer's balance
9598
uint96 fee = _periods[periodId].fee;
9699
if (isDelayed) {
97-
fee = _calculatePercentage(fee, _periods[periodId].delayedFeePercentage).toUint96();
100+
fee = fee.scaleByPercentage(_periods[periodId].delayedFeePercentage);
98101
}
99102
_balances[proposer] -= fee;
100103
}
@@ -147,7 +150,7 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
147150
(uint40 end,) = _closePeriod(period, _exitDelay(), 0);
148151

149152
// Reward the evictor and slash the prover
150-
uint96 evictorIncentive = _calculatePercentage(period.stake, _evictorIncentivePercentage()).toUint96();
153+
uint96 evictorIncentive = period.stake.scaleByBPS(_evictorIncentivePercentage());
151154
_balances[msg.sender] += evictorIncentive;
152155
period.stake -= evictorIncentive;
153156

@@ -209,7 +212,7 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
209212
uint256 delayedPubFee;
210213

211214
if (numDelayedPublications > 0) {
212-
uint256 delayedFee = _calculatePercentage(baseFee, period.delayedFeePercentage);
215+
uint96 delayedFee = baseFee.scaleByPercentage(period.delayedFeePercentage);
213216
delayedPubFee = numDelayedPublications * delayedFee;
214217
}
215218

@@ -228,8 +231,7 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
228231
require(provenPublication.timestamp > period.end, "Publication must be after period");
229232

230233
uint96 stake = period.stake;
231-
_balances[period.prover] +=
232-
period.pastDeadline ? _calculatePercentage(stake, _rewardPercentage()).toUint96() : stake;
234+
_balances[period.prover] += period.pastDeadline ? stake.scaleByBPS(_rewardPercentage()) : stake;
233235
period.stake = 0;
234236
}
235237

@@ -246,7 +248,7 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
246248

247249
Period storage period = _periods[currentPeriod];
248250
fee = period.fee;
249-
delayedFee = _calculatePercentage(fee, period.delayedFeePercentage).toUint96();
251+
delayedFee = fee.scaleByPercentage(period.delayedFeePercentage);
250252
}
251253

252254
/// @notice Get the balance of a user
@@ -273,7 +275,7 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
273275
/// @param fee The fee to be outbid (either the current period's fee or next period's winning fee)
274276
/// @param offeredFee The new bid
275277
function _ensureSufficientUnderbid(uint96 fee, uint96 offeredFee) internal view virtual {
276-
uint256 requiredMaxFee = _calculatePercentage(fee, _maxBidPercentage());
278+
uint96 requiredMaxFee = fee.scaleByBPS(_maxBidPercentage());
277279
require(offeredFee <= requiredMaxFee, "Offered fee not low enough");
278280
}
279281

@@ -310,10 +312,9 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
310312
/// @return _ The reward percentage
311313
function _rewardPercentage() internal view virtual returns (uint16);
312314

313-
/// @dev The percentage (in bps) of the fee that is charged for delayed publications
314-
/// @dev It is recommended to set this to >10,000 bps since delayed publications should usually be charged at a
315-
/// higher rate
316-
/// @return _ The multiplier expressed in basis points. This value should usually be greater than 10,000 bps(100%).
315+
/// @dev The percentage of the fee that is charged for delayed publications
316+
/// @dev It is recommended to set this to >100 since delayed publications should usually be charged at a higher rate
317+
/// @return _ The multiplier as a percentage (two decimals). This value should usually be greater than 100 (100%).
317318
function _delayedFeePercentage() internal view virtual returns (uint16);
318319

319320
/// @dev Increases `user`'s balance by `amount` and emits a `Deposit` event
@@ -339,14 +340,6 @@ abstract contract BaseProverManager is IProposerFees, IProverManager {
339340
_updatePeriod(nextPeriod, prover, fee, _livenessBond());
340341
}
341342

342-
/// @dev Calculates the percentage of a given numerator scaling up to avoid precision loss
343-
/// @param amount The number to calculate the percentage of
344-
/// @param bps The percentage expressed in basis points(https://muens.io/solidity-percentages)
345-
/// @return _ The calculated percentage of the given numerator
346-
function _calculatePercentage(uint256 amount, uint256 bps) private pure returns (uint256) {
347-
return (amount * bps) / 10_000;
348-
}
349-
350343
/// @dev Updates a period with prover information and transfers the liveness bond
351344
/// @param period The period to update
352345
/// @param prover The address of the prover

test/BaseProverManager.t.sol

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {ICheckpointTracker} from "src/protocol/ICheckpointTracker.sol";
1111
import {IPublicationFeed} from "src/protocol/IPublicationFeed.sol";
1212
import {PublicationFeed} from "src/protocol/PublicationFeed.sol";
1313

14+
import {LibPercentage} from "src/libs/LibPercentage.sol";
1415
import {MockCheckpointTracker} from "test/mocks/MockCheckpointTracker.sol";
1516
import {NullVerifier} from "test/mocks/NullVerifier.sol";
1617

@@ -24,7 +25,7 @@ uint96 constant LIVENESS_BOND = 1 ether;
2425
uint16 constant EVICTOR_INCENTIVE_PERCENTAGE = 500; // 5%
2526
uint16 constant REWARD_PERCENTAGE = 9000; // 90%
2627
uint96 constant INITIAL_FEE = 0.1 ether;
27-
uint16 constant DELAYED_FEE_PERCENTAGE = 15_000; // 150%
28+
uint16 constant DELAYED_FEE_PERCENTAGE = 150; // 150%
2829
uint256 constant INITIAL_PERIOD = 1;
2930

3031
abstract contract BaseProverManagerTest is Test {
@@ -218,7 +219,7 @@ abstract contract BaseProverManagerTest is Test {
218219
// Capture current period stake before eviction
219220
BaseProverManager.Period memory periodBefore = proverManager.getPeriod(1);
220221
uint256 stakeBefore = periodBefore.stake;
221-
uint256 incentive = _calculatePercentage(stakeBefore, EVICTOR_INCENTIVE_PERCENTAGE);
222+
uint256 incentive = LibPercentage.scaleByBPS(stakeBefore, EVICTOR_INCENTIVE_PERCENTAGE);
222223

223224
// Evict the prover
224225
vm.warp(vm.getBlockTimestamp() + LIVENESS_WINDOW + 1);
@@ -504,7 +505,7 @@ abstract contract BaseProverManagerTest is Test {
504505

505506
uint256 proverBalanceAfter = proverManager.balances(initialProver);
506507
uint256 expectedBalance = proverBalanceBefore + INITIAL_FEE * numRegularPublications
507-
+ _calculatePercentage(INITIAL_FEE, DELAYED_FEE_PERCENTAGE) * numDelayedPublications;
508+
+ LibPercentage.scaleByPercentage(INITIAL_FEE, DELAYED_FEE_PERCENTAGE) * numDelayedPublications;
508509
assertEq(proverBalanceAfter, expectedBalance, "Prover should receive fees");
509510
}
510511

@@ -848,7 +849,7 @@ abstract contract BaseProverManagerTest is Test {
848849

849850
uint256 initialProverBalanceAfter = proverManager.balances(initialProver);
850851
uint256 prover1BalanceAfter = proverManager.balances(prover1);
851-
uint256 stakeReward = _calculatePercentage(stakeBefore, REWARD_PERCENTAGE);
852+
uint256 stakeReward = LibPercentage.scaleByBPS(stakeBefore, REWARD_PERCENTAGE);
852853
assertEq(prover1BalanceAfter, prover1BalanceBefore + stakeReward, "Prover1 should receive the remaining stake");
853854
assertEq(initialProverBalanceAfter, initialProverBalanceBefore, "Initial prover should receive nothing");
854855
}
@@ -905,7 +906,7 @@ abstract contract BaseProverManagerTest is Test {
905906
assertEq(fee, INITIAL_FEE, "Fee should be the initial fee");
906907
assertEq(
907908
delayedFee,
908-
_calculatePercentage(INITIAL_FEE, DELAYED_FEE_PERCENTAGE),
909+
LibPercentage.scaleByPercentage(INITIAL_FEE, DELAYED_FEE_PERCENTAGE),
909910
"Delayed fee should be the initial fee"
910911
);
911912
}
@@ -926,7 +927,7 @@ abstract contract BaseProverManagerTest is Test {
926927
assertEq(fee, bidFee, "Fee should be the bid fee");
927928
assertEq(
928929
delayedFee,
929-
uint96(_calculatePercentage(bidFee, DELAYED_FEE_PERCENTAGE)),
930+
uint96(LibPercentage.scaleByPercentage(bidFee, DELAYED_FEE_PERCENTAGE)),
930931
"Delayed fee should be the bid fee"
931932
);
932933
}
@@ -942,8 +943,9 @@ abstract contract BaseProverManagerTest is Test {
942943
internal
943944
returns (IPublicationFeed.PublicationHeader[] memory)
944945
{
945-
uint256 depositAmount =
946-
delayed ? _calculatePercentage(fee, DELAYED_FEE_PERCENTAGE) * numPublications : fee * numPublications;
946+
uint256 depositAmount = delayed
947+
? LibPercentage.scaleByPercentage(fee, DELAYED_FEE_PERCENTAGE) * numPublications
948+
: fee * numPublications;
947949
_deposit(proposer, depositAmount);
948950

949951
IPublicationFeed.PublicationHeader[] memory headers = new IPublicationFeed.PublicationHeader[](numPublications);
@@ -974,11 +976,7 @@ abstract contract BaseProverManagerTest is Test {
974976
function _deposit(address user, uint256 amount) internal virtual;
975977

976978
function _maxAllowedFee(uint96 fee) internal pure returns (uint96) {
977-
return uint96(_calculatePercentage(fee, MAX_BID_PERCENTAGE));
978-
}
979-
980-
function _calculatePercentage(uint256 amount, uint16 percentage) internal pure returns (uint256) {
981-
return amount * percentage / 10_000;
979+
return uint96(LibPercentage.scaleByBPS(fee, MAX_BID_PERCENTAGE));
982980
}
983981

984982
function _exit(address prover) internal {

0 commit comments

Comments
 (0)