diff --git a/apps/gas_faucet/sources/gas_faucet.move b/apps/gas_faucet/sources/gas_faucet.move index 4a72d892b2..ce51b0faf1 100644 --- a/apps/gas_faucet/sources/gas_faucet.move +++ b/apps/gas_faucet/sources/gas_faucet.move @@ -65,8 +65,9 @@ module gas_faucet::gas_faucet { to_shared(faucet_obj) } + /// Anyone can call this function to help the claimer claim the faucet - public entry fun claim(faucet_obj: &mut Object, claimer: address, utxo_ids: vector){ + public entry fun claim(faucet_obj: &mut Object, claimer: address, utxo_ids: vector){ let claim_rgas_amount = Self::check_claim(faucet_obj, claimer, utxo_ids); let faucet = object::borrow_mut(faucet_obj); let rgas_coin = coin_store::withdraw(&mut faucet.rgas_store, claim_rgas_amount); @@ -75,6 +76,9 @@ module gas_faucet::gas_faucet { *total_claim_amount = *total_claim_amount + claim_rgas_amount; } + + + public entry fun deposit_rgas_coin( account: &signer, faucet_obj: &mut Object, @@ -150,6 +154,8 @@ module gas_faucet::gas_faucet { faucet.is_open = true; } + + public entry fun set_allow_repeat( faucet_obj: &mut Object, allow_repeat: bool, @@ -190,4 +196,10 @@ module gas_faucet::gas_faucet { total_sat_amount } + #[test_only] + /// init the genesis context for test + public fun init_for_test(sender: &signer){ + init(sender) + } + } diff --git a/apps/grow_bitcoin/sources/grow_information.move b/apps/grow_bitcoin/sources/grow_information.move index bbc13308fa..07eab3c9c7 100644 --- a/apps/grow_bitcoin/sources/grow_information.move +++ b/apps/grow_bitcoin/sources/grow_information.move @@ -138,6 +138,11 @@ module grow_bitcoin::grow_information_v3 { object::transfer(point_box, sender()); } + public fun get_vote(grow_project_list_obj: &Object, user: address, id: String): u256 { + let grow_project = borrow_grow_project(grow_project_list_obj, id); + *table::borrow(&grow_project.vote_detail, user) + } + public entry fun open_vote(grow_project_list_obj: &mut Object, _admin: &mut Object){ object::borrow_mut(grow_project_list_obj).is_open = true diff --git a/apps/invitation_record/Move.toml b/apps/invitation_record/Move.toml new file mode 100644 index 0000000000..74022aaf1c --- /dev/null +++ b/apps/invitation_record/Move.toml @@ -0,0 +1,20 @@ +[package] +name = "invitation_record" +version = "0.0.1" + +[dependencies] +MoveStdlib = { local = "../../frameworks/move-stdlib" } +MoveosStdlib = { local = "../../frameworks/moveos-stdlib" } +RoochFramework = { local = "../../frameworks/rooch-framework" } +app_admin = { local = "../../apps/app_admin" } +gas_faucet = {local = "../../apps/gas_faucet"} +twitter_binding = {local = "../../apps/twitter_binding"} + +[addresses] +invitation_record = "_" +gas_faucet = "_" +app_admin = "_" +twitter_binding = "_" +std = "0x1" +moveos_std = "0x2" +rooch_framework = "0x3" diff --git a/apps/invitation_record/sources/invitation.move b/apps/invitation_record/sources/invitation.move new file mode 100644 index 0000000000..bbbcb7976c --- /dev/null +++ b/apps/invitation_record/sources/invitation.move @@ -0,0 +1,289 @@ +module invitation_record::invitation { + + use std::option; + use std::string::String; + use std::vector; + use moveos_std::hash; + use rooch_framework::transaction; + use rooch_framework::transaction::TransactionSequenceInfo; + use moveos_std::timestamp; + use moveos_std::bcs; + use moveos_std::tx_context; + use rooch_framework::simple_rng::{rand_u64_range, bytes_to_u64}; + use moveos_std::timestamp::now_seconds; + use moveos_std::table_vec; + use moveos_std::table_vec::TableVec; + use rooch_framework::bitcoin_address; + use twitter_binding::twitter_account::{verify_and_binding_twitter_account, check_binding_tweet}; + use moveos_std::tx_context::sender; + use rooch_framework::account_coin_store; + use rooch_framework::gas_coin::RGas; + use rooch_framework::coin_store::CoinStore; + use rooch_framework::coin_store; + use gas_faucet::gas_faucet::{RGasFaucet, claim}; + use moveos_std::table; + use moveos_std::object; + use app_admin::admin::AdminCap; + use moveos_std::object::{Object, to_shared, ObjectID}; + use moveos_std::table::Table; + #[test_only] + use bitcoin_move::utxo; + #[test_only] + use gas_faucet::gas_faucet; + #[test_only] + use moveos_std::signer::address_of; + #[test_only] + use rooch_framework::account::create_account_for_testing; + #[test_only] + use rooch_framework::gas_coin::faucet_for_test; + + + const ErrorFaucetNotOpen: u64 = 1; + const ErrorFaucetNotEnoughRGas: u64 = 2; + const ErrorNoRemainingLuckeyTicket: u64 = 3; + + const ONE_RGAS: u256 = 1_00000000; + const ErrorInvalidArg: u64 = 0; + + struct UserInvitationRecords has key, store { + invitation_records: Table, + lottery_records: TableVec, + total_invitations: u64, + remaining_luckey_ticket: u64, + invitation_reward_amount: u256, + lottery_reward_amount: u256, + } + + struct LotteryInfo has store { + timestamp: u64, + reward_amount: u256, + } + + struct InvitationConf has key { + rgas_store: Object>, + invitation_records: Table, + is_open: bool, + unit_invitation_amount: u256, + } + + fun init() { + let invitation_obj = object::new_named_object(InvitationConf{ + rgas_store: coin_store::create_coin_store(), + invitation_records: table::new(), + is_open: true, + unit_invitation_amount: ONE_RGAS * 5 + }); + to_shared(invitation_obj) + } + + public entry fun deposit_rgas_coin( + account: &signer, + faucet_obj: &mut Object, + amount: u256 + ){ + let faucet = object::borrow_mut(faucet_obj); + deposit_to_rgas_store(account, &mut faucet.rgas_store, amount); + } + + public entry fun withdraw_rgas_coin( + faucet_obj: &mut Object, + amount: u256, + _admin: &mut Object, + ){ + let faucet = object::borrow_mut(faucet_obj); + let rgas_coin = coin_store::withdraw(&mut faucet.rgas_store, amount); + account_coin_store::deposit(sender(), rgas_coin); + } + + /// Anyone can call this function to help the claimer claim the faucet + public entry fun claim_from_faucet(faucet_obj: &mut Object, invitation_obj: &mut Object, claimer: address, utxo_ids: vector, inviter: address){ + let invitation_conf = object::borrow_mut(invitation_obj); + assert!(invitation_conf.is_open, ErrorFaucetNotOpen); + if (!table::contains(&invitation_conf.invitation_records, inviter)) { + table::add(&mut invitation_conf.invitation_records, inviter, UserInvitationRecords{ + invitation_records: table::new(), + lottery_records: table_vec::new(), + total_invitations: 0u64, + remaining_luckey_ticket: 0u64, + invitation_reward_amount: 0u256, + lottery_reward_amount: 0u256, + }) + }; + let user_invitation_records = table::borrow_mut(&mut invitation_conf.invitation_records, inviter); + let invitation_amount = table::borrow_mut_with_default(&mut user_invitation_records.invitation_records, claimer, 0u256); + *invitation_amount = *invitation_amount + invitation_conf.unit_invitation_amount; + user_invitation_records.total_invitations = user_invitation_records.total_invitations + 1u64; + user_invitation_records.invitation_reward_amount = user_invitation_records.invitation_reward_amount + invitation_conf.unit_invitation_amount; + user_invitation_records.remaining_luckey_ticket = user_invitation_records.remaining_luckey_ticket + 1u64; + let rgas_coin = coin_store::withdraw(&mut invitation_conf.rgas_store, invitation_conf.unit_invitation_amount); + account_coin_store::deposit(inviter, rgas_coin); + + claim(faucet_obj, claimer, utxo_ids); + } + + public entry fun claim_from_twitter(tweet_id: String, invitation_obj: &mut Object, inviter: address){ + let bitcoin_address = check_binding_tweet(tweet_id); + let claimer = bitcoin_address::to_rooch_address(&bitcoin_address); + let invitation_conf = object::borrow_mut(invitation_obj); + assert!(invitation_conf.is_open, ErrorFaucetNotOpen); + if (!table::contains(&invitation_conf.invitation_records, inviter)) { + table::add(&mut invitation_conf.invitation_records, inviter, UserInvitationRecords{ + invitation_records: table::new(), + lottery_records: table_vec::new(), + total_invitations: 0u64, + remaining_luckey_ticket: 0u64, + invitation_reward_amount: 0u256, + lottery_reward_amount: 0u256, + }) + }; + let user_invitation_records = table::borrow_mut(&mut invitation_conf.invitation_records, inviter); + let invitation_amount = table::borrow_mut_with_default(&mut user_invitation_records.invitation_records, claimer, 0u256); + *invitation_amount = *invitation_amount + invitation_conf.unit_invitation_amount; + user_invitation_records.total_invitations = user_invitation_records.total_invitations + 1u64; + user_invitation_records.invitation_reward_amount = user_invitation_records.invitation_reward_amount + invitation_conf.unit_invitation_amount; + user_invitation_records.remaining_luckey_ticket = user_invitation_records.remaining_luckey_ticket + 1u64; + let rgas_coin = coin_store::withdraw(&mut invitation_conf.rgas_store, invitation_conf.unit_invitation_amount); + account_coin_store::deposit(inviter, rgas_coin); + verify_and_binding_twitter_account(tweet_id); + + } + + public entry fun lottery(invitation_obj: &mut Object, amount: u64){ + let invitation_conf = object::borrow_mut(invitation_obj); + let user_invitation_records = table::borrow_mut(&mut invitation_conf.invitation_records, sender()); + assert!(user_invitation_records.remaining_luckey_ticket >= amount, ErrorNoRemainingLuckeyTicket); + while (amount > 0) { + let reward_amount = rand_u64_range(10_000_000, 100_000_000, amount); + if (reward_amount % 150 == 0) { + reward_amount = reward_amount * 1000 + }; + let rgas_coin = coin_store::withdraw(&mut invitation_conf.rgas_store, (reward_amount as u256)); + account_coin_store::deposit(sender(), rgas_coin); + table_vec::push_back(&mut user_invitation_records.lottery_records, LotteryInfo { + timestamp: now_seconds(), + reward_amount: (reward_amount as u256), + }); + user_invitation_records.remaining_luckey_ticket = user_invitation_records.remaining_luckey_ticket - 1u64; + user_invitation_records.lottery_reward_amount = user_invitation_records.lottery_reward_amount + (reward_amount as u256); + amount = amount - 1u64; + } + } + + public entry fun close_invitation( + invitation_obj: &mut Object, + _admin: &mut Object, + ){ + let invitation = object::borrow_mut(invitation_obj); + invitation.is_open = false; + } + + public entry fun open_invitation( + invitation_obj: &mut Object, + _admin: &mut Object, + ) { + let invitation = object::borrow_mut(invitation_obj); + invitation.is_open = true; + } + + public entry fun set_invitation_unit_amount( + invitation_obj: &mut Object, + unit_invitation_amount: u256, + _admin: &mut Object, + ) { + let invitation = object::borrow_mut(invitation_obj); + invitation.unit_invitation_amount = unit_invitation_amount; + } + + fun deposit_to_rgas_store( + account: &signer, + rgas_store: &mut Object>, + amount: u256 + ){ + let rgas_coin = account_coin_store::withdraw(account, amount); + coin_store::deposit(rgas_store, rgas_coin); + } + + fun seed(index: u64): vector { + // get sequence number + let sequence_number = tx_context::sequence_number(); + let sequence_number_bytes = bcs::to_bytes(&sequence_number); + + // get sender address + let sender_addr = tx_context::sender(); + let sender_addr_bytes = bcs::to_bytes(&sender_addr); + + // get now milliseconds timestamp + let timestamp_ms = timestamp::now_milliseconds(); + let timestamp_ms_bytes = bcs::to_bytes(×tamp_ms); + + let index_bytes = bcs::to_bytes(&index); + // construct a seed + let seed_bytes = vector::empty(); + + // get the tx accumulator root if exists + let tx_sequence_info_opt = tx_context::get_attribute(); + if (option::is_some(&tx_sequence_info_opt)) { + let tx_sequence_info = option::extract(&mut tx_sequence_info_opt); + let tx_accumulator_root = transaction::tx_accumulator_root(&tx_sequence_info); + let tx_accumulator_root_bytes = bcs::to_bytes(&tx_accumulator_root); + vector::append(&mut seed_bytes, tx_accumulator_root_bytes); + } else { + // if it doesn't exist, get the tx hash + let tx_hash = tx_context::tx_hash(); + let tx_hash_bytes = bcs::to_bytes(&tx_hash); + vector::append(&mut seed_bytes, tx_hash_bytes); + }; + + vector::append(&mut seed_bytes, timestamp_ms_bytes); + vector::append(&mut seed_bytes, sender_addr_bytes); + vector::append(&mut seed_bytes, sequence_number_bytes); + vector::append(&mut seed_bytes, index_bytes); + // hash seed bytes and return a seed + let seed = hash::sha3_256(seed_bytes); + seed + } + + fun rand_u64_range(low: u64, high: u64, index: u64): u64 { + assert!(high > low, ErrorInvalidArg); + let value = rand_u64(index); + (value % (high - low)) + low + } + + fun rand_u64(index: u64): u64 { + let seed_bytes = seed(index); + bytes_to_u64(seed_bytes) + } + + + #[test(sender=@0x42)] + fun test_claim_with_invitation(sender: &signer){ + bitcoin_move::genesis::init_for_test(); + create_account_for_testing(@0x42); + create_account_for_testing(@0x43); + let invitation_obj = object::new_named_object(InvitationConf{ + invitation_records: table::new(), + is_open: true, + unit_invitation_amount: ONE_RGAS * 5, + rgas_store: coin_store::create_coin_store(), + }); + faucet_for_test(address_of(sender), 5000000_00000000); + gas_faucet::init_for_test(sender); + deposit_rgas_coin(sender, &mut invitation_obj,500000_00000000); + object::to_shared(invitation_obj); + let faucet_obj = object::borrow_mut_object_shared(object::named_object_id()); + let invitation_obj = object::borrow_mut_object_shared(object::named_object_id()); + let tx_id = @0x77dfc2fe598419b00641c296181a96cf16943697f573480b023b77cce82ada21; + let sat_value = 100000000; + let test_utxo = utxo::new_for_testing(tx_id, 0u32, sat_value); + let test_utxo_id = object::id(&test_utxo); + utxo::transfer_for_testing(test_utxo, @0x43); + claim_from_faucet(faucet_obj, invitation_obj, @0x43, vector[test_utxo_id], @0x42); + let invitation_obj = object::borrow_mut_object_shared(object::named_object_id()); + let invitation = object::borrow(invitation_obj); + let records = table::borrow(&invitation.invitation_records, @0x42); + let invitation_user_record = table::borrow(&records.invitation_records, @0x43); + assert!(invitation_user_record == &500000000, 1); + assert!(records.invitation_reward_amount == 500000000, 2); + assert!(records.total_invitations == 1, 3); + } +} diff --git a/frameworks/bitcoin-move/sources/utxo.move b/frameworks/bitcoin-move/sources/utxo.move index 6149353f4f..6869c9e342 100644 --- a/frameworks/bitcoin-move/sources/utxo.move +++ b/frameworks/bitcoin-move/sources/utxo.move @@ -12,6 +12,8 @@ module bitcoin_move::utxo{ use moveos_std::address::to_string; use bitcoin_move::types::{Self, OutPoint}; use bitcoin_move::temp_state; + #[test_only] + use std::option::none; friend bitcoin_move::genesis; friend bitcoin_move::ord; @@ -370,6 +372,11 @@ module bitcoin_move::utxo{ drop(utxo); } + #[test_only] + public fun transfer_for_testing(utxo_obj: Object, receiver: address){ + transfer(utxo_obj, none(), receiver) + } + #[test] fun test_id(){ let txid = @0x77dfc2fe598419b00641c296181a96cf16943697f573480b023b77cce82ada21;