Skip to content

Commit

Permalink
Refactor bounty service
Browse files Browse the repository at this point in the history
  • Loading branch information
ffakenz committed May 2, 2024
1 parent 17821bb commit 118c9a5
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 89 deletions.
18 changes: 18 additions & 0 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion bounty/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ crate-type = ["cdylib"]
candid = "0.10.4"
ic-cdk = "0.13.2"
ic-cdk-macros = "0.13.2"
ic-ledger-types="0.10.0"
serde = "1.0.199"
serde_derive = "1.0.199"
serde_json = "1.0.116"
serde_bytes = "0.11.14"
serde_bytes = "0.11.14"
num-bigint = "0.4.4"
num-traits = "0.2.18"
11 changes: 11 additions & 0 deletions bounty/bounty.did
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@ type Contributor = record {
crypto_address: text;
};

type DepositReceipt =
variant {
Err: DepositErr;
Ok: nat;
};
type DepositErr =
variant {
TransferFailure;
};

service : (principal, int32) -> {
"healthcheck": () -> (text);
"accept": (Contributor) -> ();
"deposit": () -> (DepositReceipt);
}
48 changes: 48 additions & 0 deletions bounty/src/api/accept.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use super::state::{Contributor, BOUNTY_STATE};

pub fn accept_impl(contributor: Contributor) -> () {
BOUNTY_STATE.with(|state| {
if let Some(ref mut bounty_canister) = *state.borrow_mut() {
// Add the contributor to the interested contributors list
bounty_canister.interested_contributors.push(contributor);
}
});
}

#[cfg(test)]
mod test_accept {
use super::*;
use candid::Principal;
use crate::api::init::init_impl;

#[test]
fn test_accept() {
let authority =
Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe")
.unwrap();
init_impl(authority, 123);
BOUNTY_STATE.with(|state| {
let bounty_canister = state.borrow();
if let Some(ref bounty_canister) = *bounty_canister {
assert_eq!(bounty_canister.interested_contributors.len(), 0);
} else {
panic!("Bounty canister state not initialized");
}
});
let contributor =
Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe")
.unwrap();
accept_impl(Contributor {
address: contributor,
crypto_address: "contributor_address".to_string(),
});
BOUNTY_STATE.with(|state| {
let bounty_canister = state.borrow();
if let Some(ref bounty_canister) = *bounty_canister {
assert_eq!(bounty_canister.interested_contributors.len(), 1);
} else {
panic!("Bounty canister state not initialized");
}
});
}
}
45 changes: 45 additions & 0 deletions bounty/src/api/deposit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use ic_cdk::api::{caller, id};

use super::*;

use icrc1::{ICRC1, MAINNET_ICRC1_LEDGER_CANISTER_ID};

use candid::{CandidType, Nat, Principal};

#[derive(CandidType)]
pub enum DepositErr {
TransferFailure,
}

pub type DepositReceipt = Result<Nat, DepositErr>;

pub async fn deposit_impl() -> DepositReceipt {
// FIXME check caller equals the owner who initialized the bounty.
let caller = caller();
let icrc1_ledger_canister_id =
Principal::from_text(MAINNET_ICRC1_LEDGER_CANISTER_ID).unwrap();

let amount = deposit_icrc1(caller, icrc1_ledger_canister_id).await?;
return DepositReceipt::Ok(amount);
}

async fn deposit_icrc1(
caller: Principal,
icrc1_token_canister_id: Principal
) -> Result<Nat, DepositErr> {
let icrc1_token = ICRC1::new(icrc1_token_canister_id);
let icrc1_token_fee = icrc1_token.get_metadata().await.fee;

// depends on:
// dfx canister call icrc1_ledger_canister icrc2_approve "(record { amount = 100_000; spender = record{owner = principal \"SPENDER_PRINCIPAL\";} })"
let allowance = icrc1_token.allowance(caller, id()).await;

let available = allowance - icrc1_token_fee;

icrc1_token
.transfer_from(caller, id(), available.to_owned())
.await
.map_err(|_| DepositErr::TransferFailure)?;

Ok(available)
}
74 changes: 74 additions & 0 deletions bounty/src/api/icrc1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use candid::{CandidType, Deserialize, Nat, Principal};
use ic_cdk::api::call::call;

pub struct ICRC1 {
principal: Principal,
}

#[derive(CandidType, Debug, PartialEq, Deserialize)]
pub enum TxError {
InsufficientBalance,
InsufficientAllowance,
Unauthorized,
LedgerTrap,
AmountTooSmall,
BlockUsed,
ErrorOperationStyle,
ErrorTo,
Other,
}
pub type TxReceipt = Result<Nat, TxError>;

#[allow(non_snake_case)]
#[derive(CandidType, Clone, Debug, Deserialize)]
pub struct Metadata {
pub logo: String,
pub name: String,
pub symbol: String,
pub decimals: u8,
pub totalSupply: Nat,
pub owner: Principal,
pub fee: Nat,
}

// pub const ICRC1_FEE: u64 = 10_000;
pub const MAINNET_ICRC1_LEDGER_CANISTER_ID: &str = "mxzaz-hqaaa-aaaar-qaada-cai";

impl ICRC1 {
pub fn new(principal: Principal) -> Self {
ICRC1 { principal }
}

// pub async fn transfer(&self, target: Principal, amount: Nat) -> TxReceipt {
// let call_result: Result<(TxReceipt,), _> =
// call(self.principal, "icrc1_transfer", (target, amount)).await;

// call_result.unwrap().0
// }

pub async fn transfer_from(
&self,
source: Principal,
target: Principal,
amount: Nat,
) -> TxReceipt {
let call_result: Result<(TxReceipt,), _> =
call(self.principal, "icrc2_transfer_from", (source, target, amount)).await;

call_result.unwrap().0
}

pub async fn allowance(&self, owner: Principal, spender: Principal) -> Nat {
let call_result: Result<(Nat,), _> =
call(self.principal, "icrc2_allowance", (owner, spender)).await;

call_result.unwrap().0
}

pub async fn get_metadata(&self) -> Metadata {
let call_result: Result<(Metadata,), _> =
call(self.principal, "icrc1_metadata", ()).await;

call_result.unwrap().0
}
}
36 changes: 36 additions & 0 deletions bounty/src/api/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use candid::Principal;
use super::state::{BountyState, BOUNTY_STATE};

pub fn init_impl(authority: Principal, github_issue_id: i32) -> () {
BOUNTY_STATE.with(|state| {
*state.borrow_mut() = Some(BountyState {
authority,
github_issue_id,
interested_contributors: Vec::new(),
claimed: false
});
});
}

#[cfg(test)]
mod test_init {
use super::*;

#[test]
fn test_init() {
BOUNTY_STATE.with(|state| {
let bounty_canister = state.borrow();
assert!(bounty_canister.is_none());
});

let authority =
Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe")
.unwrap();

init_impl(authority, 123);
BOUNTY_STATE.with(|state| {
let bounty_canister = state.borrow();
assert!(bounty_canister.is_some());
});
}
}
22 changes: 22 additions & 0 deletions bounty/src/api/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

use candid::{CandidType, Principal};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, CandidType)]
pub struct BountyState {
pub authority: Principal,
pub github_issue_id: i32,
pub interested_contributors: Vec<Contributor>,
pub claimed: bool
}

#[derive(Debug, Serialize, Deserialize, CandidType)]
pub struct Contributor {
pub address: Principal,
pub crypto_address: String,
}

// Define thread-local storage for the bounty canister state
thread_local! {
pub static BOUNTY_STATE: std::cell::RefCell<Option<BountyState>> = std::cell::RefCell::new(None);
}
Loading

0 comments on commit 118c9a5

Please sign in to comment.