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

Tap to earn for Daily user active with Vault #91

Merged
merged 3 commits into from
Sep 1, 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
9 changes: 8 additions & 1 deletion onchain/cairo/src/defi/vault.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub mod Vault {
fn constructor(
ref self: ContractState, token_address: ContractAddress, admin: ContractAddress
) {
// Give MINTER role to the Vault for the token used
// Give MINTER role to the Vault for the token used
self.token_address.write(token_address);
self.accesscontrol.initializer();
self.accesscontrol._grant_role(ADMIN_ROLE, admin);
Expand Down Expand Up @@ -197,6 +197,13 @@ pub mod Vault {
assert(self.is_token_permitted(token_address), 'Non permitted token');
self.token_permitted.read(token_address).ratio_mint
}

fn mint_quest_token_reward(ref self: ContractState, user: ContractAddress, amount: u32) {
let token_mintable = IERC20MintableDispatcher {
contract_address: self.token_address.read()
};
token_mintable.mint(user, amount.into());
}
}
// Admin
// Add OPERATOR role to the Vault escrow
Expand Down
1 change: 1 addition & 0 deletions onchain/cairo/src/interfaces.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod jediswap;
pub mod quest;
35 changes: 35 additions & 0 deletions onchain/cairo/src/interfaces/quest.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use afk::types::quest::{QuestInfo, UserQuestInfo};
use afk::types::tap_types::{TapUserStats};
use starknet::ContractAddress;

#[starknet::interface]
pub trait IQuest<TContractState> {
// Return the reward for the quest. (token, nft)
fn get_reward(self: @TContractState) -> (u32, bool);
// Return if the user can claim the quest reward.
fn is_claimable(self: @TContractState, user: ContractAddress) -> bool;
}

#[starknet::interface]
pub trait ITapQuests<TContractState> {
fn get_tap_user_stats(self: @TContractState, user: ContractAddress) -> TapUserStats;
fn handle_tap_daily(ref self: TContractState);
}

#[starknet::interface]
pub trait IQuestNFT<TContractState> {
fn mint(ref self: TContractState, user: ContractAddress) -> u256;
fn set_role(
ref self: TContractState, recipient: ContractAddress, role: felt252, is_enable: bool
);
}

#[starknet::interface]
pub trait IQuestFactory<TContractState> {
fn get_reward(self: @TContractState, quest: ContractAddress) -> (u32, bool);
fn add_quest(ref self: TContractState, quest: QuestInfo);
fn get_quests(self: @TContractState) -> Span<QuestInfo>;
fn get_quest(self: @TContractState, quest_id: u32) -> QuestInfo;
fn claim_reward(ref self: TContractState, quest_id: u32);
fn get_user_quest_info(self: @TContractState, quest_id: u32) -> UserQuestInfo;
}
1 change: 1 addition & 0 deletions onchain/cairo/src/interfaces/vault.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ pub trait IERCVault<TContractState> {
);

fn get_token_ratio(ref self: TContractState, token_address: ContractAddress) -> u256;
fn mint_quest_token_reward(ref self: TContractState, user: ContractAddress, amount: u32);
}
5 changes: 5 additions & 0 deletions onchain/cairo/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod sha256;
pub mod social;
pub mod utils;
pub mod quests {
pub mod factory;
pub mod authority_quest;
pub mod chain_faction_quest;
pub mod faction_quest;
Expand All @@ -24,6 +25,7 @@ pub mod interfaces {
pub mod erc20;
pub mod erc20_mintable;
pub mod jediswap;
pub mod quest;
pub mod nfts;
pub mod pixel;
pub mod pixel_template;
Expand All @@ -48,6 +50,7 @@ pub mod types {
pub mod jediswap_types;
pub mod keys_types;
pub mod launchpad_types;
pub mod quest;
pub mod tap_types;
}

Expand All @@ -59,6 +62,7 @@ pub mod examples {
pub mod tokens {
pub mod erc20;
pub mod erc20_mintable;
pub mod quest_nft;
pub mod token;
}

Expand All @@ -84,6 +88,7 @@ pub mod tests {
pub mod identity_tests;
pub mod keys_tests;
pub mod launchpad_tests;
pub mod quest_factory_test;
pub mod tap_tests;
pub mod vault_tests;
}
Expand Down
107 changes: 107 additions & 0 deletions onchain/cairo/src/quests/factory.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#[starknet::contract]
pub mod QuestFactory {
use afk::interfaces::quest::{
IQuestFactory, IQuestDispatcher, IQuestDispatcherTrait, IQuestNFTDispatcher,
IQuestNFTDispatcherTrait
};
use afk::interfaces::vault::{IERCVaultDispatcher, IERCVaultDispatcherTrait};
use afk::types::quest::{QuestInfo, UserQuestInfo};


use starknet::{ContractAddress, get_caller_address};

#[storage]
struct Storage {
// Map quest_id -> quest info
quests: LegacyMap<u32, QuestInfo>,
quest_count: u32,
user_quests: LegacyMap<ContractAddress, u32>,
// Map (user address, quest_id) -> user quest info
user_quest_info: LegacyMap<(ContractAddress, u32), UserQuestInfo>,
quest_nft: ContractAddress,
vault: ContractAddress,
}

#[constructor]
fn constructor(ref self: ContractState, quest_nft: ContractAddress, vault: ContractAddress) {
self.quest_nft.write(quest_nft);
self.vault.write(vault);
}

#[abi(embed_v0)]
impl QuestFactoryImpl of IQuestFactory<ContractState> {
fn get_reward(self: @ContractState, quest: ContractAddress) -> (u32, bool) {
IQuestDispatcher { contract_address: quest }.get_reward()
}

fn add_quest(ref self: ContractState, quest: QuestInfo) {
let mut new_quest = quest.clone();
new_quest.quest_id = self.quest_count.read();
self.quests.write(self.quest_count.read(), new_quest);
self.quest_count.write(self.quest_count.read() + 1);
//TODO emit add quest event
}

fn get_quests(self: @ContractState) -> Span<QuestInfo> {
let mut quest_array = array![];
let mut i = 0;

while i < self
.quest_count
.read() {
let quest = self.quests.read(i);
quest_array.append(quest);
i += 1;
};

quest_array.span()
}

fn get_quest(self: @ContractState, quest_id: u32) -> QuestInfo {
self.quests.read(quest_id)
}

fn claim_reward(ref self: ContractState, quest_id: u32) {
let caller = get_caller_address();
let quest = self.get_quest(quest_id);

// let quest_dispathcer = IQuestDispatcher { contract_address: quest.address };
let quest_nft_dispatcher = IQuestNFTDispatcher {
contract_address: self.quest_nft.read()
};
let vault_dispatcher = IERCVaultDispatcher { contract_address: self.vault.read() };

// check if caller is eligible to claim reward
assert(IQuestDispatcher { contract_address: quest.address }.is_claimable(caller), 'Quest not claimable');

let (token_reward, nft_reward) = self.get_reward(quest.address);

if token_reward > 0 {
// mint erc20 token
vault_dispatcher.mint_quest_token_reward(caller, token_reward);
};

// mint nft if it part of reward
let mut nft_id = 0;
if nft_reward {
nft_id = quest_nft_dispatcher.mint(caller);
};

// update user quest info state
let mut user_quest = UserQuestInfo {
quest_id: quest_id,
user_address: caller,
is_complete: true,
claimed_token: token_reward,
claimed_nft_id: nft_id.try_into().unwrap()
};

self.user_quest_info.write((caller, quest_id), user_quest);
self.user_quests.write(caller, quest_id);
//TODO emit claim event
}

fn get_user_quest_info(self: @ContractState, quest_id: u32) -> UserQuestInfo {
self.user_quest_info.read((get_caller_address(), quest_id))}
}
}
64 changes: 47 additions & 17 deletions onchain/cairo/src/quests/tap.cairo
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
use afk::types::tap_types::{TapUserStats, TapDailyEvent};
use starknet::{ContractAddress, ClassHash};

#[starknet::interface]
pub trait ITapQuests<T> {
fn get_tap_user_stats(self: @T, user: ContractAddress) -> TapUserStats;
fn handle_tap_daily(ref self: T);
}

#[starknet::contract]
mod TapQuests {
use core::num::traits::Zero;

use afk::interfaces::quest::{ITapQuests, IQuest};
use afk::types::tap_types::{TapUserStats, TapDailyEvent};
use starknet::{
ContractAddress, get_caller_address, storage_access::StorageBaseAddress,
contract_address_const, get_block_timestamp, get_contract_address, ClassHash
};
use super::{TapUserStats, TapDailyEvent};

const DAILY_TIMESTAMP_SECONDS: u64 = 60 * 60 * 24;

#[storage]
struct Storage {
tap_by_users: LegacyMap<ContractAddress, TapUserStats>
tap_by_users: LegacyMap<ContractAddress, TapUserStats>,
token_reward: u32,
claimed: LegacyMap<ContractAddress, bool>,
is_claimable: LegacyMap<ContractAddress, bool>,
is_reward_nft: bool,
is_reward_token: bool,
}


#[constructor]
fn constructor(
ref self: ContractState, token_reward: u32, is_reward_nft: bool, is_reward_token: bool
) {
self.token_reward.write(token_reward);
self.is_reward_nft.write(is_reward_nft);
self.is_reward_token.write(is_reward_token);
}

#[event]
Expand All @@ -30,7 +39,7 @@ mod TapQuests {
}

#[abi(embed_v0)]
impl TapQuestImpl of super::ITapQuests<ContractState> {
impl TapQuestImpl of ITapQuests<ContractState> {
fn get_tap_user_stats(self: @ContractState, user: ContractAddress) -> TapUserStats {
self.tap_by_users.read(user)
}
Expand All @@ -42,18 +51,39 @@ mod TapQuests {
if tap_old.owner.is_zero() {
let tap = TapUserStats { owner: caller, last_tap: timestamp, total_tap: 1 };
self.tap_by_users.write(caller, tap);
self.is_claimable.write(caller, true);
self.emit(TapDailyEvent { owner: caller, last_tap: timestamp, total_tap: 1 });
} else {
let mut tap = tap_old.clone();
// let last_tap = tap.last_tap.clone();
// TODO Check assert in tests
// assert!(timestamp - last_tap < DAILY_TIMESTAMP_SECONDS, "too early");
tap.last_tap = timestamp;
let total = tap_old.total_tap + 1;
let mut tap = self.tap_by_users.read(caller);
let last_tap = tap.last_tap.clone();

assert(timestamp >= last_tap + DAILY_TIMESTAMP_SECONDS, 'too early');

tap.last_tap = get_block_timestamp();
let total = tap.total_tap + 1;
tap.total_tap = total.clone();
self.tap_by_users.write(caller, tap);
self.is_claimable.write(caller, true);
self.emit(TapDailyEvent { owner: caller, last_tap: timestamp, total_tap: total });
}
}
}

#[abi(embed_v0)]
impl TapQuest of IQuest<ContractState> {
fn get_reward(self: @ContractState) -> (u32, bool) {
if self.is_reward_token.read() && self.is_reward_nft.read() {
return (self.token_reward.read(), true);
} else if self.is_reward_token.read() {
return (self.token_reward.read(), false);
} else {
(0, true)
}
}

fn is_claimable(self: @ContractState, user: ContractAddress) -> bool {
//TODO self.is_claimable.read(user)
true
}
}
}
Loading
Loading