Skip to content

Commit

Permalink
Add airdrop (#63)
Browse files Browse the repository at this point in the history
* feat(airdrop): setup package

* feat(contract): add airdrop role

* feat(contract): add airdrop action

* feat(contract): add use airdrop

* chore(version): bump contract to 2.4.0
  • Loading branch information
Yeboster authored Mar 18, 2024
1 parent 5415295 commit a5a24cf
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 25 deletions.
19 changes: 18 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"

members = [
"airdrop",
"contract",
"contract-proxy",
"contract-version-base",
Expand Down
30 changes: 30 additions & 0 deletions airdrop/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "airdrop"
version = "1.0.0"
authors = ["Yeboster"]
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
pbc_contract_common = { workspace = true }
pbc_contract_codegen = { workspace = true }
pbc_traits = { workspace = true }
pbc_lib = { workspace = true }
read_write_rpc_derive = { workspace = true }
read_write_state_derive = { workspace = true }
create_type_spec_derive = { workspace = true }

utils = { path = "../utils" }
rpc-msg-derive = { path = "../rpc-msg-derive" }

serde_json = { workspace = true }

[features]
abi = [
"pbc_contract_common/abi",
"pbc_contract_codegen/abi",
"pbc_traits/abi",
"create_type_spec_derive/abi",
]
19 changes: 19 additions & 0 deletions airdrop/src/actions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use pbc_contract_common::{address::Address, avl_tree_map::AvlTreeMap};

use crate::state::AirdropState;

pub fn execute_init() -> AirdropState {
AirdropState {
inventory: AvlTreeMap::new(),
}
}

pub fn execute_airdrop(state: &mut AirdropState, address: &Address) {
assert!(state.has_airdrop(address), "No airdrop for the address");

state._use_airdrop(address);
}

pub fn execute_add_airdrop(state: &mut AirdropState, address: &Address) {
state._add_airdrop(address);
}
5 changes: 5 additions & 0 deletions airdrop/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod actions;
pub mod state;

#[cfg(test)]
mod tests;
34 changes: 34 additions & 0 deletions airdrop/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use create_type_spec_derive::CreateTypeSpec;
use pbc_contract_common::{address::Address, avl_tree_map::AvlTreeMap};
use read_write_state_derive::ReadWriteState;

#[repr(C)]
#[derive(ReadWriteState, CreateTypeSpec, Default, Debug)]
pub struct AirdropState {
pub inventory: AvlTreeMap<Address, u128>,
}

impl AirdropState {
/// Check if the address has an airdrop
pub fn has_airdrop(&self, address: &Address) -> bool {
self.inventory.contains_key(address)
}

/// Use airdrop from the address
pub fn _use_airdrop(&mut self, address: &Address) {
if let Some(airdrop) = self.inventory.get(address) {
let remaining = airdrop - 1;
if remaining == 0 {
self.inventory.remove(address);
} else {
self.inventory.insert(*address, remaining);
}
}
}

/// Add airdrop to the address
pub fn _add_airdrop(&mut self, address: &Address) {
let airdrop = self.inventory.get(address).unwrap_or(0);
self.inventory.insert(*address, airdrop + 1);
}
}
49 changes: 49 additions & 0 deletions airdrop/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Setup tests

use utils::tests::mock_address;

use crate::actions::{execute_add_airdrop, execute_airdrop, execute_init};

#[test]
fn proper_has_airdrop() {
let address = mock_address(0);
let mut state = execute_init();

assert_eq!(state.has_airdrop(&address), false);

execute_add_airdrop(&mut state, &address);

assert_eq!(state.has_airdrop(&address), true);
}

#[test]
fn proper_execute_airdrop() {
let address = mock_address(0);
let mut state = execute_init();

execute_add_airdrop(&mut state, &address);
execute_airdrop(&mut state, &address);

assert_eq!(state.has_airdrop(&address), false);
}

#[test]
fn proper_execute_add_airdrop() {
let address = mock_address(0);
let mut state = execute_init();

execute_add_airdrop(&mut state, &address);
execute_add_airdrop(&mut state, &address);

assert_eq!(state.has_airdrop(&address), true);
assert_eq!(state.inventory.get(&address).unwrap(), 2);
}

#[test]
#[should_panic(expected = "No airdrop for the address")]
fn proper_execute_airdrop_no_airdrop() {
let address = mock_address(0);
let mut state = execute_init();

execute_airdrop(&mut state, &address);
}
3 changes: 2 additions & 1 deletion contract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "meta-names-contract"
version = "2.3.1"
version = "2.4.0"
authors = ["Yeboster"]
edition = "2021"

Expand All @@ -17,6 +17,7 @@ read_write_state_derive = { workspace = true }
create_type_spec_derive = { workspace = true }

access-control = { path = "../access-control" }
airdrop = { path = "../airdrop" }
contract-version-base = { path = "../contract-version-base" }
partisia-name-system = { path = "../partisia-name-system" }
nft = { path = "../nft" }
Expand Down
89 changes: 67 additions & 22 deletions contract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use pbc_contract_common::{
use nft::{actions as nft_actions, msg as nft_msg};

use access_control::{actions as ac_actions, msg as ac_msg};
use airdrop::actions::{self as airdrop_actions, execute_airdrop};
use partisia_name_system::{actions as pns_actions, msg as pns_msg, state::RecordClass};
use utils::events::assert_callback_success;

Expand Down Expand Up @@ -58,11 +59,13 @@ pub fn initialize(ctx: ContractContext, msg: InitMsg) -> (ContractState, Vec<Eve
);
let access_control = ac_actions::execute_init(&ac_msg::ACInitMsg {
admin_addresses: msg.admin_addresses,
additional_roles: vec![UserRole::Whitelist {} as u8],
additional_roles: vec![UserRole::Whitelist {} as u8, UserRole::Airdrop {} as u8],
});
let airdrop = airdrop_actions::execute_init();

let state = ContractState {
access_control,
airdrop,
config: msg.config,
nft,
pns,
Expand Down Expand Up @@ -433,6 +436,21 @@ pub fn renew_subscription(
(state, events)
}

#[action(shortname = 0x27)]
pub fn add_airdrop(
ctx: ContractContext,
mut state: ContractState,
addresses: Vec<Address>,
) -> (ContractState, Vec<EventGroup>) {
assert_has_role(&state, UserRole::Airdrop {}, &ctx.sender);

for address in addresses {
airdrop_actions::execute_add_airdrop(&mut state.airdrop, &address);
}

(state, vec![])
}

#[callback(shortname = 0x30)]
pub fn on_mint_callback(
ctx: ContractContext,
Expand Down Expand Up @@ -529,33 +547,60 @@ fn mint_domain(
);
}

let payment_info = assert_and_get_payment_info(config, *payment_coin_id);
let subscription_years = subscription_years.unwrap_or(1);
let total_fees = payment_info.fees.get(domain) * subscription_years as u128;
let payout_transfer_events = action_build_mint_callback(
&PaymentIntent {
id: *payment_coin_id,
receiver: payment_info.receiver.unwrap(),
token: payment_info.token.unwrap(),
total_fees,
},
&MintMsg {
domain: domain.to_string(),
to: *to,
payment_coin_id: *payment_coin_id,
token_uri: token_uri.clone(),
parent_id: parent_id.clone(),
subscription_years: Some(subscription_years),
},
0x30,
);
let has_airdrop = mut_state.airdrop.has_airdrop(&ctx.sender);
if has_airdrop {
execute_airdrop(&mut mut_state.airdrop, &ctx.sender);

let (new_state, mint_events) = action_mint(
ctx,
mut_state,
domain,
to,
token_uri,
parent_id,
subscription_years,
);

mut_state = new_state;

events.extend(mint_events);
} else {
let payment_info = assert_and_get_payment_info(config, *payment_coin_id);
let subscription_years = subscription_years.unwrap_or(1);
let total_fees = payment_info.fees.get(domain) * subscription_years as u128;
let payout_transfer_events = action_build_mint_callback(
&PaymentIntent {
id: *payment_coin_id,
receiver: payment_info.receiver.unwrap(),
token: payment_info.token.unwrap(),
total_fees,
},
&MintMsg {
domain: domain.to_string(),
to: *to,
payment_coin_id: *payment_coin_id,
token_uri: token_uri.clone(),
parent_id: parent_id.clone(),
subscription_years: Some(subscription_years),
},
0x30,
);

events.extend(payout_transfer_events);
events.extend(payout_transfer_events);
}
}

(mut_state, events)
}

fn assert_has_role(state: &ContractState, role: UserRole, account: &Address) {
assert!(
state.access_control.has_role(role as u8, account),
"{}",
ContractError::Unauthorized
);
}

fn assert_contract_enabled(state: &ContractState) {
assert!(
state.config.contract_enabled,
Expand Down
4 changes: 4 additions & 0 deletions contract/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use access_control::state::AccessControlState;
use airdrop::state::AirdropState;
use contract_version_base::state::ContractVersionBase;
use create_type_spec_derive::CreateTypeSpec;
use nft::state::NFTContractState;
Expand All @@ -14,6 +15,7 @@ use crate::contract::__PBC_IS_ZK_CONTRACT;
#[derive(Default, Debug)]
pub struct ContractState {
pub access_control: AccessControlState,
pub airdrop: AirdropState,
pub config: ContractConfig,
pub nft: NFTContractState,
pub pns: PartisiaNameSystemState,
Expand All @@ -38,6 +40,8 @@ pub enum UserRole {
Admin {},
#[discriminant(1)]
Whitelist {},
#[discriminant(2)]
Airdrop {},
}

#[repr(C)]
Expand Down
Loading

0 comments on commit a5a24cf

Please sign in to comment.