Skip to content

Commit

Permalink
[portal] Implement gas airdrop and add testsuit to gas market (#2695)
Browse files Browse the repository at this point in the history
* [cli] Make balance output use symbol as key

* [rpc] Make the router log to debug

* [portal] Implement gas airdrop

* [portal] Add testsuit to gas market and gas airdrop

* fixup

* [gas airdrop] Record the claim records
  • Loading branch information
jolestar authored Sep 27, 2024
1 parent 3026658 commit 848e07c
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 30 deletions.
4 changes: 2 additions & 2 deletions crates/rooch-rpc-server/src/axum_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ async fn process_request(req: Request<'_>, call: CallData<'_>) -> MethodResponse
let id = req.id;

tracing::event!(
tracing::Level::INFO,
tracing::Level::DEBUG,
event = "on_call",
method = name_str,
params = params_str,
Expand Down Expand Up @@ -221,7 +221,7 @@ async fn process_request(req: Request<'_>, call: CallData<'_>) -> MethodResponse
};

tracing::event!(
tracing::Level::INFO,
tracing::Level::DEBUG,
event = "on_result",
method = name_str,
result = response.as_result(),
Expand Down
17 changes: 12 additions & 5 deletions crates/rooch/src/commands/account/commands/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,22 @@ impl CommandAction<Option<BalancesView>> for BalanceCommand {
match balance_info {
BalanceInfoViewUnion::Bitcoin(bitcoin_balance) => {
balances_view.insert(
bitcoin_balance.coin_info.name.to_string(),
bitcoin_balance.coin_info.symbol.to_string(),
bitcoin_balance.into(),
);
}
BalanceInfoViewUnion::Other(other_balance) => {
balances_view.insert(
other_balance.coin_info.coin_type.to_string(),
other_balance.into(),
);
if balances_view.contains_key(&other_balance.coin_info.symbol) {
balances_view.insert(
other_balance.coin_info.coin_type.to_string(),
other_balance.into(),
);
} else {
balances_view.insert(
other_balance.coin_info.symbol.to_string(),
other_balance.into(),
);
}
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions crates/rooch/src/commands/move_cli/commands/unit_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ impl CommandAction<Option<Value>> for TestCommand {

let resolution_graph = build_config
.clone()
.resolution_graph_for_package(&root_path, &mut Vec::new())
.expect("resolve package dep failed");
.resolution_graph_for_package(&root_path, &mut Vec::new())?;

let mut additional_named_address = BTreeMap::new();
let _: Vec<_> = resolution_graph
Expand Down
2 changes: 1 addition & 1 deletion crates/testsuite/features/multisign.feature
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Feature: Rooch CLI multisign integration tests
Then sleep: "10" # wait for the transaction to be confirmed

Then cmd: "account balance -a {{$.account[-2].account0.address}} --json"
Then assert: "{{$.account[-1].Bitcoin.balance}} == 100000000"
Then assert: "{{$.account[-1].BTC.balance}} == 100000000"

#transfer some gas to multisign account
Then cmd: "account transfer --to {{$.account[-2].multisign_address}} --amount 10000000000 --coin-type rooch_framework::gas_coin::RGas"
Expand Down
86 changes: 86 additions & 0 deletions crates/testsuite/features/portal.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
Feature: Rooch Portal contract tests


@serial
Scenario: gas_market
Given a bitcoind server for gas_market
Given a server for gas_market

Then cmd: "account create --json"
Then cmd: "account create --json"
Then cmd: "account list --json"

# mint utxos
Then cmd bitcoin-cli: "generatetoaddress 101 {{$.account[2].account0.bitcoin_address}}"
Then sleep: "10"

Then cmd: "move run --function rooch_framework::gas_coin::faucet_entry --args u256:1000000000000000 --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"

# publish gas_market via default address
Then cmd: "move publish -p ../../infra/rooch-portal-v2/contract/gas_market --named-addresses gas_market=default --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"

Then cmd: "event get-events-by-event-handle -t default::trusted_oracle::NewOracleEvent"

# submit the oracle price, require at least two record
Then cmd: "move run --function default::trusted_oracle::submit_data --args object:{{$.event[-1].data[0].decoded_event_data.value.oracle_id}} --args string:BTCUSD --args u256:5805106000000 --args u8:8 --args string:1 --args object:{{$.event[-1].data[0].decoded_event_data.value.admin_id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"
Then cmd: "move run --function default::trusted_oracle::submit_data --args object:{{$.event[-1].data[0].decoded_event_data.value.oracle_id}} --args string:BTCUSD --args u256:5805106000000 --args u8:8 --args string:1 --args object:{{$.event[-1].data[0].decoded_event_data.value.admin_id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"

Then cmd: "move run --function default::trusted_oracle::submit_data --args object:{{$.event[-1].data[1].decoded_event_data.value.oracle_id}} --args string:BTCUSD --args u256:5805106000000 --args u8:8 --args string:2 --args object:{{$.event[-1].data[1].decoded_event_data.value.admin_id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"
Then cmd: "move run --function default::trusted_oracle::submit_data --args object:{{$.event[-1].data[1].decoded_event_data.value.oracle_id}} --args string:BTCUSD --args u256:5805106000000 --args u8:8 --args string:2 --args object:{{$.event[-1].data[1].decoded_event_data.value.admin_id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"

Then cmd: "move run --function default::trusted_oracle::submit_data --args object:{{$.event[-1].data[2].decoded_event_data.value.oracle_id}} --args string:BTCUSD --args u256:5805106000000 --args u8:8 --args string:3 --args object:{{$.event[-1].data[2].decoded_event_data.value.admin_id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"
Then cmd: "move run --function default::trusted_oracle::submit_data --args object:{{$.event[-1].data[2].decoded_event_data.value.oracle_id}} --args string:BTCUSD --args u256:5805106000000 --args u8:8 --args string:3 --args object:{{$.event[-1].data[2].decoded_event_data.value.admin_id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"

# ensure the account0 RGas balance is 0
Then cmd: "account balance -a {{$.account[2].account0.address}} --coin-type 0x3::gas_coin::RGas --json"
Then assert: "{{$.account[-1].RGAS.balance}} == 0"

# transfer 0.01 BTC to the gas market address
Then cmd: "bitcoin transfer -s {{$.account[2].account0.bitcoin_address}} -t {{$.account[2].default.bitcoin_address}} -a 1000000"
Then cmd bitcoin-cli: "generatetoaddress 1 {{$.account[2].account0.bitcoin_address}}"
Then sleep: "10"

Then cmd: "object -t default::gas_market::RGasMarket"
# consume the utxo event
Then cmd: "move run --function default::gas_market::consume_event --args object:{{$.object[-1].data[0].id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"

# check the RGas balance of account0
Then cmd: "account balance -a {{$.account[2].account0.address}} --coin-type 0x3::gas_coin::RGas --json"
Then assert: "{{$.account[-1].RGAS.balance}} != 0"

# ensure the account1 RGas balance is 0
Then cmd: "account balance -a {{$.account[2].account1.address}} --coin-type 0x3::gas_coin::RGas --json"
Then assert: "{{$.account[-1].RGAS.balance}} == 0"

# transfer 2 utxo to account1
Then cmd: "bitcoin transfer -s {{$.account[2].account0.bitcoin_address}} -t {{$.account[2].account1.bitcoin_address}} -a 1000000"
Then cmd: "bitcoin transfer -s {{$.account[2].account0.bitcoin_address}} -t {{$.account[2].account1.bitcoin_address}} -a 1000000"
Then cmd bitcoin-cli: "generatetoaddress 1 {{$.account[2].account0.bitcoin_address}}"
Then sleep: "10"

Then cmd: "object -t default::gas_airdrop::RGasAirdrop"
Then cmd: "object -o {{$.account[2].account1.bitcoin_address}} -t 0x4::utxo::UTXO"

# the default address help the account1 to claim the airdrop
Then cmd: "move run --function default::gas_airdrop::claim --args object:{{$.object[-2].data[0].id}} --args address:{{$.account[2].account1.address}} --args vector<object_id>:{{$.object[-1].data[0].id}},{{$.object[-1].data[1].id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} == executed"

# check the RGas balance of account0
Then cmd: "account balance -a {{$.account[2].account1.address}} --coin-type 0x3::gas_coin::RGas --json"
Then assert: "{{$.account[-1].RGAS.balance}} != 0"

# try claim again
Then cmd: "move run --function default::gas_airdrop::claim --args object:{{$.object[-2].data[0].id}} --args address:{{$.account[2].account1.address}} --args vector<object_id>:{{$.object[-1].data[0].id}},{{$.object[-1].data[1].id}} --json"
Then assert: "{{$.move[-1].execution_info.status.type}} != executed"



3 changes: 3 additions & 0 deletions infra/rooch-portal-v2/contract/gas_market/Move.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ std = "0x1"
moveos_std = "0x2"
rooch_framework = "0x3"
bitcoin_move = "0x4"

[dev-addresses]
gas_market = "0x42"
139 changes: 139 additions & 0 deletions infra/rooch-portal-v2/contract/gas_market/sources/gas_airdrop.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
module gas_market::gas_airdrop {

use std::vector;

use moveos_std::signer;
use moveos_std::object::{Self, Object, ObjectID, to_shared};
use moveos_std::tx_context::{sender};
use moveos_std::table::{Self, Table};

use rooch_framework::coin_store::CoinStore;
use rooch_framework::gas_coin::RGas;
use rooch_framework::coin_store;
use rooch_framework::account_coin_store;

use bitcoin_move::utxo::{Self, UTXO};

use gas_market::gas_market::AdminCap;

const INIT_GAS_AMOUNT: u256 = 5000000_00000000;
const ONE_RGAS: u256 = 1_00000000;

//0.01 BTC
const SAT_LEVEL_ONE: u64 = 1000000;
//0.1 BTC
const SAT_LEVEL_TWO: u64 = 10000000;

const ErrorAirdropNotOpen: u64 = 0;
const ErrorInvalidUTXO: u64 = 1;
const ErrorAirdropNotEnoughRGas: u64 = 2;
const ErrorAlreadyClaimed: u64 = 3;
const ErrorUTXOValueIsZero: u64 = 4;


struct RGasAirdrop has key{
rgas_store: Object<CoinStore<RGas>>,
claim_records: Table<address, u256>,
is_open: bool
}


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 airdrop_gas_amount = if(rgas_balance > INIT_GAS_AMOUNT) {
INIT_GAS_AMOUNT
} else {
rgas_balance/3
};
Self::deposit_to_rgas_store(sender, &mut rgas_store, airdrop_gas_amount);
let rgas_airdrop_obj = object::new_named_object(RGasAirdrop{
rgas_store,
claim_records: table::new(),
is_open: true
});
to_shared(rgas_airdrop_obj)
}

/// Anyone can call this function to help the claimer claim the airdrop
public entry fun claim(airdrop_obj: &mut Object<RGasAirdrop>, claimer: address, utxo_ids: vector<ObjectID>){
let airdrop = object::borrow_mut(airdrop_obj);
assert!(!table::contains(&airdrop.claim_records, claimer), ErrorAlreadyClaimed);

let total_sat_amount = Self::total_sat_amount(claimer, utxo_ids);
assert!(total_sat_amount > 0, ErrorUTXOValueIsZero);
let claim_rgas_amount = Self::sat_amount_to_rgas(total_sat_amount);
let remaining_rgas_amount = coin_store::balance(&airdrop.rgas_store);
assert!(claim_rgas_amount <= remaining_rgas_amount, ErrorAirdropNotEnoughRGas);
let rgas_coin = coin_store::withdraw(&mut airdrop.rgas_store, claim_rgas_amount);
account_coin_store::deposit<RGas>(claimer, rgas_coin);
table::add(&mut airdrop.claim_records, claimer, claim_rgas_amount);
}

public entry fun deposit_rgas_coin(
account: &signer,
rgas_airdrop_obj: &mut Object<RGasAirdrop>,
amount: u256
){
let rgas_airdrop = object::borrow_mut(rgas_airdrop_obj);
deposit_to_rgas_store(account, &mut rgas_airdrop.rgas_store, amount);
}

public entry fun withdraw_rgas_coin(
_admin: &mut Object<AdminCap>,
rgas_airdrop_obj: &mut Object<RGasAirdrop>,
amount: u256
){
let rgas_airdrop = object::borrow_mut(rgas_airdrop_obj);
let rgas_coin = coin_store::withdraw(&mut rgas_airdrop.rgas_store, amount);
account_coin_store::deposit<RGas>(sender(), rgas_coin);
}

public entry fun close_airdrop(
_admin: &mut Object<AdminCap>,
rgas_airdrop_obj: &mut Object<RGasAirdrop>
){
let rgas_airdrop = object::borrow_mut(rgas_airdrop_obj);
rgas_airdrop.is_open = false;
}


fun deposit_to_rgas_store(
account: &signer,
rgas_store: &mut Object<CoinStore<RGas>>,
amount: u256
){
let rgas_coin = account_coin_store::withdraw<RGas>(account, amount);
coin_store::deposit(rgas_store, rgas_coin);
}

/// A view function to get the amount of RGas that can be claimed
public fun get_claimable_rgas(claimer: address, utxo_ids: vector<ObjectID>): u256 {
let total_sat_amount = Self::total_sat_amount(claimer, utxo_ids);
Self::sat_amount_to_rgas(total_sat_amount)
}

fun sat_amount_to_rgas(sat_amount: u64): u256{
if (sat_amount == 0){
0
}else if(sat_amount <= SAT_LEVEL_ONE){
ONE_RGAS
}else if(sat_amount <= SAT_LEVEL_TWO){
2 * ONE_RGAS
}else{
3 * ONE_RGAS
}
}

fun total_sat_amount(claim_address: address, utxo_ids: vector<ObjectID>): u64{
let total_sat_amount = 0;
vector::for_each(utxo_ids, |utxo_id| {
let utxo_obj = object::borrow_object<UTXO>(utxo_id);
assert!(object::owner(utxo_obj) == claim_address, ErrorInvalidUTXO);
total_sat_amount = total_sat_amount + utxo::value(object::borrow(utxo_obj));
});
total_sat_amount
}

}
Loading

0 comments on commit 848e07c

Please sign in to comment.