diff --git a/framework/cached-packages/src/libra_framework_sdk_builder.rs b/framework/cached-packages/src/libra_framework_sdk_builder.rs index 83266963c..a3bae1d52 100644 --- a/framework/cached-packages/src/libra_framework_sdk_builder.rs +++ b/framework/cached-packages/src/libra_framework_sdk_builder.rs @@ -533,18 +533,18 @@ pub enum EntryFunctionCall { /// you may want to add people who are related to you /// there are no known use cases for this at the moment. VouchInsistVouchFor { - wanna_be_my_friend: AccountAddress, + friend_account: AccountAddress, }, VouchRevoke { - its_not_me_its_you: AccountAddress, + friend_account: AccountAddress, }, /// will only succesfully vouch if the two are not related by ancestry /// prevents spending a vouch that would not be counted. /// to add a vouch and ignore this check use insist_vouch VouchVouchFor { - wanna_be_my_friend: AccountAddress, + friend_account: AccountAddress, }, } @@ -856,11 +856,9 @@ impl EntryFunctionCall { fullnode_addresses, ), VersionSetVersion { major } => version_set_version(major), - VouchInsistVouchFor { wanna_be_my_friend } => { - vouch_insist_vouch_for(wanna_be_my_friend) - } - VouchRevoke { its_not_me_its_you } => vouch_revoke(its_not_me_its_you), - VouchVouchFor { wanna_be_my_friend } => vouch_vouch_for(wanna_be_my_friend), + VouchInsistVouchFor { friend_account } => vouch_insist_vouch_for(friend_account), + VouchRevoke { friend_account } => vouch_revoke(friend_account), + VouchVouchFor { friend_account } => vouch_vouch_for(friend_account), } } @@ -2329,7 +2327,7 @@ pub fn version_set_version(major: u64) -> TransactionPayload { /// you may want to add people who are related to you /// there are no known use cases for this at the moment. -pub fn vouch_insist_vouch_for(wanna_be_my_friend: AccountAddress) -> TransactionPayload { +pub fn vouch_insist_vouch_for(friend_account: AccountAddress) -> TransactionPayload { TransactionPayload::EntryFunction(EntryFunction::new( ModuleId::new( AccountAddress::new([ @@ -2340,11 +2338,11 @@ pub fn vouch_insist_vouch_for(wanna_be_my_friend: AccountAddress) -> Transaction ), ident_str!("insist_vouch_for").to_owned(), vec![], - vec![bcs::to_bytes(&wanna_be_my_friend).unwrap()], + vec![bcs::to_bytes(&friend_account).unwrap()], )) } -pub fn vouch_revoke(its_not_me_its_you: AccountAddress) -> TransactionPayload { +pub fn vouch_revoke(friend_account: AccountAddress) -> TransactionPayload { TransactionPayload::EntryFunction(EntryFunction::new( ModuleId::new( AccountAddress::new([ @@ -2355,14 +2353,14 @@ pub fn vouch_revoke(its_not_me_its_you: AccountAddress) -> TransactionPayload { ), ident_str!("revoke").to_owned(), vec![], - vec![bcs::to_bytes(&its_not_me_its_you).unwrap()], + vec![bcs::to_bytes(&friend_account).unwrap()], )) } /// will only succesfully vouch if the two are not related by ancestry /// prevents spending a vouch that would not be counted. /// to add a vouch and ignore this check use insist_vouch -pub fn vouch_vouch_for(wanna_be_my_friend: AccountAddress) -> TransactionPayload { +pub fn vouch_vouch_for(friend_account: AccountAddress) -> TransactionPayload { TransactionPayload::EntryFunction(EntryFunction::new( ModuleId::new( AccountAddress::new([ @@ -2373,7 +2371,7 @@ pub fn vouch_vouch_for(wanna_be_my_friend: AccountAddress) -> TransactionPayload ), ident_str!("vouch_for").to_owned(), vec![], - vec![bcs::to_bytes(&wanna_be_my_friend).unwrap()], + vec![bcs::to_bytes(&friend_account).unwrap()], )) } mod decoder { @@ -3206,7 +3204,7 @@ mod decoder { pub fn vouch_insist_vouch_for(payload: &TransactionPayload) -> Option { if let TransactionPayload::EntryFunction(script) = payload { Some(EntryFunctionCall::VouchInsistVouchFor { - wanna_be_my_friend: bcs::from_bytes(script.args().first()?).ok()?, + friend_account: bcs::from_bytes(script.args().first()?).ok()?, }) } else { None @@ -3216,7 +3214,7 @@ mod decoder { pub fn vouch_revoke(payload: &TransactionPayload) -> Option { if let TransactionPayload::EntryFunction(script) = payload { Some(EntryFunctionCall::VouchRevoke { - its_not_me_its_you: bcs::from_bytes(script.args().first()?).ok()?, + friend_account: bcs::from_bytes(script.args().first()?).ok()?, }) } else { None @@ -3226,7 +3224,7 @@ mod decoder { pub fn vouch_vouch_for(payload: &TransactionPayload) -> Option { if let TransactionPayload::EntryFunction(script) = payload { Some(EntryFunctionCall::VouchVouchFor { - wanna_be_my_friend: bcs::from_bytes(script.args().first()?).ok()?, + friend_account: bcs::from_bytes(script.args().first()?).ok()?, }) } else { None diff --git a/framework/libra-framework/sources/create_signer.move b/framework/libra-framework/sources/create_signer.move index a64c29882..d28c229f6 100644 --- a/framework/libra-framework/sources/create_signer.move +++ b/framework/libra-framework/sources/create_signer.move @@ -20,6 +20,7 @@ module diem_framework::create_signer { friend ol_framework::fee_maker; friend ol_framework::epoch_boundary; friend ol_framework::multi_action_migration; // TODO: remove after offer migration is completed + friend ol_framework::vouch_migration; // TODO: remove after vouch migration is completed public(friend) native fun create_signer(addr: address): signer; } diff --git a/framework/libra-framework/sources/ol_sources/globals.move b/framework/libra-framework/sources/ol_sources/config/globals.move similarity index 95% rename from framework/libra-framework/sources/ol_sources/globals.move rename to framework/libra-framework/sources/ol_sources/config/globals.move index 0d0779697..bac2b08ca 100644 --- a/framework/libra-framework/sources/ol_sources/globals.move +++ b/framework/libra-framework/sources/ol_sources/config/globals.move @@ -25,6 +25,7 @@ module ol_framework::globals { min_blocks_per_epoch: u64, validator_vouch_threshold: u64, signing_threshold_pct: u64, + max_vouches_per_validator: u64, } const COIN_DECIMAL_PLACES: u8 = 6; // Or 10^6, 1 coin is 1_000_000 units in the database. Any human display needs to consider this scaling factor. @@ -90,6 +91,11 @@ module ol_framework::globals { get_constants().validator_vouch_threshold } + /// Get the max number of vouches per validator + public fun get_max_vouches_per_validator(): u64 { + get_constants().max_vouches_per_validator + } + /// Get the threshold of number of signed blocks in an epoch per validator public fun get_signing_threshold(): u64 { get_constants().signing_threshold_pct @@ -113,6 +119,7 @@ module ol_framework::globals { min_blocks_per_epoch: 0, validator_vouch_threshold: 0, signing_threshold_pct: 3, + max_vouches_per_validator: 10, } }; @@ -137,6 +144,7 @@ module ol_framework::globals { min_blocks_per_epoch: 10000, validator_vouch_threshold: 2, // Production must be more than 1 vouch validator (at least 2) signing_threshold_pct: 3, + max_vouches_per_validator: 10, } } else { return GlobalConstants { @@ -157,7 +165,8 @@ module ol_framework::globals { min_blocks_per_epoch: 10000, validator_vouch_threshold: 2, // Production must be more than 1 vouch validator (at least 2) signing_threshold_pct: 3, + max_vouches_per_validator: 10, } } } -} \ No newline at end of file +} diff --git a/framework/libra-framework/sources/ol_sources/epoch_boundary.move b/framework/libra-framework/sources/ol_sources/epoch_boundary.move index ba93fa84c..b4f65c337 100644 --- a/framework/libra-framework/sources/ol_sources/epoch_boundary.move +++ b/framework/libra-framework/sources/ol_sources/epoch_boundary.move @@ -11,13 +11,15 @@ module diem_framework::epoch_boundary { use diem_framework::reconfiguration; use diem_framework::transaction_fee; use diem_framework::system_addresses; - use ol_framework::stake; use ol_framework::jail; use ol_framework::safe; use ol_framework::burn; + use ol_framework::stake; + use ol_framework::vouch; use ol_framework::rewards; use ol_framework::testnet; use ol_framework::fee_maker; + use ol_framework::migrations; use ol_framework::libra_coin; use ol_framework::slow_wallet; use ol_framework::match_index; @@ -265,6 +267,7 @@ module diem_framework::epoch_boundary { // when new modules or structures are added by chain upgrades. fun migrate_data(root: &signer) { randomness::initialize(root); + migrations::execute(root); } // Contains all of 0L's business logic for end of epoch. @@ -330,6 +333,10 @@ module diem_framework::epoch_boundary { status.pof_thermo_increase = t_increase; status.pof_thermo_amount = t_amount; + print(&string::utf8(b"set_vouch_price")); + let (nominal_reward, _, _, _) = proof_of_fee::get_consensus_reward(); + vouch::set_vouch_price(root, nominal_reward); + print(&string::utf8(b"subsidize_from_infra_escrow")); let (i_success, i_fee) = subsidize_from_infra_escrow(root); status.infra_subsidize_amount = i_fee; diff --git a/framework/libra-framework/sources/ol_sources/migrations.move b/framework/libra-framework/sources/ol_sources/migrations.move new file mode 100644 index 000000000..e1bb096ea --- /dev/null +++ b/framework/libra-framework/sources/ol_sources/migrations.move @@ -0,0 +1,98 @@ +module ol_framework::migrations { + use std::vector; + use std::string; + use std::error; + use diem_framework::system_addresses; + use ol_framework::epoch_helper; + + use diem_std::debug::print; + + // migrations + use ol_framework::vouch_migration; + + //////// CONST //////// + const EMIGRATIONS_NOT_INITIALIZED: u64 = 1; + + //////// STRUCTS //////// + struct Migration has store, copy { + number: u64, + epoch: u64, + description: vector, + } + + struct Migrations has key { + last_migration: u64, + history: vector, + } + + public fun execute(root: &signer) acquires Migrations { + // ensure ol_framework + system_addresses::assert_ol(root); + + // execute all migrations + if (apply_migration(root, 1, b"Vouch migration initializes GivenVouches, ReceivedVouches, and drop MyVouches")) { + vouch_migration::migrate_my_vouches(); + }; + + } + + fun apply_migration(root: &signer, mig_number: u64, description: vector): bool acquires Migrations { + if (can_execute_migration(mig_number)) { + print(&string::utf8(b">>> Migration started:")); + print(&mig_number); + print(&string::utf8(description)); + + register_migration(root, mig_number, description); + true + } else { + false + } + } + + fun can_execute_migration(mig_number: u64): bool acquires Migrations { + get_last_migration_number() < mig_number + } + + fun register_migration(root: &signer, mig_number: u64, description: vector) acquires Migrations { + let epoch = epoch_helper::get_current_epoch(); + + if (exists(@ol_framework)) { + // update + let state = borrow_global_mut(@diem_framework); + state.last_migration = mig_number; + vector::push_back(&mut state.history, Migration { + number: mig_number, + epoch: epoch, + description: description, + }); + } else { + // initialize + move_to(root, Migrations { + last_migration: mig_number, + history: vector[Migration { + number: mig_number, + epoch: epoch, + description: description, + }], + }); + }; + } + + public fun get_last_migration_number(): u64 acquires Migrations { + if (!exists(@ol_framework)) { + return 0 + }; + + let state = borrow_global(@ol_framework); + state.last_migration + } + + public fun get_last_migrations_history(): (u64, u64, vector) acquires Migrations { + assert!(exists(@ol_framework), error::invalid_state(EMIGRATIONS_NOT_INITIALIZED)); + + let state = borrow_global(@ol_framework); + let last_migration = vector::borrow(&state.history, vector::length(&state.history) -1); + (last_migration.number, last_migration.epoch, last_migration.description) + } + +} diff --git a/framework/libra-framework/sources/ol_sources/mock.move b/framework/libra-framework/sources/ol_sources/mock.move index 40acbcecd..8bf813019 100644 --- a/framework/libra-framework/sources/ol_sources/mock.move +++ b/framework/libra-framework/sources/ol_sources/mock.move @@ -257,6 +257,17 @@ module ol_framework::mock { #[test_only] /// mock up to 6 validators alice..frank public fun genesis_n_vals(root: &signer, num: u64): vector
{ + // create vals with vouches + create_vals(root, num, true); + + 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] + public fun create_vals(root: &signer, num: u64, with_vouches: bool) { system_addresses::assert_ol(root); let framework_sig = account::create_signer_for_test(@diem_framework); ol_test_genesis(&framework_sig); @@ -273,15 +284,12 @@ module ol_framework::mock { validator_universe::test_register_validator(root, &pk, &pop, &sig, 100, true, true); vouch::init(&sig); - vouch::test_set_buddies(*val, val_addr); + if (with_vouches) { + 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] 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 bfc57be74..abf96b859 100644 --- a/framework/libra-framework/sources/ol_sources/proof_of_fee.move +++ b/framework/libra-framework/sources/ol_sources/proof_of_fee.move @@ -12,16 +12,16 @@ module ol_framework::proof_of_fee { use std::vector; use std::math64; use std::fixed_point32; - use diem_framework::validator_universe; - use diem_framework::transaction_fee; - use diem_framework::account; use diem_framework::stake; + use diem_framework::account; + use diem_framework::transaction_fee; use diem_framework::system_addresses; + use diem_framework::validator_universe; use ol_framework::jail; - use ol_framework::slow_wallet; use ol_framework::vouch; - use ol_framework::epoch_helper; use ol_framework::globals; + use ol_framework::slow_wallet; + use ol_framework::epoch_helper; use ol_framework::address_utils; //use diem_std::debug::print; @@ -32,9 +32,9 @@ module ol_framework::proof_of_fee { #[test_only] friend ol_framework::mock; + //////// CONST //////// /// The nominal reward for each validator in each epoch. - const GENESIS_BASELINE_REWARD: u64 = 1000000; - + const GENESIS_BASELINE_REWARD: u64 = 1_000_000; /// Number of vals needed before PoF becomes competitive for /// performant nodes as well const VAL_BOOT_UP_THRESHOLD: u64 = 21; @@ -51,6 +51,8 @@ module ol_framework::proof_of_fee { const SHORT_WINDOW: u64 = 5; // 5 epochs /// Long window period for extended bid trends. const LONG_WINDOW: u64 = 10; // 10 epochs + /// Margin for vouches + const VOUCH_MARGIN: u64 = 2; //////// ERRORS ///////// /// Not an active validator @@ -302,12 +304,34 @@ module ol_framework::proof_of_fee { public fun get_valid_vouchers_in_set(incoming_addr: address): (bool, u64) { let val_set = stake::get_current_validators(); let (frens_in_val_set, _found) = vouch::true_friends_in_list(incoming_addr, &val_set); - let threshold = globals::get_validator_vouch_threshold(); + let threshold = calculate_min_vouches_required(vector::length(&val_set)); let count_in_set = vector::length(&frens_in_val_set); (count_in_set >= threshold, count_in_set) } + #[view] + /// calculate the minimum vouches required for a validator to be seated + /// @params set_size the size of the validator set + /// @returns the minimum vouches required + public fun calculate_min_vouches_required(set_size: u64): u64 { + let required = globals::get_validator_vouch_threshold(); + + // TODO: set a features switch here + //if (false) { + if (set_size > VAL_BOOT_UP_THRESHOLD) { + // dynamically increase the amount of social proofing as the + // validator set increases + required = math64::min( + (set_size / 10) + 1, // formula to get the min vouches required after bootup + globals::get_max_vouches_per_validator() - VOUCH_MARGIN + ); + }; + //}; + + required + } + // Here we place the bidders into their seats. // The order of the bids will determine placement. 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 26a849121..f1aa0fe21 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 @@ -10,9 +10,10 @@ module ol_framework::test_pof { use ol_framework::testnet; use ol_framework::globals; use diem_framework::stake; + use diem_framework::chain_id; use std::vector; - // use diem_std::debug::print; + //use diem_std::debug::print; const Alice: address = @0x1000a; const Bob: address = @0x1000b; @@ -649,4 +650,84 @@ module ol_framework::test_pof { assert!(did_increment == false, 7357050); assert!(amount == nominal_reward / 10, 7357051); } + // Tests for fun calculate_min_vouches_required + + #[test(root = @ol_framework)] + fun test_calculate_min_vouches_required_equal_to_threshold(root: signer) { + chain_id::initialize_for_test(&root, 1); + let set_size = 21; // Equal to VAL_BOOT_UP_THRESHOLD + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 2, 7357001); // Expecting the global threshold value + } + + #[test(root = @ol_framework)] + fun test_calculate_min_vouches_required_boundary_over_bootup(root: signer) { + chain_id::initialize_for_test(&root, 1); + + let set_size = 22; // Boundary condition at 1 over boot-up + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 3, 7357007); // Expecting dynamic calculation result + } + + #[test(root = @ol_framework)] + fun test_calculate_min_vouches_required_boundary_below_bootup(root: signer) { + chain_id::initialize_for_test(&root, 1); + + let set_size = 20; // Boundary condition at 1 below boot-up + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 2, 7357008); // Expecting the global threshold value + } + + #[test(root = @ol_framework)] + fun test_calculate_min_vouches_required_above_threshold(root: signer) { + chain_id::initialize_for_test(&root, 1); + + let set_size = 29; + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 3, 7357010); // Expecting dynamic calculation result + + let set_size = 30; + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 4, 7357011); // Expecting dynamic calculation result + + let set_size = 31; + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 4, 7357012); // Expecting dynamic calculation result + } + + #[test(root = @ol_framework)] + fun test_calculate_min_vouches_required_min_set_size(root: signer) { + chain_id::initialize_for_test(&root, 1); + + let set_size = 0; // Smallest possible set size + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 2, 7357003); // Expecting the global threshold value + } + + #[test(root = @ol_framework)] + fun test_calculate_min_vouches_required_max_set_size(root: signer) { + chain_id::initialize_for_test(&root, 1); + + let set_size = 10_000; // Very large set size + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 8, 7357004); // Expecting max vouches minus margin + } + + #[test(root = @ol_framework)] + fun test_calculate_min_vouches_required_exact_match(root: signer) { + chain_id::initialize_for_test(&root, 1); + + let set_size = 78; // Exact calculation match case + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 8, 7357005); // Expecting max vouches minus margin + } + + #[test(root = @ol_framework)] + fun test_calculate_min_vouches_required_edge_vouch_margin(root: signer) { + chain_id::initialize_for_test(&root, 1); + + let set_size = 80; // Edge case at vouch margin + let min_vouches = proof_of_fee::calculate_min_vouches_required(set_size); + assert!(min_vouches == 8, 7357006); // Expecting max vouches minus margin + } } diff --git a/framework/libra-framework/sources/ol_sources/tests/vouch.test.move b/framework/libra-framework/sources/ol_sources/tests/vouch.test.move new file mode 100644 index 000000000..17a1acde8 --- /dev/null +++ b/framework/libra-framework/sources/ol_sources/tests/vouch.test.move @@ -0,0 +1,336 @@ +#[test_only] +module ol_framework::test_vouch { + use std::vector; + use ol_framework::vouch; + use ol_framework::mock; + use ol_framework::proof_of_fee; + + // use diem_std::debug::print; + + // Happy Day scenarios + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, carol = @0x1000c)] + fun vouch_for_unrelated(root: &signer, alice: &signer, carol: &signer) { + // create vals without vouches + mock::create_vals(root, 3, false); + vouch::set_vouch_price(root, 0); + + // check that no vouches exist + vector::for_each(vector[@0x1000a, @0x1000b, @0x1000c], |addr| { + let (given_vouches, given_epochs) = vouch::get_given_vouches(addr); + assert!(given_vouches == vector::empty(), 73570001); + assert!(given_epochs == vector::empty(), 73570002); + let (received_vouches, received_epochs) = vouch::get_received_vouches(addr); + assert!(received_vouches == vector::empty(), 73570003); + assert!(received_epochs == vector::empty(), 73570004); + }); + + // alice vouches for bob + vouch::vouch_for(alice, @0x1000b); + + // check alice + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000a); + assert!(given_vouches == vector[@0x1000b], 73570005); + assert!(given_epochs == vector[0], 73570006); + + // check bob + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000b); + assert!(received_vouches == vector[@0x1000a], 73570007); + assert!(received_epochs == vector[0], 73570008); + + // fast forward to epoch 1 + mock::trigger_epoch(root); + + // carol vouches for bob + vouch::vouch_for(carol, @0x1000b); + + // check alice + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000a); + assert!(given_vouches == vector[@0x1000b], 73570005); + assert!(given_epochs == vector[0], 73570006); + + // check carol + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000c); + assert!(given_vouches == vector[@0x1000b], 73570009); + assert!(given_epochs == vector[1], 73570010); + + // check bob + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000b); + assert!(received_vouches == vector[@0x1000a, @0x1000c], 73570011); + assert!(received_epochs == vector[0, 1], 73570012); + } + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, carol = @0x1000c)] + fun revoke_vouch(root: &signer, alice: &signer, carol: &signer) { + // create vals without vouches + mock::create_vals(root, 3, false); + vouch::set_vouch_price(root, 0); + + // alice and carol vouches for bob + vouch::vouch_for(alice, @0x1000b); + vouch::vouch_for(carol, @0x1000b); + + // alice revokes vouch for bob + vouch::revoke(alice, @0x1000b); + + // check alice + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000a); + assert!(given_vouches == vector::empty(), 73570013); + assert!(given_epochs == vector::empty(), 73570014); + + // check bob + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000b); + assert!(received_vouches == vector[@0x1000c], 73570015); + assert!(received_epochs == vector[0], 73570016); + } + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, carol = @0x1000c)] + fun epoch_boundary_update_vouch_price(root: &signer) { + // check default vouch micro-price + assert!(vouch::get_vouch_price() == 1_000, 73570025); + + // initialize vals and vouch price + mock::genesis_n_vals(root, 3); + vouch::set_vouch_price(root, 0); + assert!(vouch::get_vouch_price() == 0, 73570025); + + // mock to increase reward on the next epoch + proof_of_fee::test_mock_reward(root, 5_000, 1_000, 10, vector[10, 10, 10, 10, 10, 10]); + let (reward, _, _, _ ) = proof_of_fee::get_consensus_reward(); + assert!(reward == 5_000, 73570026); + + mock::trigger_epoch(root); + + // check new vouch price + let (reward, _, _, _ ) = proof_of_fee::get_consensus_reward(); + assert!(reward == 5_250, 73570027); + assert!(vouch::get_vouch_price() == 5_250, 73570026); + + mock::trigger_epoch(root); + + // check new vouch price + let (reward, _, _, _ ) = proof_of_fee::get_consensus_reward(); + assert!(reward == 5_512, 73570027); + assert!(vouch::get_vouch_price() == 5_512, 73570026); + + // mock to descrease reward on the next epoch + proof_of_fee::test_mock_reward(root, 10_000, 9_600, 960, vector[960, 960, 960, 960, 960, 960]); + + mock::trigger_epoch(root); + + // check new vouch price + let (reward, _, _, _ ) = proof_of_fee::get_consensus_reward(); + assert!(reward == 9_500, 73570026); + assert!(vouch::get_vouch_price() == 9_500, 73570027); + } + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b)] + fun update_vouch(root: &signer, alice: &signer) { + // create vals without vouches + mock::create_vals(root, 2, false); + vouch::set_vouch_price(root, 0); + + // alice vouches for bob + vouch::vouch_for(alice, @0x1000b); + + // check alice + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000a); + assert!(given_vouches == vector[@0x1000b], 73570005); + assert!(given_epochs == vector[0], 73570006); + + // check bob + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000b); + assert!(received_vouches == vector[@0x1000a], 73570007); + assert!(received_epochs == vector[0], 73570008); + + // fast forward to epoch 1 + mock::trigger_epoch(root); + + // alice vouches for bob again + vouch::vouch_for(alice, @0x1000b); + + // check alice + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000a); + assert!(given_vouches == vector[@0x1000b], 73570005); + assert!(given_epochs == vector[1], 73570006); + + // check bob + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000b); + assert!(received_vouches == vector[@0x1000a], 73570007); + assert!(received_epochs == vector[1], 73570008); + } + + // Sad Day scenarios + + #[test(root = @ol_framework, alice = @0x1000a)] + #[expected_failure(abort_code = 0x10001, location = ol_framework::vouch)] + fun vouch_for_self(root: &signer, alice: &signer) { + // create vals without vouches + mock::create_vals(root, 1, false); + vouch::set_vouch_price(root, 0); + + // alice vouches for herself + vouch::vouch_for(alice, @0x1000a); + } + + #[test(root = @ol_framework, alice = @0x1000a)] + #[expected_failure(abort_code = 0x10001, location = ol_framework::vouch)] + fun revoke_self_vouch(root: &signer, alice: &signer) { + // create vals without vouches + mock::create_vals(root, 1, false); + + // alice try to revoke herself + vouch::revoke(alice, @0x1000a); + } + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b)] + #[expected_failure(abort_code = 0x10005, location = ol_framework::vouch)] + fun revoke_not_vouched(root: &signer, alice: &signer) { + // create vals without vouches + mock::create_vals(root, 2, false); + + // alice vouches for bob + vouch::revoke(alice, @0x1000b); + } + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, v1 = @0x10001, v2 = @0x10002, v3 = @0x10003, v4 = @0x10004, v5 = @0x10005, v6 = @0x10006, v7 = @0x10007, v8 = @0x10008, v9 = @0x10009, v10 = @0x10010)] + #[expected_failure(abort_code = 0x30004, location = ol_framework::vouch)] + fun vouch_over_max(root: &signer, alice: &signer, v1: &signer, v2: &signer, v3: &signer, v4: &signer, v5: &signer, v6: &signer, v7: &signer, v8: &signer, v9: &signer, v10: &signer) { + // create vals without vouches + mock::create_vals(root, 2, false); + vouch::set_vouch_price(root, 0); + + // init vouch for 10 validators + vouch::init(v1); + vouch::init(v2); + vouch::init(v3); + vouch::init(v4); + vouch::init(v5); + vouch::init(v6); + vouch::init(v7); + vouch::init(v8); + vouch::init(v9); + vouch::init(v10); + + // alice vouches for 10 validators + vouch::insist_vouch_for(alice, @0x10001); + vouch::insist_vouch_for(alice, @0x10002); + vouch::insist_vouch_for(alice, @0x10003); + vouch::insist_vouch_for(alice, @0x10004); + vouch::insist_vouch_for(alice, @0x10005); + vouch::insist_vouch_for(alice, @0x10006); + vouch::insist_vouch_for(alice, @0x10007); + vouch::insist_vouch_for(alice, @0x10008); + vouch::insist_vouch_for(alice, @0x10009); + vouch::insist_vouch_for(alice, @0x10010); + + // alice try to vouch for one more + vouch::insist_vouch_for(alice, @0x1000b); + + // check alice + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000a); + assert!(given_vouches == vector[@0x10001, @0x10002, @0x10003, @0x10004, @0x10005, @0x10006, @0x10007, @0x10008, @0x10009, @0x10010], 73570017); + assert!(given_epochs == vector[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 73570018); + + // check bob + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000b); + assert!(received_vouches == vector::empty(), 73570019); + assert!(received_epochs == vector::empty(), 73570020); + } + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b)] + #[expected_failure(abort_code = 0x30006, location = ol_framework::ol_account)] + fun vouch_without_coins(root: &signer, alice: &signer) { + // create vals without vouches + mock::create_vals(root, 2, false); + vouch::set_vouch_price(root, 9_999); + + // alice vouches for bob without coins + vouch::vouch_for(alice, @0x1000b); + + // check alice + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000a); + assert!(given_vouches == vector::empty(), 73570021); + assert!(given_epochs == vector::empty(), 73570022); + + // check bob + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000b); + assert!(received_vouches == vector::empty(), 73570023); + assert!(received_epochs == vector::empty(), 73570024); + } + + #[test(root = @ol_framework, alice = @0x1000a)] + #[expected_failure(abort_code = 0x30006, location = ol_framework::vouch)] + fun vouch_without_init(alice: &signer) { + // alice vouches for bob without init + vouch::vouch_for(alice, @0x1000b); + } + + #[test(root = @ol_framework, alice = @0x1000a)] + #[expected_failure(abort_code = 0x30002, location = ol_framework::vouch)] + fun vouch_for_account_not_init(root: &signer, alice: &signer) { + mock::create_vals(root, 1, false); + + // alice vouches for bob without init + vouch::vouch_for(alice, @0x1000b); + } + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b)] + #[expected_failure(abort_code = 0x30006, location = ol_framework::vouch)] + fun revoke_without_init(alice: &signer) { + // alice try to revoke bob without vouch + vouch::revoke(alice, @0x1000b); + } + + #[test(root = @ol_framework, alice = @0x1000a)] + #[expected_failure(abort_code = 0x10005, location = ol_framework::vouch)] + fun revoke_account_not_vouched(root: &signer, alice: &signer) { + mock::create_vals(root, 2, false); + + // alice try to revoke bob without vouch + vouch::revoke(alice, @0x1000b); + } + + + #[test(root = @ol_framework, alice = @0x1000a)] + fun true_friends_not_init() { + // alice try to get true friends in list without init + let result = vouch::true_friends(@0x1000a); + + assert!(result == vector::empty(), 73570028); + } + + #[test(root = @ol_framework, alice = @0x1000a)] + fun true_friends_in_list_not_init() { + // alice try to get true friends in list without init + let (list, size) = vouch::true_friends_in_list(@0x1000a, &vector::singleton(@0x1000b)); + + assert!(list == vector::empty(), 73570028); + assert!(size == 0, 73570029); + } + + #[test(root = @ol_framework, alice = @0x1000a)] + fun get_received_vouches_not_init() { + // alice try to get received vouches without init + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000a); + + assert!(received_vouches == vector::empty(), 73570030); + assert!(received_epochs == vector::empty(), 73570031); + } + + #[test(root = @ol_framework, alice = @0x1000a)] + #[expected_failure(abort_code = 0x30003, location = ol_framework::vouch)] + fun get_given_vouches_not_init() { + // alice try to get given vouches without init + vouch::get_given_vouches(@0x1000a); + } + + #[test(root = @ol_framework, alice = @0x1000a)] + fun get_all_vouchers_not_init() { + // alice try to get all vouchers without init + let all_vouchers = vouch::all_vouchers(@0x1000a); + + assert!(all_vouchers == vector::empty(), 73570034); + } + +} diff --git a/framework/libra-framework/sources/ol_sources/tests/vouch_migration.test.move b/framework/libra-framework/sources/ol_sources/tests/vouch_migration.test.move new file mode 100644 index 000000000..bf5e864d1 --- /dev/null +++ b/framework/libra-framework/sources/ol_sources/tests/vouch_migration.test.move @@ -0,0 +1,166 @@ +/* + + This test is to ensure that the migration of vouches + from legacy to new validators is working as expected. + + 1. Legacy data: + - alice vouches for bob + - carol vouches for bob + - alice vouches for carol + 2. New data: + - dave vouches for eve + - eve vouches for frank + + trigger epoch to execute migration + - check migrated vals vouches + + */ + +#[test_only] +module ol_framework::test_vouch_migration { + use std::vector; + use ol_framework::vouch; + use ol_framework::mock; + use ol_framework::migrations; + + // use diem_std::debug::print; + + #[test(root = @ol_framework, alice = @0x1000a, bob = @0x1000b, carol = @0x1000c, dave = @0x1000d, eve = @0x1000e, frank = @0x1000f)] + fun migrate_given_and_received_vouches(root: &signer, alice: &signer, bob: &signer, carol: &signer, dave: &signer, eve: &signer) { + vouch::set_vouch_price(root, 0); + + // init validators with legacy struct MyVouches + vouch::legacy_init(alice); + vouch::legacy_init(bob); + vouch::legacy_init(carol); + + // create validators + mock::create_vals(root, 6, false); + + // alice vouches for bob using legacy struct MyVouches + vouch::legacy_vouch_for(alice, @0x1000b); + + // carol vouches for bob using legacy struct MyVouches + vouch::legacy_vouch_for(carol, @0x1000b); + + // alice vouches for carol using legacy struct MyVouches + vouch::legacy_vouch_for(alice, @0x1000c); + + // check bob received vouches on legacy struct MyVouches + let (received_vouches, received_epochs) = vouch::get_legacy_vouches(@0x1000b); + assert!(received_vouches == vector[@0x1000a, @0x1000c], 73570004); + assert!(received_epochs == vector[0, 0], 73570005); + + // check legacy validators only have legacy struct MyVouches + assert!(vouch::is_legacy_init(@0x1000a), 73570001); + assert!(vouch::is_legacy_init(@0x1000b), 73570002); + assert!(vouch::is_legacy_init(@0x1000c), 73570003); + assert!(!vouch::is_init(@0x1000a), 73570001); + assert!(!vouch::is_init(@0x1000b), 73570002); + assert!(!vouch::is_init(@0x1000c), 73570003); + + // check new validators only have new structs initialized + assert!(vouch::is_init(@0x1000d), 73570006); + assert!(vouch::is_init(@0x1000e), 73570007); + assert!(!vouch::is_legacy_init(@0x1000d), 73570008); + assert!(!vouch::is_legacy_init(@0x1000e), 73570009); + + // check legacy vouches + let (received_vouches, received_epochs) = vouch::get_legacy_vouches(@0x1000a); + assert!(received_vouches == vector::empty(), 73570010); + assert!(received_epochs == vector::empty(), 73570011); + let (received_vouches, received_epochs) = vouch::get_legacy_vouches(@0x1000b); + assert!(received_vouches == vector[@0x1000a, @0x1000c], 73570012); + assert!(received_epochs == vector[0, 0], 73570013); + let (received_vouches, received_epochs) = vouch::get_legacy_vouches(@0x1000c); + assert!(received_vouches == vector[@0x1000a], 73570014); + assert!(received_epochs == vector[0], 73570015); + + // dave vouches for eve using new structs + vouch::vouch_for(dave, @0x1000e); + + // eve vouches for frank using new structs + vouch::vouch_for(eve, @0x1000f); + + // check eve received vouches on new struct Vouches + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000e); + assert!(received_vouches == vector[@0x1000d], 73570006); + assert!(received_epochs == vector[0], 73570007); + + // check dave given vouches on new struct Vouches + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000d); + assert!(given_vouches == vector[@0x1000e], 73570008); + assert!(given_epochs == vector[0], 73570009); + + // check last migration number + assert!(migrations::get_last_migration_number() == 0, 73570010); + + // It is show time!!! RUN MIGRATION + mock::trigger_epoch(root); + + // check last migration number + assert!(migrations::get_last_migration_number() == 1, 73570011); + + // check last migration data + let (number, epoch, description) = migrations::get_last_migrations_history(); + assert!(number == 1, 73570012); + assert!(epoch == 0, 73570013); + assert!(description == b"Vouch migration initializes GivenVouches, ReceivedVouches, and drop MyVouches", 73570014); + + // check structs initialized after migration + assert!(vouch::is_init(@0x1000a), 73570010); + assert!(vouch::is_init(@0x1000b), 73570011); + assert!(vouch::is_init(@0x1000c), 73570012); + assert!(!vouch::is_legacy_init(@0x1000a), 73570013); + assert!(!vouch::is_legacy_init(@0x1000b), 73570014); + assert!(!vouch::is_legacy_init(@0x1000c), 73570015); + + // check alice vouches migrated + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000a); + assert!(given_vouches == vector[@0x1000b, @0x1000c], 73570016); + assert!(given_epochs == vector[0, 0], 73570017); + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000a); + assert!(received_vouches == vector::empty(), 73570018); + assert!(received_epochs == vector::empty(), 73570019); + + // check bob vouches migrated + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000b); + assert!(given_vouches == vector::empty(), 73570020); + assert!(given_epochs == vector::empty(), 73570021); + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000b); + assert!(received_vouches == vector[@0x1000a, @0x1000c], 73570018); + assert!(received_epochs == vector[0, 0], 73570019); + + // check carol vouches migrated + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000c); + assert!(given_vouches == vector[@0x1000b], 73570020); + assert!(given_epochs == vector[0], 73570021); + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000c); + assert!(received_vouches == vector[@0x1000a], 73570022); + assert!(received_epochs == vector[0], 73570023); + + // check dave vouches are okay + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000d); + assert!(given_vouches == vector[@0x1000e], 73570024); + assert!(given_epochs == vector[0], 73570025); + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000d); + assert!(received_vouches == vector::empty(), 73570026); + assert!(received_epochs == vector::empty(), 73570027); + + // check eve vouches are okay + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000e); + assert!(received_vouches == vector[@0x1000d], 73570028); + assert!(received_epochs == vector[0], 73570029); + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000e); + assert!(given_vouches == vector[@0x1000f], 73570030); + assert!(given_epochs == vector[0], 73570031); + + // check frank vouches are okay + let (received_vouches, received_epochs) = vouch::get_received_vouches(@0x1000f); + assert!(received_vouches == vector[@0x1000e], 73570032); + assert!(received_epochs == vector[0], 73570033); + let (given_vouches, given_epochs) = vouch::get_given_vouches(@0x1000f); + assert!(given_vouches == vector::empty(), 73570034); + assert!(given_epochs == vector::empty(), 73570035); + } +} diff --git a/framework/libra-framework/sources/ol_sources/validator_universe.move b/framework/libra-framework/sources/ol_sources/validator_universe.move index 04ed4ec9d..bcb350f76 100644 --- a/framework/libra-framework/sources/ol_sources/validator_universe.move +++ b/framework/libra-framework/sources/ol_sources/validator_universe.move @@ -4,10 +4,10 @@ module diem_framework::validator_universe { use std::signer; use std::vector; - use diem_framework::system_addresses; use ol_framework::jail; use ol_framework::vouch; use diem_framework::stake; + use diem_framework::system_addresses; #[test_only] use ol_framework::testnet; diff --git a/framework/libra-framework/sources/ol_sources/vouch.move b/framework/libra-framework/sources/ol_sources/vouch.move index b198e75ba..1f7473db9 100644 --- a/framework/libra-framework/sources/ol_sources/vouch.move +++ b/framework/libra-framework/sources/ol_sources/vouch.move @@ -1,162 +1,384 @@ module ol_framework::vouch { + use std::error; use std::signer; use std::vector; + use std::string; use ol_framework::ancestry; use ol_framework::ol_account; use ol_framework::epoch_helper; - use diem_framework::account; use diem_framework::system_addresses; use diem_framework::transaction_fee; + use diem_std::debug::print; + friend diem_framework::genesis; - friend ol_framework::validator_universe; friend ol_framework::proof_of_fee; friend ol_framework::jail; + friend ol_framework::epoch_boundary; + friend ol_framework::vouch_migration; // TODO: remove after vouch migration is completed + friend ol_framework::validator_universe; #[test_only] friend ol_framework::mock; #[test_only] friend ol_framework::test_pof; + #[test_only] + friend ol_framework::test_vouch; + #[test_only] + friend ol_framework::test_vouch_migration; + + //////// CONST //////// - /// trying to vouch for yourself? + /// How many epochs must pass before the voucher expires. + const EXPIRATION_ELAPSED_EPOCHS: u64 = 45; + + /// Maximum number of vouches + const BASE_MAX_VOUCHES: u64 = 10; + + + //////// ERROR CODES //////// + + /// Trying to vouch for yourself? const ETRY_SELF_VOUCH_REALLY: u64 = 1; - /// how many epochs must pass before the voucher expires. - const EXPIRATION_ELAPSED_EPOCHS: u64 = 90; + /// User cannot receive vouch because their vouch state is not initialized + const ERECEIVER_NOT_INIT: u64 = 2; + + /// Cannot query vouches because the GivenVouches state is not initialized + const EGIVEN_VOUCHES_NOT_INIT: u64 = 3; - // triggered once per epoch - struct MyVouches has key { - my_buddies: vector
, + /// Limit reached. You cannot give any new vouches. + const EMAX_LIMIT_GIVEN: u64 = 4; + + /// Vouch not found + const EVOUCH_NOT_FOUND: u64 = 5; + + /// Grantor state is not initialized + const EGRANTOR_NOT_INIT: u64 = 6; + + + //////// STRUCTS //////// + + // TODO: someday this should be renamed to ReceivedVouches + struct MyVouches has key, drop { + my_buddies: vector
, // incoming vouches epoch_vouched: vector, } + struct ReceivedVouches has key { + incoming_vouches: vector
, + epoch_vouched: vector, + } + + struct GivenVouches has key { + outgoing_vouches: vector
, + epoch_vouched: vector, + } + + /// struct to store the price to vouch for someone + struct VouchPrice has key { + amount: u64 + } + + /* + Oh, I get by with a little help from my friends, + Mm, I get high with a little help from my friends, + Mm, gonna try with a little help from my friends. + */ + // init the struct on a validators account. - public(friend) fun init(new_account_sig: &signer ) { + public(friend) fun init(new_account_sig: &signer) { let acc = signer::address_of(new_account_sig); - if (!is_init(acc)) { - move_to(new_account_sig, MyVouches { - my_buddies: vector::empty(), - epoch_vouched: vector::empty(), - }); - } + if (exists(acc)) { + // let migration handle the initialization of new structs + return + }; + + if (!exists(acc)) { + move_to(new_account_sig, ReceivedVouches { + incoming_vouches: vector::empty(), + epoch_vouched: vector::empty(), + }); + }; + + if (!exists(acc)) { + move_to(new_account_sig, GivenVouches { + outgoing_vouches: vector::empty(), + epoch_vouched: vector::empty(), + }); + }; } - #[view] - public fun is_init(acc: address ):bool { - exists(acc) + public(friend) fun set_vouch_price(framework: &signer, amount: u64) acquires VouchPrice { + system_addresses::assert_ol(framework); + if (!exists(@ol_framework)) { + move_to(framework, VouchPrice { amount }); + } else { + let price = borrow_global_mut(@ol_framework); + price.amount = amount; + }; } - fun vouch_impl(ill_be_your_friend: &signer, wanna_be_my_friend: address) acquires MyVouches { - let buddy_acc = signer::address_of(ill_be_your_friend); - assert!(buddy_acc != wanna_be_my_friend, ETRY_SELF_VOUCH_REALLY); + fun vouch_impl(grantor: &signer, friend_account: address, check_unrelated: bool) acquires ReceivedVouches, GivenVouches, VouchPrice { + let grantor_acc = signer::address_of(grantor); + assert!(grantor_acc != friend_account, error::invalid_argument(ETRY_SELF_VOUCH_REALLY)); + + // check if structures are initialized + assert!(is_init(grantor_acc), error::invalid_state(EGRANTOR_NOT_INIT)); + assert!(is_init(friend_account), error::invalid_state(ERECEIVER_NOT_INIT)); + + if (check_unrelated) { + ancestry::assert_unrelated(grantor_acc, friend_account); + }; + + // check if the grantor has already reached the limit of vouches + let (given_vouches, _) = get_given_vouches(grantor_acc); + assert!( + vector::length(&given_vouches) < BASE_MAX_VOUCHES, + error::invalid_state(EMAX_LIMIT_GIVEN) + ); - if (!exists(wanna_be_my_friend)) return; - let epoch = epoch_helper::get_current_epoch(); // this fee is paid to the system, cannot be reclaimed - let c = ol_account::withdraw(ill_be_your_friend, vouch_cost_microlibra()); - transaction_fee::user_pay_fee(ill_be_your_friend, c); + let price = get_vouch_price(); + if (price > 0) { + let vouch_cost = ol_account::withdraw(grantor, price); + transaction_fee::user_pay_fee(grantor, vouch_cost); + }; - let v = borrow_global_mut(wanna_be_my_friend); + let epoch = epoch_helper::get_current_epoch(); - let (found, i) = vector::index_of(&v.my_buddies, &buddy_acc); - if (found) { // prevent duplicates - // update date - let e = vector::borrow_mut(&mut v.epoch_vouched, i); - *e = epoch; + // add friend to grantor given vouches + add_given_vouches(grantor_acc, friend_account, epoch); + + // add grantor to friend received vouches + add_received_vouches(friend_account, grantor_acc, epoch); + } + + // Function to add given vouches + public fun add_given_vouches(grantor_acc: address, vouched_account: address, epoch: u64) acquires GivenVouches { + let given_vouches = borrow_global_mut(grantor_acc); + let (found, i) = vector::index_of(&given_vouches.outgoing_vouches, &vouched_account); + + if (found) { + // Update the epoch if the vouched account is already present + let epoch_value = vector::borrow_mut(&mut given_vouches.epoch_vouched, i); + *epoch_value = epoch; } else { - vector::push_back(&mut v.my_buddies, buddy_acc); - vector::push_back(&mut v.epoch_vouched, epoch); + // Add new vouched account and epoch if not already present + vector::push_back(&mut given_vouches.outgoing_vouches, vouched_account); + vector::push_back(&mut given_vouches.epoch_vouched, epoch); + } + } + + // Function to add received vouches + public fun add_received_vouches(vouched_account: address, grantor_acc: address, epoch: u64) acquires ReceivedVouches { + let received_vouches = borrow_global_mut(vouched_account); + let (found, i) = vector::index_of(&received_vouches.incoming_vouches, &grantor_acc); + + if (found) { + // Update the epoch if the vouching account is already present + let epoch_value = vector::borrow_mut(&mut received_vouches.epoch_vouched, i); + *epoch_value = epoch; + } else { + // Add new vouching account and epoch if not already present + vector::push_back(&mut received_vouches.incoming_vouches, grantor_acc); + vector::push_back(&mut received_vouches.epoch_vouched, epoch); } } /// will only succesfully vouch if the two are not related by ancestry /// prevents spending a vouch that would not be counted. /// to add a vouch and ignore this check use insist_vouch - public entry fun vouch_for(grantor: &signer, wanna_be_my_friend: address) acquires MyVouches { - ancestry::assert_unrelated(signer::address_of(grantor), wanna_be_my_friend); - vouch_impl(grantor, wanna_be_my_friend); + public entry fun vouch_for(grantor: &signer, friend_account: address) acquires ReceivedVouches, GivenVouches, VouchPrice { + vouch_impl(grantor, friend_account, true); } /// you may want to add people who are related to you /// there are no known use cases for this at the moment. - public entry fun insist_vouch_for(grantor: &signer, wanna_be_my_friend: address) acquires MyVouches { - vouch_impl(grantor, wanna_be_my_friend); + public entry fun insist_vouch_for(grantor: &signer, friend_account: address) acquires ReceivedVouches, GivenVouches, VouchPrice { + vouch_impl(grantor, friend_account, false); } - public entry fun revoke(buddy: &signer, its_not_me_its_you: address) acquires MyVouches { - let buddy_acc = signer::address_of(buddy); - assert!(buddy_acc!=its_not_me_its_you, ETRY_SELF_VOUCH_REALLY); - - if (!exists(its_not_me_its_you)) return; - - let v = borrow_global_mut(its_not_me_its_you); - let (found, i) = vector::index_of(&v.my_buddies, &buddy_acc); - if (found) { - vector::remove(&mut v.my_buddies, i); - vector::remove(&mut v.epoch_vouched, i); - }; + public entry fun revoke(grantor: &signer, friend_account: address) acquires ReceivedVouches, GivenVouches { + let grantor_acc = signer::address_of(grantor); + assert!(grantor_acc != friend_account, error::invalid_argument(ETRY_SELF_VOUCH_REALLY)); + + assert!(is_init(grantor_acc), error::invalid_state(EGRANTOR_NOT_INIT)); + + // remove friend from grantor given vouches + let v = borrow_global_mut(grantor_acc); + let (found, i) = vector::index_of(&v.outgoing_vouches, &friend_account); + assert!(found, error::invalid_argument(EVOUCH_NOT_FOUND)); + vector::remove
(&mut v.outgoing_vouches, i); + vector::remove(&mut v.epoch_vouched, i); + + // remove grantor from friends received vouches + let v = borrow_global_mut(friend_account); + let (found, i) = vector::index_of(&v.incoming_vouches, &grantor_acc); + assert!(found, error::invalid_argument(EVOUCH_NOT_FOUND)); + vector::remove(&mut v.incoming_vouches, i); + vector::remove(&mut v.epoch_vouched, i); } - public(friend) fun vm_migrate(vm: &signer, val: address, buddy_list: vector
) acquires MyVouches { + public(friend) fun vm_migrate(vm: &signer, val: address, buddy_list: vector
) acquires ReceivedVouches { system_addresses::assert_ol(vm); bulk_set(val, buddy_list); } - fun bulk_set(val: address, buddy_list: vector
) acquires MyVouches { - - if (!exists(val)) return; - - let v = borrow_global_mut(val); + fun bulk_set(val: address, buddy_list: vector
) acquires ReceivedVouches { + if (!exists(val)) return; // take self out of list + let v = borrow_global_mut(val); let (is_found, i) = vector::index_of(&buddy_list, &val); - if (is_found) { vector::swap_remove
(&mut buddy_list, i); }; - - v.my_buddies = buddy_list; + v.incoming_vouches = buddy_list; let epoch_data: vector = vector::map_ref(&buddy_list, |_e| { 0u64 } ); v.epoch_vouched = epoch_data; } + // The struct GivenVouches cannot not be lazy initialized because + // vouch module cannot depend on validator_universe (circle dependency) + // TODO: this migration function must be removed after all validators have migrated + public(friend) fun migrate_given_vouches(account_sig: &signer, all_accounts: vector
) acquires MyVouches { + let account = signer::address_of(account_sig); + + if (exists(account)) { + // struct already initialized + return + }; + + let new_outgoing_vouches = vector::empty(); + let new_epoch_vouched = vector::empty(); + + vector::for_each(all_accounts, |val| { + if (val != account && exists(val)) { + let legacy_state = borrow_global(val); + let incoming_vouches = legacy_state.my_buddies; + let epoch_vouched = legacy_state.epoch_vouched; + let (found, i) = vector::index_of(&incoming_vouches, &account); + if (found) { + vector::push_back(&mut new_outgoing_vouches, val); + vector::push_back(&mut new_epoch_vouched, *vector::borrow(&epoch_vouched, i)); + } + } + }); + + // add the new GivenVouches struct with the new data + move_to(account_sig, GivenVouches { + outgoing_vouches: new_outgoing_vouches, + epoch_vouched: new_epoch_vouched, + }); + + print(&string::utf8(b">>> Migrated GivenVouches for ")); + print(&account); + } + + // TODO: remove/deprecate after migration is completed + public(friend) fun migrate_received_vouches(account_sig: &signer) acquires MyVouches { + let account = signer::address_of(account_sig); + + if (!exists(account)) { + // struct not initialized + return + }; + + if (exists(account)) { + // struct already initialized + return + }; + + let state = borrow_global(account); + let new_incoming_vouches = state.my_buddies; + let new_epoch_vouched = state.epoch_vouched; + + // add the new ReceivedVouches struct with the legacy data + move_to(account_sig, ReceivedVouches { + incoming_vouches: new_incoming_vouches, + epoch_vouched: new_epoch_vouched, + }); + + // remove deprecated MyVouches struct + move_from(account); + + print(&string::utf8(b">>> Migrated ReceivedVouches for ")); + print(&account); + } + + ///////// GETTERS ////////// + #[view] - /// gets all buddies, including expired ones - public fun all_vouchers(val: address): vector
acquires MyVouches{ + public fun is_init(acc: address ):bool { + exists(acc) && exists(acc) + } + + #[view] + public fun get_received_vouches(acc: address): (vector
, vector) acquires ReceivedVouches { + if (!exists(acc)) { + return (vector::empty(), vector::empty()) + }; - if (!exists(val)) return vector::empty
(); - let state = borrow_global(val); - *&state.my_buddies + let state = borrow_global(acc); + (*&state.incoming_vouches, *&state.epoch_vouched) } #[view] - /// gets the buddies and checks if they are expired - public fun all_not_expired(addr: address): vector
acquires MyVouches{ + public fun get_given_vouches(acc: address): (vector
, vector) acquires GivenVouches { + assert!(exists(acc), error::invalid_state(EGIVEN_VOUCHES_NOT_INIT)); + + let state = borrow_global(acc); + (*&state.outgoing_vouches, *&state.epoch_vouched) + } + + #[view] + public fun get_vouch_price(): u64 acquires VouchPrice { + if (!exists(@ol_framework)) { + return 1_000 // previous micro-libra cost + }; + + let price = borrow_global(@ol_framework); + price.amount + } + + #[view] + /// gets all buddies, including expired ones + public fun all_vouchers(val: address): vector
acquires ReceivedVouches { + let (incoming_vouches, _) = get_received_vouches(val); + incoming_vouches + } + + #[view] + /// gets the received vouches not expired + public fun all_not_expired(addr: address): vector
acquires ReceivedVouches { let valid_vouches = vector::empty
(); - if (is_init(addr)) { - let state = borrow_global(addr); - vector::for_each(state.my_buddies, |buddy_acc| { - // account might have dropped - if (account::exists_at(buddy_acc)){ - if (is_not_expired(buddy_acc, state)) { - vector::push_back(&mut valid_vouches, buddy_acc) - } - } - }) + let (all_received, epoch_vouched) = get_received_vouches(addr); + let current_epoch = epoch_helper::get_current_epoch(); + + let i = 0; + while (i < vector::length(&all_received)) { + let vouch_received = vector::borrow(&all_received, i); + let when_vouched = *vector::borrow(&epoch_vouched, i); + // check if the vouch is expired + if ((when_vouched + EXPIRATION_ELAPSED_EPOCHS) > current_epoch) { + vector::push_back(&mut valid_vouches, *vouch_received) + }; + i = i + 1; }; + valid_vouches } #[view] /// filter expired vouches, and do ancestry check - public fun true_friends(addr: address): vector
acquires MyVouches{ - - if (!exists(addr)) return vector::empty
(); + public fun true_friends(addr: address): vector
acquires ReceivedVouches { + if (!exists(addr)) return vector::empty
(); let not_expired = all_not_expired(addr); let filtered_ancestry = ancestry::list_unrelated(not_expired); filtered_ancestry @@ -165,33 +387,20 @@ module ol_framework::vouch { #[view] /// check if the user is in fact a valid voucher public fun is_valid_voucher_for(voucher: address, recipient: address):bool - acquires MyVouches { + acquires ReceivedVouches { let list = true_friends(recipient); vector::contains(&list, &voucher) } - - fun is_not_expired(voucher: address, state: &MyVouches): bool { - let (found, i) = vector::index_of(&state.my_buddies, &voucher); - if (found) { - let when_vouched = vector::borrow(&state.epoch_vouched, i); - return (*when_vouched + EXPIRATION_ELAPSED_EPOCHS) > epoch_helper::get_current_epoch() - }; - false - } - /// for a given list find and count any of my vouchers - public(friend) fun true_friends_in_list(addr: address, list: &vector
): (vector
, u64) acquires MyVouches { - - if (!exists(addr)) return (vector::empty(), 0); + public(friend) fun true_friends_in_list(addr: address, list: &vector
): (vector
, u64) acquires ReceivedVouches { + if (!exists(addr)) return (vector::empty(), 0); let tf = true_friends(addr); - let buddies_in_list = vector::empty(); - let i = 0; + let i = 0; while (i < vector::length(&tf)) { let addr = vector::borrow(&tf, i); - if (vector::contains(list, addr)) { vector::push_back(&mut buddies_in_list, *addr); }; @@ -201,15 +410,60 @@ module ol_framework::vouch { (buddies_in_list, vector::length(&buddies_in_list)) } + #[test_only] + public fun test_set_buddies(val: address, buddy_list: vector
) acquires ReceivedVouches { + bulk_set(val, buddy_list); + } - // TODO: move to globals - // the cost to verify a vouch. Coins are burned. - fun vouch_cost_microlibra(): u64 { - 1000 + #[test_only] + public fun legacy_init(new_account_sig: &signer) { + let acc = signer::address_of(new_account_sig); + + if (!exists(acc)) { + move_to(new_account_sig, MyVouches { + my_buddies: vector::empty(), + epoch_vouched: vector::empty(), + }); + }; } #[test_only] - public fun test_set_buddies(val: address, buddy_list: vector
) acquires MyVouches { - bulk_set(val, buddy_list); + public fun legacy_vouch_for(ill_be_your_friend: &signer, wanna_be_my_friend: address) acquires MyVouches { + let buddy_acc = signer::address_of(ill_be_your_friend); + assert!(buddy_acc != wanna_be_my_friend, ETRY_SELF_VOUCH_REALLY); + + if (!exists(wanna_be_my_friend)) return; + let epoch = epoch_helper::get_current_epoch(); + + // this fee is paid to the system, cannot be reclaimed + // let c = ol_account::withdraw(ill_be_your_friend, vouch_cost_microlibra()); + // transaction_fee::user_pay_fee(ill_be_your_friend, c); + + let v = borrow_global_mut(wanna_be_my_friend); + + let (found, i) = vector::index_of(&v.my_buddies, &buddy_acc); + if (found) { // prevent duplicates + // update date + let e = vector::borrow_mut(&mut v.epoch_vouched, i); + *e = epoch; + } else { + vector::push_back(&mut v.my_buddies, buddy_acc); + vector::push_back(&mut v.epoch_vouched, epoch); + } + } + + #[test_only] + public fun get_legacy_vouches(acc: address): (vector
, vector) acquires MyVouches { + if (!exists(acc)) { + return (vector::empty(), vector::empty()) + }; + + let state = borrow_global(acc); + (*&state.my_buddies, *&state.epoch_vouched) + } + + #[test_only] + public fun is_legacy_init(acc: address): bool { + exists(acc) } } diff --git a/framework/libra-framework/sources/ol_sources/vouch_migration.move b/framework/libra-framework/sources/ol_sources/vouch_migration.move new file mode 100644 index 000000000..6a7d255a0 --- /dev/null +++ b/framework/libra-framework/sources/ol_sources/vouch_migration.move @@ -0,0 +1,33 @@ + +module ol_framework::vouch_migration { + use std::vector; + use diem_framework::validator_universe; + use diem_framework::create_signer::create_signer; + use ol_framework::vouch; + + // use diem_std::debug::print; + + #[test_only] + friend ol_framework::test_vouch_migration; + + friend ol_framework::migrations; + + public(friend) fun migrate_my_vouches() { + let all_vals = validator_universe::get_eligible_validators(); + vector::for_each(all_vals, |val| { + // We create the signer for the val here since this is required + // to add the GivenVouches resource. + let val_signer = &create_signer(val); // <<< DANGER + + vouch::migrate_given_vouches(val_signer, all_vals); + }); + + vector::for_each(all_vals, |val| { + // We create the signer for the val here since this is required + // to add the ReceivedVouches resource and drop MyVouches resource. + let val_signer = &create_signer(val); // <<< DANGER + + vouch::migrate_received_vouches(val_signer); + }); + } +} diff --git a/framework/releases/head.mrb b/framework/releases/head.mrb index 0dd805654..630a66998 100644 Binary files a/framework/releases/head.mrb and b/framework/releases/head.mrb differ diff --git a/tools/txs/src/txs_cli_vals.rs b/tools/txs/src/txs_cli_vals.rs index 4606ac2c0..6dbd3eef5 100644 --- a/tools/txs/src/txs_cli_vals.rs +++ b/tools/txs/src/txs_cli_vals.rs @@ -98,11 +98,11 @@ impl ValidatorTxs { } => { if *revoke { VouchRevoke { - its_not_me_its_you: *vouch_acct, + friend_account: *vouch_acct, } } else { VouchVouchFor { - wanna_be_my_friend: *vouch_acct, + friend_account: *vouch_acct, } } }