Skip to content

Commit 199a6a0

Browse files
committed
Adding decay with test
1 parent ecd2740 commit 199a6a0

3 files changed

Lines changed: 160 additions & 0 deletions

File tree

src/BPVoteManager.sol

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pragma solidity ^0.8.20;
44
import "./NameConversion.sol";
55
import {Ownable} from "../lib/openzeppelin-contracts/contracts/access/Ownable.sol";
66
import "../lib/openzeppelin-contracts/contracts/utils/Strings.sol";
7+
import {console} from "../lib/forge-std/src/console.sol";
78

89
/**
910
* @dev Minimal interface to the STLOS Vault (ERC4626).
@@ -27,6 +28,8 @@ contract BPVoteManager is Ownable {
2728
// ========== State ==========
2829

2930
ISTLOSVault public stlosVault;
31+
uint256 public decayStartEpoch;
32+
uint256 public decayIncreaseYearly;
3033

3134
// Aggregated "weighted TLOS" votes per BP
3235
mapping(uint64 => uint256) public totalVotes;
@@ -63,6 +66,15 @@ contract BPVoteManager is Ownable {
6366
stlosVault = ISTLOSVault(_stlosVault);
6467
}
6568

69+
// ========== Owner Functions ==========
70+
function setDecayStartEpoch(uint256 _decayStartEpoch) external onlyOwner {
71+
decayStartEpoch = _decayStartEpoch;
72+
}
73+
74+
function setDecayIncreaseYearly(uint256 _decayIncreaseYearly) external onlyOwner {
75+
decayIncreaseYearly = _decayIncreaseYearly;
76+
}
77+
6678
// ========== BP Management ==========
6779

6880
function registerBP(uint64 bpName) external onlyOwner {
@@ -212,6 +224,9 @@ contract BPVoteManager is Ownable {
212224
// Inverse weighting
213225
uint256 weightedTlos = inverseVoteWeight(tlosAmount, bps.length);
214226

227+
// Vote decay multiplier
228+
weightedTlos = applyDecayMultiplier(weightedTlos);
229+
215230
// Add aggregator
216231
for (uint256 i = 0; i < bps.length; i++) {
217232
totalVotes[bps[i]] += weightedTlos;
@@ -318,6 +333,48 @@ contract BPVoteManager is Ownable {
318333
return weighted;
319334
}
320335

336+
/**
337+
* @dev Apply the decay multiplier to the weighted TLOS.
338+
* The decay multiplier follows the below code from native C++ side:
339+
340+
auto inverse_weighted_vote = inverse_vote_weight((double)totalStaked, (double) producers.size());
341+
342+
// TODO: read these from a singleton config table
343+
auto decay_start_epoch = 1743739705;
344+
auto decay_increase_step = 0.0000001;
345+
auto decay_increase_interval_sec = 60;
346+
347+
auto new_vote_weight = inverse_weighted_vote;
348+
349+
if (decay_start_epoch > 0) {
350+
auto decay_increase = (current_time_point().sec_since_epoch() - decay_start_epoch) / decay_increase_interval_sec;
351+
auto decay_multiplier = 1 + (decay_increase * decay_increase_step);
352+
new_vote_weight = inverse_weighted_vote * decay_multiplier;
353+
}
354+
*/
355+
function applyDecayMultiplier(uint256 weightedTlos) public view returns (uint256) {
356+
if (decayStartEpoch == 0 || block.timestamp <= decayStartEpoch) {
357+
return weightedTlos;
358+
}
359+
360+
// Get current time in seconds since epoch
361+
uint256 currentTime = block.timestamp;
362+
uint256 secondsOfDecay = currentTime - decayStartEpoch;
363+
364+
// Calculate decay increase
365+
// console.log("Current time: %s", currentTime);
366+
// console.log("Decay increase yearly: %s", decayIncreaseYearly);
367+
// console.log("Seconds of decay: %s", secondsOfDecay);
368+
// 31536000 seconds per year
369+
uint256 decayMultiplier = 1e18 + ((decayIncreaseYearly * secondsOfDecay) / 31536000);
370+
// console.log("Decay multiplier: %s", decayMultiplier);
371+
372+
373+
// Apply decay multiplier
374+
uint256 newVoteWeight = Math.mulDiv(weightedTlos, decayMultiplier, 1e18);
375+
return newVoteWeight;
376+
}
377+
321378
/**
322379
* @dev Convert a uint256 to decimal string (from OpenZeppelin Strings).
323380
*/

test/BPVoteManager.t.sol

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,51 @@ contract CounterTest is Test {
412412
uint256 weight = voteManager.inverseVoteWeight(amount, bpCount);
413413
assertEq(weight, expectedWeight, "Weighted vote does not match expected");
414414
}
415+
416+
// ========== 8) Test applyDecayMultiplier calculation ==========
417+
418+
function testApplyDecayMultiplier() public {
419+
uint256 yearOfDecay = 60 * 60 * 24 * 365;
420+
// Use 183 because with 182 it rounds down and half of 50 becomes 24 but with 183 it rounds down to 25
421+
uint256 halfYearOfDecay = 60 * 60 * 24 * 183;
422+
uint256 fiftyPercentDecayRate = 500000000000000000;
423+
uint256 fivePercentDecayRate = 50000000000000000;
424+
checkDecay(100, 150, yearOfDecay, fiftyPercentDecayRate);
425+
checkDecay(100, 125, halfYearOfDecay, fiftyPercentDecayRate);
426+
checkDecay(100, 105, yearOfDecay, fivePercentDecayRate);
427+
428+
// Large number tests (10 million * 1e18 = 10 million tokens)
429+
uint256 largeInitial = 10_000_000 * 1e18;
430+
checkDecay(largeInitial, (largeInitial * 150) / 100, yearOfDecay, fiftyPercentDecayRate); // 50% increase
431+
432+
uint256 expected = calculateExpected(largeInitial, halfYearOfDecay, fiftyPercentDecayRate);
433+
checkDecay(largeInitial, expected, halfYearOfDecay, fiftyPercentDecayRate);
434+
435+
checkDecay(largeInitial, (largeInitial * 105) / 100, yearOfDecay, fivePercentDecayRate); // 5% increase
436+
}
437+
438+
function calculateExpected(
439+
uint256 amount,
440+
uint256 secondsOfDecay,
441+
uint256 decayIncreaseYearly
442+
) internal pure returns (uint256) {
443+
// Compute: decayMultiplier = 1e18 + ((decayIncreaseYearly * secondsOfDecay) / SECONDS_PER_YEAR)
444+
uint256 decayMultiplier = 1e18 + ((decayIncreaseYearly * secondsOfDecay) / 31536000);
445+
// Apply: (amount * decayMultiplier) / 1e18
446+
return (amount * decayMultiplier) / 1e18;
447+
}
448+
449+
function checkDecay(uint256 amount, uint256 expectedWeight, uint256 secondsOfDecay, uint256 decayIncreaseYearly) internal {
450+
// This can be anything, just as long as we can go back a year
451+
// (i.e. it can't be 0 which is the default if you don't set it)
452+
vm.warp(1743800000);
453+
// console.log("Block timestamp: %s", vm.getBlockTimestamp());
454+
voteManager.setDecayStartEpoch(vm.getBlockTimestamp() - secondsOfDecay);
455+
//decayIncreaseYearly = 0.5 × 1e18 = 500000000000000000
456+
voteManager.setDecayIncreaseYearly(decayIncreaseYearly);
457+
// console.log("Decay start epoch: %s", voteManager.decayStartEpoch());
458+
// console.log("Decay increase yearly: %s", voteManager.decayIncreaseYearly());
459+
uint256 weight = voteManager.applyDecayMultiplier(amount);
460+
assertEq(weight, expectedWeight, "applyDecayMultiplier does not match expected");
461+
}
415462
}

test/test_native_function.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include <cstdint>
2+
#include <cstdio>
3+
#include <cmath>
4+
5+
// Updated implementation for C++ without Solidity scaling
6+
double apply_decay_multiplier(double weighted_vote, uint32_t sec_since_epoch, uint64_t decay_start_epoch, uint64_t decay_increase_yearly) {
7+
if (decay_start_epoch == 0 || sec_since_epoch <= decay_start_epoch) {
8+
return weighted_vote;
9+
}
10+
11+
const uint32_t SECONDS_PER_YEAR = 31536000;
12+
uint64_t seconds_of_decay = sec_since_epoch - decay_start_epoch;
13+
14+
// decay_increase_yearly is now directly a percentage (e.g., 50 for 50%)
15+
double decay_multiplier = 1.0 + (static_cast<double>(decay_increase_yearly) / 100.0) * (static_cast<double>(seconds_of_decay) / SECONDS_PER_YEAR);
16+
17+
double new_vote_weight = weighted_vote * decay_multiplier;
18+
19+
return new_vote_weight;
20+
}
21+
22+
// Basic test function
23+
void checkDecay(double initialVote, double expectedVote, uint32_t decaySeconds, uint64_t decayIncreaseYearly) {
24+
uint64_t decayStartEpoch = 1000000000; // arbitrary epoch
25+
uint32_t testEpoch = decayStartEpoch + decaySeconds;
26+
double result = apply_decay_multiplier(initialVote, testEpoch, decayStartEpoch, decayIncreaseYearly);
27+
28+
printf("Initial vote: %.2f, Expected vote: %.2f, Actual vote: %.2f\n", initialVote, expectedVote, result);
29+
30+
if (fabs(result - expectedVote) < 0.1) {
31+
printf("Test PASSED\n\n");
32+
} else {
33+
printf("Test FAILED\n\n");
34+
}
35+
}
36+
37+
int main() {
38+
uint32_t yearOfDecay = 60 * 60 * 24 * 365;
39+
uint32_t halfYearOfDecay = yearOfDecay / 2;
40+
41+
checkDecay(100, 150, yearOfDecay, 50);
42+
checkDecay(100, 125, halfYearOfDecay, 50);
43+
checkDecay(100, 105, yearOfDecay, 5);
44+
45+
// Large value test (10 million * 10,000 for 4 decimal precision)
46+
double largeInitial = 10000000.0 * 10000.0;
47+
double expectedLargeFullYear = largeInitial * 1.5; // 50% increase
48+
double expectedLargeHalfYear = largeInitial * 1.25; // 25% increase
49+
double expectedLargeFullYearFivePercent = largeInitial * 1.05; // 5% increase`
50+
51+
checkDecay(largeInitial, expectedLargeFullYear, yearOfDecay, 50);
52+
checkDecay(largeInitial, expectedLargeHalfYear, halfYearOfDecay, 50);
53+
checkDecay(largeInitial, expectedLargeFullYearFivePercent, yearOfDecay, 5);
54+
55+
return 0;
56+
}

0 commit comments

Comments
 (0)