From 6c655490464218feb8ffdb9dc9336da5a7a7dc16 Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Wed, 10 Jul 2024 19:10:58 -0300
Subject: [PATCH 01/16] shuffles validators with same bid value
---
.../sources/ol_sources/proof_of_fee.move | 234 +++++++++++++++---
.../libra-framework/sources/randomness.move | 2 +-
2 files changed, 205 insertions(+), 31 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/proof_of_fee.move b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
index 1dcb3e87a..f3159c711 100644
--- a/framework/libra-framework/sources/ol_sources/proof_of_fee.move
+++ b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
@@ -14,14 +14,15 @@ module ol_framework::proof_of_fee {
use std::vector;
use std::fixed_point32;
use diem_framework::validator_universe;
- use ol_framework::jail;
- use ol_framework::slow_wallet;
- use ol_framework::vouch;
use diem_framework::transaction_fee;
- use ol_framework::epoch_helper;
+ use diem_framework::randomness;
use diem_framework::account;
use diem_framework::stake;
use diem_framework::system_addresses;
+ use ol_framework::jail;
+ use ol_framework::slow_wallet;
+ use ol_framework::vouch;
+ use ol_framework::epoch_helper;
use ol_framework::globals;
// use diem_std::debug::print;
@@ -32,7 +33,6 @@ module ol_framework::proof_of_fee {
#[test_only]
friend ol_framework::mock;
-
/// The nominal reward for each validator in each epoch.
const GENESIS_BASELINE_REWARD: u64 = 1000000;
@@ -63,7 +63,6 @@ module ol_framework::proof_of_fee {
/// not enough coin balance
const ELOW_UNLOCKED_COIN_BALANCE: u64 = 17;
-
// A struct on the validators account which indicates their
// latest bid (and epoch)
struct ProofOfFeeAuction has key {
@@ -249,40 +248,67 @@ module ol_framework::proof_of_fee {
k = k + 1;
};
- // Sorting the accounts vector based on value (weights).
- // Bubble sort algorithm
- let len_filtered = vector::length
(&filtered_vals);
- // if there's only one person (testing)
- if (len_filtered < 2) return (filtered_vals, bids);
- let i = 0;
- while (i < len_filtered){
- let j = 0;
- while(j < len_filtered-i-1){
+ // Sorting the accounts vector based on value (weights)
+ bubble_sort_bids(&mut bids, &mut filtered_vals);
- let value_j = *(vector::borrow(&bids, j));
+ // Reverse to have sorted order - high to low.
+ vector::reverse(&mut filtered_vals);
+ vector::reverse(&mut bids);
- let value_jp1 = *(vector::borrow(&bids, j+1));
- if(value_j > value_jp1){
+ // Shuffle duplicates to garantee randomness/fairness
+ shuffle_duplicates(&mut bids, &mut filtered_vals);
- vector::swap(&mut bids, j, j+1);
+ return (filtered_vals, bids)
+ }
- vector::swap(&mut filtered_vals, j, j+1);
+ fun bubble_sort_bids(bids: &mut vector, addresses: &mut vector) {
+ let len = vector::length(bids);
+ let i = 0;
+ while (i < len) {
+ let j = 0;
+ while (j < len - i - 1) {
+ let value_j = *vector::borrow(bids, j);
+ let value_jp1 = *vector::borrow(bids, j + 1);
+ if (value_j > value_jp1) {
+ vector::swap(bids, j, j + 1);
+ vector::swap(addresses, j, j + 1);
};
j = j + 1;
-
};
i = i + 1;
-
};
+ }
-
- // Reverse to have sorted order - high to low.
- vector::reverse(&mut filtered_vals);
- vector::reverse(&mut bids);
-
- return (filtered_vals, bids)
+ fun shuffle_duplicates(bids: &vector, addresses: &mut vector) {
+ let len = vector::length(bids);
+ let i = 0;
+ while (i < len) {
+ let j = i + 1;
+ while (j < len && *vector::borrow(bids, i) == *vector::borrow(bids, j)) {
+ j = j + 1;
+ };
+ if (j > i + 1) {
+ let slice_len = j - i;
+ let perm = randomness::permutation(slice_len);
+ let temp_addresses = vector::empty();
+ let k = 0;
+ while (k < slice_len) {
+ let pos = i + k;
+ vector::push_back(&mut temp_addresses, *vector::borrow(addresses, pos));
+ k = k + 1;
+ };
+ k = 0;
+ while (k < slice_len) {
+ let perm_pos = *vector::borrow(&perm, k);
+ *vector::borrow_mut(addresses, i + k) = *vector::borrow(&temp_addresses, perm_pos);
+ k = k + 1;
+ };
+ };
+ i = j;
+ };
}
+
#[view]
/// checks if the validator has enough vouchers in the current set (prior to entry)
/// @params incoming address to be evaluated
@@ -1003,7 +1029,7 @@ module ol_framework::proof_of_fee {
}
- // Scenario: The reward is too low during 5 days (short window). People are not bidding very high.
+ // Scenario: The reward is too low during 5 days (short window). People are not bidding very high.
#[test(vm = @ol_framework)]
fun thermostat_decrease_long(vm: signer) acquires ConsensusReward {
use diem_framework::chain_id;
@@ -1024,7 +1050,6 @@ module ol_framework::proof_of_fee {
i = i + 1;
};
-
test_mock_reward(
&vm,
100,
@@ -1063,4 +1088,153 @@ module ol_framework::proof_of_fee {
// validator_universe::is_in_universe(@0x123);
// }
+
+ // Bubble Sort tests
+
+ #[test]
+ fun test_bubble_sort_empty_vectors() {
+ let bids: vector = vector::empty();
+ let addresses: vector = vector::empty();
+ bubble_sort_bids(&mut bids, &mut addresses);
+ assert!(bids == vector[], 7357001);
+ assert!(addresses == vector[], 7357002);
+ }
+
+ #[test]
+ fun test_bubble_sort_single_element() {
+ let bids = vector[10];
+ let addresses = vector[@0x1];
+ bubble_sort_bids(&mut bids, &mut addresses);
+ assert!(bids == vector[10], 7357003);
+ assert!(addresses == vector[@0x1], 7357004);
+ }
+
+ #[test]
+ fun test_bubble_sort_already_sorted() {
+ let bids = vector[1, 2, 3, 4, 5];
+ let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
+ bubble_sort_bids(&mut bids, &mut addresses);
+ assert!(bids == vector[1, 2, 3, 4, 5], 7357005);
+ assert!(addresses == vector[@0x1, @0x2, @0x3, @0x4, @0x5], 7357006);
+ }
+
+ #[test]
+ fun test_bubble_sort_reverse_order() {
+ let bids = vector[5, 4, 3, 2, 1];
+ let addresses = vector[@0x5, @0x4, @0x3, @0x2, @0x1];
+ bubble_sort_bids(&mut bids, &mut addresses);
+ assert!(bids == vector[1, 2, 3, 4, 5], 7357007);
+ assert!(addresses == vector[@0x1, @0x2, @0x3, @0x4, @0x5], 7357008);
+ }
+
+ #[test]
+ fun test_bubble_sort_with_duplicates() {
+ let bids = vector[4, 2, 2, 3, 1];
+ let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
+ bubble_sort_bids(&mut bids, &mut addresses);
+ assert!(bids == vector[1, 2, 2, 3, 4], 7357009);
+ assert!(addresses == vector[@0x5, @0x2, @0x3, @0x4, @0x1], 7357010);
+ }
+
+ #[test]
+ fun test_bubble_sort_random_order() {
+ let bids = vector[3, 1, 4, 5, 2];
+ let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
+ bubble_sort_bids(&mut bids, &mut addresses);
+ assert!(bids == vector[1, 2, 3, 4, 5], 7357011);
+ assert!(addresses == vector[@0x2, @0x5, @0x1, @0x3, @0x4], 7357012);
+ }
+
+ #[test]
+ fun test_bubble_sort_all_elements_equal() {
+ let bids = vector[3, 3, 3, 3, 3];
+ let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
+ bubble_sort_bids(&mut bids, &mut addresses);
+ assert!(bids == vector[3, 3, 3, 3, 3], 7357013);
+ assert!(addresses == vector[@0x1, @0x2, @0x3, @0x4, @0x5], 7357014);
+ }
+
+ // Shuffle Tests
+
+ #[test]
+ fun test_shuffle_no_duplicates() {
+ // No duplicates in the bids vector
+ let bids = vector[1, 2, 3, 4, 5];
+ let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
+ shuffle_duplicates(&mut bids, &mut addresses);
+ assert!(bids == vector[1, 2, 3, 4, 5], 7357015);
+ assert!(addresses == vector[@0x1, @0x2, @0x3, @0x4, @0x5], 7357016);
+ }
+
+ #[test(root = @ol_framework)]
+ fun test_shuffle_with_duplicates(root: &signer) {
+ // One group of duplicates in the bids vector
+ randomness::initialize(root);
+ randomness::set_seed(x"0000000000000000000000000000000000000000000000000000000000000000");
+ let bids = vector[1, 2, 2, 3, 4];
+ let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
+ let original_addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
+ let shuffled = false;
+ let i = 0;
+
+ while (i < 10) {
+ shuffle_duplicates(&mut bids, &mut addresses);
+ if (addresses != original_addresses) {
+ shuffled = true;
+ break
+ };
+ i = i + 1;
+ };
+
+ assert!(bids == vector[1, 2, 2, 3, 4], 7357017);
+ assert!(shuffled, 7357018);
+ }
+
+ #[test(root = @ol_framework)]
+ fun test_shuffle_multiple_duplicate_groups(root: &signer) {
+ // Multiple groups of duplicates in the bids vector
+ randomness::initialize(root);
+ randomness::set_seed(x"0000000000000000000000000000000000000000000000000000000000000000");
+ let bids = vector[1, 2, 2, 3, 3, 4];
+ let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5, @0x6];
+ let original_addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5, @0x6];
+ let shuffled = false;
+ let i = 0;
+
+ while (i < 10) {
+ shuffle_duplicates(&mut bids, &mut addresses);
+ if (addresses != original_addresses) {
+ shuffled = true;
+ break
+ };
+ i = i + 1;
+ };
+
+ assert!(bids == vector[1, 2, 2, 3, 3, 4], 7357019);
+ assert!(shuffled, 7357020);
+ }
+
+ #[test(root = @ol_framework)]
+ fun test_shuffle_all_elements_equal(root: &signer) {
+ // All elements in the bids vector are the same
+ randomness::initialize(root);
+ randomness::set_seed(x"0000000000000000000000000000000000000000000000000000000000000000");
+ let bids = vector[2, 2, 2, 2];
+ let addresses = vector[@0x1, @0x2, @0x3, @0x4];
+ let original_addresses = vector[@0x1, @0x2, @0x3, @0x4];
+ let shuffled = false;
+ let i = 0;
+
+ while (i < 10) {
+ shuffle_duplicates(&mut bids, &mut addresses);
+ if (addresses != original_addresses) {
+ shuffled = true;
+ break
+ };
+ i = i + 1;
+ };
+
+ assert!(bids == vector[2, 2, 2, 2], 7357021);
+ assert!(shuffled, 7357022);
+ }
}
diff --git a/framework/libra-framework/sources/randomness.move b/framework/libra-framework/sources/randomness.move
index 53061cce5..7fab83a78 100644
--- a/framework/libra-framework/sources/randomness.move
+++ b/framework/libra-framework/sources/randomness.move
@@ -342,7 +342,7 @@ module diem_framework::randomness {
/// Generate a permutation of `[0, 1, ..., n-1]` uniformly at random.
/// If n is 0, returns the empty vector.
- fun permutation(n: u64): vector acquires PerBlockRandomness {
+ public fun permutation(n: u64): vector acquires PerBlockRandomness {
let values = vector[];
if(n == 0) {
From 6cf0e6f6c79f120e0241bbcc412bec4c45967284 Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Fri, 12 Jul 2024 07:20:03 -0300
Subject: [PATCH 02/16] reduces val set for competition between qualified
bidders having 21 or more compliant
---
.../sources/ol_sources/proof_of_fee.move | 340 +++++++++++++-----
1 file changed, 249 insertions(+), 91 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/proof_of_fee.move b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
index f3159c711..e494b933f 100644
--- a/framework/libra-framework/sources/ol_sources/proof_of_fee.move
+++ b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
@@ -4,14 +4,13 @@
/////////////////////////////////////////////////////////////////////////
// NOTE: this module replaces NodeWeight.move, which becomes redundant since
// all validators have equal weight in consensus.
-// TODO: the bubble sort functions were lifted directly from NodeWeight, needs checking.
///////////////////////////////////////////////////////////////////////////
-
module ol_framework::proof_of_fee {
use std::error;
use std::signer;
use std::vector;
+ use std::math64;
use std::fixed_point32;
use diem_framework::validator_universe;
use diem_framework::transaction_fee;
@@ -24,7 +23,7 @@ module ol_framework::proof_of_fee {
use ol_framework::vouch;
use ol_framework::epoch_helper;
use ol_framework::globals;
- // use diem_std::debug::print;
+ //use diem_std::debug::print;
friend diem_framework::genesis;
friend ol_framework::epoch_boundary;
@@ -38,8 +37,12 @@ module ol_framework::proof_of_fee {
/// Number of vals needed before PoF becomes competitive for
/// performant nodes as well
- const VAL_BOOT_UP_THRESHOLD: u64 = 19;
-
+ const VAL_BOOT_UP_THRESHOLD: u64 = 21;
+ /// This figure is experimental and a different percentage may be finalized
+ /// after some experience in the wild. Additionally it could be dynamic
+ /// based on another function or simply randomized within a range
+ /// (as originally proposed in this feature request)
+ const PCT_REDUCTION_FOR_COMPETITION: u64 = 10; // 10%
//////// ERRORS /////////
/// Not an active validator
@@ -131,57 +134,86 @@ module ol_framework::proof_of_fee {
}
}
- /// Consolidates all the logic for the epoch boundary/
- /// Includes: getting the sorted bids,
- /// filling the seats (determined by MusicalChairs), and getting a price.
- /// and finally charging the validators for their bid (everyone pays the lowest)
- /// for audit instrumentation returns: final set size, auction winners, all the bidders, (including not-qualified), and all qualified bidders.
- /// we also return the auction entry price (clearing price)
+ /// Consolidates all the logic for the epoch boundary, including:
+ /// 1. Getting the sorted bidders,
+ /// 2. Calculate final validators set size (number of seats to fill),
+ /// 3. Filling the seats,
+ /// 4. Getting a price,
+ /// 5. Finally charging the validators for their bid (everyone pays the lowest)
+ /// For audit instrumentation returns: final set size, auction winners, all the bidders, (including not-qualified), and all qualified bidders.
+ /// We also return the auction entry price (clearing price)
/// (final_set_size, auction_winners, all_bidders, only_qualified_bidders, actually_paid, entry_fee)
public(friend) fun end_epoch(
vm: &signer,
outgoing_compliant_set: &vector,
- final_set_size: u64
+ mc_set_size: u64 // musical chairs set size suggestion
): (vector, vector, vector, u64) acquires ProofOfFeeAuction, ConsensusReward {
system_addresses::assert_ol(vm);
let all_bidders = get_bidders(false);
let only_qualified_bidders = get_bidders(true);
- // The set size as determined by musical chairs is a target size
- // but the actual final size depends on how much can we expand the set
- // without adding too many unproven nodes (which we don't know if they are prepared to validate, and risk halting th network).
-
-
- // Boot up
- // After an upgrade or incident the network may need to rebuild the
- // validator set from a small base.
- // we should increase the available seats starting from a base of
- // compliant nodes. And make it competitive for the unknown nodes.
- // Instead of increasing the seats by +1 the compliant vals we should
- // increase by compliant + (1/2 compliant - 1) or another
- // safe threshold.
- // Another effect is that with PoF we might be dropping compliant nodes,
- // in favor of unknown nodes with high bids.
- // So in the case of a small validator set, we ignore the musical_chairs
- // suggestion, and increase the seats offered, and guarantee seats to
- // performant nodes.
- let performant_len = vector::length(outgoing_compliant_set);
- if (
- performant_len < VAL_BOOT_UP_THRESHOLD &&
- performant_len > 2
- ) {
- final_set_size = performant_len + (performant_len/2 - 1)
- };
+
+ // Calculate the final set size considering the number of compliant validators,
+ // number of qualified bidders, and musical chairs set size suggestion
+ let final_set_size = calculate_final_set_size(
+ vector::length(outgoing_compliant_set),
+ vector::length(&only_qualified_bidders),
+ mc_set_size);
// This is the core of the mechanism, the uniform price auction
// the winners of the auction will be the validator set.
- // other lists are created for audit purposes of the BoundaryStatus
+ // Other lists are created for audit purposes of the BoundaryStatus
let (auction_winners, entry_fee, _clearing_bid, _proven, _unproven) = fill_seats_and_get_price(vm, final_set_size, &only_qualified_bidders, outgoing_compliant_set);
-
(auction_winners, all_bidders, only_qualified_bidders, entry_fee)
}
+
+ // The set size as determined by musical chairs is a target size
+ // but the actual final size depends on:
+ // a. how much can we expand the set without adding too many unproven nodes (which we don't know
+ // if they are prepared to validate, and risk halting the network).
+ // b. how many qualified bidders are there, and how many seats can we offer to ensure competition.
+
+ // 1. Boot Up
+ // After an upgrade or incident the network may need to rebuild the validator set
+ // from a small base. We should increase the available seats starting
+ // from a base of compliant nodes. And make it competitive for the unknown nodes.
+ // Instead of increasing the seats by +1 the compliant vals we should
+ // increase by compliant + (1/2 compliant - 1) or another safe threshold.
+ // Another effect is that with PoF we might be dropping compliant nodes,
+ // in favor of unknown nodes with high bids. So in the case of a small validator set,
+ // we ignore the musical_chairs suggestion, and increase the seats offered, and guarantee seats to
+ // performant nodes.
+ //
+ // 2. Competitive Set
+ // If we have more qualified bidders than the threshold, we should limit the final set size
+ // to 90% of the qualified bidders to ensure that vals will compete for seats.
+
+ fun calculate_final_set_size(
+ outgoing_compliant: u64,
+ qualified_bidders: u64,
+ mc_set_size: u64 // musical chairs set size suggestion
+ ): u64 {
+ // 1. Boot Up
+ if (
+ outgoing_compliant < VAL_BOOT_UP_THRESHOLD &&
+ outgoing_compliant > 2
+ ) {
+ return math64::min(outgoing_compliant + (outgoing_compliant/2 - 1), VAL_BOOT_UP_THRESHOLD)
+ };
+
+ // 2. Competitive Set
+ if (mc_set_size >= VAL_BOOT_UP_THRESHOLD && qualified_bidders >= VAL_BOOT_UP_THRESHOLD) {
+ let seats_to_remove = qualified_bidders * PCT_REDUCTION_FOR_COMPETITION / 100;
+ let max_qualified = qualified_bidders - seats_to_remove;
+ // do note increase beyond musical chairs suggestion and competitive set size
+ return math64::min(max_qualified, mc_set_size)
+ };
+
+ mc_set_size
+ }
+
/// The fees are charged seperate from the auction and seating loop
/// this is because there are edge conditions in which the winners
/// may not be the ones seated (once we consider failover rules).
@@ -196,9 +228,6 @@ module ol_framework::proof_of_fee {
}
-
-
-
//////// CONSENSUS CRITICAL ////////
// Get the validator universe sorted by bid
// By default this will return a FILTERED list of validators
@@ -233,11 +262,8 @@ module ol_framework::proof_of_fee {
let k = 0;
while (k < length) {
// TODO: Ensure that this address is an active validator
-
let cur_address = *vector::borrow(eligible_validators, k);
-
let (bid, _expire) = current_bid(cur_address);
-
let (_, qualified) = audit_qualification(cur_address);
if (remove_unqualified && !qualified) {
k = k + 1;
@@ -248,7 +274,7 @@ module ol_framework::proof_of_fee {
k = k + 1;
};
- // Sorting the accounts vector based on value (weights)
+ // Sorting the accounts vector based on their bids
bubble_sort_bids(&mut bids, &mut filtered_vals);
// Reverse to have sorted order - high to low.
@@ -261,6 +287,7 @@ module ol_framework::proof_of_fee {
return (filtered_vals, bids)
}
+ // Bubble sort bids and corresponding addresses
fun bubble_sort_bids(bids: &mut vector, addresses: &mut vector) {
let len = vector::length(bids);
let i = 0;
@@ -279,6 +306,7 @@ module ol_framework::proof_of_fee {
};
}
+ // Shuffle addresses with the same bid amount to ensure randomness position
fun shuffle_duplicates(bids: &vector, addresses: &mut vector) {
let len = vector::length(bids);
let i = 0;
@@ -326,7 +354,7 @@ module ol_framework::proof_of_fee {
// Here we place the bidders into their seats.
// The order of the bids will determine placement.
// One important aspect of picking the next validator set:
- // it should have 2/3rds of known good ("proven") validators
+ // It should have 2/3rds of known good ("proven") validators
// from the previous epoch. Otherwise the unproven nodes, who
// may not be ready for consensus, might be offline and cause a halt.
// Validators can be inattentive and have bids that qualify, but their nodes
@@ -340,7 +368,9 @@ module ol_framework::proof_of_fee {
// This is a potential issue again with inattentive validators who
// have have a high bid, but again they fail repeatedly to finalize an epoch
// successfully. Their bids should not penalize validators who don't have
- // a streak of jailed epochs. So of the 1/3 unproven nodes, we'll first seat the validators with Jail.consecutive_failure_to_rejoin < 2, and after that the remainder.
+ // a streak of jailed epochs. So of the 1/3 unproven nodes,
+ // we'll first seat the validators with Jail.consecutive_failure_to_rejoin < 2,
+ // and after that the remainder.
// There's some code implemented which is not enabled in the current form.
// Unsealed auctions are tricky. The Proof Of Fee
@@ -350,14 +380,18 @@ module ol_framework::proof_of_fee {
// As such per epoch the validator is only allowed to revise their bids /
// down once. To do this in practice they need to retract a bid (sit out
// the auction), and then place a new bid.
- // A validator can always leave the auction, but if they rejoin a second time in the epoch, then they've committed a bid until the next epoch.
- // So retracting should be done with care. The ergonomics are not great.
+ // A validator can always leave the auction, but if they rejoin a second time in the epoch,
+ // then they've committed a bid until the next epoch.
+ // So retracting should be done with care. The ergonomics are not great.
// The preference would be not to have this constraint if on the margins
// the ergonomics brings more bidders than attackers.
// After more experience in the wild, the network may decide to
// limit bid retracting.
- // The Validator must qualify on a number of metrics: have funds in their Unlocked account to cover bid, have miniumum viable vouches, and not have been jailed in the previous round.
+ // The Validator must qualify on a number of metrics:
+ // 1. have funds in their Unlocked account to cover bid,
+ // 2. have miniumum viable vouches,
+ // 3. and not have been jailed in the previous round.
/// Showtime.
/// This is where we take all the bidders and seat them.
@@ -365,11 +399,11 @@ module ol_framework::proof_of_fee {
/// This function assumes we have already filtered out ineligible validators.
/// but we will check again here.
/// we return:
- // a. the list of winning validators (the validator set)
- // b. the entry fee paid
- // c. the clearing bid (percentage paid)
- // d. the list of proven nodes added, for audit and instrumentation
- // e. the list of unproven, for audit and instrumentation
+ /// a. the list of winning validators (the validator set)
+ /// b. the entry fee paid
+ /// c. the clearing bid (percentage paid)
+ /// d. the list of proven nodes added, for audit and instrumentation
+ /// e. the list of unproven, for audit and instrumentation
public(friend) fun fill_seats_and_get_price(
vm: &signer,
final_set_size: u64,
@@ -522,14 +556,12 @@ module ol_framework::proof_of_fee {
let cr = borrow_global_mut(@ol_framework);
-
let len = vector::length(&cr.median_history);
let i = 0;
let epochs_above = 0;
let epochs_below = 0;
while (i < 16 && i < len) { // max ten days, but may have less in history, filling set should truncate the history at 15 epochs.
-
let avg_bid = *vector::borrow(&cr.median_history, i);
if (avg_bid > bid_upper_bound) {
@@ -541,10 +573,7 @@ module ol_framework::proof_of_fee {
i = i + 1;
};
-
if (cr.nominal_reward > 0) {
-
-
// TODO: this is an initial implementation, we need to
// decide if we want more granularity in the reward adjustment
// Note: making this readable for now, but we can optimize later
@@ -562,51 +591,43 @@ module ol_framework::proof_of_fee {
// at 1% median bid, the implicit bond is 100x the reward.
// We need to DECREASE the reward
-
if (epochs_above > long_window) {
-
// decrease the reward by 10%
let less_ten_pct = (cr.nominal_reward / 10);
cr.nominal_reward = cr.nominal_reward - less_ten_pct;
return (true, false, less_ten_pct)
-
} else if (epochs_above > short_window) {
// decrease the reward by 5%
let less_five_pct = (cr.nominal_reward / 20);
cr.nominal_reward = cr.nominal_reward - less_five_pct;
-
return (true, false, less_five_pct)
}
};
// return early since we can't increase and decrease simultaneously
+ // if validators are bidding low percentages
+ // it means the nominal reward is not high enough.
+ // That is the validator's opportunity cost is not met within a
+ // range where the bond is meaningful.
+ // For example: if the bids for the epoch's reward is 50% of the value, that means the potential profit, is the same as the potential loss.
+ // At a 25% bid (potential loss), the profit is thus 75% of the value, which means the implicit bond is 25/75, or 1/3 of the bond, the risk favors the validator. This means among other things, that an attacker can pay for the cost of the attack with the profits. See paper, for more details.
+
+ // we need to INCREASE the reward, so that the bond is more meaningful.
+
+ if (epochs_below > long_window) {
+ // increase the reward by 10%
+ let increase_ten_pct = (cr.nominal_reward / 10);
+ cr.nominal_reward = cr.nominal_reward + increase_ten_pct;
+ return (true, true, increase_ten_pct)
+ } else if (epochs_below > short_window) {
+ // increase the reward by 5%
+ let increase_five_pct = (cr.nominal_reward / 20);
+ cr.nominal_reward = cr.nominal_reward + increase_five_pct;
+ return (true, true, increase_five_pct)
+ };
- // if validators are bidding low percentages
- // it means the nominal reward is not high enough.
- // That is the validator's opportunity cost is not met within a
- // range where the bond is meaningful.
- // For example: if the bids for the epoch's reward is 50% of the value, that means the potential profit, is the same as the potential loss.
- // At a 25% bid (potential loss), the profit is thus 75% of the value, which means the implicit bond is 25/75, or 1/3 of the bond, the risk favors the validator. This means among other things, that an attacker can pay for the cost of the attack with the profits. See paper, for more details.
-
- // we need to INCREASE the reward, so that the bond is more meaningful.
-
-
- if (epochs_below > long_window) {
- // increase the reward by 10%
- let increase_ten_pct = (cr.nominal_reward / 10);
- cr.nominal_reward = cr.nominal_reward + increase_ten_pct;
- return (true, true, increase_ten_pct)
- } else if (epochs_below > short_window) {
-
-
- // increase the reward by 5%
- let increase_five_pct = (cr.nominal_reward / 20);
- cr.nominal_reward = cr.nominal_reward + increase_five_pct;
- return (true, true, increase_five_pct)
- };
-
- // we ran the thermostat but no change was made.
- return (true, false, 0)
+ // we ran the thermostat but no change was made.
+ return (true, false, 0)
};
// nominal reward is zero, there's a problem
@@ -1089,6 +1110,141 @@ module ol_framework::proof_of_fee {
// }
+
+ // Calculate Final Set Size tests
+
+ #[test]
+ fun test_calculate_final_set_size_boot_up_happy_day() {
+ // Happy Day: test complete boot up with plenty qualified bidders over multiple epochs
+ // having validators always compliant
+
+ // Epoch 1
+ let outgoing_compliant = 0;
+ let qualified_bidders = 100;
+ let mc_set_size = 4;
+ let result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ assert!(result == 4, 7357023);
+
+ // Epoch 2
+ outgoing_compliant = 4;
+ mc_set_size = 5;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // 4 + (4/2 - 1) = 5
+ assert!(result == 5, 7357024);
+
+ // Epoch 3
+ outgoing_compliant = 5;
+ mc_set_size = 6;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // 5 + (5/2 - 1) = 6
+ assert!(result == 6, 7357025);
+
+ // Epoch 4
+ outgoing_compliant = 6;
+ mc_set_size = 7;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // 6 + (6/2 - 1) = 8
+ assert!(result == 8, 7357026);
+
+ // Epoch 5
+ outgoing_compliant = 8;
+ mc_set_size = 9;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // 8 + (8/2 - 1) = 11
+ assert!(result == 11, 7357027);
+
+ // Epoch 6
+ outgoing_compliant = 11;
+ mc_set_size = 12;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // 11 + (11/2 - 1) = 15
+ assert!(result == 15, 7357028);
+
+ // Epoch 6
+ outgoing_compliant = 15;
+ mc_set_size = 16;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // 15 + (15/2 - 1) = 21
+ // min (21, 21)
+ assert!(result == 21, 7357028);
+
+ // Epoch 7 - Boot up ended
+ outgoing_compliant = 21;
+ mc_set_size = 22;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // min(22, 100*90%) = 22
+ assert!(result == 22, 7357029);
+ }
+
+ #[test]
+ fun test_calculate_final_set_size_boot_up_threshold() {
+ let qualified_bidders = 100;
+
+ // Test boot up increases maximum set size to 21
+ let outgoing_compliant = 20;
+ let mc_set_size = 20;
+ let result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ assert!(result == 21, 7357030);
+
+ // Test set size stays at 21
+ outgoing_compliant = 21;
+ mc_set_size = 21;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ assert!(result == 21, 7357030);
+
+ // Test set size increases to 22
+ outgoing_compliant = 22;
+ mc_set_size = 22;
+ result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // min (22, 100*90%) = 22
+ assert!(result == 22, 7357030);
+ }
+
+ #[test]
+ fun test_calculate_final_set_size_competitive_set_no_changes() {
+ let outgoing_compliant = 21;
+ let qualified_bidders = 40;
+ let mc_set_size = 21;
+ let result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ assert!(result == 21, 7357030);
+ }
+
+ #[test]
+ fun test_calculate_final_set_size_competitive_set_increases() {
+ let outgoing_compliant = 30;
+ let qualified_bidders = 40;
+ let mc_set_size = 31;
+ let result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ assert!(result == 31, 7357030);
+ }
+
+ #[test]
+ fun test_calculate_final_set_size_competitive_set_decreases() {
+ let outgoing_compliant = 50;
+ let qualified_bidders = 50;
+ let mc_set_size = 50;
+ let result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ assert!(result == 45, 7357030);
+ }
+
+ #[test]
+ fun test_calculate_final_set_size_competitive_set_decreases_to_boot_up() {
+ let outgoing_compliant = 21;
+ let qualified_bidders = 21;
+ let mc_set_size = 21;
+ let result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // min(21, 21*90%) = 19
+ assert!(result == 19, 7357030);
+
+ let outgoing_compliant = 21;
+ let qualified_bidders = 20;
+ let mc_set_size = 21;
+ let result = calculate_final_set_size(outgoing_compliant, qualified_bidders, mc_set_size);
+ // mc value
+ assert!(result == 21, 7357030);
+ }
+
+
// Bubble Sort tests
#[test]
@@ -1154,6 +1310,8 @@ module ol_framework::proof_of_fee {
assert!(addresses == vector[@0x1, @0x2, @0x3, @0x4, @0x5], 7357014);
}
+
+
// Shuffle Tests
#[test]
From 15b55fb7bcd64e47e057b7f4e64f7448bc8e6bce Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Fri, 12 Jul 2024 09:31:14 -0300
Subject: [PATCH 03/16] initializes randomness on ol_reconfigure_for_test
---
.../sources/ol_sources/epoch_boundary.move | 2 ++
.../sources/ol_sources/proof_of_fee.move | 9 +++------
.../ol_sources/tests/proof_of_fee.test.move | 14 +-------------
3 files changed, 6 insertions(+), 19 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/epoch_boundary.move b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
index b8df79f4f..36fb97925 100644
--- a/framework/libra-framework/sources/ol_sources/epoch_boundary.move
+++ b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
@@ -506,8 +506,10 @@ module diem_framework::epoch_boundary {
public fun ol_reconfigure_for_test(vm: &signer, closing_epoch: u64,
epoch_round: u64) acquires BoundaryStatus {
use diem_framework::system_addresses;
+ use diem_framework::randomness;
system_addresses::assert_ol(vm);
+ randomness::initialize_for_testing(vm);
epoch_boundary(vm, closing_epoch, epoch_round);
}
diff --git a/framework/libra-framework/sources/ol_sources/proof_of_fee.move b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
index e494b933f..7ba4351f9 100644
--- a/framework/libra-framework/sources/ol_sources/proof_of_fee.move
+++ b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
@@ -1327,8 +1327,7 @@ module ol_framework::proof_of_fee {
#[test(root = @ol_framework)]
fun test_shuffle_with_duplicates(root: &signer) {
// One group of duplicates in the bids vector
- randomness::initialize(root);
- randomness::set_seed(x"0000000000000000000000000000000000000000000000000000000000000000");
+ randomness::initialize_for_testing(root);
let bids = vector[1, 2, 2, 3, 4];
let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
let original_addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5];
@@ -1351,8 +1350,7 @@ module ol_framework::proof_of_fee {
#[test(root = @ol_framework)]
fun test_shuffle_multiple_duplicate_groups(root: &signer) {
// Multiple groups of duplicates in the bids vector
- randomness::initialize(root);
- randomness::set_seed(x"0000000000000000000000000000000000000000000000000000000000000000");
+ randomness::initialize_for_testing(root);
let bids = vector[1, 2, 2, 3, 3, 4];
let addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5, @0x6];
let original_addresses = vector[@0x1, @0x2, @0x3, @0x4, @0x5, @0x6];
@@ -1375,8 +1373,7 @@ module ol_framework::proof_of_fee {
#[test(root = @ol_framework)]
fun test_shuffle_all_elements_equal(root: &signer) {
// All elements in the bids vector are the same
- randomness::initialize(root);
- randomness::set_seed(x"0000000000000000000000000000000000000000000000000000000000000000");
+ randomness::initialize_for_testing(root);
let bids = vector[2, 2, 2, 2];
let addresses = vector[@0x1, @0x2, @0x3, @0x4];
let original_addresses = vector[@0x1, @0x2, @0x3, @0x4];
diff --git a/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move b/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
index 8be5ac065..abea11e07 100644
--- a/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
@@ -331,18 +331,15 @@ module ol_framework::test_pof {
let sorted_two = proof_of_fee::get_bidders(true);
assert!(vector::length(&sorted_two) != vector::length(&val_universe), 1004);
-
assert!(vector::length(&sorted_two) == vector::length(&val_universe) - 1, 1005);
-
}
// We can send the fill seats function a list of validators, and the list of performing validators, and it will return the winning bidders and the bid.
#[test(root = @ol_framework)]
fun fill_seats_happy(root: signer) {
let set = mock::genesis_n_vals(&root, 5);
- mock::ol_initialize_coin_and_fund_vals(&root, 500000, true);
let len = vector::length(&set);
-
+ mock::ol_initialize_coin_and_fund_vals(&root, 500000, true);
mock::pof_default();
slow_wallet::slow_wallet_epoch_drip(&root, 500000);
@@ -351,7 +348,6 @@ module ol_framework::test_pof {
assert!(vector::length(&sorted) == vector::length(&set), 1003);
let (seats, _, _, _, _) = proof_of_fee::fill_seats_and_get_price(&root, len, &sorted, &sorted);
-
assert!(vector::contains(&seats, vector::borrow(&set, 0)), 1004);
// filling the seat updated the computation of the consensu reward.
@@ -359,7 +355,6 @@ module ol_framework::test_pof {
assert!(reward == 1000000, 1005);
assert!(clear_percent == 1, 1006);
assert!(median_bid == 3, 1007);
-
}
// We fill all the seats, and run the thermostat
@@ -410,11 +405,9 @@ module ol_framework::test_pof {
mock::ol_initialize_coin_and_fund_vals(&root, 500000, true);
mock::pof_default();
-
// Ok now EVE changes her mind. Will force the bid to expire.
let a_sig = account::create_signer_for_test(*vector::borrow(&set, 4));
proof_of_fee::pof_update_bid(&a_sig, 0, 0);
-
slow_wallet::slow_wallet_epoch_drip(&root, 500000);
let sorted = proof_of_fee::get_bidders(true);
@@ -452,14 +445,9 @@ module ol_framework::test_pof {
fun fill_seats_many_bidders(root: signer) {
let set = mock::genesis_n_vals(&root, 5);
mock::pof_default();
-
mock::ol_initialize_coin_and_fund_vals(&root, 500000, true);
-
let sorted = proof_of_fee::get_bidders(true);
-
-
-
let set_size = 3;
let (seats, _, _, _, _) = proof_of_fee::fill_seats_and_get_price(&root, set_size, &sorted, &sorted);
From 7a761fb54f35cf563daa8c0a0ff2f837ff91fa13 Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Fri, 12 Jul 2024 10:51:42 -0300
Subject: [PATCH 04/16] refactors musical chairs mod and adds more tests
---
.../sources/ol_sources/mock.move | 502 +++++++++---------
.../sources/ol_sources/musical_chairs.move | 495 +++++++++--------
.../ol_sources/tests/musical_chairs.test.move | 86 ++-
3 files changed, 557 insertions(+), 526 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/mock.move b/framework/libra-framework/sources/ol_sources/mock.move
index b99e36079..2ff072302 100644
--- a/framework/libra-framework/sources/ol_sources/mock.move
+++ b/framework/libra-framework/sources/ol_sources/mock.move
@@ -36,265 +36,265 @@ module ol_framework::mock {
#[test_only]
public fun reset_val_perf_all(vm: &signer) {
- let vals = stake::get_current_validators();
- let i = 0;
- while (i < vector::length(&vals)) {
- let a = vector::borrow(&vals, i);
- stake::mock_performance(vm, *a, 0, 0);
- i = i + 1;
- };
+ let vals = stake::get_current_validators();
+ let i = 0;
+ while (i < vector::length(&vals)) {
+ let a = vector::borrow(&vals, i);
+ stake::mock_performance(vm, *a, 0, 0);
+ i = i + 1;
+ };
}
#[test_only]
- public fun mock_case_1(vm: &signer, addr: address){
- assert!(stake::is_valid(addr), 01);
- stake::mock_performance(vm, addr, 100, 10);
- let (compliant, _, _) = grade::get_validator_grade(addr);
- assert!(compliant, 01);
- }
-
-
- #[test_only]
- // did not do enough mining, but did validate.
- public fun mock_case_4(vm: &signer, addr: address){
- assert!(stake::is_valid(addr), 01);
- stake::mock_performance(vm, addr, 0, 100); // 100 failing proposals
- // let (compliant, _, _, _) = grade::get_validator_grade(addr, 0);
- let (compliant, _, _) = grade::get_validator_grade(addr);
- assert!(!compliant, 02);
- }
-
- // Mock all nodes being compliant case 1
- #[test_only]
- public fun mock_all_vals_good_performance(vm: &signer) {
-
- let vals = stake::get_current_validators();
-
- let i = 0;
- while (i < vector::length(&vals)) {
-
- let a = vector::borrow(&vals, i);
- mock_case_1(vm, *a);
- i = i + 1;
- };
-
- }
-
- //////// PROOF OF FEE ////////
- #[test_only]
- public fun pof_default(): (vector, vector, vector){
-
- let vals = stake::get_current_validators();
-
- let (bids, expiry) = mock_bids(&vals);
-
- // make all validators pay auction fee
- // the clearing price in the fibonacci sequence is is 1
- let (alice_bid, _) = proof_of_fee::current_bid(*vector::borrow(&vals, 0));
- assert!(alice_bid == 1, 03);
- (vals, bids, expiry)
- }
-
- #[test_only]
- public fun mock_bids(vals: &vector): (vector, vector) {
- // system_addresses::assert_ol(vm);
- let bids = vector::empty();
- let expiry = vector::empty();
- let i = 0;
- let prev = 0;
- let fib = 1;
- while (i < vector::length(vals)) {
-
- vector::push_back(&mut expiry, 1000);
- let b = prev + fib;
- vector::push_back(&mut bids, b);
-
- let a = vector::borrow(vals, i);
- let sig = account::create_signer_for_test(*a);
- // initialize and set.
- proof_of_fee::pof_update_bid(&sig, b, 1000);
- prev = fib;
- fib = b;
- i = i + 1;
- };
-
- (bids, expiry)
-
- }
-
- use diem_framework::chain_status;
- #[test_only]
- public fun ol_test_genesis(root: &signer) {
- system_addresses::assert_ol(root);
- genesis::setup();
- genesis::test_end_genesis(root);
-
- let mint_cap = init_coin_impl(root);
- libra_coin::restore_mint_cap(root, mint_cap);
-
- assert!(!chain_status::is_genesis(), 0);
- }
-
- #[test_only]
- public fun ol_initialize_coin(root: &signer) {
- system_addresses::assert_ol(root);
-
- let mint_cap = init_coin_impl(root);
-
- libra_coin::restore_mint_cap(root, mint_cap);
- }
-
- #[test_only]
- public fun ol_mint_to(root: &signer, addr: address, amount: u64) {
- system_addresses::assert_ol(root);
-
-
- let mint_cap = if (coin::is_coin_initialized()) {
- libra_coin::extract_mint_cap(root)
- } else {
- init_coin_impl(root)
- };
+ public fun mock_case_1(vm: &signer, addr: address) {
+ assert!(stake::is_valid(addr), 01);
+ stake::mock_performance(vm, addr, 100, 10);
+ let (compliant, _, _) = grade::get_validator_grade(addr);
+ assert!(compliant, 01);
+ }
+
+
+ #[test_only]
+ // did not do enough mining, but did validate.
+ public fun mock_case_4(vm: &signer, addr: address) {
+ assert!(stake::is_valid(addr), 01);
+ stake::mock_performance(vm, addr, 0, 100); // 100 failing proposals
+ // let (compliant, _, _, _) = grade::get_validator_grade(addr, 0);
+ let (compliant, _, _) = grade::get_validator_grade(addr);
+ assert!(!compliant, 02);
+ }
+
+ // Mock all nodes being compliant case 1
+ #[test_only]
+ public fun mock_all_vals_good_performance(vm: &signer) {
+ let vals = stake::get_current_validators();
+ let i = 0;
+ while (i < vector::length(&vals)) {
+ let a = vector::borrow(&vals, i);
+ mock_case_1(vm, *a);
+ i = i + 1;
+ };
+ }
+
+ // Mock some nodes not compliant
+ /*#[test_only]
+ public fun mock_some_vals_bad_performance(vm: &signer, bad_vals: u64) {
+ let vals = stake::get_current_validators();
+ let vals_compliant = vector::length(&vals) - bad_vals;
+ let i = 0;
+ while (i < vals_compliant) {
+ let a = vector::borrow(&vals, i);
+ stake::mock_performance(vm, *a, 0, 100); // 100 failing proposals
+ i = i + 1;
+ };
+ }*/
+
+ //////// PROOF OF FEE ////////
+ #[test_only]
+ public fun pof_default(): (vector, vector, vector){
+
+ let vals = stake::get_current_validators();
+
+ let (bids, expiry) = mock_bids(&vals);
+
+ // make all validators pay auction fee
+ // the clearing price in the fibonacci sequence is is 1
+ let (alice_bid, _) = proof_of_fee::current_bid(*vector::borrow(&vals, 0));
+ assert!(alice_bid == 1, 03);
+ (vals, bids, expiry)
+ }
+
+ #[test_only]
+ public fun mock_bids(vals: &vector): (vector, vector) {
+ // system_addresses::assert_ol(vm);
+ let bids = vector::empty();
+ let expiry = vector::empty();
+ let i = 0;
+ let prev = 0;
+ let fib = 1;
+ while (i < vector::length(vals)) {
+
+ vector::push_back(&mut expiry, 1000);
+ let b = prev + fib;
+ vector::push_back(&mut bids, b);
+
+ let a = vector::borrow(vals, i);
+ let sig = account::create_signer_for_test(*a);
+ // initialize and set.
+ proof_of_fee::pof_update_bid(&sig, b, 1000);
+ prev = fib;
+ fib = b;
+ i = i + 1;
+ };
+
+ (bids, expiry)
+ }
+
+ use diem_framework::chain_status;
+ #[test_only]
+ public fun ol_test_genesis(root: &signer) {
+ system_addresses::assert_ol(root);
+ genesis::setup();
+ genesis::test_end_genesis(root);
+
+ let mint_cap = init_coin_impl(root);
+ libra_coin::restore_mint_cap(root, mint_cap);
+
+ assert!(!chain_status::is_genesis(), 0);
+ }
+
+ #[test_only]
+ public fun ol_initialize_coin(root: &signer) {
+ system_addresses::assert_ol(root);
+
+ let mint_cap = init_coin_impl(root);
+
+ libra_coin::restore_mint_cap(root, mint_cap);
+ }
+
+ #[test_only]
+ public fun ol_mint_to(root: &signer, addr: address, amount: u64) {
+ system_addresses::assert_ol(root);
+
+ let mint_cap = if (coin::is_coin_initialized()) {
+ libra_coin::extract_mint_cap(root)
+ } else {
+ init_coin_impl(root)
+ };
+
+ let c = coin::test_mint(amount, &mint_cap);
+ ol_account::deposit_coins(addr, c);
+
+ let b = coin::balance(addr);
+ assert!(b == amount, 0001);
+
+ libra_coin::restore_mint_cap(root, mint_cap);
+ }
+
+ #[test_only]
+ public fun ol_initialize_coin_and_fund_vals(root: &signer, amount: u64,
+ drip: bool) {
+ system_addresses::assert_ol(root);
+
+ let mint_cap = if (coin::is_coin_initialized()) {
+ libra_coin::extract_mint_cap(root)
+ } else {
+ init_coin_impl(root)
+ };
+
+ let vals = stake::get_current_validators();
+ let i = 0;
+ while (i < vector::length(&vals)) {
+ let addr = vector::borrow(&vals, i);
let c = coin::test_mint(amount, &mint_cap);
- ol_account::deposit_coins(addr, c);
+ ol_account::deposit_coins(*addr, c);
- let b = coin::balance(addr);
+ let b = libra_coin::balance(*addr);
assert!(b == amount, 0001);
- libra_coin::restore_mint_cap(root, mint_cap);
- }
-
- #[test_only]
- public fun ol_initialize_coin_and_fund_vals(root: &signer, amount: u64,
- drip: bool) {
- system_addresses::assert_ol(root);
-
- let mint_cap = if (coin::is_coin_initialized()) {
- libra_coin::extract_mint_cap(root)
- } else {
- init_coin_impl(root)
- };
-
- let vals = stake::get_current_validators();
- let i = 0;
-
- while (i < vector::length(&vals)) {
- let addr = vector::borrow(&vals, i);
- let c = coin::test_mint(amount, &mint_cap);
- ol_account::deposit_coins(*addr, c);
-
- let b = libra_coin::balance(*addr);
- assert!(b == amount, 0001);
-
-
- i = i + 1;
- };
-
- if (drip) {
- slow_wallet::slow_wallet_epoch_drip(root, amount);
- };
- libra_coin::restore_mint_cap(root, mint_cap);
- }
-
- #[test_only]
- fun init_coin_impl(root: &signer): coin::MintCapability {
- system_addresses::assert_ol(root);
-
- let (burn_cap, mint_cap) = libra_coin::initialize_for_test_without_aggregator_factory(root);
- coin::destroy_burn_cap(burn_cap);
-
- transaction_fee::initialize_fee_collection_and_distribution(root, 0);
-
- let initial_fees = 1000000 * 100; // coin scaling * 100 coins
- let tx_fees = coin::test_mint(initial_fees, &mint_cap);
- transaction_fee::vm_pay_fee(root, @ol_framework, tx_fees);
- let supply_pre = libra_coin::supply();
- assert!(supply_pre == initial_fees, ESUPPLY_MISMATCH);
- libra_coin::test_set_final_supply(root, initial_fees);
-
- mint_cap
- }
-
- #[test_only]
- public fun personas(): vector {
- let val_addr = vector::empty();
-
- vector::push_back(&mut val_addr, @0x1000a);
- vector::push_back(&mut val_addr, @0x1000b);
- vector::push_back(&mut val_addr, @0x1000c);
- vector::push_back(&mut val_addr, @0x1000d);
- vector::push_back(&mut val_addr, @0x1000e);
- vector::push_back(&mut val_addr, @0x1000f);
- vector::push_back(&mut val_addr, @0x10010); // g
- vector::push_back(&mut val_addr, @0x10011); // h
- vector::push_back(&mut val_addr, @0x10012); // i
- vector::push_back(&mut val_addr, @0x10013); // k
- val_addr
- }
-
- #[test_only]
- /// mock up to 6 validators alice..frank
- public fun genesis_n_vals(root: &signer, num: u64): vector {
- system_addresses::assert_ol(root);
- let framework_sig = account::create_signer_for_test(@diem_framework);
- ol_test_genesis(&framework_sig);
- // need to initialize musical chairs separate from genesis.
- let musical_chairs_default_seats = 10;
- musical_chairs::initialize(root, musical_chairs_default_seats);
-
- let val_addr = personas();
- let i = 0;
- while (i < num) {
- let val = vector::borrow(&val_addr, i);
- let sig = account::create_signer_for_test(*val);
-
- let (_sk, pk, pop) = stake::generate_identity();
-
- validator_universe::test_register_validator(root, &pk, &pop, &sig, 100,
- true, true);
-
- vouch::init(&sig);
- vouch::test_set_buddies(*val, val_addr);
-
- i = i + 1;
- };
-
- timestamp::fast_forward_seconds(2); // or else reconfigure wont happen
- stake::test_reconfigure(root, validator_universe::get_eligible_validators());
-
- stake::get_current_validators()
- }
-
- #[test_only]
- const EPOCH_DURATION: u64 = 60;
-
- #[test_only]
- // NOTE: The order of these is very important.
- // ol first runs its own accounting at end of epoch with epoch_boundary
- // Then the stake module needs to update the validators.
- // the reconfiguration module must run last, since no other
- // transactions or operations can happen after the reconfig.
- public fun trigger_epoch(root: &signer) {
- trigger_epoch_exactly_at(
- root,
- reconfiguration::get_current_epoch(),
- block::get_current_block_height()
- );
- }
-
- public fun trigger_epoch_exactly_at(root: &signer, old_epoch: u64, round: u64) {
- timestamp::fast_forward_seconds(EPOCH_DURATION);
-
- epoch_boundary::ol_reconfigure_for_test(root, old_epoch, round);
-
- // always advance
- assert!(reconfiguration::get_current_epoch() > old_epoch,
- EDID_NOT_ADVANCE_EPOCH);
-
- // epoch helper should always be in sync
- assert!(reconfiguration::get_current_epoch() ==
- epoch_helper::get_current_epoch(), 666);
- }
+ i = i + 1;
+ };
+
+ if (drip) {
+ slow_wallet::slow_wallet_epoch_drip(root, amount);
+ };
+ libra_coin::restore_mint_cap(root, mint_cap);
+ }
+
+ #[test_only]
+ fun init_coin_impl(root: &signer): coin::MintCapability {
+ system_addresses::assert_ol(root);
+
+ let (burn_cap, mint_cap) = libra_coin::initialize_for_test_without_aggregator_factory(root);
+ coin::destroy_burn_cap(burn_cap);
+
+ transaction_fee::initialize_fee_collection_and_distribution(root, 0);
+
+ let initial_fees = 1000000 * 100; // coin scaling * 100 coins
+ let tx_fees = coin::test_mint(initial_fees, &mint_cap);
+ transaction_fee::vm_pay_fee(root, @ol_framework, tx_fees);
+ let supply_pre = libra_coin::supply();
+ assert!(supply_pre == initial_fees, ESUPPLY_MISMATCH);
+ libra_coin::test_set_final_supply(root, initial_fees);
+
+ mint_cap
+ }
+
+ #[test_only]
+ public fun personas(): vector {
+ let val_addr = vector::empty();
+ vector::push_back(&mut val_addr, @0x1000a);
+ vector::push_back(&mut val_addr, @0x1000b);
+ vector::push_back(&mut val_addr, @0x1000c);
+ vector::push_back(&mut val_addr, @0x1000d);
+ vector::push_back(&mut val_addr, @0x1000e);
+ vector::push_back(&mut val_addr, @0x1000f);
+ vector::push_back(&mut val_addr, @0x10010); // g
+ vector::push_back(&mut val_addr, @0x10011); // h
+ vector::push_back(&mut val_addr, @0x10012); // i
+ vector::push_back(&mut val_addr, @0x10013); // k
+ val_addr
+ }
+
+ #[test_only]
+ /// mock up to 6 validators alice..frank
+ public fun genesis_n_vals(root: &signer, num: u64): vector {
+ system_addresses::assert_ol(root);
+ let framework_sig = account::create_signer_for_test(@diem_framework);
+ ol_test_genesis(&framework_sig);
+ // need to initialize musical chairs separate from genesis.
+ let musical_chairs_default_seats = 10;
+ musical_chairs::initialize(root, musical_chairs_default_seats);
+
+ let val_addr = personas();
+ let i = 0;
+ while (i < num) {
+ let val = vector::borrow(&val_addr, i);
+ let sig = account::create_signer_for_test(*val);
+ let (_sk, pk, pop) = stake::generate_identity();
+ validator_universe::test_register_validator(root, &pk, &pop, &sig, 100, true, true);
+
+ vouch::init(&sig);
+ vouch::test_set_buddies(*val, val_addr);
+
+ i = i + 1;
+ };
+
+ timestamp::fast_forward_seconds(2); // or else reconfigure wont happen
+ stake::test_reconfigure(root, validator_universe::get_eligible_validators());
+
+ stake::get_current_validators()
+ }
+
+ #[test_only]
+ const EPOCH_DURATION: u64 = 60;
+
+ #[test_only]
+ // NOTE: The order of these is very important.
+ // ol first runs its own accounting at end of epoch with epoch_boundary
+ // Then the stake module needs to update the validators.
+ // the reconfiguration module must run last, since no other
+ // transactions or operations can happen after the reconfig.
+ public fun trigger_epoch(root: &signer) {
+ trigger_epoch_exactly_at(
+ root,
+ reconfiguration::get_current_epoch(),
+ block::get_current_block_height()
+ );
+ }
+
+ public fun trigger_epoch_exactly_at(root: &signer, old_epoch: u64, round: u64) {
+ timestamp::fast_forward_seconds(EPOCH_DURATION);
+ epoch_boundary::ol_reconfigure_for_test(root, old_epoch, round);
+
+ // always advance
+ assert!(reconfiguration::get_current_epoch() > old_epoch,
+ EDID_NOT_ADVANCE_EPOCH);
+
+ // epoch helper should always be in sync
+ assert!(reconfiguration::get_current_epoch() == epoch_helper::get_current_epoch(), 666);
+ }
//////// META TESTS ////////
diff --git a/framework/libra-framework/sources/ol_sources/musical_chairs.move b/framework/libra-framework/sources/ol_sources/musical_chairs.move
index fee0eff82..8e3283a8b 100644
--- a/framework/libra-framework/sources/ol_sources/musical_chairs.move
+++ b/framework/libra-framework/sources/ol_sources/musical_chairs.move
@@ -1,240 +1,273 @@
module ol_framework::musical_chairs {
- use diem_framework::chain_status;
- use diem_framework::system_addresses;
- use diem_framework::stake;
- use ol_framework::grade;
- use ol_framework::testnet;
- use std::fixed_point32;
- use std::vector;
-
- // use diem_std::debug::print;
-
- friend diem_framework::genesis;
- friend diem_framework::diem_governance;
- friend ol_framework::epoch_boundary;
- #[test_only]
- friend ol_framework::mock;
-
-
- /// we don't want to play the validator selection games
+ use std::fixed_point32;
+ use std::vector;
+ use diem_framework::chain_status;
+ use diem_framework::system_addresses;
+ use diem_framework::stake;
+ use ol_framework::grade;
+ use ol_framework::testnet;
+ //use diem_std::debug::print;
+
+ friend diem_framework::genesis;
+ friend diem_framework::diem_governance;
+ friend ol_framework::epoch_boundary;
+ #[test_only]
+ friend ol_framework::mock;
+
+ /// We don't want to play the validator selection games
/// before we're clear out of genesis
const EPOCH_TO_START_EVAL: u64 = 2;
-
- /// we can't evaluate the performance of validators
+ /// We can't evaluate the performance of validators
/// when there are too few rounds committed
const MINIMUM_ROUNDS_PER_EPOCH: u64 = 1000;
-
- struct Chairs has key {
- // The number of chairs in the game
- seats_offered: u64,
- // TODO: A small history, for future use.
- history: vector,
- }
-
- // With musical chairs we are trying to estimate
- // the number of nodes which the network can support
- // BFT has upperbounds in the low hundreds, but we
- // don't need to hard code it.
- // There also needs to be an upper bound so that there is some
- // competition among validators.
- // Instead of hard coding a number, and needing to reach social
- // consensus to change it: we'll determine the size based on
- // the network's performance as a whole.
- // There are many metrics that can be used. For now we'll use
- // a simple heuristic that is already on chain: compliant node cardinality.
- // Other heuristics may be explored, so long as the information
- // reliably committed to the chain.
-
- // The rules:
- // Validators who perform, are not guaranteed entry into the
- // next epoch of the chain. All we are establishing is the ceiling
- // for validators.
- // When the 100% of the validators are performing well
- // the network can safely increase the threshold by 1 node.
- // We can also say if less that 5% fail, no change happens.
- // When the network is performing poorly, greater than 5%,
- // the threshold is reduced not by a predetermined unit, but
- // to the number of compliant and performant nodes.
-
- /// Called by root in genesis to initialize the GAS coin
- public(friend) fun initialize(
- vm: &signer,
- genesis_seats: u64,
- ) {
- // system_addresses::assert_vm(vm);
- // TODO: replace with VM
- system_addresses::assert_ol(vm);
-
- chain_status::is_genesis();
- if (exists(@ol_framework)) {
- return
- };
-
- move_to(vm, Chairs {
- seats_offered: genesis_seats,
- history: vector::empty(),
- });
- }
-
- /// get the number of seats in the game
- /// returns the list of compliant validators and the number of seats
- /// we should offer in the next epoch
- /// (compliant_vals, seats_offered)
- public(friend) fun stop_the_music( // sorry, had to.
- vm: &signer,
- epoch: u64,
- round: u64,
- ): (vector, u64) acquires Chairs {
- system_addresses::assert_ol(vm);
-
- let validators = stake::get_current_validators();
-
- let (compliant_vals, _non, fail_ratio) =
- eval_compliance_impl(validators, epoch, round);
-
- let chairs = borrow_global_mut(@ol_framework);
-
- let num_compliant_nodes = vector::length(&compliant_vals);
-
-
- // Error handle. We should not have gone into an epoch where we
- // had MORE validators than seats offered.
- // If this happens it's because we are in some kind of a fork condition.
- // return with no changes
- if (num_compliant_nodes > chairs.seats_offered) {
- return (compliant_vals, chairs.seats_offered)
- };
-
- // The happiest case. All filled seats performed well in the last epoch
- if (fixed_point32::is_zero(*&fail_ratio)) { // handle this here to prevent multiplication error below
- chairs.seats_offered = chairs.seats_offered + 1;
- return (compliant_vals, chairs.seats_offered)
- };
-
- // Conditions under which seats should be one more than the number of compliant nodes(<= 5%)
- // Sad case. If we are not getting compliance, need to ratchet down the offer of seats in next epoch.
- // See below find_safe_set_size, how we determine what that number
- // should be
- let non_compliance_pct = fixed_point32::multiply_u64(100, *&fail_ratio);
-
- if (non_compliance_pct > 5) {
- // If network is bootstrapping don't reduce the seat count below
- // compliant nodes,
-
- chairs.seats_offered = num_compliant_nodes;
-
- } else {
- // Ok case. If it's between 0 and 5% then we accept that margin as if it was fully compliant
- chairs.seats_offered = chairs.seats_offered + 1;
- };
-
- // catch failure mode
- // mostly for genesis, or testnets
- if (chairs.seats_offered < 4) {
- chairs.seats_offered = 4;
- };
-
- (compliant_vals, chairs.seats_offered)
- }
-
- // Update seat count to match filled seats post-PoF auction.
- // in case we were not able to fill all the seats offered
- // we don't want to keep incrementing from a baseline which we cannot fill
- // it can spiral out of range.
- public(friend) fun set_current_seats(framework: &signer, filled_seats: u64): u64 acquires Chairs{
- system_addresses::assert_diem_framework(framework);
- let chairs = borrow_global_mut(@ol_framework);
- chairs.seats_offered = filled_seats;
+ struct Chairs has key {
+ // The number of chairs in the game
+ seats_offered: u64,
+ // TODO: A small history, for future use.
+ history: vector,
+ }
+
+ // With musical chairs we are trying to estimate
+ // the number of nodes which the network can support
+ // BFT has upperbounds in the low hundreds, but we
+ // don't need to hard code it.
+ // There also needs to be an upper bound so that there is some
+ // competition among validators.
+ // Instead of hard coding a number, and needing to reach social
+ // consensus to change it: we'll determine the size based on
+ // the network's performance as a whole.
+ // There are many metrics that can be used. For now we'll use
+ // a simple heuristic that is already on chain: compliant node cardinality.
+ // Other heuristics may be explored, so long as the information
+ // reliably committed to the chain.
+
+ // The rules:
+ // Validators who perform, are not guaranteed entry into the
+ // next epoch of the chain. All we are establishing is the ceiling
+ // for validators.
+ // When the 100% of the validators are performing well
+ // the network can safely increase the threshold by 1 node.
+ // We can also say if less that 5% fail, no change happens.
+ // When the network is performing poorly, greater than 5%,
+ // the threshold is reduced not by a predetermined unit, but
+ // to the number of compliant and performant nodes.
+
+ /// Called by root in genesis to initialize the GAS coin
+ public(friend) fun initialize(
+ vm: &signer,
+ genesis_seats: u64,
+ ) {
+ // system_addresses::assert_vm(vm);
+ // TODO: replace with VM
+ system_addresses::assert_ol(vm);
+
+ chain_status::is_genesis();
+ if (exists(@ol_framework)) {
+ return
+ };
+
+ move_to(vm, Chairs {
+ seats_offered: genesis_seats,
+ history: vector::empty(),
+ });
+ }
+
+ /// get the number of seats in the game
+ /// returns the list of compliant validators and the number of seats
+ /// we should offer in the next epoch
+ /// (compliant_vals, seats_offered)
+ public(friend) fun stop_the_music(
+ vm: &signer,
+ epoch: u64,
+ round: u64,
+ ): (vector, u64) acquires Chairs {
+ system_addresses::assert_ol(vm);
+
+ let chairs = borrow_global_mut(@ol_framework);
+ let validators = stake::get_current_validators();
+ let (compliant_vals, _non, fail_ratio) = eval_compliance_impl(validators, epoch, round);
+ let num_compliant_vals = vector::length(&compliant_vals);
+
+ // Error handle. We should not have gone into an epoch where we had MORE validators than seats offered.
+ // If this happens it's because we are in some kind of a fork condition. Return with no changes
+ if (num_compliant_vals > chairs.seats_offered) {
+ return (compliant_vals, chairs.seats_offered)
+ };
+
+ // Calculate the number of seats to offer
+ chairs.seats_offered = calculate_seats_to_offer(
+ num_compliant_vals,
+ fail_ratio,
chairs.seats_offered
+ );
+
+ (compliant_vals, chairs.seats_offered)
+ }
+
+ // Case: All seats performed well in the last epoch
+ // - The happiest case: All filled seats performed well in the last epoch.
+ // - Increment the number of seats offered by 1.
+ //
+ // Case: Non-compliance detected
+ // - If non-compliance is detected (> 5%), reduce the number of seats offered to the number of compliant nodes.
+ // - Otherwise, if non-compliance is between 0 and 5%, increment the number of seats offered by 1.
+ //
+ // Error Handling
+ // - If there are more validators than seats offered, it indicates a fork condition. In such a case, return with no changes.
+ //
+ // Bootstrap Mode
+ // - If the network is bootstrapping, ensure that the seat count does not fall below the number of compliant nodes.
+ // - Ensure that the number of seats offered is not less than 4, especially during genesis or in testnets.
+
+ public fun calculate_seats_to_offer(
+ num_compliant_vals: u64,
+ fail_ratio: fixed_point32::FixedPoint32,
+ current_seats_offered: u64
+ ): u64 {
+ let new_seats_offered = if (fixed_point32::is_zero(fail_ratio)) {
+ // The happiest case. All filled seats performed well in the last epoch
+ current_seats_offered + 1
+ } else {
+ let non_compliance_pct = fixed_point32::multiply_u64(100, fail_ratio);
+ if (non_compliance_pct > 5) {
+ // Sad case. If we are not getting compliance, need to ratchet down the offer of seats in the next epoch.
+ // See below find_safe_set_size, how we determine what that number should be
+ num_compliant_vals
+ } else {
+ // Ok case. If it's between 0 and 5% then we accept that margin as if it was fully compliant
+ current_seats_offered + 1
+ }
+ };
+
+ // Catch failure mode mostly for genesis, or testnets
+ if (new_seats_offered < 4) {
+ 4
+ } else {
+ new_seats_offered
}
-
- // use the Case statistic to determine what proportion of the network is compliant.
- // private function prevent list DoS.
- fun eval_compliance_impl(
- validators: vector,
- epoch: u64,
- _round: u64,
- ): (vector, vector, fixed_point32::FixedPoint32) {
-
- let val_set_len = vector::length(&validators);
-
- // skip first epoch, don't evaluate if the network doesn't have sufficient data
- if (epoch < EPOCH_TO_START_EVAL) return (validators, vector::empty(), fixed_point32::create_from_rational(1, 1));
-
- let (compliant_nodes, non_compliant_nodes) = grade::eval_compliant_vals(validators);
-
- let good_len = vector::length(&compliant_nodes) ;
- let bad_len = vector::length(&non_compliant_nodes);
-
- // Note: sorry for repetition but necessary for writing tests and debugging.
- let null = fixed_point32::create_from_raw_value(0);
- if (good_len > val_set_len) { // safety
- return (vector::empty(), vector::empty(), null)
- };
-
- if (bad_len > val_set_len) { // safety
- return (vector::empty(), vector::empty(), null)
- };
-
- if ((good_len + bad_len) != val_set_len) { // safety
- return (vector::empty(), vector::empty(), null)
- };
-
- let ratio = if (bad_len > 0) {
- fixed_point32::create_from_rational(bad_len, val_set_len)
- } else {
- null
- };
-
- (compliant_nodes, non_compliant_nodes, ratio)
- }
-
- // Check for genesis, upgrade or recovery mode scenarios
- // if we are at genesis or otherwise at start of an epoch and don't
- // have a sufficient amount of history to evaluate nodes
- // we might reduce the validator set too agressively.
- // Musical chairs should not evaluate performance with less than 1000 rounds
- // created on mainnet,
- // there's something else very wrong in that case.
-
- fun is_booting_up(epoch: u64, round: u64): bool {
- !testnet::is_testnet() &&
- (epoch < EPOCH_TO_START_EVAL ||
- round < MINIMUM_ROUNDS_PER_EPOCH)
- }
-
- //////// GETTERS ////////
-
- #[view]
- public fun get_current_seats(): u64 acquires Chairs {
- borrow_global(@ol_framework).seats_offered
- }
-
- //////// TEST HELPERS ////////
-
- #[test_only]
- use diem_framework::chain_id;
-
- #[test_only]
- public fun test_stop(vm: &signer, epoch: u64, epoch_round: u64): (vector, u64) acquires Chairs {
- stop_the_music(vm, epoch, epoch_round)
- }
-
- #[test_only]
- public fun test_eval_compliance(root: &signer, validators: vector,
- epoch: u64, round: u64): (vector, vector,
- fixed_point32::FixedPoint32) {
- system_addresses::assert_ol(root);
- eval_compliance_impl(validators, epoch, round)
-
- }
- //////// TESTS ////////
-
- #[test(vm = @ol_framework)]
- public entry fun initialize_chairs(vm: signer) acquires Chairs {
- chain_id::initialize_for_test(&vm, 4);
- initialize(&vm, 10);
- assert!(get_current_seats() == 10, 1004);
- }
+ }
+
+ // Update seat count to match filled seats post-PoF auction.
+ // in case we were not able to fill all the seats offered
+ // we don't want to keep incrementing from a baseline which we cannot fill
+ // it can spiral out of range.
+ public(friend) fun set_current_seats(framework: &signer, filled_seats: u64): u64 acquires Chairs{
+ system_addresses::assert_diem_framework(framework);
+ let chairs = borrow_global_mut(@ol_framework);
+ chairs.seats_offered = filled_seats;
+ chairs.seats_offered
+ }
+
+ // use the Case statistic to determine what proportion of the network is compliant.
+ // private function prevent list DoS.
+ fun eval_compliance_impl(
+ validators: vector,
+ epoch: u64,
+ _round: u64,
+ ): (vector, vector, fixed_point32::FixedPoint32) {
+ let val_set_len = vector::length(&validators);
+
+ // skip first epoch, don't evaluate if the network doesn't have sufficient data
+ if (epoch < EPOCH_TO_START_EVAL) return (validators, vector::empty(), fixed_point32::create_from_rational(1, 1));
+
+ let (compliant_nodes, non_compliant_nodes) = grade::eval_compliant_vals(validators);
+
+ let good_len = vector::length(&compliant_nodes) ;
+ let bad_len = vector::length(&non_compliant_nodes);
+
+ // Note: sorry for repetition but necessary for writing tests and debugging.
+ let null = fixed_point32::create_from_raw_value(0);
+ if (good_len > val_set_len) { // safety
+ return (vector::empty(), vector::empty(), null)
+ };
+
+ if (bad_len > val_set_len) { // safety
+ return (vector::empty(), vector::empty(), null)
+ };
+
+ if ((good_len + bad_len) != val_set_len) { // safety
+ return (vector::empty(), vector::empty(), null)
+ };
+
+ let ratio = if (bad_len > 0) {
+ fixed_point32::create_from_rational(bad_len, val_set_len)
+ } else {
+ null
+ };
+
+ (compliant_nodes, non_compliant_nodes, ratio)
+ }
+
+ // Check for genesis, upgrade or recovery mode scenarios
+ // if we are at genesis or otherwise at start of an epoch and don't
+ // have a sufficient amount of history to evaluate nodes
+ // we might reduce the validator set too agressively.
+ // Musical chairs should not evaluate performance with less than 1000 rounds
+ // created on mainnet,
+ // there's something else very wrong in that case.
+
+ fun is_booting_up(epoch: u64, round: u64): bool {
+ !testnet::is_testnet() &&
+ (epoch < EPOCH_TO_START_EVAL ||
+ round < MINIMUM_ROUNDS_PER_EPOCH)
+ }
+
+ //////// GETTERS ////////
+
+ #[view]
+ public fun get_current_seats(): u64 acquires Chairs {
+ borrow_global(@ol_framework).seats_offered
+ }
+
+ //////// TEST HELPERS ////////
+
+ #[test_only]
+ use diem_framework::chain_id;
+
+ #[test_only]
+ public fun test_stop(vm: &signer, epoch: u64, epoch_round: u64): (vector, u64) acquires Chairs {
+ stop_the_music(vm, epoch, epoch_round)
+ }
+
+ #[test_only]
+ public fun test_eval_compliance(root: &signer, validators: vector,
+ epoch: u64, round: u64): (vector, vector,
+ fixed_point32::FixedPoint32) {
+ system_addresses::assert_ol(root);
+ eval_compliance_impl(validators, epoch, round)
+
+ }
+
+ //////// TESTS ////////
+
+ #[test(vm = @ol_framework)]
+ public entry fun initialize_chairs(vm: signer) acquires Chairs {
+ chain_id::initialize_for_test(&vm, 4);
+ initialize(&vm, 10);
+ assert!(get_current_seats() == 10, 1004);
+ }
+
+ #[test]
+ fun test_calculate_seats_to_offer() {
+ // Test case: all seats are compliant
+ let result = calculate_seats_to_offer(5, fixed_point32::create_from_rational(0, 1), 5);
+ assert!(result == 6, 1001);
+
+ // Test case: more than 5% non-compliance
+ let result = calculate_seats_to_offer(18, fixed_point32::create_from_rational(2, 20), 20);
+ assert!(result == 18, 1002);
+
+ // Test case: 5% non-compliance
+ let result = calculate_seats_to_offer(19, fixed_point32::create_from_rational(1, 20), 20);
+ assert!(result == 21, 1003);
+
+ // Test case: less than 5% non-compliance
+ let result = calculate_seats_to_offer(98, fixed_point32::create_from_rational(2, 100), 100);
+ assert!(result == 101, 1003);
+
+ // Test case: seats offered should not be less than 4
+ let result = calculate_seats_to_offer(2, fixed_point32::create_from_rational(1, 1), 2);
+ assert!(result == 4, 1004);
+ }
}
diff --git a/framework/libra-framework/sources/ol_sources/tests/musical_chairs.test.move b/framework/libra-framework/sources/ol_sources/tests/musical_chairs.test.move
index e6e6a485b..6305519be 100644
--- a/framework/libra-framework/sources/ol_sources/tests/musical_chairs.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/musical_chairs.test.move
@@ -3,61 +3,59 @@
/// tests for external apis, and where a dependency cycle with genesis is created.
module ol_framework::test_musical_chairs {
- use ol_framework::musical_chairs;
- use ol_framework::mock;
- use std::vector;
- use std::fixed_point32;
+ use ol_framework::musical_chairs;
+ use ol_framework::mock;
+ use std::vector;
+ use std::fixed_point32;
- // use diem_std::debug::print;
+ // use diem_std::debug::print;
- #[test(root = @ol_framework)]
- public entry fun eval_compliance_happy(root: signer) {
+ #[test(root = @ol_framework)]
+ public entry fun eval_compliance_happy(root: signer) {
- let musical_chairs_default_seats = 10;
- let vals = mock::genesis_n_vals(&root, 5);
- assert!(vector::length(&vals) == 5, 7357001);
+ let musical_chairs_default_seats = 10;
+ let vals = mock::genesis_n_vals(&root, 5);
+ assert!(vector::length(&vals) == 5, 7357001);
- // all vals compliant
- mock::mock_all_vals_good_performance(&root);
- let epoch = 10; // we don't evaluate epoch 0, 1
- let round = 2000; // we don't evaluate is rounds are below 1000 (one thousand)
- let (good, bad, ratio) = musical_chairs::test_eval_compliance(&root, vals,
- epoch, round);
- assert!(vector::length(&good) == 5, 7357002);
- assert!(vector::length(&bad) == 0, 7357003);
- assert!(fixed_point32::is_zero(ratio), 7357004);
+ // all vals compliant
+ mock::mock_all_vals_good_performance(&root);
+ let epoch = 10; // we don't evaluate epoch 0, 1
+ let round = 2000; // we don't evaluate is rounds are below 1000 (one thousand)
+ let (good, bad, ratio) = musical_chairs::test_eval_compliance(&root, vals,
+ epoch, round);
+ assert!(vector::length(&good) == 5, 7357002);
+ assert!(vector::length(&bad) == 0, 7357003);
+ assert!(fixed_point32::is_zero(ratio), 7357004);
+ let (outgoing_compliant_set, new_set_size) =
+ musical_chairs::test_stop(&root, epoch, round);
- let (outgoing_compliant_set, new_set_size) =
- musical_chairs::test_stop(&root, epoch, round);
+ assert!(vector::length(&outgoing_compliant_set) == 5, 7357005);
+ assert!(new_set_size == (musical_chairs_default_seats + 1), 7357006);
+ }
+ #[test(root = @ol_framework)]
+ // only one seat opens up
+ public entry fun eval_compliance_one_val(root: signer) {
- assert!(vector::length(&outgoing_compliant_set) == 5, 7357005);
- assert!(new_set_size == (musical_chairs_default_seats + 1), 7357006);
- }
+ let vals = mock::genesis_n_vals(&root, 5);
+ assert!(vector::length(&vals) == 5, 7357001);
- #[test(root = @ol_framework)]
- // only one seat opens up
- public entry fun eval_compliance_one_val(root: signer) {
+ // all vals compliant
+ mock::mock_case_1(&root, *vector::borrow(&vals, 0));
- let vals = mock::genesis_n_vals(&root, 5);
- assert!(vector::length(&vals) == 5, 7357001);
+ let epoch = 10; // we don't evaluate epoch 0, 1
+ let round = 2000; // we don't evaluate is rounds are below 1000 (one thousand)
+ let (good, bad, bad_ratio) = musical_chairs::test_eval_compliance(&root,
+ vals, epoch, round);
+ assert!(vector::length(&good) == 1, 7357002);
+ assert!(vector::length(&bad) == 4, 7357003);
+ assert!(!fixed_point32::is_zero(bad_ratio), 7357004);
+ assert!(fixed_point32::create_from_rational(4, 5) == bad_ratio, 7357005);
- // all vals compliant
- mock::mock_case_1(&root, *vector::borrow(&vals, 0));
- let epoch = 10; // we don't evaluate epoch 0, 1
- let round = 2000; // we don't evaluate is rounds are below 1000 (one thousand)
- let (good, bad, bad_ratio) = musical_chairs::test_eval_compliance(&root,
- vals, epoch, round);
- assert!(vector::length(&good) == 1, 7357002);
- assert!(vector::length(&bad) == 4, 7357003);
- assert!(!fixed_point32::is_zero(bad_ratio), 7357004);
- assert!(fixed_point32::create_from_rational(4, 5) == bad_ratio, 7357005);
+ let (_outgoing_compliant_set, _new_set_size) =
+ musical_chairs::test_stop(&root, epoch, round);
-
- let (_outgoing_compliant_set, _new_set_size) =
- musical_chairs::test_stop(&root, epoch, round);
-
- }
+ }
}
From 0671c3cb57ca85e38075878d2926ebb0b0456b30 Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Fri, 12 Jul 2024 11:11:55 -0300
Subject: [PATCH 05/16] gitignore target folders, and move coverage files
---
.gitignore | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/.gitignore b/.gitignore
index 21446d574..1a8778d90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
# Generated by Cargo
# will have compiled files and executables
-/target/
+**/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
@@ -24,4 +24,8 @@ sccache.log
*.bpl
# exclude diem dependency
-diem/
\ No newline at end of file
+diem/
+
+# exclude move coverage
+.coverage_map.mvcov
+.trace
\ No newline at end of file
From 0ee11d8665f73281a7478ce9a65cedac07d92ebc Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Tue, 16 Jul 2024 15:49:40 -0300
Subject: [PATCH 06/16] fixes reward thermostat call order - WIP clean up code
---
.../sources/ol_sources/ancestry.move | 2 +-
.../sources/ol_sources/epoch_boundary.move | 762 +++++++++---------
.../sources/ol_sources/infra_escrow.move | 12 +-
.../sources/ol_sources/mock.move | 41 +-
.../sources/ol_sources/pledge_accounts.move | 88 +-
.../sources/ol_sources/proof_of_fee.move | 74 +-
.../tests/validator_reward.test.move | 131 +--
7 files changed, 567 insertions(+), 543 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/ancestry.move b/framework/libra-framework/sources/ol_sources/ancestry.move
index 3bed49f99..0bb89368c 100644
--- a/framework/libra-framework/sources/ol_sources/ancestry.move
+++ b/framework/libra-framework/sources/ol_sources/ancestry.move
@@ -174,7 +174,7 @@ module ol_framework::ancestry {
// now loop through all the accounts again, and check if this target
// account is related to anyone.
- let k = 0;
+ let k = 0;
while (k < vector::length(&list)) {
let comparison_acc = vector::borrow(&list, k);
// skip if you're the same person
diff --git a/framework/libra-framework/sources/ol_sources/epoch_boundary.move b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
index 36fb97925..48bf35888 100644
--- a/framework/libra-framework/sources/ol_sources/epoch_boundary.move
+++ b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
@@ -1,382 +1,377 @@
module diem_framework::epoch_boundary {
- use ol_framework::slow_wallet;
- use ol_framework::musical_chairs;
- use ol_framework::proof_of_fee;
- use ol_framework::stake;
- use ol_framework::libra_coin::LibraCoin;
- use ol_framework::rewards;
- use ol_framework::jail;
- use ol_framework::safe;
- use ol_framework::burn;
- use ol_framework::donor_voice_txs;
- use ol_framework::fee_maker;
- use ol_framework::infra_escrow;
- use ol_framework::libra_coin;
- use ol_framework::match_index;
- use ol_framework::community_wallet_init;
- use ol_framework::testnet;
-
- use diem_framework::account;
- use diem_framework::reconfiguration;
- use diem_framework::transaction_fee;
- use diem_framework::system_addresses;
- use diem_framework::coin::{Coin};
- use std::vector;
- use std::error;
- use std::string;
- use std::signer;
- use diem_framework::create_signer;
-
- use diem_std::debug::print;
-
- friend diem_framework::genesis;
- friend diem_framework::diem_governance;
- friend diem_framework::block; // for testnet only
-
- //////// ERRORS ////////
- /// The transaction fee coin has not been initialized
- const ETX_FEES_NOT_INITIALIZED: u64 = 0;
-
- /// Epoch trigger can only be called on mainnet or in smoketests
- const ETRIGGER_EPOCH_UNAUTHORIZED: u64 = 1;
-
- /// Epoch is not ready for reconfiguration
- const ETRIGGER_NOT_READY: u64 = 2;
+ use ol_framework::slow_wallet;
+ use ol_framework::musical_chairs;
+ use ol_framework::proof_of_fee;
+ use ol_framework::stake;
+ use ol_framework::libra_coin::LibraCoin;
+ use ol_framework::rewards;
+ use ol_framework::jail;
+ use ol_framework::safe;
+ use ol_framework::burn;
+ use ol_framework::donor_voice_txs;
+ use ol_framework::fee_maker;
+ use ol_framework::infra_escrow;
+ use ol_framework::libra_coin;
+ use ol_framework::match_index;
+ use ol_framework::community_wallet_init;
+ use ol_framework::testnet;
+
+ use diem_framework::account;
+ use diem_framework::reconfiguration;
+ use diem_framework::transaction_fee;
+ use diem_framework::system_addresses;
+ use diem_framework::coin::{Coin};
+ use std::vector;
+ use std::error;
+ use std::string;
+ use std::signer;
+ use diem_framework::create_signer;
+
+ use diem_std::debug::print;
+
+ friend diem_framework::genesis;
+ friend diem_framework::diem_governance;
+ friend diem_framework::block; // for testnet only
+
+ //////// ERRORS ////////
+ /// The transaction fee coin has not been initialized
+ const ETX_FEES_NOT_INITIALIZED: u64 = 0;
+ /// Epoch trigger can only be called on mainnet or in smoketests
+ const ETRIGGER_EPOCH_UNAUTHORIZED: u64 = 1;
+ /// Epoch is not ready for reconfiguration
+ const ETRIGGER_NOT_READY: u64 = 2;
+ /// Epoch number mismat
+ const ENOT_SAME_EPOCH: u64 = 3;
+
+ /////// Constants ////////
+ /// How many PoF baseline rewards to we set aside for the miners.
+ /// equivalent reward of one seats of the validator set
+ const ORACLE_PROVIDERS_SEATS: u64 = 1;
+
+ /// The VM can set the boundary bit to allow reconfiguration
+ struct BoundaryBit has key {
+ ready: bool,
+ closing_epoch: u64,
+ }
- /// Epoch number mismat
- const ENOT_SAME_EPOCH: u64 = 3;
+ /// I just checked in, to see what condition my condition was in.
+ struct BoundaryStatus has key, drop {
+ security_bill_count: u64,
+ security_bill_amount: u64,
+ security_bill_success: bool,
+
+ dd_accounts_count: u64,
+ dd_accounts_amount: u64,
+ dd_accounts_success: bool,
+
+ set_fee_makers_success: bool,
+ system_fees_collected: u64,
+ // Process Outgoing
+ outgoing_vals_paid: vector,
+ outgoing_total_reward: u64,
+ outgoing_nominal_reward_to_vals: u64,
+ outgoing_entry_fee: u64,
+ outgoing_clearing_percent: u64,
+ outgoing_vals_success: bool, // TODO
+
+ // Oracle / Tower
+ tower_state_success: bool, // TODO
+ oracle_budget: u64,
+ oracle_pay_count: u64,
+ oracle_pay_amount: u64,
+ oracle_pay_success: bool,
+
+ epoch_burn_fees: u64,
+ epoch_burn_success: bool,
+
+ slow_wallet_drip_amount: u64,
+ slow_wallet_drip_success: bool,
+ // Process Incoming
+ // musical chairs
+ incoming_compliant: vector,
+ incoming_compliant_count: u64,
+ incoming_seats_offered: u64,
+
+ // proof of fee
+ incoming_all_bidders: vector,
+ incoming_only_qualified_bidders: vector,
+ incoming_auction_winners: vector,
+ incoming_filled_seats: u64,
+ incoming_fees: u64,
+ incoming_fees_success: bool,
+
+ // reconfiguration
+ incoming_post_failover_check: vector,
+ incoming_vals_missing_configs: vector,
+ incoming_actual_vals: vector,
+ incoming_final_set_size: u64,
+ incoming_reconfig_success: bool,
+
+ infra_subsidize_amount: u64, // TODO
+ infra_subsidize_success: bool, // TODO
+
+ pof_thermo_success: bool,
+ pof_thermo_increase: bool,
+ pof_thermo_amount: u64,
+ }
- /////// Constants ////////
- /// how many PoF baseline rewards to we set aside for the miners.
- /// equivalent reward of one seats of the validator set
- const ORACLE_PROVIDERS_SEATS: u64 = 1;
+ /// initialize structs, requires both signers since BoundaryBit can only be
+ // accessed by VM
+ public(friend) fun initialize(framework_signer: &signer) {
+ if (!exists(@ol_framework)){
+ move_to(framework_signer, reset());
+ };
- // the VM can set the boundary bit to allow reconfiguration
- struct BoundaryBit has key {
- ready: bool,
- closing_epoch: u64,
+ // boundary bit can only be written by VM
+ if (!exists(@ol_framework)) {
+ move_to(framework_signer, BoundaryBit {
+ ready: false,
+ closing_epoch: 0,
+ });
}
+ }
- // I just checked in, to see what condition my condition was in.
- struct BoundaryStatus has key, drop {
- security_bill_count: u64,
- security_bill_amount: u64,
- security_bill_success: bool,
+ fun reset(): BoundaryStatus {
+ BoundaryStatus {
+ security_bill_count: 0,
+ security_bill_amount: 0,
+ security_bill_success: false,
+
+ dd_accounts_count: 0,
+ dd_accounts_amount: 0,
+ dd_accounts_success: false,
- dd_accounts_count: u64,
- dd_accounts_amount: u64,
- dd_accounts_success: bool,
+ set_fee_makers_success: false,
+ system_fees_collected: 0,
- set_fee_makers_success: bool,
- system_fees_collected: u64,
// Process Outgoing
- outgoing_vals_paid: vector,
- outgoing_total_reward: u64,
- outgoing_nominal_reward_to_vals: u64,
- outgoing_entry_fee: u64,
- outgoing_clearing_percent: u64,
- outgoing_vals_success: bool, // TODO
+ outgoing_vals_paid: vector::empty(),
+ outgoing_total_reward: 0,
+ outgoing_vals_success: false,
+ outgoing_nominal_reward_to_vals: 0,
+ outgoing_entry_fee: 0,
+ outgoing_clearing_percent: 0,
// Oracle / Tower
- tower_state_success: bool, // TODO
- oracle_budget: u64,
- oracle_pay_count: u64,
- oracle_pay_amount: u64,
- oracle_pay_success: bool,
-
- epoch_burn_fees: u64,
- epoch_burn_success: bool,
-
- slow_wallet_drip_amount: u64,
- slow_wallet_drip_success: bool,
+ tower_state_success: false,
+ oracle_budget: 0,
+ oracle_pay_count: 0,
+ oracle_pay_amount: 0,
+ oracle_pay_success: false,
+ epoch_burn_fees: 0,
+ epoch_burn_success: false,
+
+ slow_wallet_drip_amount: 0,
+ slow_wallet_drip_success: false,
// Process Incoming
- // musical chairs
- incoming_compliant: vector,
- incoming_compliant_count: u64,
- incoming_seats_offered: u64,
-
- // proof of fee
- incoming_all_bidders: vector,
- incoming_only_qualified_bidders: vector,
- incoming_auction_winners: vector,
- incoming_filled_seats: u64,
- incoming_fees: u64,
- incoming_fees_success: bool,
-
- // reconfiguration
- incoming_post_failover_check: vector,
- incoming_vals_missing_configs: vector,
- incoming_actual_vals: vector,
- incoming_final_set_size: u64,
- incoming_reconfig_success: bool,
-
- infra_subsidize_amount: u64, // TODO
- infra_subsidize_success: bool, // TODO
-
- pof_thermo_success: bool,
- pof_thermo_increase: bool,
- pof_thermo_amount: u64,
- }
-
- /// initialize structs, requires both signers since BoundaryBit can only be
- // accessed by VM
- public(friend) fun initialize(framework_signer: &signer) {
- if (!exists(@ol_framework)){
- move_to(framework_signer, reset());
- };
-
- // boundary bit can only be written by VM
- if (!exists(@ol_framework)) {
- move_to(framework_signer, BoundaryBit {
- ready: false,
- closing_epoch: 0,
- });
- }
- }
-
- fun reset(): BoundaryStatus {
- BoundaryStatus {
- security_bill_count: 0,
- security_bill_amount: 0,
- security_bill_success: false,
-
- dd_accounts_count: 0,
- dd_accounts_amount: 0,
- dd_accounts_success: false,
-
- set_fee_makers_success: false,
- system_fees_collected: 0,
-
- // Process Outgoing
- outgoing_vals_paid: vector::empty(),
- outgoing_total_reward: 0,
- outgoing_vals_success: false,
- outgoing_nominal_reward_to_vals: 0,
- outgoing_entry_fee: 0,
- outgoing_clearing_percent: 0,
-
- // Oracle / Tower
- tower_state_success: false,
- oracle_budget: 0,
- oracle_pay_count: 0,
- oracle_pay_amount: 0,
- oracle_pay_success: false,
- epoch_burn_fees: 0,
- epoch_burn_success: false,
-
- slow_wallet_drip_amount: 0,
- slow_wallet_drip_success: false,
- // Process Incoming
- incoming_compliant: vector::empty(),
- incoming_compliant_count: 0,
- incoming_seats_offered: 0,
- incoming_filled_seats: 0,
- incoming_auction_winners: vector::empty(),
- incoming_all_bidders: vector::empty(),
- incoming_only_qualified_bidders: vector::empty(),
- incoming_final_set_size: 0,
- incoming_fees: 0,
- incoming_fees_success: false,
-
- incoming_post_failover_check: vector::empty(),
- incoming_vals_missing_configs: vector::empty(),
- incoming_actual_vals: vector::empty(),
- incoming_reconfig_success: false,
-
- infra_subsidize_amount: 0,
- infra_subsidize_success: false,
-
- pof_thermo_success: false,
- pof_thermo_increase: false,
- pof_thermo_amount: 0,
- }
+ incoming_compliant: vector::empty(),
+ incoming_compliant_count: 0,
+ incoming_seats_offered: 0,
+ incoming_filled_seats: 0,
+ incoming_auction_winners: vector::empty(),
+ incoming_all_bidders: vector::empty(),
+ incoming_only_qualified_bidders: vector::empty(),
+ incoming_final_set_size: 0,
+ incoming_fees: 0,
+ incoming_fees_success: false,
+
+ incoming_post_failover_check: vector::empty(),
+ incoming_vals_missing_configs: vector::empty(),
+ incoming_actual_vals: vector::empty(),
+ incoming_reconfig_success: false,
+
+ infra_subsidize_amount: 0,
+ infra_subsidize_success: false,
+
+ pof_thermo_success: false,
+ pof_thermo_increase: false,
+ pof_thermo_amount: 0,
}
+ }
- ///TODO: epoch trigger is currently disabled and requires further testing.
- /// refer to block.move and std::features
- /// flip the bit to allow the epoch to be reconfigured on any transaction
- public(friend) fun enable_epoch_trigger(vm_signer: &signer, closing_epoch:
- u64) acquires BoundaryBit {
- if (signer::address_of(vm_signer) != @vm_reserved) return;
-
- // though the VM is calling this, we need the struct to be on
- // diem_framework. So we need to use create_signer
-
- let framework_signer = create_signer::create_signer(@ol_framework);
- if (!exists(@ol_framework)) {
- // Just like a prayer, your voice can take me there
- // Just like a muse to me, you are a mystery
- // Just like a dream, you are not what you seem
- // Just like a prayer, no choice your voice can take me there...
- move_to(&framework_signer, BoundaryBit {
- closing_epoch: closing_epoch,
- ready: true,
- })
- } else {
- let state = borrow_global_mut(@ol_framework);
- state.closing_epoch = closing_epoch;
- state.ready = true;
- }
- }
- /// Once epoch boundary time has passed, and the BoundaryBit set to true
- /// any user can trigger the epoch boundary.
- /// Why do this? It's preferable that the VM never trigger any function.
- /// An abort by the VM will cause a network halt. The same abort, if called
- /// by a user, would not cause a halt.
- public(friend) fun trigger_epoch(framework_signer: &signer) acquires BoundaryBit,
- BoundaryStatus {
- // COMMIT NOTE: there's no reason to gate this, if th trigger is not
- // ready (which only happens on Main and Stage, then user will get an error)
- // assert!(!testnet::is_testnet(), ETRIGGER_EPOCH_MAINNET);
- // must get root permission from governance.move
- system_addresses::assert_diem_framework(framework_signer);
- let _ = can_trigger(); // will abort if false
-
- // update the state and flip the Bit
- // note we are using the 0x0 address for BoundaryBit
+ ///TODO: epoch trigger is currently disabled and requires further testing.
+ /// refer to block.move and std::features
+ /// flip the bit to allow the epoch to be reconfigured on any transaction
+ public(friend) fun enable_epoch_trigger(vm_signer: &signer, closing_epoch:
+ u64) acquires BoundaryBit {
+ if (signer::address_of(vm_signer) != @vm_reserved) return;
+
+ // though the VM is calling this, we need the struct to be on
+ // diem_framework. So we need to use create_signer
+
+ let framework_signer = create_signer::create_signer(@ol_framework);
+ if (!exists(@ol_framework)) {
+ // Just like a prayer, your voice can take me there
+ // Just like a muse to me, you are a mystery
+ // Just like a dream, you are not what you seem
+ // Just like a prayer, no choice your voice can take me there...
+ move_to(&framework_signer, BoundaryBit {
+ closing_epoch: closing_epoch,
+ ready: true,
+ })
+ } else {
let state = borrow_global_mut(@ol_framework);
- state.ready = false;
-
- epoch_boundary(framework_signer, state.closing_epoch, 0);
+ state.closing_epoch = closing_epoch;
+ state.ready = true;
}
+ }
- // utility to use in smoke tests
- public entry fun smoke_trigger_epoch(framework_signer: &signer) acquires BoundaryBit,
- BoundaryStatus {
- // cannot call this on mainnet
- // only for smoke testing
- assert!(testnet::is_not_mainnet(), ETRIGGER_EPOCH_UNAUTHORIZED);
- // must get 0x1 sig from governance.move
- system_addresses::assert_diem_framework(framework_signer);
- let state = borrow_global_mut(@ol_framework);
- epoch_boundary(framework_signer, state.closing_epoch, 0);
- }
+ /// Once epoch boundary time has passed, and the BoundaryBit set to true
+ /// any user can trigger the epoch boundary.
+ /// Why do this? It's preferable that the VM never trigger any function.
+ /// An abort by the VM will cause a network halt. The same abort, if called
+ /// by a user, would not cause a halt.
+ public(friend) fun trigger_epoch(framework_signer: &signer)
+ acquires BoundaryBit, BoundaryStatus {
+ // COMMIT NOTE: there's no reason to gate this, if th trigger is not
+ // ready (which only happens on Main and Stage, then user will get an error)
+ // assert!(!testnet::is_testnet(), ETRIGGER_EPOCH_MAINNET);
+ // must get root permission from governance.move
+ system_addresses::assert_diem_framework(framework_signer);
+ let _ = can_trigger(); // will abort if false
+
+ // update the state and flip the Bit
+ // note we are using the 0x0 address for BoundaryBit
+ let state = borrow_global_mut(@ol_framework);
+ state.ready = false;
+
+ epoch_boundary(framework_signer, state.closing_epoch, 0);
+ }
- #[view]
- /// check to see if the epoch Boundary Bit is true
- public fun can_trigger(): bool acquires BoundaryBit {
- let state = borrow_global_mut(@ol_framework);
- assert!(state.ready, ETRIGGER_NOT_READY);
- assert!(state.closing_epoch == reconfiguration::get_current_epoch(),
- ENOT_SAME_EPOCH);
- true
- }
+ // utility to use in smoke tests
+ public entry fun smoke_trigger_epoch(framework_signer: &signer)
+ acquires BoundaryBit, BoundaryStatus {
+ // cannot call this on mainnet
+ // only for smoke testing
+ assert!(testnet::is_not_mainnet(), ETRIGGER_EPOCH_UNAUTHORIZED);
+ // must get 0x1 sig from governance.move
+ system_addresses::assert_diem_framework(framework_signer);
+ let state = borrow_global_mut(@ol_framework);
+ epoch_boundary(framework_signer, state.closing_epoch, 0);
+ }
- // Contains all of 0L's business logic for end of epoch.
- // This removed business logic from reconfiguration.move
- // and prevents dependency cycling.
- public(friend) fun epoch_boundary(root: &signer, closing_epoch: u64,
- epoch_round: u64) acquires BoundaryStatus {
- print(&string::utf8(b"EPOCH BOUNDARY BEGINS"));
- // either 0x0 or 0x1 can call, but we will always use framework signer
- system_addresses::assert_ol(root);
- let root = &create_signer::create_signer(@ol_framework);
- let status = borrow_global_mut(@ol_framework);
-
- print(&string::utf8(b"status reset"));
- *status = reset();
-
- // bill root service fees;
- print(&string::utf8(b"root_service_billing"));
- root_service_billing(root, status);
-
- print(&string::utf8(b"process_donor_voice_accounts"));
- // run the transactions of donor directed accounts
- let (count, amount, success) = donor_voice_txs::process_donor_voice_accounts(root, closing_epoch);
- status.dd_accounts_count = count;
- status.dd_accounts_amount = amount;
- status.dd_accounts_success = success;
-
- print(&string::utf8(b"tower_state::reconfig"));
- // reset fee makers tracking
- status.set_fee_makers_success = fee_maker::epoch_reset_fee_maker(root);
-
- print(&string::utf8(b"musical_chairs::stop_the_music"));
- let (compliant_vals, n_seats) = musical_chairs::stop_the_music(root,
- closing_epoch, epoch_round);
- status.incoming_compliant_count = vector::length(&compliant_vals);
- status.incoming_compliant = compliant_vals;
- status.incoming_seats_offered = n_seats;
-
- print(&string::utf8(b"settle_accounts"));
-
- settle_accounts(root, compliant_vals, status);
-
- print(&string::utf8(b"slow_wallet::on_new_epoch"));
- // drip coins
- let (s_success, s_amount) = slow_wallet::on_new_epoch(root);
-
- status.slow_wallet_drip_amount = s_amount;
- status.slow_wallet_drip_success = s_success;
-
- // ======= THE BOUNDARY =======
- // And to know yourself
- // is to be yourself
- // keeps you walking through these tears.
-
- print(&string::utf8(b"process_incoming_validators"));
- process_incoming_validators(root, status, compliant_vals, n_seats);
-
- print(&string::utf8(b"subsidize_from_infra_escrow"));
- let (i_success, i_fee) = subsidize_from_infra_escrow(root);
- status.infra_subsidize_amount = i_fee;
- status.infra_subsidize_success = i_success;
-
- print(&string::utf8(b"reward_thermostat"));
- let (t_success, t_increase, t_amount) =
- proof_of_fee::reward_thermostat(root);
- status.pof_thermo_success = t_success;
- status.pof_thermo_increase = t_increase;
- status.pof_thermo_amount = t_amount;
-
- print(&string::utf8(b"EPOCH BOUNDARY END"));
- print(status);
- reconfiguration::reconfigure();
+ #[view]
+ /// check to see if the epoch Boundary Bit is true
+ public fun can_trigger(): bool acquires BoundaryBit {
+ let state = borrow_global_mut(@ol_framework);
+ assert!(state.ready, ETRIGGER_NOT_READY);
+ assert!(state.closing_epoch == reconfiguration::get_current_epoch(),
+ ENOT_SAME_EPOCH);
+ true
+ }
+
+ // Contains all of 0L's business logic for end of epoch.
+ // This removed business logic from reconfiguration.move
+ // and prevents dependency cycling.
+ public(friend) fun epoch_boundary(root: &signer, closing_epoch: u64, epoch_round: u64)
+ acquires BoundaryStatus {
+ print(&string::utf8(b"EPOCH BOUNDARY BEGINS"));
+ // either 0x0 or 0x1 can call, but we will always use framework signer
+ system_addresses::assert_ol(root);
+ let root = &create_signer::create_signer(@ol_framework);
+ let status = borrow_global_mut(@ol_framework);
+
+ print(&string::utf8(b"status reset"));
+ *status = reset();
+
+ // bill root service fees;
+ print(&string::utf8(b"root_service_billing"));
+ root_service_billing(root, status);
+
+ print(&string::utf8(b"process_donor_voice_accounts"));
+ // run the transactions of donor directed accounts
+ let (count, amount, success) = donor_voice_txs::process_donor_voice_accounts(root, closing_epoch);
+ status.dd_accounts_count = count;
+ status.dd_accounts_amount = amount;
+ status.dd_accounts_success = success;
+
+ print(&string::utf8(b"tower_state::reconfig"));
+ // reset fee makers tracking
+ status.set_fee_makers_success = fee_maker::epoch_reset_fee_maker(root);
+
+ print(&string::utf8(b"musical_chairs::stop_the_music"));
+ let (compliant_vals, n_seats) = musical_chairs::stop_the_music(root,
+ closing_epoch, epoch_round);
+ status.incoming_compliant_count = vector::length(&compliant_vals);
+ status.incoming_compliant = compliant_vals;
+ status.incoming_seats_offered = n_seats;
+
+ print(&string::utf8(b"settle_accounts"));
+ settle_accounts(root, compliant_vals, status);
+
+ print(&string::utf8(b"slow_wallet::on_new_epoch"));
+ // drip coins
+ let (s_success, s_amount) = slow_wallet::on_new_epoch(root);
+ status.slow_wallet_drip_amount = s_amount;
+ status.slow_wallet_drip_success = s_success;
+
+ // ======= THE BOUNDARY =======
+ // And to know yourself
+ // is to be yourself
+ // keeps you walking through these tears.
+
+ print(&string::utf8(b"process_incoming_validators"));
+ process_incoming_validators(root, status, compliant_vals, n_seats);
+
+ print(&string::utf8(b"reward_thermostat"));
+ let (t_success, t_increase, t_amount) =
+ proof_of_fee::reward_thermostat(root);
+ status.pof_thermo_success = t_success;
+ status.pof_thermo_increase = t_increase;
+ status.pof_thermo_amount = t_amount;
+
+ print(&string::utf8(b"subsidize_from_infra_escrow"));
+ let (i_success, i_fee) = subsidize_from_infra_escrow(root);
+ status.infra_subsidize_amount = i_fee;
+ status.infra_subsidize_success = i_success;
+
+ print(&string::utf8(b"EPOCH BOUNDARY END"));
+ print(status);
+ reconfiguration::reconfigure();
}
/// withdraw coins and settle accounts for validators and oracles
/// returns the list of compliant_vals
fun settle_accounts(root: &signer, compliant_vals: vector, status: &mut BoundaryStatus): vector {
- assert!(transaction_fee::is_fees_collection_enabled(), error::invalid_state(ETX_FEES_NOT_INITIALIZED));
-
- if (transaction_fee::system_fees_collected() > 0) {
- let all_fees = transaction_fee::root_withdraw_all(root);
- status.system_fees_collected = libra_coin::value(&all_fees);
-
- // Nominal fee set by the PoF thermostat
- let (nominal_reward_to_vals, entry_fee, clearing_percent, _ ) = proof_of_fee::get_consensus_reward();
- status.outgoing_nominal_reward_to_vals = nominal_reward_to_vals;
- status.outgoing_entry_fee = entry_fee;
- status.outgoing_clearing_percent = clearing_percent;
-
- // validators get the gross amount of the reward, since they already paid to enter. This results in a net payment equivalent to:
- // nominal_reward_to_vals - entry_fee.
- let (compliant_vals, total_reward) = process_outgoing_validators(root, &mut all_fees, nominal_reward_to_vals, compliant_vals);
-
- status.outgoing_vals_paid = compliant_vals;
- status.outgoing_total_reward = total_reward;
-
- // check that the sustem fees collect were greater than reward
- status.outgoing_vals_success = (status.system_fees_collected >= total_reward);
- // check the the total actually deposited/paid is the expected amount
- if (nominal_reward_to_vals > 0) { // check for zero
- status.outgoing_vals_success = total_reward == (vector::length(&compliant_vals) * nominal_reward_to_vals)
- };
-
- // Commit note: deprecated with tower mining.
-
- // remainder gets burnt according to fee maker preferences
- let (b_success, b_fees) = burn::epoch_burn_fees(root, &mut all_fees);
- status.epoch_burn_success = b_success;
- status.epoch_burn_fees = b_fees;
-
- // coin can finally be destroyed. Up to here we have been extracting from a mutable.
- // It's possible there might be some dust, that should get burned
- burn::burn_and_track(all_fees);
- };
+ assert!(transaction_fee::is_fees_collection_enabled(), error::invalid_state(ETX_FEES_NOT_INITIALIZED));
+
+ if (transaction_fee::system_fees_collected() > 0) {
+ let all_fees = transaction_fee::root_withdraw_all(root);
+ status.system_fees_collected = libra_coin::value(&all_fees);
+
+ // Nominal fee set by the PoF thermostat
+ let (nominal_reward_to_vals, entry_fee, clearing_percent, _ ) = proof_of_fee::get_consensus_reward();
+ status.outgoing_nominal_reward_to_vals = nominal_reward_to_vals;
+ status.outgoing_entry_fee = entry_fee;
+ status.outgoing_clearing_percent = clearing_percent;
+
+ // validators get the gross amount of the reward, since they already paid to enter. This results in a net payment equivalent to:
+ // nominal_reward_to_vals - entry_fee.
+ let (compliant_vals, total_reward) = process_outgoing_validators(root, &mut all_fees, nominal_reward_to_vals, compliant_vals);
+ status.outgoing_vals_paid = compliant_vals;
+ status.outgoing_total_reward = total_reward;
+
+ // check that the system fees collect were greater than reward
+ status.outgoing_vals_success = (status.system_fees_collected >= total_reward);
+ // check the the total actually deposited/paid is the expected amount
+ if (nominal_reward_to_vals > 0) { // check for zero
+ status.outgoing_vals_success = total_reward == (vector::length(&compliant_vals) * nominal_reward_to_vals)
+ };
- compliant_vals
+ // Commit note: deprecated with tower mining.
+
+ // remainder gets burnt according to fee maker preferences
+ let (b_success, b_fees) = burn::epoch_burn_fees(root, &mut all_fees);
+ status.epoch_burn_success = b_success;
+ status.epoch_burn_fees = b_fees;
+
+ // coin can finally be destroyed. Up to here we have been extracting from a mutable.
+ // It's possible there might be some dust, that should get burned
+ burn::burn_and_track(all_fees);
+ };
+
+ compliant_vals
}
@@ -407,7 +402,7 @@ module diem_framework::epoch_boundary {
let user_coin = libra_coin::extract(reward_budget, reward_per);
reward_deposited = reward_deposited + libra_coin::value(&user_coin);
rewards::process_single(root, *addr, user_coin, 1);
- }
+ };
};
i = i + 1;
@@ -427,7 +422,6 @@ module diem_framework::epoch_boundary {
status.incoming_only_qualified_bidders = only_qualified_bidders;
status.incoming_auction_winners = auction_winners;
-
let post_failover_check = stake::check_failover_rules(auction_winners, n_seats);
status.incoming_post_failover_check = post_failover_check;
@@ -449,25 +443,25 @@ module diem_framework::epoch_boundary {
// set up rewards subsidy for coming epoch
fun subsidize_from_infra_escrow(root: &signer): (bool, u64) {
- system_addresses::assert_ol(root);
- let (reward_per, _, _, _ ) = proof_of_fee::get_consensus_reward();
- let vals = stake::get_current_validators();
- let count_vals = vector::length(&vals);
- count_vals = count_vals + ORACLE_PROVIDERS_SEATS;
- let total_epoch_budget = count_vals * reward_per;
- infra_escrow::epoch_boundary_collection(root,
- total_epoch_budget)
+ system_addresses::assert_ol(root);
+ let (reward_per, _, _, _ ) = proof_of_fee::get_consensus_reward();
+ let vals = stake::get_current_validators();
+ let count_vals = vector::length(&vals);
+ count_vals = count_vals + ORACLE_PROVIDERS_SEATS;
+ let total_epoch_budget = count_vals * reward_per;
+ print(&999);
+ print(&total_epoch_budget);
+ infra_escrow::epoch_boundary_collection(root, total_epoch_budget)
}
- /// check qualifications of community wallets
- /// need to check every epoch so that wallets who no longer qualify are not biasing the Match algorithm.
- fun reset_match_index_ratios(root: &signer) {
- system_addresses::assert_ol(root);
- let list = match_index::get_address_list();
- let good = community_wallet_init::get_qualifying(list);
- match_index::calc_ratios(root, good);
- }
-
+ /// check qualifications of community wallets
+ /// need to check every epoch so that wallets who no longer qualify are not biasing the Match algorithm.
+ fun reset_match_index_ratios(root: &signer) {
+ system_addresses::assert_ol(root);
+ let list = match_index::get_address_list();
+ let good = community_wallet_init::get_qualifying(list);
+ match_index::calc_ratios(root, good);
+ }
// all services the root collective security is billing for
fun root_service_billing(vm: &signer, status: &mut BoundaryStatus) {
@@ -505,27 +499,27 @@ module diem_framework::epoch_boundary {
#[test_only]
public fun ol_reconfigure_for_test(vm: &signer, closing_epoch: u64,
epoch_round: u64) acquires BoundaryStatus {
- use diem_framework::system_addresses;
- use diem_framework::randomness;
+ use diem_framework::system_addresses;
+ use diem_framework::randomness;
- system_addresses::assert_ol(vm);
- randomness::initialize_for_testing(vm);
- epoch_boundary(vm, closing_epoch, epoch_round);
+ system_addresses::assert_ol(vm);
+ randomness::initialize_for_testing(vm);
+ epoch_boundary(vm, closing_epoch, epoch_round);
}
#[test_only]
public fun test_set_boundary_ready(framework: &signer, closing_epoch: u64) acquires
BoundaryBit {
- system_addresses::assert_ol(framework);
- // don't check for "testnet" here, otherwise we can't test the production
- // settings
- let vm_signer = create_signer::create_signer(@vm_reserved);
- enable_epoch_trigger(&vm_signer, closing_epoch);
+ system_addresses::assert_ol(framework);
+ // don't check for "testnet" here, otherwise we can't test the production
+ // settings
+ let vm_signer = create_signer::create_signer(@vm_reserved);
+ enable_epoch_trigger(&vm_signer, closing_epoch);
}
#[test_only]
public fun test_trigger(vm: &signer) acquires BoundaryStatus, BoundaryBit {
- // don't check for "testnet" here, otherwise we can't test the production settings
- trigger_epoch(vm);
+ // don't check for "testnet" here, otherwise we can't test the production settings
+ trigger_epoch(vm);
}
}
diff --git a/framework/libra-framework/sources/ol_sources/infra_escrow.move b/framework/libra-framework/sources/ol_sources/infra_escrow.move
index c41061c5a..c9c121e5d 100644
--- a/framework/libra-framework/sources/ol_sources/infra_escrow.move
+++ b/framework/libra-framework/sources/ol_sources/infra_escrow.move
@@ -10,21 +10,21 @@
///////////////////////////////////////////////////////////////////////////
module ol_framework::infra_escrow{
+ use std::error;
use std::option::{Self, Option};
+ use diem_framework::coin;
+ use diem_framework::transaction_fee;
use diem_framework::system_addresses;
+ use ol_framework::ol_account;
use ol_framework::libra_coin::LibraCoin;
use ol_framework::pledge_accounts;
// use ol_framework::slow_wallet;
- use ol_framework::ol_account;
- use diem_framework::coin;
- use diem_framework::transaction_fee;
// use std::fixed_point32;
// use std::signer;
- use std::error;
- // use diem_std::debug::print;
+ use diem_std::debug::print;
- friend ol_framework::epoch_boundary;
friend diem_framework::genesis;
+ friend ol_framework::epoch_boundary;
const EGENESIS_REWARD: u64 = 0;
/// for use on genesis, creates the infra escrow pledge policy struct
diff --git a/framework/libra-framework/sources/ol_sources/mock.move b/framework/libra-framework/sources/ol_sources/mock.move
index 2ff072302..006f5288e 100644
--- a/framework/libra-framework/sources/ol_sources/mock.move
+++ b/framework/libra-framework/sources/ol_sources/mock.move
@@ -1,26 +1,28 @@
// Some fixtures are complex and are repeatedly needed
#[test_only]
module ol_framework::mock {
+ use std::vector;
+ use std::signer;
+ use diem_framework::coin;
+ use diem_framework::block;
use diem_framework::stake;
+ use diem_framework::account;
+ use diem_framework::genesis;
+ use diem_framework::timestamp;
use diem_framework::reconfiguration;
+ use diem_framework::system_addresses;
+ use diem_framework::transaction_fee;
use ol_framework::grade;
use ol_framework::vouch;
- use std::vector;
- use diem_framework::genesis;
- use diem_framework::account;
use ol_framework::slow_wallet;
use ol_framework::proof_of_fee;
use ol_framework::validator_universe;
- use diem_framework::timestamp;
- use diem_framework::system_addresses;
use ol_framework::epoch_boundary;
- use diem_framework::coin;
use ol_framework::libra_coin::{Self, LibraCoin};
- use diem_framework::transaction_fee;
use ol_framework::ol_account;
use ol_framework::epoch_helper;
use ol_framework::musical_chairs;
- use diem_framework::block;
+ use ol_framework::pledge_accounts;
// use diem_std::debug::print;
@@ -158,7 +160,7 @@ module ol_framework::mock {
system_addresses::assert_ol(root);
let mint_cap = if (coin::is_coin_initialized()) {
- libra_coin::extract_mint_cap(root)
+ libra_coin::extract_mint_cap(root)
} else {
init_coin_impl(root)
};
@@ -178,7 +180,7 @@ module ol_framework::mock {
system_addresses::assert_ol(root);
let mint_cap = if (coin::is_coin_initialized()) {
- libra_coin::extract_mint_cap(root)
+ libra_coin::extract_mint_cap(root)
} else {
init_coin_impl(root)
};
@@ -211,11 +213,26 @@ module ol_framework::mock {
transaction_fee::initialize_fee_collection_and_distribution(root, 0);
- let initial_fees = 1000000 * 100; // coin scaling * 100 coins
+ let initial_fees = 5_000_000; // coin scaling * 100 coins
let tx_fees = coin::test_mint(initial_fees, &mint_cap);
transaction_fee::vm_pay_fee(root, @ol_framework, tx_fees);
+
+ // Forge Bruce
+ let fortune = 100_000_000_000;
+ let bruce_address = @0xBA7;
+ ol_account::create_account(root, bruce_address);
+
+ // Bruce mints a fortune
+ let bruce = account::create_signer_for_test(bruce_address);
+ let fortune_mint = coin::test_mint(fortune, &mint_cap);
+ ol_account::deposit_coins(bruce_address, fortune_mint);
+
+ // Bruce funds infra escrow
+ let framework = signer::address_of(root);
+ pledge_accounts::user_pledge(&bruce, framework, 37_000_000_000);
+
let supply_pre = libra_coin::supply();
- assert!(supply_pre == initial_fees, ESUPPLY_MISMATCH);
+ assert!(supply_pre == (initial_fees + fortune), ESUPPLY_MISMATCH);
libra_coin::test_set_final_supply(root, initial_fees);
mint_cap
diff --git a/framework/libra-framework/sources/ol_sources/pledge_accounts.move b/framework/libra-framework/sources/ol_sources/pledge_accounts.move
index 1aebfd115..c25cf387c 100644
--- a/framework/libra-framework/sources/ol_sources/pledge_accounts.move
+++ b/framework/libra-framework/sources/ol_sources/pledge_accounts.move
@@ -51,12 +51,15 @@
use diem_framework::coin;
use diem_framework::system_addresses;
- // use diem_std::debug::print;
+ //use diem_std::debug::print;
friend ol_framework::infra_escrow;
friend ol_framework::genesis_migration;
friend ol_framework::genesis;
+ #[test_only]
+ friend ol_framework::mock;
+
/// no policy at this address
const ENO_BENEFICIARY_POLICY: u64 = 1;
/// there is a non zero balance
@@ -160,7 +163,7 @@
sig: &signer,
address_of_beneficiary: address,
pledge: coin::Coin
- ) acquires MyPledges, BeneficiaryPolicy {
+ ) acquires MyPledges, BeneficiaryPolicy {
maybe_initialize_my_pledges(sig);
assert!(exists(address_of_beneficiary), error::invalid_state(ENO_BENEFICIARY_POLICY));
let sender_addr = signer::address_of(sig);
@@ -248,57 +251,56 @@
// withdraw an amount from all pledge accounts. Check first that there are remaining funds before attempting to withdraw.
public(friend) fun withdraw_from_all_pledge_accounts(sig_beneficiary: &signer, amount: u64): option::Option> acquires MyPledges, BeneficiaryPolicy {
- let address_of_beneficiary = signer::address_of(sig_beneficiary);
- if (!exists(address_of_beneficiary)) {
- return option::none>()
- };
-
- let pledgers = *&borrow_global(address_of_beneficiary).pledgers;
- let amount_available = *&borrow_global(address_of_beneficiary).amount_available;
-
- if (amount_available == 0 || amount == 0) {
- return option::none>()
- };
-
- let pct_withdraw = fixed_point64::create_from_rational((amount as u128), (amount_available as u128));
-
- let i = 0;
- let all_coins = option::none>();
- while (i < vector::length(&pledgers)) {
- let pledge_account = *vector::borrow(&pledgers, i);
-
- // DANGER: this is a private function that changes balances.
- if (!exists(pledge_account)) continue;
+ let address_of_beneficiary = signer::address_of(sig_beneficiary);
+ if (!exists(address_of_beneficiary)) {
+ return option::none>()
+ };
- let c = withdraw_pct_from_one_pledge_account(&address_of_beneficiary, &pledge_account, &pct_withdraw);
- // GROSS: dealing with options in Move.
- // TODO: find a better way.
- if (option::is_none(&all_coins) && option::is_some(&c)) {
+ let pledgers = *&borrow_global(address_of_beneficiary).pledgers;
+ let amount_available = *&borrow_global(address_of_beneficiary).amount_available;
- let coin = option::extract(&mut c);
- option::fill(&mut all_coins, coin);
- option::destroy_none(c);
- } else if (option::is_some(&c)) {
+ if (amount_available == 0 || amount == 0) {
+ return option::none>()
+ };
- let temp = option::extract(&mut all_coins);
- let coin = option::extract(&mut c);
- libra_coin::merge(&mut temp, coin);
- option::destroy_none(all_coins);
- all_coins = option::some(temp);
- option::destroy_none(c);
- } else {
- option::destroy_none(c);
- };
+ let pct_withdraw = fixed_point64::create_from_rational((amount as u128), (amount_available as u128));
- i = i + 1;
+ let i = 0;
+ let all_coins = option::none>();
+ while (i < vector::length(&pledgers)) {
+ let pledge_account = *vector::borrow(&pledgers, i);
+
+ // DANGER: this is a private function that changes balances.
+ if (!exists(pledge_account)) continue;
+
+ let c = withdraw_pct_from_one_pledge_account(&address_of_beneficiary, &pledge_account, &pct_withdraw);
+ // GROSS: dealing with options in Move.
+ // TODO: find a better way.
+ if (option::is_none(&all_coins) && option::is_some(&c)) {
+
+ let coin = option::extract(&mut c);
+ option::fill(&mut all_coins, coin);
+ option::destroy_none(c);
+ } else if (option::is_some(&c)) {
+
+ let temp = option::extract(&mut all_coins);
+ let coin = option::extract(&mut c);
+ libra_coin::merge(&mut temp, coin);
+ option::destroy_none(all_coins);
+ all_coins = option::some(temp);
+ option::destroy_none(c);
+ } else {
+ option::destroy_none(c);
};
+ i = i + 1;
+ };
+
all_coins
}
- fun get_user_pledges(account: &address): vector acquires
- MyPledges {
+ fun get_user_pledges(account: &address): vector acquires MyPledges {
let list = vector::empty();
if (!exists(*account)) return list;
let user_state = borrow_global(*account);
diff --git a/framework/libra-framework/sources/ol_sources/proof_of_fee.move b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
index 7ba4351f9..03bf9b22b 100644
--- a/framework/libra-framework/sources/ol_sources/proof_of_fee.move
+++ b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
@@ -148,24 +148,24 @@ module ol_framework::proof_of_fee {
outgoing_compliant_set: &vector,
mc_set_size: u64 // musical chairs set size suggestion
): (vector, vector, vector, u64) acquires ProofOfFeeAuction, ConsensusReward {
- system_addresses::assert_ol(vm);
+ system_addresses::assert_ol(vm);
- let all_bidders = get_bidders(false);
- let only_qualified_bidders = get_bidders(true);
+ let all_bidders = get_bidders(false);
+ let only_qualified_bidders = get_bidders(true);
- // Calculate the final set size considering the number of compliant validators,
- // number of qualified bidders, and musical chairs set size suggestion
- let final_set_size = calculate_final_set_size(
- vector::length(outgoing_compliant_set),
- vector::length(&only_qualified_bidders),
- mc_set_size);
+ // Calculate the final set size considering the number of compliant validators,
+ // number of qualified bidders, and musical chairs set size suggestion
+ let final_set_size = calculate_final_set_size(
+ vector::length(outgoing_compliant_set),
+ vector::length(&only_qualified_bidders),
+ mc_set_size);
- // This is the core of the mechanism, the uniform price auction
- // the winners of the auction will be the validator set.
- // Other lists are created for audit purposes of the BoundaryStatus
- let (auction_winners, entry_fee, _clearing_bid, _proven, _unproven) = fill_seats_and_get_price(vm, final_set_size, &only_qualified_bidders, outgoing_compliant_set);
+ // This is the core of the mechanism, the uniform price auction
+ // the winners of the auction will be the validator set.
+ // Other lists are created for audit purposes of the BoundaryStatus
+ let (auction_winners, entry_fee, _clearing_bid, _proven, _unproven) = fill_seats_and_get_price(vm, final_set_size, &only_qualified_bidders, outgoing_compliant_set);
- (auction_winners, all_bidders, only_qualified_bidders, entry_fee)
+ (auction_winners, all_bidders, only_qualified_bidders, entry_fee)
}
@@ -436,12 +436,12 @@ module ol_framework::proof_of_fee {
(i < vector::length(&sorted_vals_by_bid))
) {
let val = vector::borrow(&sorted_vals_by_bid, i);
- if (!account::exists_at(*val)) {
- i = i + 1;
- continue
- };
+ if (!account::exists_at(*val)) {
+ i = i + 1;
+ continue
+ };
// check if a proven node
- // NOTE: if the top bidders all all "proven" nodes, then there will
+ // NOTE: if the top bidders are all "proven" nodes, then there will
// be no reason to add an unproven. Unproven nodes will only
// be picked if they have bids higher than the bottom 1/3 bids of the proven nodes
if (vector::contains(proven_nodes, val)) {
@@ -532,6 +532,7 @@ module ol_framework::proof_of_fee {
fun bid_as_fixedpoint(bid_pct: u64): fixed_point32::FixedPoint32 {
fixed_point32::create_from_rational(bid_pct, 1000)
}
+
/// Adjust the reward at the end of the epoch
/// as described in the paper, the epoch reward needs to be adjustable
/// given that the implicit bond needs to be sufficient, eg 5-10x the reward.
@@ -670,8 +671,6 @@ module ol_framework::proof_of_fee {
}
//////////////// GETTERS ////////////////
- // get the current bid for a validator
-
#[view]
/// get the baseline reward from ConsensusReward
@@ -681,6 +680,7 @@ module ol_framework::proof_of_fee {
return (b.nominal_reward, b.entry_fee, b.clearing_bid, b.median_win_bid)
}
+ // get the current bid for a validator
// CONSENSUS CRITICAL
// ALL EYES ON THIS
// Proof of Fee returns the current bid of the validator during the auction for upcoming epoch seats.
@@ -714,19 +714,19 @@ module ol_framework::proof_of_fee {
// Get the top N validators by bid, this is FILTERED by default
public(friend) fun top_n_accounts(account: &signer, n: u64, unfiltered: bool): vector acquires ProofOfFeeAuction, ConsensusReward {
- system_addresses::assert_vm(account);
+ system_addresses::assert_vm(account);
- let eligible_validators = get_bidders(unfiltered);
- let len = vector::length(&eligible_validators);
- if(len <= n) return eligible_validators;
+ let eligible_validators = get_bidders(unfiltered);
+ let len = vector::length(&eligible_validators);
+ if(len <= n) return eligible_validators;
- let diff = len - n;
- while(diff > 0){
- vector::pop_back(&mut eligible_validators);
- diff = diff - 1;
- };
+ let diff = len - n;
+ while(diff > 0){
+ vector::pop_back(&mut eligible_validators);
+ diff = diff - 1;
+ };
- eligible_validators
+ eligible_validators
}
@@ -755,13 +755,11 @@ module ol_framework::proof_of_fee {
/// Note that the validator will not be bidding on any future
/// epochs if they retract their bid. The must set a new bid.
fun retract_bid(account_sig: &signer) acquires ProofOfFeeAuction {
-
let acc = signer::address_of(account_sig);
if (!exists(acc)) {
init(account_sig);
};
-
let pof = borrow_global_mut(acc);
let this_epoch = epoch_helper::get_current_epoch();
@@ -772,7 +770,6 @@ module ol_framework::proof_of_fee {
// assert!(this_epoch > pof.last_epoch_retracted, error::ol_tx(EABOVE_RETRACT_LIMIT));
//////// LEAVE COMMENTED. Code for a potential upgrade. ////////
-
pof.epoch_expiration = 0;
pof.bid = 0;
pof.last_epoch_retracted = this_epoch;
@@ -952,10 +949,8 @@ module ol_framework::proof_of_fee {
assert!(reward == 105, 1003);
assert!(clear_percent == 50, 1004);
assert!(median_bid == 33, 1005);
-
}
-
// Scenario: The reward is too low during 5 days (short window). People are not bidding very high.
#[test(vm = @ol_framework)]
fun thermostat_increase_long(vm: signer) acquires ConsensusReward {
@@ -977,7 +972,6 @@ module ol_framework::proof_of_fee {
i = i + 1;
};
-
test_mock_reward(
&vm,
100,
@@ -1000,10 +994,8 @@ module ol_framework::proof_of_fee {
assert!(reward == 110, 1003);
assert!(clear_percent == 50, 1004);
assert!(median_bid == 33, 1005);
-
}
-
// Scenario: The reward is too high during 5 days (short window). People are bidding over 95% of the baseline fee.
#[test(vm = @ol_framework)]
fun thermostat_decrease_short(vm: signer) acquires ConsensusReward {
@@ -1024,7 +1016,6 @@ module ol_framework::proof_of_fee {
i = i + 1;
};
-
test_mock_reward(
&vm,
100,
@@ -1047,7 +1038,6 @@ module ol_framework::proof_of_fee {
assert!(reward == 95, 1000);
assert!(clear_percent == 50, 1004);
assert!(median_bid == 33, 1005);
-
}
// Scenario: The reward is too low during 5 days (short window). People are not bidding very high.
@@ -1093,7 +1083,6 @@ module ol_framework::proof_of_fee {
assert!(reward == 90, 1003);
assert!(clear_percent == 50, 1004);
assert!(median_bid == 33, 1005);
-
}
// #[test(vm = @ol_framework)]
@@ -1311,7 +1300,6 @@ module ol_framework::proof_of_fee {
}
-
// Shuffle Tests
#[test]
diff --git a/framework/libra-framework/sources/ol_sources/tests/validator_reward.test.move b/framework/libra-framework/sources/ol_sources/tests/validator_reward.test.move
index 8d41753b5..bcccaba78 100644
--- a/framework/libra-framework/sources/ol_sources/tests/validator_reward.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/validator_reward.test.move
@@ -4,6 +4,7 @@
module ol_framework::test_reconfiguration {
use std::vector;
use diem_framework::stake;
+ use diem_framework::transaction_fee;
// use diem_framework::coin;
use ol_framework::mock;
use ol_framework::testnet;
@@ -12,90 +13,112 @@ module ol_framework::test_reconfiguration {
use diem_framework::reconfiguration;
use ol_framework::epoch_helper;
use ol_framework::ol_account;
+ use ol_framework::infra_escrow;
- // use diem_std::debug::print;
+ use diem_std::debug::print;
// Scenario: all genesis validators make it to next epoch
#[test(root = @ol_framework)]
fun reconfig_reward_happy_case(root: signer) {
- let vals = mock::genesis_n_vals(&root, 5);
+ let vals = mock::genesis_n_vals(&root, 5);
+ mock::pof_default();
+ assert!(vector::length(&vals) == 5, 7357001);
+ let vals = stake::get_current_validators();
+ assert!(vector::length(&vals) == 5, 7357002);
+ // all vals compliant
+ mock::mock_all_vals_good_performance(&root);
+ let (unlocked, alice_bal) = ol_account::balance(@0x1000a);
+ assert!(unlocked==0, 7367001);
+ assert!(alice_bal==0, 7357002);
- mock::pof_default();
- assert!(vector::length(&vals) == 5, 7357001);
- let vals = stake::get_current_validators();
- assert!(vector::length(&vals) == 5, 7357002);
- // all vals compliant
- mock::mock_all_vals_good_performance(&root);
+ let (reward_one, _entry_fee, _, _ ) = proof_of_fee::get_consensus_reward();
- let (unlocked, alice_bal) = ol_account::balance(@0x1000a);
- assert!(unlocked==0, 7367001);
- assert!(alice_bal==0, 7357002);
+ // The epoch's reward BEFORE reconfiguration
+ assert!(reward_one == 1000000, 7357004);
- let (reward_one, _entry_fee, _, _ ) = proof_of_fee::get_consensus_reward();
- // The epoch's reward BEFORE reconfiguration
- assert!(reward_one == 1000000, 7357004);
- // run ol reconfiguration
- mock::trigger_epoch(&root);
+ let infra = infra_escrow::infra_escrow_balance();
+ print(&555);
+ print(&infra);
- let vals = stake::get_current_validators();
+ let subsidy = transaction_fee::system_fees_collected();
+ print(&666);
+ print(&subsidy);
- assert!(vector::length(&vals) == 5, 7357005);
- // let alice_bal = libra_coin::balance(@0x1000a);
- let (_unlocked, alice_bal) = ol_account::balance(@0x1000a);
+ // run ol reconfiguration
+ mock::trigger_epoch(&root);
- let (_, entry_fee, _, _ ) = proof_of_fee::get_consensus_reward();
- // need to check that the user paid an PoF entry fee for next epoch.
- // which means the balance will be the nominal reward, net of the PoF clearing price bid
- assert!(alice_bal == (reward_one-entry_fee), 7357006)
+ let infra = infra_escrow::infra_escrow_balance();
+ print(&5552);
+ print(&infra);
+ let subsidy = transaction_fee::system_fees_collected();
+ print(&6662);
+ print(&subsidy);
- }
+ let vals = stake::get_current_validators();
+ assert!(vector::length(&vals) == 5, 7357005);
+ // let alice_bal = libra_coin::balance(@0x1000a);
+ let (_unlocked, alice_bal) = ol_account::balance(@0x1000a);
- #[test(root = @ol_framework)]
- fun drop_non_performing(root: signer) {
- let _vals = mock::genesis_n_vals(&root, 5);
- // mock::ol_initialize_coin(&root);
- mock::pof_default();
- assert!(libra_coin::balance(@0x1000a) == 0, 7357000);
+ let (_, entry_fee, _, _ ) = proof_of_fee::get_consensus_reward();
+ // need to check that the user paid an PoF entry fee for next epoch.
+ // which means the balance will be the nominal reward, net of the PoF clearing price bid
+ assert!(alice_bal == (reward_one - entry_fee), 7357006);
- // NOTE: epoch 0 and 1 are a special case, we don't run performance grades on that one. Need to move two epochs ahead
- reconfiguration::test_helper_increment_epoch_dont_reconfigure(1);
- reconfiguration::test_helper_increment_epoch_dont_reconfigure(1);
+ // test new subsidy
+ let (reward_two, _entry_fee, _, _ ) = proof_of_fee::get_consensus_reward();
+ print(&777);
+ print(&reward_two);
+ let new_budget = reward_two * 5;
+ print(&new_budget);
- assert!(epoch_helper::get_current_epoch() == 2, 7357001);
+ let subsidy = transaction_fee::system_fees_collected();
+ print(&888);
+ print(&subsidy);
- let vals = stake::get_current_validators();
- assert!(vector::length(&vals) == 5, 7357002);
+ }
- // all vals compliant
- mock::mock_all_vals_good_performance(&root);
+ #[test(root = @ol_framework)]
+ fun drop_non_performing(root: signer) {
+ let _vals = mock::genesis_n_vals(&root, 5);
+ // mock::ol_initialize_coin(&root);
+ mock::pof_default();
+ assert!(libra_coin::balance(@0x1000a) == 0, 7357000);
- // make alice non performant
- mock::mock_case_4(&root, *vector::borrow(&vals, 0));
+ // NOTE: epoch 0 and 1 are a special case, we don't run performance grades on that one. Need to move two epochs ahead
+ reconfiguration::test_helper_increment_epoch_dont_reconfigure(1);
+ reconfiguration::test_helper_increment_epoch_dont_reconfigure(1);
- let (reward, _, _, _ ) = proof_of_fee::get_consensus_reward();
+ assert!(epoch_helper::get_current_epoch() == 2, 7357001);
- // run ol reconfiguration
- mock::trigger_epoch(&root);
- // mock::trigger_epoch(&root);
+ let vals = stake::get_current_validators();
+ assert!(vector::length(&vals) == 5, 7357002);
- let vals = stake::get_current_validators();
+ // all vals compliant
+ mock::mock_all_vals_good_performance(&root);
+
+ // make alice non performant
+ mock::mock_case_4(&root, *vector::borrow(&vals, 0));
- // one validator missing.
- assert!(vector::length(&vals) == 4, 7357003);
- assert!(!vector::contains(&vals, &@0x1000a), 7357004);
+ let (reward, _, _, _ ) = proof_of_fee::get_consensus_reward();
- let (_, entry_fee, _, _ ) = proof_of_fee::get_consensus_reward();
+ // run ol reconfiguration
+ mock::trigger_epoch(&root);
- // alice doesn't get paid
+ let vals = stake::get_current_validators();
- assert!(libra_coin::balance(@0x1000a) == 0, 7357005);
- // bob does
- assert!(libra_coin::balance(@0x1000b) == (reward - entry_fee), 7357006);
+ // one validator missing.
+ assert!(vector::length(&vals) == 4, 7357003);
+ assert!(!vector::contains(&vals, &@0x1000a), 7357004);
+ let (_, entry_fee, _, _ ) = proof_of_fee::get_consensus_reward();
+ // alice doesn't get paid
+ assert!(libra_coin::balance(@0x1000a) == 0, 7357005);
+ // bob does
+ assert!(libra_coin::balance(@0x1000b) == (reward - entry_fee), 7357006);
}
From b16d97e570ead67c959eb1df32dbbec26bb679fb Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Tue, 16 Jul 2024 21:07:06 -0300
Subject: [PATCH 07/16] fix budget to collect, and fee calculus test subsidy,
fees and reward payments
---
.../sources/ol_sources/epoch_boundary.move | 3 +-
.../sources/ol_sources/infra_escrow.move | 3 +-
.../sources/ol_sources/mock.move | 2 +-
.../sources/ol_sources/proof_of_fee.move | 2 +-
.../ol_sources/tests/boundary.test.move | 78 ++++++++++++++++---
.../ol_sources/tests/proof_of_fee.test.move | 4 -
6 files changed, 73 insertions(+), 19 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/epoch_boundary.move b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
index 48bf35888..73f873985 100644
--- a/framework/libra-framework/sources/ol_sources/epoch_boundary.move
+++ b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
@@ -447,8 +447,7 @@ module diem_framework::epoch_boundary {
let (reward_per, _, _, _ ) = proof_of_fee::get_consensus_reward();
let vals = stake::get_current_validators();
let count_vals = vector::length(&vals);
- count_vals = count_vals + ORACLE_PROVIDERS_SEATS;
- let total_epoch_budget = count_vals * reward_per;
+ let total_epoch_budget = (count_vals * reward_per) + 1; // +1 for rounding
print(&999);
print(&total_epoch_budget);
infra_escrow::epoch_boundary_collection(root, total_epoch_budget)
diff --git a/framework/libra-framework/sources/ol_sources/infra_escrow.move b/framework/libra-framework/sources/ol_sources/infra_escrow.move
index c9c121e5d..b91fbd34d 100644
--- a/framework/libra-framework/sources/ol_sources/infra_escrow.move
+++ b/framework/libra-framework/sources/ol_sources/infra_escrow.move
@@ -21,7 +21,7 @@ module ol_framework::infra_escrow{
// use ol_framework::slow_wallet;
// use std::fixed_point32;
// use std::signer;
- use diem_std::debug::print;
+ // use diem_std::debug::print;
friend diem_framework::genesis;
friend ol_framework::epoch_boundary;
@@ -71,7 +71,6 @@ module ol_framework::infra_escrow{
}
-
// Transaction script for user to pledge to infra escrow.
fun user_pledge_infra(user_sig: &signer, amount: u64){
pledge_accounts::user_pledge(user_sig, @ol_framework, amount);
diff --git a/framework/libra-framework/sources/ol_sources/mock.move b/framework/libra-framework/sources/ol_sources/mock.move
index 006f5288e..fd6f7c4fd 100644
--- a/framework/libra-framework/sources/ol_sources/mock.move
+++ b/framework/libra-framework/sources/ol_sources/mock.move
@@ -213,7 +213,7 @@ module ol_framework::mock {
transaction_fee::initialize_fee_collection_and_distribution(root, 0);
- let initial_fees = 5_000_000; // coin scaling * 100 coins
+ let initial_fees = 5_000_000 * 100; // coin scaling * 100 coins
let tx_fees = coin::test_mint(initial_fees, &mint_cap);
transaction_fee::vm_pay_fee(root, @ol_framework, tx_fees);
diff --git a/framework/libra-framework/sources/ol_sources/proof_of_fee.move b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
index 03bf9b22b..4a3e690a1 100644
--- a/framework/libra-framework/sources/ol_sources/proof_of_fee.move
+++ b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
@@ -477,7 +477,7 @@ module ol_framework::proof_of_fee {
cr.clearing_bid = lowest_bid_pct;
if (lowest_bid_pct > 0) {
- cr.entry_fee = fixed_point32::multiply_u64(cr.nominal_reward, bid_as_fixedpoint(lowest_bid_pct));
+ cr.entry_fee = cr.nominal_reward * lowest_bid_pct / 1000;
if (cr.nominal_reward > cr.entry_fee) {
cr.net_reward = cr.nominal_reward - cr.entry_fee;
diff --git a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
index d89036ca8..3060e1cb1 100644
--- a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
@@ -4,6 +4,11 @@ module ol_framework::test_boundary {
use std::vector;
use std::features;
use diem_std::bls12381;
+ use diem_framework::stake;
+ use diem_framework::timestamp;
+ use diem_framework::reconfiguration;
+ use diem_framework::diem_governance;
+ use diem_framework::transaction_fee;
use ol_framework::mock;
use ol_framework::proof_of_fee;
use ol_framework::jail;
@@ -14,12 +19,8 @@ module ol_framework::test_boundary {
use ol_framework::epoch_boundary;
use ol_framework::block;
use ol_framework::ol_account;
- use diem_framework::stake;
- use diem_framework::reconfiguration;
- use diem_framework::timestamp;
- use diem_framework::diem_governance;
- // use diem_std::debug::print;
+ use diem_std::debug::print;
const Alice: address = @0x1000a;
const Bob: address = @0x1000b;
@@ -46,7 +47,13 @@ module ol_framework::test_boundary {
// We need to test e2e of the epoch boundary
#[test(root = @ol_framework)]
fun e2e_boundary_happy(root: signer) {
- let _vals = common_test_setup(&root);
+ let vals = common_test_setup(&root);
+
+ // check vals balance
+ vector::for_each(vals, |addr| {
+ let (_unlocked, total) = ol_account::balance(addr);
+ assert!(total == 500_000, 7357000);
+ });
mock::trigger_epoch(&root);
@@ -56,17 +63,28 @@ module ol_framework::test_boundary {
// all validators were compliant, should be +1 of the 10 vals
assert!(epoch_boundary::get_seats_offered() == 11, 7357002);
-
// all vals had winning bids, but it was less than the seats on offer
assert!(vector::length(&epoch_boundary::get_auction_winners()) == 10, 7357003);
// all of the auction winners became the validators ulitmately
assert!(vector::length(&epoch_boundary::get_actual_vals()) == 10, 7357004);
+
+ // check vals rewards received and bid fees collected
+ // balance + reward - fee = 500_000 + 1_000_000 - 10_000 = 1_490_000
+ vector::for_each(vals, |addr| {
+ let (_unlocked, total) = ol_account::balance(addr);
+ assert!(total == 1_499_000, 7357005);
+ });
+
+ // check subsidy for new rewards and fees collected
+ // fees collected = 10 * 1_000_000 + 10 * 1_000 = 10_010_000
+ print(&transaction_fee::system_fees_collected());
+ assert!(transaction_fee::system_fees_collected() == 10_010_000, 7357006);
}
#[test(root = @ol_framework, alice = @0x1000a, marlon_rando = @0x12345)]
fun e2e_add_validator_happy(root: signer, alice: signer, marlon_rando: signer) {
- let _vals = common_test_setup(&root);
+ let initial_vals = common_test_setup(&root);
// generate credentials for validator registration
ol_account::transfer(&alice, @0x12345, 200000);
@@ -78,6 +96,7 @@ module ol_framework::test_boundary {
// MARLON PAYS THE BID
let vals = validator_universe::get_eligible_validators();
assert!(vector::length(&vals) == 11, 7357000);
+
mock::mock_bids(&vals);
// MARLON HAS MANY FRIENDS
@@ -86,24 +105,65 @@ module ol_framework::test_boundary {
let (errs, _pass) = proof_of_fee::audit_qualification(@0x12345);
assert!(vector::length(&errs) == 0, 7357001);
+ // get initial vals balance
+ let balances = vector::map(initial_vals, |addr| {
+ let (_unlocked, total) = ol_account::balance(addr);
+ total
+ });
+
mock::trigger_epoch(&root);
assert!(epoch_boundary::get_reconfig_success(), 7357002);
// all validators were compliant, should be +1 of the 10 vals
assert!(epoch_boundary::get_seats_offered() == 11, 7357003);
-
// NOTE: now MARLON is INCLUDED in this, and we filled all the seats on offer.
// all vals had winning bids, but it was less than the seats on offer
assert!(vector::length(&epoch_boundary::get_auction_winners()) == 11, 7357003);
// all of the auction winners became the validators ulitmately
assert!(vector::length(&epoch_boundary::get_actual_vals()) == 11, 7357004);
+ // check initial vals rewards received and bid fees collected
+ // previous balance = current - reward + fee
+ let i = 0;
+ while (i < vector::length(&initial_vals)) {
+ let (_unlocked, current) = ol_account::balance(*vector::borrow(&initial_vals, i));
+ let previous = *vector::borrow(&balances, i);
+ assert!(current == (previous + 1_000_000 - 1_000), 7357005);
+ i = i + 1;
+ };
+
+ // check Marlon's balance: 200_000 - 1_000 = 199_000
+ let (_unlocked, marlon_balance) = ol_account::balance(@0x12345);
+ assert!(marlon_balance == 199_000, 7357006);
+
+ // check subsidy for new rewards and fees collected
+ // fees collected = 11 * 1_000_000 + 11 * 1_000 = 11_011_000
+ assert!(transaction_fee::system_fees_collected() == 11_011_000, 7357006);
+
// another epoch and everyone is compliant as well
mock::mock_all_vals_good_performance(&root);
mock::trigger_epoch(&root);
assert!(epoch_boundary::get_seats_offered() == 12, 7357005);
assert!(vector::length(&epoch_boundary::get_actual_vals()) == 11, 7357006);
+
+ // check initial vals rewards received and bid fees collected
+ // previous balance = current - 2*reward + 2*fee
+ let i = 0;
+ while (i < vector::length(&initial_vals)) {
+ let (_unlocked, current) = ol_account::balance(*vector::borrow(&initial_vals, i));
+ let previous = *vector::borrow(&balances, i);
+ assert!(current == (previous + 2_000_000 - 2_000), 7357005);
+ i = i + 1;
+ };
+
+ // check Marlon's balance: 200_000 + 1_000_000 - 2_000 = 1_198_000
+ let (_unlocked, marlon_balance) = ol_account::balance(@0x12345);
+ assert!(marlon_balance == 1_198_000, 7357006);
+
+ // check subsidy for new rewards and fees collected
+ // fees collected = 11 * 1_000_000 + 11 * 1_000 = 11_011_000
+ assert!(transaction_fee::system_fees_collected() == 11_011_000, 7357007);
}
#[test(root = @ol_framework, alice = @0x1000a, marlon_rando = @0x12345)]
diff --git a/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move b/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
index abea11e07..6de127ede 100644
--- a/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
@@ -21,8 +21,6 @@ module ol_framework::test_pof {
const Eve: address = @0x1000e;
const Frank: address = @0x1000f;
-
-
#[test_only]
fun mock_good_bid(_root: &signer, alice: &address) {
let a_sig = account::create_signer_for_test(*alice);
@@ -56,13 +54,11 @@ module ol_framework::test_pof {
assert!(bid == 0, 1001);
assert!(expires == 0, 1002);
-
proof_of_fee::pof_update_bid(&a_sig, 100, 0);
let (bid, expires) = proof_of_fee::current_bid(*alice);
assert!(bid == 100, 1003);
assert!(expires == 0, 1004);
-
// now retract
proof_of_fee::pof_retract_bid(a_sig);
let (bid, expires) = proof_of_fee::current_bid(*alice);
From 2afd4e92b0f4c78e296d88e7409b8c334da71852 Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Wed, 17 Jul 2024 19:40:19 -0300
Subject: [PATCH 08/16] test subsidy and rewards having a non compliant val
---
.../ol_sources/tests/boundary.test.move | 33 +++++++++++++++++--
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
index 3060e1cb1..d48790bb0 100644
--- a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
@@ -200,12 +200,39 @@ module ol_framework::test_boundary {
fun e2e_boundary_excludes_jail(root: signer) {
let vals = common_test_setup(&root);
let alice_addr = *vector::borrow(&vals, 0);
- jail::jail(&root, alice_addr);
- assert!(jail::is_jailed(alice_addr), 7357000);
+
+ // mock vals performance
+ let i = 1;
+ while (i < vector::length(&vals)) {
+ let addr = *vector::borrow(&vals, i);
+ stake::mock_performance(&root, addr, 10, 0);
+ i = i + 1;
+ };
+
+ // make Alice val not compliant to end up in jail
+ stake::mock_performance(&root, alice_addr, 10, 10);
+
+ // get Alice balance before epoch boundary
+ let (_unlocked, alice_before) = ol_account::balance(alice_addr);
+
+ // new epoch
mock::trigger_epoch(&root);
+ // check that Alice is jailed
+ assert!(jail::is_jailed(alice_addr), 7357000);
+
+ // ensure Alice did not receive rewards
+ let (_unlocked, alice_after) = ol_account::balance(alice_addr);
+ assert!(alice_before == alice_after, 7357001);
+
+ // check that validator set reduced by 1
let qualified_bidders = epoch_boundary::get_qualified_bidders();
- assert!(vector::length(&qualified_bidders) == (vector::length(&vals) - 1), 7357003);
+ assert!(vector::length(&qualified_bidders) == 9, 7357003);
+
+ // check subsidy for new rewards and fees collected
+ // fees collected = 9 * 1_000_000 + 9 * 2_000 = 9_018_000
+ print(&transaction_fee::system_fees_collected());
+ assert!(transaction_fee::system_fees_collected() == 9_018_000, 7357006);
// all vals had winning bids, but it was less than the seats on offer
assert!(vector::length(&epoch_boundary::get_auction_winners()) == vector::length(&qualified_bidders) , 7357003);
From a01514ecfe54795619d5758fa1184b35efaf8dfc Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Thu, 18 Jul 2024 12:09:56 -0300
Subject: [PATCH 09/16] test that entry fee sum is burnt on epoch trigger
---
.../sources/ol_sources/epoch_boundary.move | 2 -
.../ol_sources/tests/boundary.test.move | 54 +++++++++++--------
2 files changed, 32 insertions(+), 24 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/epoch_boundary.move b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
index 73f873985..76332d728 100644
--- a/framework/libra-framework/sources/ol_sources/epoch_boundary.move
+++ b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
@@ -448,8 +448,6 @@ module diem_framework::epoch_boundary {
let vals = stake::get_current_validators();
let count_vals = vector::length(&vals);
let total_epoch_budget = (count_vals * reward_per) + 1; // +1 for rounding
- print(&999);
- print(&total_epoch_budget);
infra_escrow::epoch_boundary_collection(root, total_epoch_budget)
}
diff --git a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
index d48790bb0..1fea71e5d 100644
--- a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
@@ -9,6 +9,7 @@ module ol_framework::test_boundary {
use diem_framework::reconfiguration;
use diem_framework::diem_governance;
use diem_framework::transaction_fee;
+ use ol_framework::burn;
use ol_framework::mock;
use ol_framework::proof_of_fee;
use ol_framework::jail;
@@ -95,7 +96,7 @@ module ol_framework::test_boundary {
// MARLON PAYS THE BID
let vals = validator_universe::get_eligible_validators();
- assert!(vector::length(&vals) == 11, 7357000);
+ assert!(vector::length(&vals) == 11, 7357001);
mock::mock_bids(&vals);
@@ -103,7 +104,7 @@ module ol_framework::test_boundary {
vouch::test_set_buddies(@0x12345, vals);
let (errs, _pass) = proof_of_fee::audit_qualification(@0x12345);
- assert!(vector::length(&errs) == 0, 7357001);
+ assert!(vector::length(&errs) == 0, 7357002);
// get initial vals balance
let balances = vector::map(initial_vals, |addr| {
@@ -113,15 +114,15 @@ module ol_framework::test_boundary {
mock::trigger_epoch(&root);
- assert!(epoch_boundary::get_reconfig_success(), 7357002);
+ assert!(epoch_boundary::get_reconfig_success(), 7357003);
// all validators were compliant, should be +1 of the 10 vals
- assert!(epoch_boundary::get_seats_offered() == 11, 7357003);
+ assert!(epoch_boundary::get_seats_offered() == 11, 7357004);
// NOTE: now MARLON is INCLUDED in this, and we filled all the seats on offer.
// all vals had winning bids, but it was less than the seats on offer
- assert!(vector::length(&epoch_boundary::get_auction_winners()) == 11, 7357003);
+ assert!(vector::length(&epoch_boundary::get_auction_winners()) == 11, 7357005);
// all of the auction winners became the validators ulitmately
- assert!(vector::length(&epoch_boundary::get_actual_vals()) == 11, 7357004);
+ assert!(vector::length(&epoch_boundary::get_actual_vals()) == 11, 7357006);
// check initial vals rewards received and bid fees collected
// previous balance = current - reward + fee
@@ -129,23 +130,24 @@ module ol_framework::test_boundary {
while (i < vector::length(&initial_vals)) {
let (_unlocked, current) = ol_account::balance(*vector::borrow(&initial_vals, i));
let previous = *vector::borrow(&balances, i);
- assert!(current == (previous + 1_000_000 - 1_000), 7357005);
+ assert!(current == (previous + 1_000_000 - 1_000), 7357007);
i = i + 1;
};
// check Marlon's balance: 200_000 - 1_000 = 199_000
let (_unlocked, marlon_balance) = ol_account::balance(@0x12345);
- assert!(marlon_balance == 199_000, 7357006);
+ assert!(marlon_balance == 199_000, 7357008);
// check subsidy for new rewards and fees collected
// fees collected = 11 * 1_000_000 + 11 * 1_000 = 11_011_000
- assert!(transaction_fee::system_fees_collected() == 11_011_000, 7357006);
+ assert!(transaction_fee::system_fees_collected() == 11_011_000, 7357009);
// another epoch and everyone is compliant as well
mock::mock_all_vals_good_performance(&root);
mock::trigger_epoch(&root);
- assert!(epoch_boundary::get_seats_offered() == 12, 7357005);
- assert!(vector::length(&epoch_boundary::get_actual_vals()) == 11, 7357006);
+
+ assert!(epoch_boundary::get_seats_offered() == 12, 7357010);
+ assert!(vector::length(&epoch_boundary::get_actual_vals()) == 11, 7357011);
// check initial vals rewards received and bid fees collected
// previous balance = current - 2*reward + 2*fee
@@ -153,17 +155,26 @@ module ol_framework::test_boundary {
while (i < vector::length(&initial_vals)) {
let (_unlocked, current) = ol_account::balance(*vector::borrow(&initial_vals, i));
let previous = *vector::borrow(&balances, i);
- assert!(current == (previous + 2_000_000 - 2_000), 7357005);
+ assert!(current == (previous + 2_000_000 - 2_000), 7357012);
i = i + 1;
};
// check Marlon's balance: 200_000 + 1_000_000 - 2_000 = 1_198_000
let (_unlocked, marlon_balance) = ol_account::balance(@0x12345);
- assert!(marlon_balance == 1_198_000, 7357006);
+ assert!(marlon_balance == 1_198_000, 7357013);
- // check subsidy for new rewards and fees collected
- // fees collected = 11 * 1_000_000 + 11 * 1_000 = 11_011_000
- assert!(transaction_fee::system_fees_collected() == 11_011_000, 7357007);
+ // CHECK BURNT FEES
+
+ // prepare clean epoch
+ mock::trigger_epoch(&root);
+
+ let (before, _) = burn::get_lifetime_tracker();
+
+ mock::trigger_epoch(&root);
+
+ // check that only the entry fee sum is being burnt
+ let (after,_) = burn::get_lifetime_tracker();
+ assert!(after - before == 11_000_000, 7357014); // scale 1_000
}
#[test(root = @ol_framework, alice = @0x1000a, marlon_rando = @0x12345)]
@@ -219,11 +230,11 @@ module ol_framework::test_boundary {
mock::trigger_epoch(&root);
// check that Alice is jailed
- assert!(jail::is_jailed(alice_addr), 7357000);
+ assert!(jail::is_jailed(alice_addr), 7357001);
// ensure Alice did not receive rewards
let (_unlocked, alice_after) = ol_account::balance(alice_addr);
- assert!(alice_before == alice_after, 7357001);
+ assert!(alice_before == alice_after, 7357002);
// check that validator set reduced by 1
let qualified_bidders = epoch_boundary::get_qualified_bidders();
@@ -231,12 +242,11 @@ module ol_framework::test_boundary {
// check subsidy for new rewards and fees collected
// fees collected = 9 * 1_000_000 + 9 * 2_000 = 9_018_000
- print(&transaction_fee::system_fees_collected());
- assert!(transaction_fee::system_fees_collected() == 9_018_000, 7357006);
+ assert!(transaction_fee::system_fees_collected() == 9_018_000, 7357004);
// all vals had winning bids, but it was less than the seats on offer
- assert!(vector::length(&epoch_boundary::get_auction_winners()) == vector::length(&qualified_bidders) , 7357003);
- assert!(epoch_boundary::get_reconfig_success(), 7357001);
+ assert!(vector::length(&epoch_boundary::get_auction_winners()) == vector::length(&qualified_bidders) , 7357005);
+ assert!(epoch_boundary::get_reconfig_success(), 7357006);
}
#[test(root = @ol_framework, marlon = @0x12345)]
From f6ab542cc5965ab600b1a473b51308d065f9f414 Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Thu, 18 Jul 2024 17:53:51 -0300
Subject: [PATCH 10/16] adds tests to reward_thermostat increasing and
decreasing rewards fixes tests broken by mock changes
---
.../sources/ol_sources/epoch_boundary.move | 2 +-
.../sources/ol_sources/mock.move | 11 +-
.../ol_sources/tests/boundary.test.move | 60 ++
.../sources/ol_sources/tests/burn.test.move | 553 +++++++++---------
.../ol_sources/tests/donor_voice.test.move | 13 +-
.../ol_sources/tests/slow_wallet.test.move | 9 +-
6 files changed, 350 insertions(+), 298 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/epoch_boundary.move b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
index 76332d728..e78bb6f1b 100644
--- a/framework/libra-framework/sources/ol_sources/epoch_boundary.move
+++ b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
@@ -398,7 +398,7 @@ module diem_framework::epoch_boundary {
jail::jail(root, *addr);
} else {
// vector::push_back(&mut compliant_vals, *addr);
- if (libra_coin::value(reward_budget) > reward_per) {
+ if (libra_coin::value(reward_budget) >= reward_per) {
let user_coin = libra_coin::extract(reward_budget, reward_per);
reward_deposited = reward_deposited + libra_coin::value(&user_coin);
rewards::process_single(root, *addr, user_coin, 1);
diff --git a/framework/libra-framework/sources/ol_sources/mock.move b/framework/libra-framework/sources/ol_sources/mock.move
index fd6f7c4fd..40acbcecd 100644
--- a/framework/libra-framework/sources/ol_sources/mock.move
+++ b/framework/libra-framework/sources/ol_sources/mock.move
@@ -356,11 +356,12 @@ module ol_framework::mock {
let n_vals = 5;
let _vals = genesis_n_vals(root, n_vals); // need to include eve to init funds
- let genesis_mint = 1000000;
+ let genesis_mint = 1_000_000;
ol_initialize_coin_and_fund_vals(root, genesis_mint, true);
let supply_pre = libra_coin::supply();
- let mocked_tx_fees = 1000000 * 100;
- assert!(supply_pre == mocked_tx_fees + (n_vals * genesis_mint), 73570001);
+ let bruce_fortune = 100_000_000_000;
+ let mocked_tx_fees = 5_000_000 * 100;
+ assert!(supply_pre == bruce_fortune + mocked_tx_fees + (n_vals * genesis_mint), 73570001);
}
@@ -375,9 +376,9 @@ module ol_framework::mock {
let (nominal_reward, entry_fee, clearing_percent, median_bid ) = proof_of_fee::get_consensus_reward();
- assert!(nominal_reward == 1000000, 73570001);
+ assert!(nominal_reward == 1_000_000, 73570001);
assert!(clearing_percent == 1, 73570002);
- assert!(entry_fee == 999, 73570003);
+ assert!(entry_fee == 1_000, 73570003);
assert!(median_bid == 3, 73570004);
}
}
diff --git a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
index 1fea71e5d..659f207d1 100644
--- a/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/boundary.test.move
@@ -9,6 +9,7 @@ module ol_framework::test_boundary {
use diem_framework::reconfiguration;
use diem_framework::diem_governance;
use diem_framework::transaction_fee;
+ use diem_framework::account;
use ol_framework::burn;
use ol_framework::mock;
use ol_framework::proof_of_fee;
@@ -275,6 +276,65 @@ module ol_framework::test_boundary {
assert!(epoch == 4, 7357003);
}
+ #[test(root = @ol_framework)]
+ fun epoch_increase_thermostat(root: &signer) {
+ let vals = common_test_setup(root);
+
+ // mock bids
+ vector::for_each(vals, |a| {
+ let sig = account::create_signer_for_test(a);
+ proof_of_fee::pof_update_bid(&sig, 0100, 30); // 10% for 30 epochs
+ });
+
+ // mock bid history to increase thermostat by 5%
+ proof_of_fee::test_mock_reward(
+ root,
+ 1000, // nominal reward
+ 0100, // clearing bid
+ 0100, // median win bid
+ vector[ 0100, 0100, 0100, 0100, 0100, 0100 ] // median history bellow 50%
+ );
+
+ // trigger epoch
+ mock::trigger_epoch(root);
+
+ // check subsidy increased by 5%
+ // fees collected = entry fee + reward * 105%
+ // entry fee = 100 * 10 = 1_000
+ // reward = 1050 * 10 = 10_500
+ // fees collected = 11_500
+ assert!(transaction_fee::system_fees_collected() == 11_500, 7357001);
+ }
+
+ #[test(root = @ol_framework)]
+ fun epoch_decrease_thermostat(root: &signer) {
+ let vals = common_test_setup(root);
+
+ // mock bids
+ vector::for_each(vals, |a| {
+ let sig = account::create_signer_for_test(a);
+ proof_of_fee::pof_update_bid(&sig, 0970, 30); // 97% for 30 epochs
+ });
+
+ // mock bid history to decrease thermostat by 5%
+ proof_of_fee::test_mock_reward(
+ root,
+ 100_000, // nominal reward
+ 0970, // clearing bid
+ 0970, // median win bid
+ vector[ 0970, 0970, 0970, 0970, 0970, 0970 ] // median history above 95%
+ );
+
+ // trigger epoch
+ mock::trigger_epoch(root);
+
+ // check subsidy decreased by 5%
+ // fees collected = entry fee + reward * 95%
+ // entry fee = 97_000 * 10 = 970_000
+ // reward = 95_000 * 10 = 950_000
+ // fees collected = 1_920_000
+ assert!(transaction_fee::system_fees_collected() == 1_920_000, 7357001);
+ }
#[test(root = @ol_framework, marlon = @0x12345)]
#[expected_failure(abort_code = 2, location = 0x1::epoch_boundary)]
diff --git a/framework/libra-framework/sources/ol_sources/tests/burn.test.move b/framework/libra-framework/sources/ol_sources/tests/burn.test.move
index 2f79941ad..51c66a9f6 100644
--- a/framework/libra-framework/sources/ol_sources/tests/burn.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/burn.test.move
@@ -66,303 +66,297 @@ module ol_framework::test_burn {
}
- #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000d, eve = @0x1000e)]
- fun burn_to_match_index(root: &signer, alice: &signer, bob: &signer, eve: &signer) {
- // Scenario:
- // There are two community wallets. Alice and Bob's
- // EVE donates to both. But mostly to Alice.
- // The Match Index, should reflect that.
-
- let vals = mock::genesis_n_vals(root, 5); // need to include eve to init funds
- mock::ol_initialize_coin_and_fund_vals(root, 1000000, true);
- // start at epoch 1, since turnout tally needs epoch info, and 0 may cause issues
- mock::trigger_epoch(root);
-
- let (communityA, _cap) = ol_account::test_ol_create_resource_account(alice, b"0x1");
- let addr_A = signer::address_of(&communityA);
- community_wallet_init::init_community(&communityA, vals, 2); // make all the vals signers
-
- let (communityB, _cap) = ol_account::test_ol_create_resource_account(bob, b"0xdeadbeef");
- let addr_B = signer::address_of(&communityB);
- community_wallet_init::init_community(&communityB, vals, 2); // make all the vals signers
-
- let eve_donation_to_A = 75;
- ol_account::transfer(eve, addr_A, eve_donation_to_A);
- let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_A);
- assert!(total_funds_sent_by_eve == eve_donation_to_A, 7357001);
-
- let eve_donation_to_B = 25;
- ol_account::transfer(eve, addr_B, eve_donation_to_B);
- let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_B);
- assert!(total_funds_sent_by_eve == eve_donation_to_B, 7357002);
-
- let qualifying = vector::singleton(addr_A);
- vector::push_back(&mut qualifying, addr_B);
-
- // simulate index recalculation at epoch boundary
- match_index::test_reset_ratio(root, qualifying);
- let (_addrs_vec, _index_vec, ratio_vec) = match_index::get_ratios();
-
- // so far addr_A should have 75 coins from Eve's transfer
- let(_, balance_pre) = ol_account::balance(addr_A);
- assert!(balance_pre == eve_donation_to_A, 7357003);
-
- // let's burn some of bob's coins
- // first he sets his burn preference
- burn::set_send_community(bob, true);
- let bob_burn = 100000;
- let c = ol_account::withdraw(bob, bob_burn);
- // then the system burns based on his preference.
- burn::test_vm_burn_with_user_preference(root, signer::address_of(bob), c);
- // check that a recycle to match index happened instead of a straight burn
- let (_lifetime_burn, lifetime_match) = burn::get_lifetime_tracker();
- assert!(lifetime_match > 0, 7357003);
-
-
- // the balance of address A should be increase by the ratio above 75% * bob's burn of 100_000
- let ratio_A = vector::borrow(&ratio_vec, 0);
- let bob_burn_share_A = fixed_point32::multiply_u64(bob_burn, *ratio_A);
-
- let(_, balance) = ol_account::balance(addr_A);
- assert!(balance > balance_pre, 7357004);
- assert!(balance == (bob_burn_share_A + eve_donation_to_A), 7357005);
- }
-
-
- #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000d, eve = @0x1000e)]
- fun simple_burn(root: &signer, alice: &signer, bob: &signer, eve: &signer) {
- // Scenario:
- // There are two community wallets. Alice and Bob's
- // EVE donates to both. But mostly to Alice.
- // The Match Index, should reflect that.
-
- let vals = mock::genesis_n_vals(root, 5); // need to include eve to init funds
- mock::ol_initialize_coin_and_fund_vals(root, 1000000, true);
- // start at epoch 1, since turnout tally needs epoch info, and 0 may cause issues
- mock::trigger_epoch(root);
-
- let (communityA, _cap) = ol_account::test_ol_create_resource_account(alice, b"0x1");
- let addr_A = signer::address_of(&communityA);
- community_wallet_init::init_community(&communityA, vals, 2); // make all the vals signers
-
- let (communityB, _cap) = ol_account::test_ol_create_resource_account(bob, b"0xdeadbeef");
- let addr_B = signer::address_of(&communityB);
- community_wallet_init::init_community(&communityB, vals, 2); // make all the vals signers
-
- let eve_donation_to_A = 75;
- ol_account::transfer(eve, addr_A, eve_donation_to_A);
- let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_A);
- assert!(total_funds_sent_by_eve == eve_donation_to_A, 7357001);
-
- let eve_donation_to_B = 25;
- ol_account::transfer(eve, addr_B, eve_donation_to_B);
- let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_B);
- assert!(total_funds_sent_by_eve == eve_donation_to_B, 7357002);
-
- let qualifying = vector::singleton(addr_A);
- vector::push_back(&mut qualifying, addr_B);
-
- // simulate index recalculation at epoch boundary
- match_index::test_reset_ratio(root, qualifying);
- // let (_addrs_vec, _index_vec, ratio_vec) = match_index::get_ratios();
-
- // so far addr_A should have 75 coins from Eve's transfer
- let(_, balance_pre) = ol_account::balance(addr_A);
- assert!(balance_pre == eve_donation_to_A, 7357003);
-
- // let's burn some of bob's coins
- // first he sets his burn preference
- // IMPORTANT: BOB IS SETTING PREFERENCE TO SIMPLE BURN
- burn::set_send_community(bob, false);
- let bob_burn = 100000;
- let c = ol_account::withdraw(bob, bob_burn);
- // then the system burns based on his preference.
- burn::test_vm_burn_with_user_preference(root, signer::address_of(bob), c);
-
- // no match recycling happened
- let (lifetime_burn, lifetime_match) = burn::get_lifetime_tracker();
- assert!(lifetime_match == 0, 7357003);
- assert!(lifetime_burn > 0, 7357004);
-
-
- // NONE OF THE MATCH INDEX ACCOUNTS HAVE ANY CHANGE
- let(_, balance) = ol_account::balance(addr_A);
- assert!(balance == balance_pre, 7357005);
- assert!(balance == eve_donation_to_A, 7357006);
- }
-
- #[test(root = @ol_framework)]
- fun epoch_fees_burn(root: &signer) {
- // Scenario:
- // the network starts.
- // we mock some tx fees going into the collection
- // the epoch turns and the fee account should be empty
- // and the supply should be lower
-
- let n_vals = 5;
- let _vals = mock::genesis_n_vals(root, n_vals); // need to include eve to init funds
- let genesis_mint = 1000000; // 1 coin per
- let epoch_reward = genesis_mint; // just to be explicit
-
- mock::ol_initialize_coin_and_fund_vals(root, genesis_mint, true);
- let supply_pre = libra_coin::supply();
- let mocked_tx_fees = 1000000 * 100; // 100 coins in tx fee account
- // 105 coins total
- assert!(supply_pre == mocked_tx_fees + (n_vals * genesis_mint), 73570001);
-
- let fees = transaction_fee::system_fees_collected();
- assert!(fees == mocked_tx_fees, 73570002);
- // start at epoch 1. NOTE Validators WILL GET PAID FOR EPOCH 1
- mock::trigger_epoch(root); // under 1000 rounds we don't
- // evaluate performance of validators
- let fees = transaction_fee::system_fees_collected();
- // There should be no fees left in tx fee wallet
- assert!(fees == 0, 73570003);
-
- let validator_rewards = epoch_reward * n_vals;
-
- // of the initial supply,
- // we expect to burn everything which was in the TX FEEs AFTER PAYING VALIDATOR REWARDS
- let amount_burned_excess_tx_account = mocked_tx_fees - validator_rewards;
-
- // So the current supply should be lower,
- // The the old supply was reduced by what was burned (the excess in tx bucket)
-
- let supply_post = libra_coin::supply();
-
- assert!(supply_post == supply_pre - amount_burned_excess_tx_account, 73570003);
-
- // this ALSO means that the only supply left in this example
- // is the rewards to validators from genesis, and from epoch 1
- assert!(supply_post == validator_rewards + (n_vals * genesis_mint), 73570003);
-
- }
-
-
- #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000d, eve = @0x1000e)]
- fun epoch_fees_match(root: &signer, alice: &signer, bob: &signer, eve: &signer) {
- // Scenario:
- // There are two community wallets. Alice and Bob's
- // EVE donates to both. But mostly to Alice.
- // The Match Index, should reflect that.
-
- let vals = mock::genesis_n_vals(root, 5); // need to include eve to init funds
- mock::ol_initialize_coin_and_fund_vals(root, 1000000, true);
- // start at epoch 1, since turnout tally needs epoch info, and 0 may cause issues
- mock::trigger_epoch(root);
-
- let (communityA, _cap) = ol_account::test_ol_create_resource_account(alice, b"0x1");
- let addr_A = signer::address_of(&communityA);
- community_wallet_init::init_community(&communityA, vals, 2); // make all the vals signers
-
- let (communityB, _cap) = ol_account::test_ol_create_resource_account(bob, b"0xdeadbeef");
- let addr_B = signer::address_of(&communityB);
- community_wallet_init::init_community(&communityB, vals, 2); // make all the vals signers
-
- let eve_donation_to_A = 75;
- ol_account::transfer(eve, addr_A, eve_donation_to_A);
- let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_A);
- assert!(total_funds_sent_by_eve == eve_donation_to_A, 7357001);
-
- let eve_donation_to_B = 25;
- ol_account::transfer(eve, addr_B, eve_donation_to_B);
- let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_B);
- assert!(total_funds_sent_by_eve == eve_donation_to_B, 7357002);
-
- let qualifying = vector::singleton(addr_A);
- vector::push_back(&mut qualifying, addr_B);
-
- // simulate index recalculation at epoch boundary
- match_index::test_reset_ratio(root, qualifying);
- let (_addrs_vec, _index_vec, ratio_vec) = match_index::get_ratios();
-
- // so far addr_A should have 75 coins from Eve's transfer
- let(_, balance_pre) = ol_account::balance(addr_A);
- assert!(balance_pre == eve_donation_to_A, 7357003);
-
- // let's burn some of bob's coins
- // first he sets his burn preference
- burn::set_send_community(bob, true);
- let bob_burn = 100000;
- let c = ol_account::withdraw(bob, bob_burn);
- // then the system burns based on his preference.
- burn::test_vm_burn_with_user_preference(root, signer::address_of(bob), c);
- // check that a recycle to match index happened instead of a straight burn
- let (_lifetime_burn, lifetime_match) = burn::get_lifetime_tracker();
- assert!(lifetime_match > 0, 7357003);
+ #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000d, eve = @0x1000e)]
+ fun burn_to_match_index(root: &signer, alice: &signer, bob: &signer, eve: &signer) {
+ // Scenario:
+ // There are two community wallets. Alice and Bob's
+ // EVE donates to both. But mostly to Alice.
+ // The Match Index, should reflect that.
+
+ let vals = mock::genesis_n_vals(root, 5); // need to include eve to init funds
+ mock::ol_initialize_coin_and_fund_vals(root, 1000000, true);
+ // start at epoch 1, since turnout tally needs epoch info, and 0 may cause issues
+ mock::trigger_epoch(root);
+
+ let (communityA, _cap) = ol_account::test_ol_create_resource_account(alice, b"0x1");
+ let addr_A = signer::address_of(&communityA);
+ community_wallet_init::init_community(&communityA, vals, 2); // make all the vals signers
+
+ let (communityB, _cap) = ol_account::test_ol_create_resource_account(bob, b"0xdeadbeef");
+ let addr_B = signer::address_of(&communityB);
+ community_wallet_init::init_community(&communityB, vals, 2); // make all the vals signers
+
+ let eve_donation_to_A = 75;
+ ol_account::transfer(eve, addr_A, eve_donation_to_A);
+ let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_A);
+ assert!(total_funds_sent_by_eve == eve_donation_to_A, 7357001);
+
+ let eve_donation_to_B = 25;
+ ol_account::transfer(eve, addr_B, eve_donation_to_B);
+ let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_B);
+ assert!(total_funds_sent_by_eve == eve_donation_to_B, 7357002);
+
+ let qualifying = vector::singleton(addr_A);
+ vector::push_back(&mut qualifying, addr_B);
+
+ // simulate index recalculation at epoch boundary
+ match_index::test_reset_ratio(root, qualifying);
+ let (_addrs_vec, _index_vec, ratio_vec) = match_index::get_ratios();
+
+ // so far addr_A should have 75 coins from Eve's transfer
+ let(_, balance_pre) = ol_account::balance(addr_A);
+ assert!(balance_pre == eve_donation_to_A, 7357003);
+
+ // let's burn some of bob's coins
+ // first he sets his burn preference
+ burn::set_send_community(bob, true);
+ let bob_burn = 100000;
+ let c = ol_account::withdraw(bob, bob_burn);
+ // then the system burns based on his preference.
+ burn::test_vm_burn_with_user_preference(root, signer::address_of(bob), c);
+ // check that a recycle to match index happened instead of a straight burn
+ let (_lifetime_burn, lifetime_match) = burn::get_lifetime_tracker();
+ assert!(lifetime_match > 0, 7357003);
+
+
+ // the balance of address A should be increase by the ratio above 75% * bob's burn of 100_000
+ let ratio_A = vector::borrow(&ratio_vec, 0);
+ let bob_burn_share_A = fixed_point32::multiply_u64(bob_burn, *ratio_A);
+
+ let(_, balance) = ol_account::balance(addr_A);
+ assert!(balance > balance_pre, 7357004);
+ assert!(balance == (bob_burn_share_A + eve_donation_to_A), 7357005);
+ }
+
+
+ #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000d, eve = @0x1000e)]
+ fun simple_burn(root: &signer, alice: &signer, bob: &signer, eve: &signer) {
+ // Scenario:
+ // There are two community wallets. Alice and Bob's
+ // EVE donates to both. But mostly to Alice.
+ // The Match Index, should reflect that.
+
+ let vals = mock::genesis_n_vals(root, 5); // need to include eve to init funds
+ mock::ol_initialize_coin_and_fund_vals(root, 1000000, true);
+ // start at epoch 1, since turnout tally needs epoch info, and 0 may cause issues
+ mock::trigger_epoch(root);
+
+ let (communityA, _cap) = ol_account::test_ol_create_resource_account(alice, b"0x1");
+ let addr_A = signer::address_of(&communityA);
+ community_wallet_init::init_community(&communityA, vals, 2); // make all the vals signers
+
+ let (communityB, _cap) = ol_account::test_ol_create_resource_account(bob, b"0xdeadbeef");
+ let addr_B = signer::address_of(&communityB);
+ community_wallet_init::init_community(&communityB, vals, 2); // make all the vals signers
+
+ let eve_donation_to_A = 75;
+ ol_account::transfer(eve, addr_A, eve_donation_to_A);
+ let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_A);
+ assert!(total_funds_sent_by_eve == eve_donation_to_A, 7357001);
+
+ let eve_donation_to_B = 25;
+ ol_account::transfer(eve, addr_B, eve_donation_to_B);
+ let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_B);
+ assert!(total_funds_sent_by_eve == eve_donation_to_B, 7357002);
+
+ let qualifying = vector::singleton(addr_A);
+ vector::push_back(&mut qualifying, addr_B);
+
+ // simulate index recalculation at epoch boundary
+ match_index::test_reset_ratio(root, qualifying);
+ // let (_addrs_vec, _index_vec, ratio_vec) = match_index::get_ratios();
+
+ // so far addr_A should have 75 coins from Eve's transfer
+ let(_, balance_pre) = ol_account::balance(addr_A);
+ assert!(balance_pre == eve_donation_to_A, 7357003);
+
+ // let's burn some of bob's coins
+ // first he sets his burn preference
+ // IMPORTANT: BOB IS SETTING PREFERENCE TO SIMPLE BURN
+ burn::set_send_community(bob, false);
+ let bob_burn = 100000;
+ let c = ol_account::withdraw(bob, bob_burn);
+ // then the system burns based on his preference.
+ burn::test_vm_burn_with_user_preference(root, signer::address_of(bob), c);
+
+ // no match recycling happened
+ let (lifetime_burn, lifetime_match) = burn::get_lifetime_tracker();
+ assert!(lifetime_match == 0, 7357003);
+ assert!(lifetime_burn > 0, 7357004);
+
+
+ // NONE OF THE MATCH INDEX ACCOUNTS HAVE ANY CHANGE
+ let(_, balance) = ol_account::balance(addr_A);
+ assert!(balance == balance_pre, 7357005);
+ assert!(balance == eve_donation_to_A, 7357006);
+ }
+
+ #[test(root = @ol_framework)]
+ fun epoch_fees_burn(root: &signer) {
+ // Scenario:
+ // the network starts.
+ // we mock some tx fees going into the collection
+ // the epoch turns and the fee account should be empty
+ // and the supply should be lower
+ let n_vals = 5;
+ let _vals = mock::genesis_n_vals(root, n_vals); // need to include eve to init funds
+ let genesis_mint = 1_000_000; // 1 coin per
+ let epoch_reward = genesis_mint; // just to be explicit
- // the balance of address A should be increase by the ratio above 75% * bob's burn of 100_000
- let ratio_A = vector::borrow(&ratio_vec, 0);
- let bob_burn_share_A = fixed_point32::multiply_u64(bob_burn, *ratio_A);
-
- let(_, balance) = ol_account::balance(addr_A);
- assert!(balance > balance_pre, 7357004);
- assert!(balance == (bob_burn_share_A + eve_donation_to_A), 7357005);
- }
+ mock::ol_initialize_coin_and_fund_vals(root, genesis_mint, true);
+ let supply_pre = libra_coin::supply();
+ let bruce_fortune = 100_000_000_000;
+ let mocked_tx_fees = 5_000_000 * 100; // 100 coins in tx fee account
+ assert!(supply_pre == mocked_tx_fees + bruce_fortune + (n_vals * genesis_mint), 73570001);
+ let fees = transaction_fee::system_fees_collected();
+ assert!(fees == mocked_tx_fees, 73570002);
- // TODO:
+ // start at epoch 1. NOTE Validators WILL GET PAID FOR EPOCH 1
+ mock::trigger_epoch(root); // under 1000 rounds we don't
- // #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000d, eve = @0x1000e)]
- // fun fee_makers_calc(root: &signer, alice: &signer, bob: &signer, eve: &signer) {
- // // Scenario:
- // assert!(TransactionFee::get_fees_collected()==0, 735701);
- // let coin = Diem::mint(&vm, 1);
- // TransactionFee::pay_fee_and_track(@Alice, coin);
+ // evaluate performance of validators
+ let fees = transaction_fee::system_fees_collected();
+ // There should be only entry fee left in tx fee wallet
+ assert!(fees == 5_000_000, 73570003);
- // let fee_makers = TransactionFee::get_fee_makers();
- // assert!(Vector::length(&fee_makers)==1, 735702);
- // assert!(TransactionFee::get_fees_collected()==1, 735703);
- // }
+ let validator_rewards = epoch_reward * n_vals;
- #[test(root=@ol_framework, alice=@0x1000a)]
- fun track_fees(root: &signer, alice: address) {
- // use ol_framework::libra_coin;
- let _vals = mock::genesis_n_vals(root, 1); // need to include eve to init funds
- mock::ol_initialize_coin_and_fund_vals(root, 10000, true);
+ // of the initial supply,
+ // we expect to burn everything which was in the TX FEEs AFTER PAYING VALIDATOR REWARDS
+ let amount_burned_excess_tx_account = mocked_tx_fees - validator_rewards;
- let marlon_rando = @0x12345;
- ol_account::create_account(root, marlon_rando);
+ // So the current supply should be lower,
+ // The the old supply was reduced by what was burned (the excess in tx bucket)
+ let supply_post = libra_coin::supply();
+ assert!(supply_post == supply_pre - amount_burned_excess_tx_account, 73570003);
- let rando_money = 5;
- let coin_option = ol_account::test_vm_withdraw(root, alice, rando_money);
+ // this ALSO means that the only supply left in this example
+ // is the rewards to validators from genesis, and from epoch 1
+ // and the bruce fortune used to pledge infra escrow
+ assert!(supply_post == validator_rewards + (n_vals * genesis_mint) + bruce_fortune, 73570003);
+ }
- if (option::is_some(&coin_option)) {
- let c = option::extract(&mut coin_option);
- // shortcut: we have the VM use alices money to pay a fee for marlon.
- transaction_fee::vm_pay_fee(root, marlon_rando, c);
- };
- option::destroy_none(coin_option);
+ #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000d, eve = @0x1000e)]
+ fun epoch_fees_match(root: &signer, alice: &signer, bob: &signer, eve: &signer) {
+ // Scenario:
+ // There are two community wallets. Alice and Bob's
+ // EVE donates to both. But mostly to Alice.
+ // The Match Index, should reflect that.
+
+ let vals = mock::genesis_n_vals(root, 5); // need to include eve to init funds
+ mock::ol_initialize_coin_and_fund_vals(root, 1000000, true);
+ // start at epoch 1, since turnout tally needs epoch info, and 0 may cause issues
+ mock::trigger_epoch(root);
+
+ let (communityA, _cap) = ol_account::test_ol_create_resource_account(alice, b"0x1");
+ let addr_A = signer::address_of(&communityA);
+ community_wallet_init::init_community(&communityA, vals, 2); // make all the vals signers
+
+ let (communityB, _cap) = ol_account::test_ol_create_resource_account(bob, b"0xdeadbeef");
+ let addr_B = signer::address_of(&communityB);
+ community_wallet_init::init_community(&communityB, vals, 2); // make all the vals signers
+
+ let eve_donation_to_A = 75;
+ ol_account::transfer(eve, addr_A, eve_donation_to_A);
+ let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_A);
+ assert!(total_funds_sent_by_eve == eve_donation_to_A, 7357001);
+
+ let eve_donation_to_B = 25;
+ ol_account::transfer(eve, addr_B, eve_donation_to_B);
+ let (_, _, total_funds_sent_by_eve) = receipts::read_receipt(signer::address_of(eve), addr_B);
+ assert!(total_funds_sent_by_eve == eve_donation_to_B, 7357002);
+
+ let qualifying = vector::singleton(addr_A);
+ vector::push_back(&mut qualifying, addr_B);
+
+ // simulate index recalculation at epoch boundary
+ match_index::test_reset_ratio(root, qualifying);
+ let (_addrs_vec, _index_vec, ratio_vec) = match_index::get_ratios();
+
+ // so far addr_A should have 75 coins from Eve's transfer
+ let(_, balance_pre) = ol_account::balance(addr_A);
+ assert!(balance_pre == eve_donation_to_A, 7357003);
+
+ // let's burn some of bob's coins
+ // first he sets his burn preference
+ burn::set_send_community(bob, true);
+ let bob_burn = 100000;
+ let c = ol_account::withdraw(bob, bob_burn);
+ // then the system burns based on his preference.
+ burn::test_vm_burn_with_user_preference(root, signer::address_of(bob), c);
+ // check that a recycle to match index happened instead of a straight burn
+ let (_lifetime_burn, lifetime_match) = burn::get_lifetime_tracker();
+ assert!(lifetime_match > 0, 7357003);
+
+
+ // the balance of address A should be increase by the ratio above 75% * bob's burn of 100_000
+ let ratio_A = vector::borrow(&ratio_vec, 0);
+ let bob_burn_share_A = fixed_point32::multiply_u64(bob_burn, *ratio_A);
+
+ let(_, balance) = ol_account::balance(addr_A);
+ assert!(balance > balance_pre, 7357004);
+ assert!(balance == (bob_burn_share_A + eve_donation_to_A), 7357005);
+ }
- let fees = transaction_fee::system_fees_collected();
- assert!(fees == 100000005, 735701);
- // Fees will include the initialization by MOCK. ol_initialize_coin_and_fund_vals
-
- // marlon is the only fee maker (since genesis)
- let fee_makers = fee_maker::get_fee_makers();
- // includes 0x1 which makes a deposit on
- assert!(vector::length(&fee_makers)==1, 735702);
- let marlon_fees_made = fee_maker::get_user_fees_made(marlon_rando);
- assert!(marlon_fees_made == 5, 735703);
+ // TODO:
- mock::trigger_epoch(root);
- // should reset the fee counter on everything.
- // TODO: no proof of fee was charged on alice. But this is changed in
- // feature branch `coin-methods-hygiene`
+ // #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000d, eve = @0x1000e)]
+ // fun fee_makers_calc(root: &signer, alice: &signer, bob: &signer, eve: &signer) {
+ // // Scenario:
+ // assert!(TransactionFee::get_fees_collected()==0, 735701);
+ // let coin = Diem::mint(&vm, 1);
+ // TransactionFee::pay_fee_and_track(@Alice, coin);
- let marlon_fees_made = fee_maker::get_user_fees_made(marlon_rando);
- assert!(marlon_fees_made == 0, 735704);
- let fee_makers = fee_maker::get_fee_makers();
- assert!(vector::length(&fee_makers)==0, 735705);
- let fees = transaction_fee::system_fees_collected();
- assert!(fees == 0, 7357008);
+ // let fee_makers = TransactionFee::get_fee_makers();
+ // assert!(Vector::length(&fee_makers)==1, 735702);
+ // assert!(TransactionFee::get_fees_collected()==1, 735703);
+ // }
- }
+ #[test(root=@ol_framework, alice=@0x1000a)]
+ fun track_fees(root: &signer, alice: address) {
+ // use ol_framework::libra_coin;
+ let _vals = mock::genesis_n_vals(root, 1); // need to include eve to init funds
+ mock::ol_initialize_coin_and_fund_vals(root, 10000, true);
+ let marlon_rando = @0x12345;
+ ol_account::create_account(root, marlon_rando);
+
+ let rando_money = 5;
+ let coin_option = ol_account::test_vm_withdraw(root, alice, rando_money);
+
+ if (option::is_some(&coin_option)) {
+ let c = option::extract(&mut coin_option);
+ // shortcut: we have the VM use alices money to pay a fee for marlon.
+ transaction_fee::vm_pay_fee(root, marlon_rando, c);
+ };
+ option::destroy_none(coin_option);
+
+ let fees = transaction_fee::system_fees_collected();
+ assert!(fees == 500_000_005, 7357001);
+ // Fees will include the initialization by MOCK. ol_initialize_coin_and_fund_vals
+
+ // marlon is the only fee maker (since genesis)
+ let fee_makers = fee_maker::get_fee_makers();
+ // includes 0x1 which makes a deposit on
+ assert!(vector::length(&fee_makers)==1, 7357002);
+
+ let marlon_fees_made = fee_maker::get_user_fees_made(marlon_rando);
+ assert!(marlon_fees_made == 5, 7357003);
+
+ mock::trigger_epoch(root);
+
+ let marlon_fees_made = fee_maker::get_user_fees_made(marlon_rando);
+ assert!(marlon_fees_made == 0, 7357004);
+ let fee_makers = fee_maker::get_fee_makers();
+ assert!(vector::length(&fee_makers) == 0, 7357005);
+ let fees = transaction_fee::system_fees_collected();
+ assert!(fees == 1_000_000, 7357006); // val set entry fee
+ }
#[test(root = @ol_framework, alice_val = @0x1000a)]
fun test_init_burn_tracker(root: &signer, alice_val: &signer) {
@@ -383,7 +377,6 @@ module ol_framework::test_burn {
assert!(prev_balance == marlon_salary, 7357002);
assert!(burn_at_last_calc == 0, 7357003);
assert!(cumu_burn == 0, 7357004);
-
}
#[test(root = @ol_framework, alice_val=@0x1000a)]
diff --git a/framework/libra-framework/sources/ol_sources/tests/donor_voice.test.move b/framework/libra-framework/sources/ol_sources/tests/donor_voice.test.move
index bff2ba214..2b39cd964 100644
--- a/framework/libra-framework/sources/ol_sources/tests/donor_voice.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/donor_voice.test.move
@@ -40,7 +40,7 @@ module ol_framework::test_donor_voice {
// vals claim the offer
multi_action::claim_offer(alice, donor_voice_address);
multi_action::claim_offer(bob, donor_voice_address);
-
+
//need to be caged to finalize donor directed workflow and release control of the account
multi_action::finalize_and_cage(&resource_sig, 2);
@@ -61,7 +61,7 @@ module ol_framework::test_donor_voice {
// vals claim the offer
multi_action::claim_offer(alice, donor_voice_address);
- multi_action::claim_offer(bob, donor_voice_address);
+ multi_action::claim_offer(bob, donor_voice_address);
//need to be caged to finalize donor directed workflow and release control of the account
multi_action::finalize_and_cage(&resource_sig, vector::length(&vals));
@@ -578,7 +578,7 @@ module ol_framework::test_donor_voice {
multi_action::claim_offer(alice, donor_voice_address);
multi_action::claim_offer(bob, donor_voice_address);
multi_action::claim_offer(carol, donor_voice_address);
-
+
//need to be caged to finalize donor directed workflow and release control of the account
multi_action::finalize_and_cage(&resource_sig, 2);
@@ -676,7 +676,7 @@ module ol_framework::test_donor_voice {
// Dave and Eve are unhappy, and vote to liquidate the account.
let vals = mock::genesis_n_vals(root, 5); // need to include eve to init funds
- mock::ol_initialize_coin_and_fund_vals(root, 100000, true);
+ mock::ol_initialize_coin_and_fund_vals(root, 100_000, true);
// start at epoch 1, since turnout tally needs epoch info, and 0 may cause
// issues
mock::trigger_epoch(root);
@@ -723,7 +723,6 @@ module ol_framework::test_donor_voice {
assert!(*vector::borrow(&addrs, 0) == @0x1000e, 7357006);
assert!(*vector::borrow(&addrs, 1) == @0x1000d, 7357007);
-
let eve_donation_pro_rata = vector::borrow(&refunds, 0);
let superman_3 = 1; // rounding from fixed_point32
assert!((*eve_donation_pro_rata + superman_3) == eve_donation, 7357008);
@@ -732,7 +731,6 @@ module ol_framework::test_donor_voice {
let superman_3 = 1; // rounding from fixed_point32
assert!((*dave_donation_pro_rata + superman_3) == dave_donation, 7357009);
-
let (_, program_balance_pre) = ol_account::balance(donor_voice_address);
let (_, eve_balance_pre) = ol_account::balance(@0x1000e);
@@ -747,10 +745,9 @@ module ol_framework::test_donor_voice {
// eve shoul have received funds back
assert!(eve_balance > eve_balance_pre, 7357010);
- let (lifetime_burn_now, _) = burn::get_lifetime_tracker();
// nothing should have been burned, it was a refund
+ let (lifetime_burn_now, _) = burn::get_lifetime_tracker();
assert!(lifetime_burn_now == lifetime_burn_pre, 7357011);
-
}
#[test(root = @ol_framework, alice = @0x1000a, dave = @0x1000d, eve = @0x1000e)]
diff --git a/framework/libra-framework/sources/ol_sources/tests/slow_wallet.test.move b/framework/libra-framework/sources/ol_sources/tests/slow_wallet.test.move
index b640e0c26..e4b9103c6 100644
--- a/framework/libra-framework/sources/ol_sources/tests/slow_wallet.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/slow_wallet.test.move
@@ -18,7 +18,7 @@ module ol_framework::test_slow_wallet {
use std::vector;
use std::signer;
- // use diem_std::debug::print;
+ use diem_std::debug::print;
#[test(root = @ol_framework)]
// we are testing that genesis creates the needed struct
@@ -112,8 +112,8 @@ module ol_framework::test_slow_wallet {
fun test_epoch_drip(root: signer) {
let set = mock::genesis_n_vals(&root, 4);
mock::ol_initialize_coin_and_fund_vals(&root, 100, false);
- let a = *vector::borrow(&set, 0);
+ let a = *vector::borrow(&set, 0);
assert!(slow_wallet::is_slow(a), 7357000);
assert!(slow_wallet::unlocked_amount(a) == 100, 735701);
@@ -121,12 +121,13 @@ module ol_framework::test_slow_wallet {
rewards::test_helper_pay_reward(&root, a, coin, 0);
let (u, b) = ol_account::balance(a);
- assert!(b==100000100, 735702);
+ print(&b);
+ assert!(b==500_000_100, 735702);
assert!(u==100, 735703);
slow_wallet::slow_wallet_epoch_drip(&root, 233);
let (u, b) = ol_account::balance(a);
- assert!(b==100000100, 735704);
+ assert!(b==500_000_100, 735704);
assert!(u==333, 735705);
}
From 58bbe964ac310f84e1423882fa4ae53fab70bb05 Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Thu, 18 Jul 2024 23:26:03 -0300
Subject: [PATCH 11/16] extracts pure function from reward_thermostat and adds
tests creates view query_reward_adjustment
---
.../sources/ol_sources/proof_of_fee.move | 336 ++++++++++++++----
.../ol_sources/tests/proof_of_fee.test.move | 55 +++
2 files changed, 312 insertions(+), 79 deletions(-)
diff --git a/framework/libra-framework/sources/ol_sources/proof_of_fee.move b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
index 4a3e690a1..327a88f86 100644
--- a/framework/libra-framework/sources/ol_sources/proof_of_fee.move
+++ b/framework/libra-framework/sources/ol_sources/proof_of_fee.move
@@ -43,6 +43,14 @@ module ol_framework::proof_of_fee {
/// based on another function or simply randomized within a range
/// (as originally proposed in this feature request)
const PCT_REDUCTION_FOR_COMPETITION: u64 = 10; // 10%
+ /// Upper bound threshold for bid percentages.
+ const BID_UPPER_BOUND: u64 = 0950; // 95%
+ /// Lower bound threshold for bid percentages.
+ const BID_LOWER_BOUND: u64 = 0500; // 50%
+ /// Short window period for recent bid trends.
+ const SHORT_WINDOW: u64 = 5; // 5 epochs
+ /// Long window period for extended bid trends.
+ const LONG_WINDOW: u64 = 10; // 10 epochs
//////// ERRORS /////////
/// Not an active validator
@@ -533,106 +541,87 @@ module ol_framework::proof_of_fee {
fixed_point32::create_from_rational(bid_pct, 1000)
}
- /// Adjust the reward at the end of the epoch
- /// as described in the paper, the epoch reward needs to be adjustable
- /// given that the implicit bond needs to be sufficient, eg 5-10x the reward.
+
+ /// Calculates the reward adjustment based on bid history and nominal reward.
+ /// @param median_history - The median history of bids.
+ /// @param nominal_reward - The current nominal reward.
/// @return Tuple (bool, bool, u64)
/// 0: did the thermostat run,
/// 1: did it increment, or decrease, bool
/// 2: how much
- /// if the thermostat returns (false, false, 0), it means there was an error running
- public(friend) fun reward_thermostat(vm: &signer): (bool, bool, u64) acquires ConsensusReward {
- system_addresses::assert_ol(vm);
- // check the bid history
- // if there are 5 days above 95% adjust the reward up by 5%
- // adjust by more if it has been 10 days then, 10%
- // if there are 5 days below 50% adjust the reward down.
- // adjust by more if it has been 10 days then 10%
-
- let bid_upper_bound = 0950;
- let bid_lower_bound = 0500;
-
- let short_window: u64 = 5;
- let long_window: u64 = 10;
-
- let cr = borrow_global_mut(@ol_framework);
-
- let len = vector::length(&cr.median_history);
- let i = 0;
-
+ fun calculate_reward_adjustment(
+ median_history: &vector,
+ nominal_reward: u64
+ ): (bool, bool, u64) {
+ let history_length = vector::length(median_history);
+ let index = 0;
let epochs_above = 0;
let epochs_below = 0;
- while (i < 16 && i < len) { // max ten days, but may have less in history, filling set should truncate the history at 15 epochs.
- let avg_bid = *vector::borrow(&cr.median_history, i);
- if (avg_bid > bid_upper_bound) {
+ while (index < 16 && index < history_length) {
+ let avg_bid = *vector::borrow(median_history, index);
+
+ if (avg_bid > BID_UPPER_BOUND) {
epochs_above = epochs_above + 1;
- } else if (avg_bid < bid_lower_bound) {
+ } else if (avg_bid < BID_LOWER_BOUND) {
epochs_below = epochs_below + 1;
};
- i = i + 1;
+ index = index + 1;
};
- if (cr.nominal_reward > 0) {
- // TODO: this is an initial implementation, we need to
- // decide if we want more granularity in the reward adjustment
- // Note: making this readable for now, but we can optimize later
+ if (nominal_reward > 0) {
if (epochs_above > epochs_below) {
-
- // if (epochs_above > short_window) {
-
- // check for zeros.
- // TODO: put a better safety check here
-
- // If the Validators are bidding near 100% that means
- // the reward is very generous, i.e. their opportunity
- // cost is met at small percentages. This means the
- // implicit bond is very high on validators. E.g.
- // at 1% median bid, the implicit bond is 100x the reward.
- // We need to DECREASE the reward
-
- if (epochs_above > long_window) {
- // decrease the reward by 10%
- let less_ten_pct = (cr.nominal_reward / 10);
- cr.nominal_reward = cr.nominal_reward - less_ten_pct;
+ if (epochs_above > LONG_WINDOW) {
+ let less_ten_pct = (nominal_reward / 10);
return (true, false, less_ten_pct)
- } else if (epochs_above > short_window) {
- // decrease the reward by 5%
- let less_five_pct = (cr.nominal_reward / 20);
- cr.nominal_reward = cr.nominal_reward - less_five_pct;
+ } else if (epochs_above > SHORT_WINDOW) {
+ let less_five_pct = (nominal_reward / 20);
return (true, false, less_five_pct)
}
+ } else {
+ if (epochs_below > LONG_WINDOW) {
+ let increase_ten_pct = (nominal_reward / 10);
+ return (true, true, increase_ten_pct)
+ } else if (epochs_below > SHORT_WINDOW) {
+ let increase_five_pct = (nominal_reward / 20);
+ return (true, true, increase_five_pct)
+ }
};
- // return early since we can't increase and decrease simultaneously
-
- // if validators are bidding low percentages
- // it means the nominal reward is not high enough.
- // That is the validator's opportunity cost is not met within a
- // range where the bond is meaningful.
- // For example: if the bids for the epoch's reward is 50% of the value, that means the potential profit, is the same as the potential loss.
- // At a 25% bid (potential loss), the profit is thus 75% of the value, which means the implicit bond is 25/75, or 1/3 of the bond, the risk favors the validator. This means among other things, that an attacker can pay for the cost of the attack with the profits. See paper, for more details.
-
- // we need to INCREASE the reward, so that the bond is more meaningful.
-
- if (epochs_below > long_window) {
- // increase the reward by 10%
- let increase_ten_pct = (cr.nominal_reward / 10);
- cr.nominal_reward = cr.nominal_reward + increase_ten_pct;
- return (true, true, increase_ten_pct)
- } else if (epochs_below > short_window) {
- // increase the reward by 5%
- let increase_five_pct = (cr.nominal_reward / 20);
- cr.nominal_reward = cr.nominal_reward + increase_five_pct;
- return (true, true, increase_five_pct)
- };
-
- // we ran the thermostat but no change was made.
return (true, false, 0)
};
- // nominal reward is zero, there's a problem
- return (false, false, 0)
+ (false, false, 0)
+ }
+
+
+ /// Adjust the reward at the end of the epoch
+ /// as described in the paper, the epoch reward needs to be adjustable
+ /// given that the implicit bond needs to be sufficient, eg 5-10x the reward.
+ /// @param vm - The signer.
+ /// @return Tuple (bool, bool, u64)
+ /// 0: did the thermostat run,
+ /// 1: did it increment, or decrease, bool
+ /// 2: how much
+ /// if the thermostat returns (false, false, 0), it means there was an error running
+ public(friend) fun reward_thermostat(vm: &signer): (bool, bool, u64) acquires ConsensusReward {
+ system_addresses::assert_ol(vm);
+ let cr = borrow_global_mut(@ol_framework);
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(
+ &cr.median_history,
+ cr.nominal_reward
+ );
+
+ if (did_run) {
+ if (did_increment) {
+ cr.nominal_reward = cr.nominal_reward + amount;
+ } else {
+ cr.nominal_reward = cr.nominal_reward - amount;
+ }
+ };
+
+ (did_run, did_increment, amount)
}
/// find the median bid to push to history
@@ -712,6 +701,26 @@ module ol_framework::proof_of_fee {
return (false, 0)
}
+ #[view]
+ /// Query the reward adjustment without altering the nominal reward.
+ /// @param vm - The signer.
+ /// @return Tuple (bool, bool, u64)
+ /// 0: did the thermostat run,
+ /// 1: did it increment, or decrease, bool
+ /// 2: how much
+ /// if the thermostat returns (false, false, 0), it means there was an error running
+ public fun query_reward_adjustment(): (bool, bool, u64) acquires ConsensusReward {
+ let cr = borrow_global(@ol_framework);
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(
+ &cr.median_history,
+ cr.nominal_reward
+ );
+
+ (did_run, did_increment, amount)
+ }
+
+
// Get the top N validators by bid, this is FILTERED by default
public(friend) fun top_n_accounts(account: &signer, n: u64, unfiltered: bool): vector acquires ProofOfFeeAuction, ConsensusReward {
system_addresses::assert_vm(account);
@@ -1380,4 +1389,173 @@ module ol_framework::proof_of_fee {
assert!(bids == vector[2, 2, 2, 2], 7357021);
assert!(shuffled, 7357022);
}
+
+
+ // Tests for calculate_reward_adjustment
+
+ #[test]
+ public fun cra_nominal_reward_zero() {
+ let median_history = vector::empty();
+ let nominal_reward = 0;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == false, 7357001);
+ assert!(did_increment == false, 7357002);
+ assert!(amount == 0, 7357003);
+ }
+
+ #[test]
+ public fun cra_empty_bid_history() {
+ let median_history = vector::empty();
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357004);
+ assert!(did_increment == false, 7357005);
+ assert!(amount == 0, 7357006);
+ }
+
+ #[test]
+ public fun cra_less_than_16_bids() {
+ // 10 entries all with value 600
+ let median_history = vector[600, 600, 600, 600, 600, 600, 600, 600, 600, 600];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357007);
+ assert!(did_increment == false, 7357008);
+ assert!(amount == 0, 7357009);
+ }
+
+ #[test]
+ public fun cra_exactly_16_bids() {
+ // 16 entries all with value 600
+ let median_history = vector[600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357010);
+ assert!(did_increment == false, 7357011);
+ assert!(amount == 0, 7357012);
+ }
+
+ #[test]
+ public fun cra_more_than_16_bids() {
+ // 20 entries all with value 600
+ let median_history = vector[600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357013);
+ assert!(did_increment == false, 7357014);
+ assert!(amount == 0, 7357015);
+ }
+
+ #[test]
+ public fun cra_all_bids_above_upper_bound_short_window() {
+ // 6 entries all with value 960
+ let median_history = vector[960, 960, 960, 960, 960, 960];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357016);
+ assert!(did_increment == false, 7357017);
+ assert!(amount == nominal_reward / 20, 7357018);
+ }
+
+ #[test]
+ public fun cra_all_bids_above_upper_bound_long_window() {
+ // 11 entries all with value 960
+ let median_history = vector[960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357019);
+ assert!(did_increment == false, 7357020);
+ assert!(amount == nominal_reward / 10, 7357021);
+ }
+
+ #[test]
+ public fun cra_all_bids_below_lower_bound_short_window() {
+ // 6 entries all with value 400
+ let median_history = vector[400, 400, 400, 400, 400, 400];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357022);
+ assert!(did_increment == true, 7357023);
+ assert!(amount == nominal_reward / 20, 7357024);
+ }
+
+ #[test]
+ public fun cra_all_bids_below_lower_bound_long_window() {
+ // 11 entries all with value 400
+ let median_history = vector[400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357025);
+ assert!(did_increment == true, 7357026);
+ assert!(amount == nominal_reward / 10, 7357027);
+ }
+
+ #[test]
+ public fun cra_mixed_bids_with_majority_above() {
+ // 9 entries above upper bound and 7 entries below lower bound
+ let median_history = vector[960, 960, 960, 960, 960, 960, 960, 960, 960, 400, 400, 400, 400, 400, 400, 400];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357028);
+ assert!(did_increment == false, 7357029);
+ assert!(amount == nominal_reward / 20, 7357030); // Since the total entries are 16, it falls under the short window
+ }
+
+ #[test]
+ public fun cra_mixed_bids_with_majority_below() {
+ // 9 entries below lower bound and 7 entries above upper bound
+ let median_history = vector[400, 400, 400, 400, 400, 400, 400, 400, 400, 960, 960, 960, 960, 960, 960, 960];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357031);
+ assert!(did_increment == true, 7357032);
+ assert!(amount == nominal_reward / 20, 7357033); // Since the total entries are 16, it falls under the short window
+ }
+
+ #[test]
+ public fun cra_mixed_bids_without_clear_majority() {
+ // 4 entries below lower bound and 4 entries above upper bound
+ let median_history = vector[400, 400, 400, 400, 960, 960, 960, 960];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357034);
+ assert!(did_increment == false, 7357035);
+ assert!(amount == 0, 7357036);
+ }
+
+ #[test]
+ public fun cra_majority_above_long_window() {
+ // 12 entries above upper bound and 4 entries below lower bound
+ let median_history = vector[960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 400, 400, 400, 400];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357037);
+ assert!(did_increment == false, 7357038);
+ assert!(amount == nominal_reward / 10, 7357039); // Majority above and longer than long window
+ }
+
+ #[test]
+ public fun cra_majority_above_short_window() {
+ // 7 entries above upper bound and 4 entries below lower bound
+ let median_history = vector[960, 960, 960, 960, 960, 960, 960, 400, 400, 400, 400];
+ let nominal_reward = 1000;
+
+ let (did_run, did_increment, amount) = calculate_reward_adjustment(&median_history, nominal_reward);
+ assert!(did_run == true, 7357040);
+ assert!(did_increment == false, 7357041);
+ assert!(amount == nominal_reward / 20, 7357042); // Majority above and longer than short window but not long window
+ }
}
diff --git a/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move b/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
index 6de127ede..26a849121 100644
--- a/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
+++ b/framework/libra-framework/sources/ol_sources/tests/proof_of_fee.test.move
@@ -594,4 +594,59 @@ module ol_framework::test_pof {
assert!(median_bid == 3, 10014);
}
+ // Tests for query_reward_adjustment
+
+ #[test(root = @ol_framework)]
+ public fun test_query_reward_adjustment_no_change(root: &signer) {
+ use diem_framework::chain_id;
+ proof_of_fee::init_genesis_baseline_reward(root);
+ chain_id::initialize_for_test(root, 4);
+
+ // 16 entries all with value 600
+ let median_history = vector[600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600];
+ let nominal_reward = 1000;
+
+ proof_of_fee::test_mock_reward(root, nominal_reward, 500, 500, median_history);
+
+ let (did_run, did_increment, amount) = proof_of_fee::query_reward_adjustment();
+ assert!(did_run == true, 7357043);
+ assert!(did_increment == false, 7357044);
+ assert!(amount == 0, 7357045);
+ }
+
+ #[test(root = @ol_framework)]
+ public fun test_query_reward_adjustment_increase(root: &signer) {
+ use diem_framework::chain_id;
+ proof_of_fee::init_genesis_baseline_reward(root);
+ chain_id::initialize_for_test(root, 4);
+
+ // 11 entries all with value 400
+ let median_history = vector[400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400];
+ let nominal_reward = 1000;
+
+ proof_of_fee::test_mock_reward(root, nominal_reward, 500, 500, median_history);
+
+ let (did_run, did_increment, amount) = proof_of_fee::query_reward_adjustment();
+ assert!(did_run == true, 7357046);
+ assert!(did_increment == true, 7357047);
+ assert!(amount == nominal_reward / 10, 7357048);
+ }
+
+ #[test(root = @ol_framework)]
+ public fun test_query_reward_adjustment_decrease(root: &signer) {
+ use diem_framework::chain_id;
+ proof_of_fee::init_genesis_baseline_reward(root);
+ chain_id::initialize_for_test(root, 4);
+
+ // 11 entries all with value 960
+ let median_history = vector[960, 960, 960, 960, 960, 960, 960, 960, 960, 960, 960];
+ let nominal_reward = 1000;
+
+ proof_of_fee::test_mock_reward(root, nominal_reward, 500, 500, median_history);
+
+ let (did_run, did_increment, amount) = proof_of_fee::query_reward_adjustment();
+ assert!(did_run == true, 7357049);
+ assert!(did_increment == false, 7357050);
+ assert!(amount == nominal_reward / 10, 7357051);
+ }
}
From e3ceeb0c6b07e0b0a895911e97ea9d990e618e8d Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Fri, 19 Jul 2024 18:26:17 -0300
Subject: [PATCH 12/16] fixes persist_genesis smoke test
---
tools/config/src/make_yaml_public_fullnode.rs | 34 ++++++++++++++-----
1 file changed, 25 insertions(+), 9 deletions(-)
diff --git a/tools/config/src/make_yaml_public_fullnode.rs b/tools/config/src/make_yaml_public_fullnode.rs
index 04ee7e1e3..24c68bb6d 100644
--- a/tools/config/src/make_yaml_public_fullnode.rs
+++ b/tools/config/src/make_yaml_public_fullnode.rs
@@ -181,8 +181,7 @@ mempool:
/// download genesis blob and save it to the specified directory.
pub async fn download_genesis(home_dir: Option) -> anyhow::Result<()> {
// Base URL for GitHub API requests
- let base_url =
- "https://api.github.com/repos/0LNetworkCommunity/epoch-archive-mainnet/contents/upgrades";
+ let base_url = "https://api.github.com/repos/0LNetworkCommunity/epoch-archive-mainnet/contents/upgrades";
let client = reqwest::Client::new();
let resp = client
.get(base_url)
@@ -191,6 +190,7 @@ pub async fn download_genesis(home_dir: Option) -> anyhow::Result<()> {
.await?
.json::>()
.await?;
+
// Find the latest version by parsing version numbers and sorting
let latest_version = resp
.iter()
@@ -202,16 +202,23 @@ pub async fn download_genesis(home_dir: Option) -> anyhow::Result<()> {
"https://raw.githubusercontent.com/0LNetworkCommunity/epoch-archive-mainnet/main/upgrades",
latest_version.unwrap_or(DEFAULT_WAYPOINT_VERSION)
);
+
// Fetch the latest waypoint
let blob_bytes = reqwest::get(&latest_path).await?.bytes().await?;
let home = home_dir.unwrap_or_else(libra_types::global_config_dir);
let genesis_dir = home.join("genesis/");
+
+ // Ensure the genesis directory exists
+ std::fs::create_dir_all(&genesis_dir)?;
+
let p = genesis_dir.join("genesis.blob");
+ // Write the genesis blob to the file
std::fs::write(p, &blob_bytes)?;
Ok(())
}
+
/// Fetch the genesis waypoint from the GitHub repository.
pub async fn get_genesis_waypoint(home_dir: Option) -> anyhow::Result {
// Base URL for GitHub API requests
@@ -289,11 +296,20 @@ async fn persist_genesis() {
let path = p.path().to_owned();
- download_genesis(Some(path)).await.unwrap();
- let l = std::fs::read_dir(p.path())
- .unwrap()
- .next()
- .unwrap()
- .unwrap();
- assert!(l.file_name().to_str().unwrap().contains("genesis.blob"));
+ // Ensure the directory exists
+ assert!(std::fs::metadata(&path).is_ok(), "Directory does not exist: {:?}", path);
+
+ // Verify path is a directory
+ assert!(Path::new(&path).is_dir(), "Path is not a directory: {:?}", path);
+
+ // Attempt to download genesis
+ download_genesis(Some(path.clone())).await.unwrap();
+
+ // Check if the genesis.blob file exists
+ let genesis_blob_path = path.join("genesis").join("genesis.blob");
+ assert!(
+ genesis_blob_path.exists(),
+ "genesis.blob file does not exist: {:?}",
+ genesis_blob_path
+ );
}
From cf21355497474172bb4e76f9a92ce9c1a0478f33 Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Fri, 19 Jul 2024 18:45:28 -0300
Subject: [PATCH 13/16] applies rlint
---
tools/config/src/make_yaml_public_fullnode.rs | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/tools/config/src/make_yaml_public_fullnode.rs b/tools/config/src/make_yaml_public_fullnode.rs
index 24c68bb6d..0ecd48389 100644
--- a/tools/config/src/make_yaml_public_fullnode.rs
+++ b/tools/config/src/make_yaml_public_fullnode.rs
@@ -181,7 +181,8 @@ mempool:
/// download genesis blob and save it to the specified directory.
pub async fn download_genesis(home_dir: Option) -> anyhow::Result<()> {
// Base URL for GitHub API requests
- let base_url = "https://api.github.com/repos/0LNetworkCommunity/epoch-archive-mainnet/contents/upgrades";
+ let base_url =
+ "https://api.github.com/repos/0LNetworkCommunity/epoch-archive-mainnet/contents/upgrades";
let client = reqwest::Client::new();
let resp = client
.get(base_url)
@@ -218,7 +219,6 @@ pub async fn download_genesis(home_dir: Option) -> anyhow::Result<()> {
Ok(())
}
-
/// Fetch the genesis waypoint from the GitHub repository.
pub async fn get_genesis_waypoint(home_dir: Option) -> anyhow::Result {
// Base URL for GitHub API requests
@@ -297,10 +297,18 @@ async fn persist_genesis() {
let path = p.path().to_owned();
// Ensure the directory exists
- assert!(std::fs::metadata(&path).is_ok(), "Directory does not exist: {:?}", path);
+ assert!(
+ std::fs::metadata(&path).is_ok(),
+ "Directory does not exist: {:?}",
+ path
+ );
// Verify path is a directory
- assert!(Path::new(&path).is_dir(), "Path is not a directory: {:?}", path);
+ assert!(
+ Path::new(&path).is_dir(),
+ "Path is not a directory: {:?}",
+ path
+ );
// Attempt to download genesis
download_genesis(Some(path.clone())).await.unwrap();
From c86d64b4f8421ad00c16b516a65a9d7eb78b34da Mon Sep 17 00:00:00 2001
From: soaresa <10797037+soaresa@users.noreply.github.com>
Date: Sun, 21 Jul 2024 15:00:27 -0300
Subject: [PATCH 14/16] initializes randomness data
---
.../src/libra_framework_sdk_builder.rs | 30 +++++------
.../libra-framework/sources/genesis.move | 2 +
.../sources/ol_sources/epoch_boundary.move | 47 +++++++++++-------
framework/releases/head.mrb | Bin 863549 -> 878837 bytes
4 files changed, 46 insertions(+), 33 deletions(-)
diff --git a/framework/cached-packages/src/libra_framework_sdk_builder.rs b/framework/cached-packages/src/libra_framework_sdk_builder.rs
index 4458a648e..83266963c 100644
--- a/framework/cached-packages/src/libra_framework_sdk_builder.rs
+++ b/framework/cached-packages/src/libra_framework_sdk_builder.rs
@@ -32,6 +32,8 @@ type Bytes = Vec;
/// }
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "fuzzing", derive(proptest_derive::Arbitrary))]
+#[cfg_attr(feature = "fuzzing", proptest(no_params))]
pub enum EntryFunctionCall {
/// Offers rotation capability on behalf of `account` to the account at address `recipient_address`.
/// An account can delegate its rotation capability to only one other address at one time. If the account
@@ -93,14 +95,14 @@ pub enum EntryFunctionCall {
/// Generic authentication key rotation function that allows the user to rotate their authentication key from any scheme to any scheme.
/// To authorize the rotation, we need two signatures:
/// - the first signature `cap_rotate_key` refers to the signature by the account owner's current key on a valid `RotationProofChallenge`,
- /// demonstrating that the user intends to and has the capability to rotate the authentication key of this account;
+ /// demonstrating that the user intends to and has the capability to rotate the authentication key of this account;
/// - the second signature `cap_update_table` refers to the signature by the new key (that the account owner wants to rotate to) on a
- /// valid `RotationProofChallenge`, demonstrating that the user owns the new private key, and has the authority to update the
- /// `OriginatingAddress` map with the new address mapping ``.
- /// To verify these two signatures, we need their corresponding public key and public key scheme: we use `from_scheme` and `from_public_key_bytes`
- /// to verify `cap_rotate_key`, and `to_scheme` and `to_public_key_bytes` to verify `cap_update_table`.
- /// A scheme of 0 refers to an Ed25519 key and a scheme of 1 refers to Multi-Ed25519 keys.
- /// `originating address` refers to an account's original/first address.
+ /// valid `RotationProofChallenge`, demonstrating that the user owns the new private key, and has the authority to update the
+ /// `OriginatingAddress` map with the new address mapping ``.
+ /// To verify these two signatures, we need their corresponding public key and public key scheme: we use `from_scheme` and `from_public_key_bytes`
+ /// to verify `cap_rotate_key`, and `to_scheme` and `to_public_key_bytes` to verify `cap_update_table`.
+ /// A scheme of 0 refers to an Ed25519 key and a scheme of 1 refers to Multi-Ed25519 keys.
+ /// `originating address` refers to an account's original/first address.
///
/// Here is an example attack if we don't ask for the second signature `cap_update_table`:
/// Alice has rotated her account `addr_a` to `new_addr_a`. As a result, the following entry is created, to help Alice when recovering her wallet:
@@ -1027,14 +1029,14 @@ pub fn account_revoke_signer_capability(
/// Generic authentication key rotation function that allows the user to rotate their authentication key from any scheme to any scheme.
/// To authorize the rotation, we need two signatures:
/// - the first signature `cap_rotate_key` refers to the signature by the account owner's current key on a valid `RotationProofChallenge`,
-/// demonstrating that the user intends to and has the capability to rotate the authentication key of this account;
+/// demonstrating that the user intends to and has the capability to rotate the authentication key of this account;
/// - the second signature `cap_update_table` refers to the signature by the new key (that the account owner wants to rotate to) on a
-/// valid `RotationProofChallenge`, demonstrating that the user owns the new private key, and has the authority to update the
-/// `OriginatingAddress` map with the new address mapping ``.
-/// To verify these two signatures, we need their corresponding public key and public key scheme: we use `from_scheme` and `from_public_key_bytes`
-/// to verify `cap_rotate_key`, and `to_scheme` and `to_public_key_bytes` to verify `cap_update_table`.
-/// A scheme of 0 refers to an Ed25519 key and a scheme of 1 refers to Multi-Ed25519 keys.
-/// `originating address` refers to an account's original/first address.
+/// valid `RotationProofChallenge`, demonstrating that the user owns the new private key, and has the authority to update the
+/// `OriginatingAddress` map with the new address mapping ``.
+/// To verify these two signatures, we need their corresponding public key and public key scheme: we use `from_scheme` and `from_public_key_bytes`
+/// to verify `cap_rotate_key`, and `to_scheme` and `to_public_key_bytes` to verify `cap_update_table`.
+/// A scheme of 0 refers to an Ed25519 key and a scheme of 1 refers to Multi-Ed25519 keys.
+/// `originating address` refers to an account's original/first address.
///
/// Here is an example attack if we don't ask for the second signature `cap_update_table`:
/// Alice has rotated her account `addr_a` to `new_addr_a`. As a result, the following entry is created, to help Alice when recovering her wallet:
diff --git a/framework/libra-framework/sources/genesis.move b/framework/libra-framework/sources/genesis.move
index 161e92c1b..f61594a56 100644
--- a/framework/libra-framework/sources/genesis.move
+++ b/framework/libra-framework/sources/genesis.move
@@ -24,6 +24,7 @@ module diem_framework::genesis {
use diem_framework::transaction_fee;
use diem_framework::transaction_validation;
use diem_framework::version;
+ use diem_framework::randomness;
//////// 0L ////////
use diem_framework::validator_universe;
@@ -137,6 +138,7 @@ module diem_framework::genesis {
reconfiguration::initialize(&diem_framework_account);
block::initialize(&diem_framework_account, epoch_interval_microsecs);
state_storage::initialize(&diem_framework_account);
+ randomness::initialize(&diem_framework_account);
//////// 0L ////////
diff --git a/framework/libra-framework/sources/ol_sources/epoch_boundary.move b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
index e78bb6f1b..ba93fa84c 100644
--- a/framework/libra-framework/sources/ol_sources/epoch_boundary.move
+++ b/framework/libra-framework/sources/ol_sources/epoch_boundary.move
@@ -1,32 +1,32 @@
module diem_framework::epoch_boundary {
- use ol_framework::slow_wallet;
- use ol_framework::musical_chairs;
- use ol_framework::proof_of_fee;
+ use std::error;
+ use std::vector;
+ use std::string;
+ use std::signer;
+ use diem_framework::account;
+ use diem_framework::randomness;
+ use diem_framework::coin::{Coin};
+ use diem_framework::create_signer;
+ use diem_framework::reconfiguration;
+ use diem_framework::transaction_fee;
+ use diem_framework::system_addresses;
use ol_framework::stake;
- use ol_framework::libra_coin::LibraCoin;
- use ol_framework::rewards;
use ol_framework::jail;
use ol_framework::safe;
use ol_framework::burn;
- use ol_framework::donor_voice_txs;
+ use ol_framework::rewards;
+ use ol_framework::testnet;
use ol_framework::fee_maker;
- use ol_framework::infra_escrow;
use ol_framework::libra_coin;
+ use ol_framework::slow_wallet;
use ol_framework::match_index;
+ use ol_framework::proof_of_fee;
+ use ol_framework::infra_escrow;
+ use ol_framework::musical_chairs;
+ use ol_framework::donor_voice_txs;
+ use ol_framework::libra_coin::LibraCoin;
use ol_framework::community_wallet_init;
- use ol_framework::testnet;
-
- use diem_framework::account;
- use diem_framework::reconfiguration;
- use diem_framework::transaction_fee;
- use diem_framework::system_addresses;
- use diem_framework::coin::{Coin};
- use std::vector;
- use std::error;
- use std::string;
- use std::signer;
- use diem_framework::create_signer;
use diem_std::debug::print;
@@ -261,6 +261,12 @@ module diem_framework::epoch_boundary {
true
}
+ // This function handles the necessary migrations that occur at the epoch boundary
+ // when new modules or structures are added by chain upgrades.
+ fun migrate_data(root: &signer) {
+ randomness::initialize(root);
+ }
+
// Contains all of 0L's business logic for end of epoch.
// This removed business logic from reconfiguration.move
// and prevents dependency cycling.
@@ -272,6 +278,9 @@ module diem_framework::epoch_boundary {
let root = &create_signer::create_signer(@ol_framework);
let status = borrow_global_mut(@ol_framework);
+ print(&string::utf8(b"migrate_data"));
+ migrate_data(root);
+
print(&string::utf8(b"status reset"));
*status = reset();
diff --git a/framework/releases/head.mrb b/framework/releases/head.mrb
index b63243772b0f84f612318448d21b2e022d261b20..14d5715b6d642cdb7a4b6128a30d563883a4fc88 100644
GIT binary patch
delta 103672
zcmW)n1z3|`8^-DG?rtPTH;isZNq48xk|QMrq(_b(2%>a{0tyTe86d5cFiMb+{`UXi
z+WR~A{oLogyV!Qvd1GKj;&M(hHW57$qaAbqvkE{~9jGQLEdvzOlorzzl@-%aRhQHN
z09C~_0iqHz5^55Xq7qUPn$nVD04Yg;n6#$4hK#7Xl&qwxhNifOa`W0-)AwefJicGNZ|A1!nCkm
z?a3X<);xa%&7Fh~Zs|J)X^G|Tv!N5*`yT&8{2Ne_fWdu3vn*`Q`ZMbg+Bi;HbjO
z_j2gd*nD~tqih3*TmhzP-+35j%lawc?DEt5nFzJErEZ_br9;cI6#Dq><~7(cXRWxfwwRDUWg0b|a{v8{b%K||Z3Vok?G`|k(iDSy=~
zIB$U-_fLsy0i32t8b8#AA-zWtHyt0C8o7A3Ik@0+X0>&Vl;3WXEE
zyOm-{Sd?7M!jYt;-*5k$?Q_~L+R(B<+eV1epW&l@nnSu;gr$1??G+e*BAyO4w2`qvCzv#taPx>rc~jA>V;&GC%R0&D6j=H{KV=h?+%zx%bvePAIxfrf}uuX1jw;F?s0v9dj@vL!T6
z`jB?63G+@g$g^JH;@|s~RV#4$gm_zNVMw!%3&HxvB7v@_b$d=36pq
z2P{N$%1*DgpI-Mz9VX%z7(?mcm!nmOi>6wo@sF&kZj;-;lV9e0!38B=*s>@qC#>X*
zXH@XO-b=XO`zGCnGl<9ZRz`d;I-^YTHu_Rewrl?t_&}`QL+Oq8i>j8=!ce1I{f2#}
z5a;0eQQPQlFX~+m*chH#o?hkor98D87~PDX!ywNJ
zw3`xaya-(29RKRVg8YJ4!N&+?Ag8agC8gH*r_s>Dq=u!wjK7eCm)44lbW+#OdjR!)
zzx>2C5eW^g7j*9lr@wjZRx?evH|2hm+Sf_EoQx1S2+IC_IRU61lZRmD9=p=aA3|wl
zA@wB$Ap^Nq`n)Oz!D8uPFLDF{Z(EUY!paUJKZVPua-|IJ+)L5;dE!~!-ooyf*}Dq1
zl%)%GP=iN&F^@mh-&^)!_QdLl==S%o`LFDgV{m`H&w8J3-l_gc=%co}?XQ60-zn;c
z`G?w32HFiBDS2=E#L~SEK13lf-;rBJjCSQ^&C@0pRVolRijETPg6Yh(Qf@SzU7VLHLmal)Ge04>}KZjlE*9pU7SF$G%*n6*Q
z)PF>QuM@(>SP{Xv;%C!AlaxbvNOx4Ttul}aJ~|t3eeB5dxYo_Zav)G>VkO+WGl5ff
z>a168`!y=j&n(0xI@(1|VMrpa+5?v)g5sCzXQpN!LUAyszgn6}Gr#_J)Te|@>|ZHT
z$y&l}%mLCTwYa~!zk7>^|LeACa7qlSS#l@Q85Zgx^X^e_iHR#Y2my$X_lJa!6R8}B
z0CX1a70%asnOmTrr-??bVRF*qT^u0msi1chjZ4$;=E%z4b@X#hijYI-qsePkQ1)CRWPSI*Tyv%QduI|T%}bZ$&Vs)L>3(Cg21dU8RcXYuAY0tB1q>8q*Xg2iaPtLysMMFrqU}rQR3bZ
zW{9e65-x1d_1)+6bH-c-RIOom-~#Nyc}8l-YWifmQm}`V)T*O|2x5Rv-Yjq+vFO?4
zXV8<1KKbtEmPLiYA6LH#|Sh#Gg+_(0k@Uk#?1QC^FxJ!W`r
ze=fcx^;xuWo!T3Xo%BE@vl+0bU0wPnUL%z;+hjP%4X^5c`fBUDeXolRelLMjL%LLY
zH_b~-4!|$1Elsi?Q}&&d?ewrP48c9biig7M?be=OQGJpo&uSEiu~{BZ)(
zMVYlS!n>Jn0E9rofhCQKkVH`?H&MqDssjy7nNFXz6(4DsaG(~0zu+Sp{dFA_jWeHo
zl(^!C5-tJ!*X*RK$5KT~qfvUm+v%zGF*3v^ab4dl79@7UfpB?J#EfHaIwlAg<$be=
znL`N|5I(p;fA%HQdCkZLQGMkMqyFrlkM@10kn)j!Bj?CE*9?=c%%0X-I9L6u$~&@7
zsv{mL^YNA7
z0#~E-tLz9DPFyIOZ!mV%S1~&lzHnLpHJz$%XGL}?@o=BUMj*b_>}&%4t^xc#q~4m@
zI_*hxBGMkBshw1f&k}UnQByj-ewCzFQ>o`>oZQHWQ?(avRsJs
z-$;YxAM|x&@Q_ZZ5#%eC@I?7vvn1i4PxW9es9ib4Kl&MGFaNn?`2E>L&G(<(
z>w5*%mVKHwl}ni1lSP2{(!Tf4L0fDAeRKxGaQ`FU<0@Ps&7$X|LXg1
zI%#rYNH15f;IoeIzw>qbe`B&{G6k+_K1{N>9P
zmcl@QeRr`nc+nK7DX)T_Pe
z0*w9h=)T25sV*GXB*mXz#H;U;}@_%_HsX6666WJcb`D$n
z;=)4ccgFL;z#zTn)P;pfLArlGN$)mU+M87(q#a`Mpk4I#Sd|0l?+W`Eu^GVlkO?V>
zt*SeZYa08rG(rjv3Mu?_ynPuiN}aFq5G*q?EJ3W}zuFosRMja^YM_SthvVv4HiAn3
zAMveY*60lcrcB_uQz0WE24ha7`vwJe6I%EM0VdG5gtnKD%OSq
z?IVBh(|3`2)6LzE?93O=_|aw=Bbl)tpTJMFF2qZcGsJM>#f7Be?Lu!RY)&xc^&oK_
z0lDBLAt)dfJ`f3X#me5D1}E9UGIDl#Ffrm%ZL=
zoq|L%iCYo#T=;=+l0*5LXLwo>^4^Z*(%PtwbMW;oIXUm-HI#&NO9PTyXNJ!K%rmXT
zNKrSIQkCS?uVNzTzF6(
z3a!z@(zk!4hLzcKtQn9Df*whYHQ;X~QDjbj{sdBq@6-$o&W8?SpLjW>*w>hRX>wzL
zthFraMVF*_SnnD~9X^UWG(1lXoCsH@JUNM~J4;rT+!YUhB0>b+=x>0PF*<}RChqoM
zpiRR_Z(0;rUzS->=
z?(B)Gd!km%w;Ypa-5X|!c6sxaC+anmR=Cva!PPc{eSD{%FYSRB6uS|yLh^$uTZ|5Z
zU!bE`p=p7vh%YHb#)Dw|OB#;coJex3JVe+m&8`(pSUr<3OC{uDj*^md2)JWnvbk>H
zfd@!ZEJ#k}H)>(luj>UR7~3j1hP$1Ol`K9x{;_ggUw?Y>-ty@fiF~>b*t?(S6_^*M
z$!CQfqdifOJ6KTCEGbrCvS^Ja*&w;^#MijhRmEAhy>&|S%-2qBjLW4PG&
z5~(qgVpror0#L)-Wp^(-n8s1v-7N67-Jmj=HO=urZWXpdut$^nH<_1Ma9>GFZMC=oLwZ>_Qbz
zl8RsKf9Ki#P7L}?7GCu%p%>pNrIpHCrmBmtCDYYzkPm!Wrp0!fV&aiJQ;%V6nC@Dp
zWIHj6FC^HZ*1b^xf?OR$>0+I`{vcx0t*ehhBn}TZI7WE6gW;k*=UnG?r$evi$$R@oh>i3Ja-g4=Dn#0QO
zdYUUQ?0RZZ@jC;oKJh{qZ;Otfwn2i?@GCGaLd~AF?O_x5@?+NW;}GuU;D5jlF|V|*
z)M+I(1G2ZN25Hpvs|qId_GDXCzKJgeeQo-tshUqKCfLpnq9lY+TT>Tqv0-zdM;2nU
zW6OXmJ4=eJCW-RBo!uKe1<9v_*%)!pmgR68DDL_9ou;YJH~>nxZ;C(HIPePJm`dfw
zSQiIT&9@94CP~ww7Vtc9k_|x-P5apO6jA9U0U!6JM269MnBO;fvec2=aT>@e?gMOT
z_cNg9thd(Xy`BfdEP8(KjKoIx+FJfh#ya>H$mKiaqdbYXiJW*SA(W?HT&H278P8dJ
z(fMO1ms*R#jlT`SkV3*qXT8c2hjaI}p&jafBxUVnSrpaDJEFSWYhZNINbva`Y9Ub8
zkBjU>Sw>nlYZE;Xneho%Ia!)UEgtdgY9RrS&m!ISzBJ-MAFpZWx^*g8xfQ$}?3187
z<4lvh`SYX<0^b5nz1MdSfYrtrh6ljR8R9+b*6EU%Be=!`gZ61Z@XjApF5uthmhU)^
zwor`A=QY0cToJ+lB6$wPI_e;C_q=?4E?ZRTrs5?;Dnx4rNualuRg?kluO^=3D!4K0D{m^Ju?+VijXYq5&dZjV<
zn!(5yx3uJ#k1l3qF1_cYWW1?g(B&nbk9x$YeH5W9Ffh0V)gtU&aLml5#MdpyRDu^{DeyC4YaW
zpPF%1Z_Ck=EYKm!-AdHB_9^e{2B;e4OQ62i>D3S-ISGR#ZpaWWG<8MKRV8hToNwh2
zt0-}jxZ>iJc3=$)6VeA5v~x%|!{>IwQD3{9-6s8UcXS9%Lyb@`T;Vz==&(tDStuUq
zfhrK24dnCp_j6tQedpgjD
ze6KrdTj0#Hosp$Remv$wtBHdGlPAP3DVO5xjZL*(LlR!2>oMBen-p*ZxdTk4S$%XN
ziDC4!c(=a16&_eUJ(#_TxH7UBb7;_ls-9fJ5Hnt0Ap$#DvxU>QP}#biyopfCEbVDO
z-aj8PWwQF%+pJExhqq>C9az#i)EQ;HeVTxP|t
zerIFPzD6WDInzWo4g{||dpyUht{h*1P=9&})0J;ig{5fsoERk${DAbbCKfw+AY(30
z0@ob~7Q9i-VC(F|M@o><3aVH}xzqc^fxwYBy_N_hG1
z3-^Ms+|at>(la-)^i$_u_--1q+CpD5rZn&qpI#DI0!X*h>?ow4r4R}9L9B(
z+3sxjc@ks~JM%ALgDLOSmr1?Zsx0${;(F2N9A?NKe8{-Zami3epwI(jREg(j1t!g4
zsVP8k;BMMiAN6o)1k)&)uCTf-FRPMT2giHbT})7V@S$L+la!f~b>d2Vw{hMJmE-Tt
zBB%6~ogBKiIp;t~
z!alfY$YOi*IG6Hy02kQ(--b1TI#SNgQwb1YF7>3SB2r0K0Pt}O4~agieq)Y-z<9tq
z|J@0?X5*Q1q4Xx-dTy+Q+ra|Rc$A>`rreV1nN%5gQzY=40zon=ag)u26m{;6+QJPX
z3h29Erpj9k`|2SAa$v_e*NOr0M)JAmiuP(Z$fsX~jby%z-fjXxWvTp0%%r4}Fbu$z
zFJ1I<`&r9U!H+6)`S-iWrfkQ{6|4&rv(aDPVq0u^
zgd%AFpkdy_Gb1R>1}c_~9dH+ZdR$G+s`~vAzuOrD3Esn%WLFH&mBOXX`YM)rU&O7P
zpz78q7pSt(+rBAWpL*!vwRplVm<{2i+*ZAK&Kl#c6)hSV7OvI25MEY!ew@{lCwEw-
z{8t|LHeqky{A?vC@GM5IQ0Gw>R&2|2g9)nG6Pp-9NABA0$s8ENI&IyHj$-Aqr?{R<
z$oYAnjA1T#9GLFAP+-vhOfhaRD2V9U&5%f(y}?UB*(I~^jT*_kl?N1D*H<(n5XW%j
zEGLNBI6M~oTo$2DJ8xA>&L6fo0J`ogf(T%B4A&O@^8WbCaZEGJM+&xDH${EgS9~Ba7Dd?8{`EY9U9N5YJQcB?QTX456skrydme)FqJ0cHg
z{;J4$J6YgNw7L#Y*C!nDs*>fuHjTiMlcoV$b%weTCt=Uija&AzHlrAeim(676gO|bn4L|Fl0{&(87NXPYpWwFA83t1in!)uIfr-uyH
z@aLTAnO{@-()5Bg4>F6Yit8fod<%PRv4y}A=4%Owy-$RP)@C^cI39^&EG7`*pIx6;
z3b)egv@L?`2|a0XkGbbaw^uGL*Hn!PJ*5=`56;STgcZ`X!c~U?UM%~SVO&q+jr-Ae
zO!~j$WwB~`o3Scp-+R=#rV^gvx>hhp?AAq1;60)}?q`5&epcW
z6J{txV#u+Yfc%D%Cb(>bHfsg{c7ShQ1lUkCsO97HG~<1!Xg&HV@a<4QON>OA(riI{
zeB>D8@*V|xC6&;ZWUGmmZUCR@~crZ)fJ48{+cuq!ARhpMwojkMTa2!VHu7|23xf&y6;v8
zrT+&s(3!A)jIAt;`ajUXOlZqkd811n|HWe($SE6gOhfmIgTXPS>Ns%(QU~`ksio-N
z1vSXIi}g^QRHPJ@&v_8ByU
zg&^aR8g
zW-UUu=kS1?#Ddv?o6TXfIHuHD+``_n)X_JO+*(`U65#RrjQW-vw766JZTFe=j`qF$
zdLS`nD&wq9=BO5Q&`UBS)-{T)HCBSuOq*D|P^x!8L7O|ax8XH;c8*FdLR8Q{6-u)p
zYGO*ocSoYBa*MW;#Gt9ti-$BJ=44|=59vV#N#wPyRKzr2SgA0?lRN4QwMi|JBYs%(
zP=G5)1%?VxPiDk6bM(!eZszC<%#cVS)b8!|GDo`-R?Mf=xB7sROJZ+dVZz2yJ%%t5
zksbP6gRBTb6duK3)&GG5R_G1Uz7GHyp_2bXK+K_{Z(s3(X5@>s?}?B{g9)jliaMS#3{aCC7&SmPQl@=?ycKF!~1{@gI18H)UGNE;Qq
z_a0XjQCR1`%xzCQOfV5-(F3nY!fj*bGWa|vA@90^E{HMwX#%u%4U2GpnxF%@I1%o;
z@Th>_w0MLz?%zrt92!;R7b6}i{#a4c%j?2#8fP6xDnd7c7K0EU7X
zF(Ng>-6);eOhd7)zLo$niBeP3(B})N&d(EWwNS3cQoUEWk)9aL1_@CVx{iyWDd4Er
zid%>|fzjww1!0OK9gr>aEPMs}HuaJMSL)bB^d;JIaEHg+Hvt8J|OUne3o>iwB0
zx_3ao5D%pc3DC_6JlSb2_ufe&KH_WHji^NU*Rq2XEtP@Yncy&k@(>TXGg**wuXved
zZW%A2tQ+06P$v!ef-#+b{s-e>@g?7NtZ@1gyT}L3BZuGZ=5z#1D?*i?h6v~%GSuUF~>z62F}!XTVkH^4FNK8#gTbK
zw_{Mgl3;ICv$S-k46`78}geQ8cUM!z4!DGohE;EunwW|?n0)*WyV2^>(
z3im(53~5?~@&>_lupoRQLc>;feE&t)y`X(G?<*8UR|@5O&f0ej{S^dz4jGIZ38Twe
zKd<0<&%4$gGKB5uJ9vcMZtTv5(;!QZN1}-JfVHo{qJmX=bAg$-Z5#|3Vu50991-Hk
z)t!;16c(%yc!CxAItDVD)LkQ37Uqq@1ty5~Q_7d%Ny51-S}@nmZ^zll1I~P#my5|H
z*RPSdpIggI-EjQhOaJvV!jPOTx#@g!$?+$;K#v4@HD&U|L_-G8_kJNXJ%H{j#4>(!~RfC
zrkBC37%3>WiS8#qd@2*_>~d5_UCl6yAR1C@#p8~j1wJ;X~%
zxrvFSH{$uR3MN8Y8SQaOSrYt|G39Ol&TzosKcDPERPz2AQhrOr4L?3&163=I02<-$&~Apa!lEPg
zhBN<014)
zyG(kX=;9H(hTu_XSdXyC@KOmj^&dF-e&f}dMw6Z=W9!#PY#Q?d-oJihA`k$kdg!J>
zw-eKyhnE)$v30%KD)$rWPSym>F2eFH*RVr^ss8$ACCEvpg?AdoMc8cS9;MEx%sfO8
zJ@^ixM;LE++uw|aJZsBfnNoN^1C7XVV=3hMl3|{42rsQSyXh~eAZz_?LwLE8S~QCm
z_0Z2KTl^8dmLs;Tz{3=>a#+O+#uuO+r&c-l8vV-;vpMQ@H)>Y
z8<$)sMOLt9`1DdE#>h0QZmAGSs34da5yq|$@^2T$-R*+b`;&s*oZF~CNtGG6l7nc=
z;R=MvOOQKX6b?eG68<%i750=U2D}+SeG+kZ`+y-OvtM;AR6-#r)B{7yP|!SVEj1%W
z4&G#xwqqb}QE<)BZ!Nu$gI({52Y%Os&dhr3)Py?iYdK|r1y^?1mTBjsF8M+HNFq&msP{F&=+f88f5~g_gL$MpxCpzM%Yk
zeNPq)fsZ-()5J?`$$Qh#^8X6c&`t}n${QX~ae><35NH(mUD3CD>TJKpqS!mkY+3>USRy#UWo)Qp6AGAQ>C=8SO?2gNZ5pof7$n5bAEFU;N?K6C!@mf>Ad*=|_uis{(V
zvrSxNssPX0i@@x_ZU++N1gb5qsw}(qVu)fkh@1K(fuS`A{~9-*p&bR0Er!)5*G~@+
zs596?>{pBe%uNuosltRi*_pGPz(TCdWIBBW%)-qF{ReDni~oQ|ZH2;;(54v*U2!WY
z^Vuwhxg81)*xo*pZEaEVusZKOX|gwmTx(a!^t#cxbLWhB@GQD$!V8n}Q`u)iCHvL@
z*<1SE2v_G|7Zde8S^;p!`)oaOS(5h_-K|gO_e>YYi9WqUtUI}GRPUf1!gJU!Qc
z`t!OQqoNk^v1w6Qnz~9|vgveF_v0yz^nQVX0L3htLiT!<_P>VRRu1ww|1xJ6Dty$L
zXkY8M*}cQ>_N`g}fxR^1A9PnHe)A{vLi?Ju%dvM4Td47`o*iUfSbZy1?_s|Ke
zoXt7Wt>9jNqolV2QPS&`k1hx%y?u62jtUJNwAI{w^U%4joy{qjtv^X)S(>jf=JNk6
z-9A3fN1*{-TxsWES2Ai>SawL3qL3v*$bg%*wRf;Ux%VAl4)(XvOwFXVvro*L6;JY|
zN>5=}(T}IvxAzXTsD}mWksEdTs=(