Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add invitation for gas faucet #3008

Merged
merged 18 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions apps/grow_bitcoin/sources/grow_information.move
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ module grow_bitcoin::grow_information_v3 {
assert!(object::borrow(grow_project_list_obj).is_open, ErrorVoteNotOpen);
let grow_project = borrow_mut_grow_project(grow_project_list_obj, id);
coin_store::deposit(&mut grow_project.vote_store, coin);
let vote_detail = table::borrow_mut_with_default(&mut grow_project.vote_detail, sender(), 0);
let vote_detail = table::borrow_mut_with_default(&mut grow_project.vote_detail, address_of(account), 0);
*vote_detail = *vote_detail + coin_value;

grow_project.vote_value = coin_store::balance(&grow_project.vote_store);
Expand All @@ -134,8 +134,8 @@ module grow_bitcoin::grow_information_v3 {
value: coin_value,
timestamp: now_milliseconds()
});
let point_box = mint_point_box(grow_project.id, coin_value, sender());
object::transfer(point_box, sender());
let point_box = mint_point_box(grow_project.id, coin_value, address_of(account));
object::transfer(point_box, address_of(account));
}

public fun get_vote(grow_project_list_obj: &Object<GrowProjectList>, user: address, id: String): u256 {
Expand Down
6 changes: 6 additions & 0 deletions apps/invitation_record/Move.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ twitter_binding = "_"
std = "0x1"
moveos_std = "0x2"
rooch_framework = "0x3"

[dev-addresses]
invitation_record = "0x41"
app_admin = "0x42"
gas_faucet = "0x43"
twitter_binding = "0x44"
173 changes: 152 additions & 21 deletions apps/invitation_record/sources/invitation.move
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ module invitation_record::invitation {
use std::option;
use std::string::String;
use std::vector;
use moveos_std::signer;
use moveos_std::consensus_codec;
use twitter_binding::tweet_v2;
use rooch_framework::bitcoin_address::BitcoinAddress;
use rooch_framework::ecdsa_k1;
use moveos_std::hash;
use rooch_framework::transaction;
use rooch_framework::transaction::TransactionSequenceInfo;
Expand All @@ -14,7 +19,7 @@ module invitation_record::invitation {
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 twitter_binding::twitter_account::{verify_and_binding_twitter_account, check_binding_tweet, check_user_claimed};
use moveos_std::tx_context::sender;
use rooch_framework::account_coin_store;
use rooch_framework::gas_coin::RGas;
Expand All @@ -26,6 +31,9 @@ module invitation_record::invitation {
use app_admin::admin::AdminCap;
use moveos_std::object::{Object, to_shared, ObjectID};
use moveos_std::table::Table;
#[test_only]
use std::string;

#[test_only]
use bitcoin_move::utxo;
#[test_only]
Expand All @@ -41,19 +49,34 @@ module invitation_record::invitation {
const ErrorFaucetNotOpen: u64 = 1;
const ErrorFaucetNotEnoughRGas: u64 = 2;
const ErrorNoRemainingLuckeyTicket: u64 = 3;
const ErrorNoInvitationSignature: u64 = 4;
const ErrorNoInvitationBitcoinSignature: u64 = 5;
const ErrorNotClaimerAddress: u64 = 6;
const ErrorCannotInviteOneself: u64 = 7;
const ErrorInvalidSignature: u64 = 8;

const ONE_RGAS: u256 = 1_00000000;
const INIT_GAS_AMOUNT: u256 = 1000000_00000000;
const ErrorInvalidArg: u64 = 0;

const MessagePrefix : vector<u8> = b"Bitcoin Signed Message:\n";


struct UserInvitationRecords has key, store {
invitation_records: Table<address, u256>,
invitation_records: TableVec<InvitationRecordInfo>,
lottery_records: TableVec<LotteryInfo>,
total_invitations: u64,
remaining_luckey_ticket: u64,
invitation_reward_amount: u256,
lottery_reward_amount: u256,
}

struct InvitationRecordInfo has store {
timestamp: u64,
address: address,
reward_amount: u256,
}

struct LotteryInfo has store {
timestamp: u64,
reward_amount: u256,
Expand All @@ -66,9 +89,18 @@ module invitation_record::invitation {
unit_invitation_amount: u256,
}

fun init() {
fun init(sender: &signer) {
let sender_addr = signer::address_of(sender);
let rgas_store = coin_store::create_coin_store<RGas>();
let rgas_balance = account_coin_store::balance<RGas>(sender_addr);
let market_gas_amount = if (rgas_balance > INIT_GAS_AMOUNT) {
INIT_GAS_AMOUNT
} else {
rgas_balance / 3
};
deposit_to_rgas_store(sender, &mut rgas_store, market_gas_amount);
let invitation_obj = object::new_named_object(InvitationConf{
rgas_store: coin_store::create_coin_store<RGas>(),
rgas_store,
invitation_records: table::new(),
is_open: true,
unit_invitation_amount: ONE_RGAS * 5
Expand Down Expand Up @@ -96,12 +128,30 @@ module invitation_record::invitation {
}

/// Anyone can call this function to help the claimer claim the faucet
public entry fun claim_from_faucet(faucet_obj: &mut Object<RGasFaucet>, invitation_obj: &mut Object<InvitationConf>, claimer: address, utxo_ids: vector<ObjectID>, inviter: address){
public entry fun claim_from_faucet(
faucet_obj: &mut Object<RGasFaucet>,
invitation_obj: &mut Object<InvitationConf>,
claimer_bitcoin_address: String,
utxo_ids: vector<ObjectID>,
inviter: address,
public_key: vector<u8>,
signature: vector<u8>,
message: vector<u8>,
){
let bitcoin_address = bitcoin_address::from_string(&claimer_bitcoin_address);
let full_message = encode_full_message(MessagePrefix, message);
verify_btc_signature(bitcoin_address, public_key, signature, full_message);
let claimer = bitcoin_address::to_rooch_address(&bitcoin_address);
assert!(inviter != claimer, ErrorCannotInviteOneself);
let invitation_conf = object::borrow_mut(invitation_obj);
assert!(invitation_conf.is_open, ErrorFaucetNotOpen);
if (inviter == @rooch_framework){
claim(faucet_obj, claimer, utxo_ids);
return
};
if (!table::contains(&invitation_conf.invitation_records, inviter)) {
table::add(&mut invitation_conf.invitation_records, inviter, UserInvitationRecords{
invitation_records: table::new(),
invitation_records: table_vec::new(),
lottery_records: table_vec::new(),
total_invitations: 0u64,
remaining_luckey_ticket: 0u64,
Expand All @@ -110,8 +160,11 @@ module invitation_record::invitation {
})
};
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;
table_vec::push_back(&mut user_invitation_records.invitation_records, InvitationRecordInfo{
reward_amount: invitation_conf.unit_invitation_amount,
address: claimer,
timestamp: now_seconds()
});
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;
Expand All @@ -121,14 +174,31 @@ module invitation_record::invitation {
claim(faucet_obj, claimer, utxo_ids);
}

public entry fun claim_from_twitter(tweet_id: String, invitation_obj: &mut Object<InvitationConf>, inviter: address){
public entry fun claim_from_twitter(
tweet_id: String,
invitation_obj: &mut Object<InvitationConf>,
inviter: address,
public_key: vector<u8>,
signature: vector<u8>,
message: vector<u8>,
){
let bitcoin_address = check_binding_tweet(tweet_id);
let claimer = bitcoin_address::to_rooch_address(&bitcoin_address);
assert!(inviter != claimer, ErrorCannotInviteOneself);
let full_message = encode_full_message(MessagePrefix, message);
verify_btc_signature(bitcoin_address, public_key, signature, full_message);
let invitation_conf = object::borrow_mut(invitation_obj);
assert!(invitation_conf.is_open, ErrorFaucetNotOpen);
let tweet_obj = tweet_v2::borrow_tweet_object(tweet_id);
let tweet = object::borrow(tweet_obj);
let author_id = *tweet_v2::tweet_author_id(tweet);
if (inviter == @rooch_framework || check_user_claimed(author_id)){
verify_and_binding_twitter_account(tweet_id);
return
};
if (!table::contains(&invitation_conf.invitation_records, inviter)) {
table::add(&mut invitation_conf.invitation_records, inviter, UserInvitationRecords{
invitation_records: table::new(),
invitation_records: table_vec::new(),
lottery_records: table_vec::new(),
total_invitations: 0u64,
remaining_luckey_ticket: 0u64,
Expand All @@ -137,8 +207,11 @@ module invitation_record::invitation {
})
};
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;
table_vec::push_back(&mut user_invitation_records.invitation_records, InvitationRecordInfo{
reward_amount: invitation_conf.unit_invitation_amount,
address: claimer,
timestamp: now_seconds()
});
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;
Expand Down Expand Up @@ -169,6 +242,12 @@ module invitation_record::invitation {
}
}

public fun invitation_user_record(invitation_obj: &Object<InvitationConf>, account: address): &UserInvitationRecords{
let invitation_conf = object::borrow(invitation_obj);
table::borrow(&invitation_conf.invitation_records, account)
}


public entry fun close_invitation(
invitation_obj: &mut Object<InvitationConf>,
_admin: &mut Object<AdminCap>,
Expand Down Expand Up @@ -203,6 +282,54 @@ module invitation_record::invitation {
coin_store::deposit(rgas_store, rgas_coin);
}

fun encode_full_message(message_prefix: vector<u8>, message_info: vector<u8>): vector<u8> {
encode_full_message_consensus(message_prefix, message_info)
}

fun starts_with(haystack: &vector<u8>, needle: &vector<u8>): bool {
let haystack_len = vector::length(haystack);
let needle_len = vector::length(needle);

if (needle_len > haystack_len) {
return false
};

let i = 0;
while (i < needle_len) {
if (vector::borrow(haystack, i) != vector::borrow(needle, i)) {
return false
};
i = i + 1;
};

true
}

fun encode_full_message_consensus(message_prefix: vector<u8>, message_info: vector<u8>): vector<u8> {

let encoder = consensus_codec::encoder();
consensus_codec::emit_var_slice(&mut encoder, message_prefix);
consensus_codec::emit_var_slice(&mut encoder, message_info);
consensus_codec::unpack_encoder(encoder)
}

public fun verify_btc_signature(bitcoin_address: BitcoinAddress, public_key: vector<u8>, signature: vector<u8>, message: vector<u8>) {
let message_hash = hash::sha2_256(message);
mx819812523 marked this conversation as resolved.
Show resolved Hide resolved
assert!(
ecdsa_k1::verify(
&signature,
&public_key,
&message_hash,
ecdsa_k1::sha256()
),
ErrorNoInvitationSignature
);
assert!(
bitcoin_address::verify_bitcoin_address_with_public_key(&bitcoin_address, &public_key),
ErrorNoInvitationBitcoinSignature
);
}

fun seed(index: u64): vector<u8> {
// get sequence number
let sequence_number = tx_context::sequence_number();
Expand Down Expand Up @@ -255,11 +382,11 @@ module invitation_record::invitation {
}


#[test(sender=@0x42)]
#[test(sender=@0xf0919849a42aa204673b15e586614963649a634851589dfbfde326816bed4161)]
fun test_claim_with_invitation(sender: &signer){
bitcoin_move::genesis::init_for_test();
create_account_for_testing(@0x42);
create_account_for_testing(@0x43);
create_account_for_testing(@0xf0919849a42aa204673b15e586614963649a634851589dfbfde326816bed4161);
create_account_for_testing(@0x7efa53965d5cdd8c3a6f69e4001a6920a53d427a8b4b99de1d1ceb8bd2e0dc5d);
let invitation_obj = object::new_named_object(InvitationConf{
invitation_records: table::new(),
is_open: true,
Expand All @@ -276,13 +403,17 @@ module invitation_record::invitation {
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 signature = x"5a1a4923742b43c73db01430fb0bea005eb54e9d764dbada3f00155981827ab076355636bbd89920ae12b50d91acfd8d5b31e078785afd3fd23928def8b53e41";
let message = b"hello, rooch";
let pk = x"02645681b3197f99f8763bccb34fab611778bf61806c2bd2fd8f335e87ed8c23fd";
let claimer_address= string::utf8(b"bc1pewcwnlshuxedpfywzk9vztnvpj54zmd0a29ydseygtuu7kfjcm9qjngxn0");
utxo::transfer_for_testing(test_utxo, bitcoin_address::to_rooch_address(&bitcoin_address::from_string(&claimer_address)));
claim_from_faucet(faucet_obj, invitation_obj, claimer_address, vector[test_utxo_id], @0x7efa53965d5cdd8c3a6f69e4001a6920a53d427a8b4b99de1d1ceb8bd2e0dc5d, pk, signature, message);
let invitation_obj = object::borrow_mut_object_shared<InvitationConf>(object::named_object_id<InvitationConf>());
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);
let invitation = object::borrow(invitation_obj);
let records = table::borrow(&invitation.invitation_records, @0x7efa53965d5cdd8c3a6f69e4001a6920a53d427a8b4b99de1d1ceb8bd2e0dc5d);
let invitation_user_record = table_vec::borrow(&records.invitation_records, 0);
assert!(invitation_user_record.reward_amount == 500000000, 1);
assert!(records.invitation_reward_amount == 500000000, 2);
assert!(records.total_invitations == 1, 3);
}
Expand Down
5 changes: 5 additions & 0 deletions apps/twitter_binding/sources/twitter_account.move
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ module twitter_binding::twitter_account {
};
}

public fun check_user_claimed(author_id: String): bool{
let faucet = borrow_twitter_rgas_faucet();
return table::contains(&faucet.claim_records, author_id)
}

public entry fun unbinding_twitter_account(owner: &signer){
let user_rooch_address = signer::address_of(owner);
unbinding_twitter_account_internal(user_rooch_address);
Expand Down
1 change: 1 addition & 0 deletions crates/rooch-faucet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ moveos-types = { workspace = true }
rooch-types = { workspace = true }
rooch-rpc-client = { workspace = true }
rooch-rpc-api = { workspace = true }
hex = "0.4.3"

Loading
Loading