From a06f47abd2a138c611dd04f8996064315f80929e Mon Sep 17 00:00:00 2001 From: Marcelo Manuel Baraschi Date: Tue, 7 May 2024 09:07:06 -0300 Subject: [PATCH 1/9] Added register & unregister endpoints. -> wip(more validations needed) -> validate with Franco. -> non final solution --- README.md | 63 +++++++++++++ backend/backend.did | 18 ++++ backend/src/bounty/api/register_issue.rs | 103 +++++++++++++++++++++ backend/src/bounty/api/state.rs | 4 +- backend/src/bounty/api/unregister_issue.rs | 75 +++++++++++++++ backend/src/lib.rs | 24 ++++- creates | 0 7 files changed, 282 insertions(+), 5 deletions(-) create mode 100644 backend/src/bounty/api/register_issue.rs create mode 100644 backend/src/bounty/api/unregister_issue.rs create mode 100644 creates diff --git a/README.md b/README.md index 2ea881d..603ce58 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,66 @@ dfx start --background # Deploys your canisters to the replica and generates your candid interface dfx deploy ``` +# Install +1/ dfx start +2/ dfx deploy -> creates .env +3/ define GITHUB_TOKEN in .env +4/ cargo build --target wasm32-unknown-unknown --release +5/ cargo install cargo-audit --locked +6/ make test-1 +7/ make test-2 + +# to restart your local network +dfx start --clean + +## other useful dfx commands +dfx identity list + anonymous + default * +dfx identity whoami + default +dfx identity get-principal + viost-xztac-z5ppy-xg3l4-rcpbr-622pc-pvluy-f4lq4-jwrgo-nusmt-cae +dfx identity get-wallet + bnz7o-iuaaa-aaaaa-qaaaa-cai +dfx canister info $(dfx identity get-wallet) +dfx canister status $(dfx identity get-wallet) + +## ICRC-1 endpoints +> To fetch the balance of an account +dfx canister call icrc1_ledger icrc1_balance_of "(record {owner = principal \"$(dfx identity get-principal)\"; })" +> To transfer +dfx canister call icrc1_ledger icrc1_transfer '(record { to = record { owner = principal "hdq6b-ncywm-yajd5-4inc6-hgpzp-55xnp-py7d5-uqt6o-cv5c6-rrhwa-zqe";}; amount = 100_000:nat;})' +> To fetch the balance of receiving account +dfx canister call icrc1_ledger icrc1_balance_of "(record {owner = principal \"hdq6b-ncywm-yajd5-4inc6-hgpzp-55xnp-py7d5-uqt6o-cv5c6-rrhwa-zqe\"; })" +> others +dfx canister call icrc1_ledger get_blocks '(record{start=0:nat;length=2:nat})' +dfx canister call icrc1_ledger get_transactions '(record{start=0:nat;length=5:nat})' + +## ICRC-2 endpoints +> To approve tokens to a certain spender +dfx canister call icrc1_ledger_canister icrc2_approve "(record { amount = 100_000; spender = record{owner = principal \"${EXAMPLE}\";} })" +> To check the allowance of the spender +dfx canister call icrc1_ledger_canister icrc2_allowance "(record { account = record{owner = principal "${DEFAULT}";}; spender = record{owner = principal "${EXAMPLE}";} })" + +## index endpoints +> dfx canister call icrc1_index status '()' +> dfx canister call icrc1_index get_blocks '(record{start=0:nat;length=2:nat})' +> dfx canister call icrc1_index get_account_transactions "(record{account=record {owner = principal \"$(dfx identity get-principal)\"; }; max_results=2:nat})" +> dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"$(dfx identity get-principal)\"; })" +> dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"hdq6b-ncywm-yajd5-4inc6-hgpzp-55xnp-py7d5-uqt6o-cv5c6-rrhwa-zqe\"; })" + + + + +Description +marcelomanuelbaraschi +2 minutes ago (edited) +Memory management: + +add register issue endpoint (should return error when issue not found or when max limit of 100.000 exceeded) +check max limit of 10 exceed when accepting pr attempts for issue +no hacer checks por caller todavia (eso es otro ticket) +agregar endpoints de unregister issue o attempt (may fail with not found) +tanto los endpoints de accept como de unregister deben ser idempotentes (agregar 2 veces la misma entrada debe retornar success) +expirar items en mapas (issue y pre) cada vez q se agrega (accept) o elimina (unregister) una entrada diff --git a/backend/backend.did b/backend/backend.did index 27ac4a8..32c10d5 100644 --- a/backend/backend.did +++ b/backend/backend.did @@ -94,6 +94,20 @@ type AcceptErr = variant { type AcceptReceipt = opt AcceptErr; + +type RegisterIssueErr = variant { + CantRegisterIssueTwice; +}; + +type RegisterIssueReceipt = opt RegisterIssueErr; + + +type UnRegisterIssueErr = variant { + IssueNotFound; +}; + +type UnRegisterIssueReceipt = opt UnRegisterIssueErr; + service : (authority: principal) -> { // GitHub Service "get_issue": (GithubToken) -> (IssueReceipt); @@ -104,6 +118,10 @@ service : (authority: principal) -> { "healthcheck": () -> (text); "accept": (Contributor, github_issue_id: text, github_pr_id: text) -> (AcceptReceipt); "deposit": () -> (DepositReceipt); + "register_issue": (github_issue: text) -> (RegisterIssueReceipt); + "unregister_issue": (github_issue_id: text) -> (UnRegisterIssueReceipt); } + + diff --git a/backend/src/bounty/api/register_issue.rs b/backend/src/bounty/api/register_issue.rs new file mode 100644 index 0000000..f59c1dd --- /dev/null +++ b/backend/src/bounty/api/register_issue.rs @@ -0,0 +1,103 @@ +use super::state::{Bounty, Contributor, Issue, PullRequest, BOUNTY_STATE}; + +use candid::CandidType; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, CandidType)] +pub enum RegisterIssueError { + CantRegisterIssueTwice, +} + +pub type RegisterIssueReceipt = Option; + +pub fn register_issue_impl(github_issue: Issue) -> RegisterIssueReceipt { + print!("Registering issue: {:?}", github_issue); + return BOUNTY_STATE.with(|state| { + if let Some(ref mut bounty_canister) = *state.borrow_mut() { + let mut issue_exists = false; + + if let Some(ref mut issue) = bounty_canister.github_issues.get_mut(&github_issue.id) { + issue_exists = true; + } + + if issue_exists { + Some(RegisterIssueError::CantRegisterIssueTwice) + } else { + bounty_canister + .github_issues + .insert(github_issue.id.clone(), github_issue); + None + } + } else { + panic!("Bounty canister state not initialized") + } + }); +} + +#[cfg(test)] +mod test_register_issue { + use super::*; + use crate::bounty::api::init::init_impl; + use candid::Principal; + + #[test] + fn test_register_issue() { + let authority = + Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe") + .unwrap(); + init_impl(authority); + let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); + let r: Option = register_issue_impl(Issue { + id: github_issue_id, + maintainer: Contributor { + address: Principal::anonymous(), + crypto_address: "0x1234".to_string(), + }, + bounty: Bounty { + amount: 100, + winner: None, + accepted_prs: Default::default(), + }, + }); + assert!(r.is_none()); + } + #[test] + fn test_cant_register_issue_twice() { + let authority = + Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe") + .unwrap(); + init_impl(authority); + let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); + let r1: Option = register_issue_impl(Issue { + id: github_issue_id.clone(), + maintainer: Contributor { + address: Principal::anonymous(), + crypto_address: "0x1234".to_string(), + }, + bounty: Bounty { + amount: 100, + winner: None, + accepted_prs: Default::default(), + }, + }); + assert!(r1.is_none()); + let r2: Option = register_issue_impl(Issue { + id: github_issue_id.clone(), + maintainer: Contributor { + address: Principal::anonymous(), + crypto_address: "0x1234".to_string(), + }, + bounty: Bounty { + amount: 100, + winner: None, + //not sure if next line is correct + accepted_prs: Default::default(), + }, + }); + assert!(r2.is_some()); + assert!(matches!( + r2, + Some(RegisterIssueError::CantRegisterIssueTwice) + )); + } +} diff --git a/backend/src/bounty/api/state.rs b/backend/src/bounty/api/state.rs index 64126be..b705fec 100644 --- a/backend/src/bounty/api/state.rs +++ b/backend/src/bounty/api/state.rs @@ -35,7 +35,7 @@ pub struct Issue { #[derive(Debug, Serialize, Deserialize, CandidType)] pub struct BountyState { pub authority: Principal, - pub github_issues: HashMap, + pub github_issues: HashMap, } // Define thread-local storage for the bounty canister state // WASM is single-threaded by nature. [RefCell] and [thread_local!] are used despite being not totally safe primitives. @@ -44,7 +44,7 @@ pub struct BountyState { // Here we use [thread_local!] because it is simpler. thread_local! { // Currently, a single canister smart contract is limited to 4 GB of storage due to WebAssembly limitations. - // To ensure that our canister does not exceed this limit, we restrict memory usage to at most 2 GB because + // To ensure that our canister does not exceed this limit, we restrict memory usage to at most 2 GB because // up to 2x memory may be needed for data serialization during canister upgrades. pub static BOUNTY_STATE: std::cell::RefCell> = std::cell::RefCell::new(None); } diff --git a/backend/src/bounty/api/unregister_issue.rs b/backend/src/bounty/api/unregister_issue.rs new file mode 100644 index 0000000..e0251a8 --- /dev/null +++ b/backend/src/bounty/api/unregister_issue.rs @@ -0,0 +1,75 @@ +use super::state::{Bounty, Contributor, Issue, PullRequest, BOUNTY_STATE}; + +use crate::bounty::api::register_issue::RegisterIssueError; +use crate::register_issue_impl; +use candid::CandidType; +use serde::{Deserialize, Serialize}; +#[derive(Debug, Serialize, Deserialize, CandidType)] +pub enum UnRegisterIssueError { + IssueNotFound, +} + +pub type UnRegisterIssueReceipt = Option; + +pub fn unregister_issue_impl(github_issue: String) -> UnRegisterIssueReceipt { + print!("Unregistering issue: {:?}", github_issue); + return BOUNTY_STATE.with(|state| { + if let Some(ref mut bounty_canister) = *state.borrow_mut() { + let mut issue_exists = false; + + if let Some(ref mut issue) = bounty_canister.github_issues.get_mut(&github_issue) { + issue_exists = true; + } + + if issue_exists { + bounty_canister.github_issues.remove(&github_issue); + None + } else { + Some(UnRegisterIssueError::IssueNotFound) + } + } else { + panic!("Bounty canister state not initialized") + } + }); +} + +#[cfg(test)] +mod test_unregister_issue { + use super::*; + use crate::bounty::api::init::init_impl; + use candid::Principal; + + #[test] + fn test_unregister_issue() { + let authority = + Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe") + .unwrap(); + init_impl(authority); + let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); + let r1: Option = register_issue_impl(Issue { + id: github_issue_id.clone(), + maintainer: Contributor { + address: Principal::anonymous(), + crypto_address: "0x1234".to_string(), + }, + bounty: Bounty { + amount: 100, + winner: None, + accepted_prs: Default::default(), + }, + }); + let r2: Option = unregister_issue_impl(github_issue_id.clone()); + assert!(r2.is_none()); + } + #[test] + fn test_cant_unregister_a_non_existent_issue() { + let authority = + Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe") + .unwrap(); + init_impl(authority); + let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); + let r: Option = unregister_issue_impl(github_issue_id.clone()); + assert!(r.is_some()); + assert!(matches!(r, Some(UnRegisterIssueError::IssueNotFound))); + } +} diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 6b2a569..e4488ab 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -28,14 +28,18 @@ pub mod bounty { pub mod deposit; pub mod icrc1; pub mod init; + pub mod register_issue; pub mod state; + pub mod unregister_issue; } } use bounty::api::accept::{accept_impl, AcceptReceipt}; -use bounty::api::init::init_impl; use bounty::api::deposit::{deposit_impl, DepositReceipt}; -use bounty::api::state::Contributor; +use bounty::api::init::init_impl; +use bounty::api::register_issue::{register_issue_impl, RegisterIssueReceipt}; +use bounty::api::state::{Contributor, Issue}; +use bounty::api::unregister_issue::{unregister_issue_impl, UnRegisterIssueReceipt}; // GITHUB SERVICE #[ic_cdk::update] @@ -97,7 +101,11 @@ fn init(authority: Principal) -> () { } #[ic_cdk::update] -fn accept(contributor: Contributor, github_issue_id: String, github_pr_id: String) -> AcceptReceipt { +fn accept( + contributor: Contributor, + github_issue_id: String, + github_pr_id: String, +) -> AcceptReceipt { return accept_impl(contributor, github_issue_id, github_pr_id); } @@ -110,3 +118,13 @@ async fn deposit() -> DepositReceipt { async fn healthcheck() -> String { return "OK".to_string(); } + +#[ic_cdk::update] +fn register_issue(github_issue: Issue) -> RegisterIssueReceipt { + return register_issue_impl(github_issue); +} + +#[ic_cdk::update] +fn unregister_issue(github_issue_id: String) -> UnRegisterIssueReceipt { + return unregister_issue_impl(github_issue_id); +} diff --git a/creates b/creates new file mode 100644 index 0000000..e69de29 From 4e65140d12ba58d4f93375ce1ab4fe150f5bc576 Mon Sep 17 00:00:00 2001 From: Marcelo Manuel Baraschi Date: Wed, 8 May 2024 14:25:05 -0300 Subject: [PATCH 2/9] update readme.ms --- README.md | 65 +------------------------------------------------------ 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/README.md b/README.md index 603ce58..058a349 100644 --- a/README.md +++ b/README.md @@ -26,67 +26,4 @@ dfx start --background # Deploys your canisters to the replica and generates your candid interface dfx deploy -``` -# Install -1/ dfx start -2/ dfx deploy -> creates .env -3/ define GITHUB_TOKEN in .env -4/ cargo build --target wasm32-unknown-unknown --release -5/ cargo install cargo-audit --locked -6/ make test-1 -7/ make test-2 - -# to restart your local network -dfx start --clean - -## other useful dfx commands -dfx identity list - anonymous - default * -dfx identity whoami - default -dfx identity get-principal - viost-xztac-z5ppy-xg3l4-rcpbr-622pc-pvluy-f4lq4-jwrgo-nusmt-cae -dfx identity get-wallet - bnz7o-iuaaa-aaaaa-qaaaa-cai -dfx canister info $(dfx identity get-wallet) -dfx canister status $(dfx identity get-wallet) - -## ICRC-1 endpoints -> To fetch the balance of an account -dfx canister call icrc1_ledger icrc1_balance_of "(record {owner = principal \"$(dfx identity get-principal)\"; })" -> To transfer -dfx canister call icrc1_ledger icrc1_transfer '(record { to = record { owner = principal "hdq6b-ncywm-yajd5-4inc6-hgpzp-55xnp-py7d5-uqt6o-cv5c6-rrhwa-zqe";}; amount = 100_000:nat;})' -> To fetch the balance of receiving account -dfx canister call icrc1_ledger icrc1_balance_of "(record {owner = principal \"hdq6b-ncywm-yajd5-4inc6-hgpzp-55xnp-py7d5-uqt6o-cv5c6-rrhwa-zqe\"; })" -> others -dfx canister call icrc1_ledger get_blocks '(record{start=0:nat;length=2:nat})' -dfx canister call icrc1_ledger get_transactions '(record{start=0:nat;length=5:nat})' - -## ICRC-2 endpoints -> To approve tokens to a certain spender -dfx canister call icrc1_ledger_canister icrc2_approve "(record { amount = 100_000; spender = record{owner = principal \"${EXAMPLE}\";} })" -> To check the allowance of the spender -dfx canister call icrc1_ledger_canister icrc2_allowance "(record { account = record{owner = principal "${DEFAULT}";}; spender = record{owner = principal "${EXAMPLE}";} })" - -## index endpoints -> dfx canister call icrc1_index status '()' -> dfx canister call icrc1_index get_blocks '(record{start=0:nat;length=2:nat})' -> dfx canister call icrc1_index get_account_transactions "(record{account=record {owner = principal \"$(dfx identity get-principal)\"; }; max_results=2:nat})" -> dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"$(dfx identity get-principal)\"; })" -> dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"hdq6b-ncywm-yajd5-4inc6-hgpzp-55xnp-py7d5-uqt6o-cv5c6-rrhwa-zqe\"; })" - - - - -Description -marcelomanuelbaraschi -2 minutes ago (edited) -Memory management: - -add register issue endpoint (should return error when issue not found or when max limit of 100.000 exceeded) -check max limit of 10 exceed when accepting pr attempts for issue -no hacer checks por caller todavia (eso es otro ticket) -agregar endpoints de unregister issue o attempt (may fail with not found) -tanto los endpoints de accept como de unregister deben ser idempotentes (agregar 2 veces la misma entrada debe retornar success) -expirar items en mapas (issue y pre) cada vez q se agrega (accept) o elimina (unregister) una entrada +``` \ No newline at end of file From 47e719d3aa00bd66f7dd96ea121006f821f0312a Mon Sep 17 00:00:00 2001 From: Marcelo Manuel Baraschi Date: Wed, 8 May 2024 14:46:55 -0300 Subject: [PATCH 3/9] Better types and Better Interface --- backend/backend.did | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/backend/backend.did b/backend/backend.did index 32c10d5..8aa5b8e 100644 --- a/backend/backend.did +++ b/backend/backend.did @@ -71,6 +71,12 @@ type User = record { type GithubToken = text; +type GithubIssueId = text; + +type GithubPullRequestId = text; + +type BountyAmmount = nat; + // Bounty Service type Contributor = record { @@ -88,20 +94,15 @@ type DepositReceipt = variant { }; type AcceptErr = variant { - IssueNotFound : record { github_issue_id : text }; - CantAcceptedTwice; + IssueNotFound : record { GithubIssueId }; }; type AcceptReceipt = opt AcceptErr; - -type RegisterIssueErr = variant { - CantRegisterIssueTwice; -}; +type RegisterIssueErr = () type RegisterIssueReceipt = opt RegisterIssueErr; - type UnRegisterIssueErr = variant { IssueNotFound; }; @@ -109,19 +110,17 @@ type UnRegisterIssueErr = variant { type UnRegisterIssueReceipt = opt UnRegisterIssueErr; service : (authority: principal) -> { + // GitHub Service "get_issue": (GithubToken) -> (IssueReceipt); "get_fixed_by": (GithubToken) -> (FixedByReceipt); "get_is_merged": (GithubToken) -> (IsMergedReceipt); "get_merged_details": (GithubToken) -> (MergeDetailsReceipt); + // Bounty Service "healthcheck": () -> (text); - "accept": (Contributor, github_issue_id: text, github_pr_id: text) -> (AcceptReceipt); + "accept": (Contributor, GithubIssueId, GithubPullRequestId) -> (AcceptReceipt); "deposit": () -> (DepositReceipt); - "register_issue": (github_issue: text) -> (RegisterIssueReceipt); - "unregister_issue": (github_issue_id: text) -> (UnRegisterIssueReceipt); -} - - - - + "register_issue": (Contributor, GithubIssueId, BountyAmmount) -> (RegisterIssueReceipt); + "unregister_issue": (GithubIssueId) -> (UnRegisterIssueReceipt); +} \ No newline at end of file From 76f444193f2baf38d089d58a512e7bc009d425a5 Mon Sep 17 00:00:00 2001 From: Marcelo Manuel Baraschi Date: Wed, 8 May 2024 15:04:01 -0300 Subject: [PATCH 4/9] fix register_issue_impl + backend.did --- backend/backend.did | 6 +- backend/src/bounty/api/register_issue.rs | 93 +++++++++++------------- 2 files changed, 45 insertions(+), 54 deletions(-) diff --git a/backend/backend.did b/backend/backend.did index 8aa5b8e..da33a05 100644 --- a/backend/backend.did +++ b/backend/backend.did @@ -75,7 +75,7 @@ type GithubIssueId = text; type GithubPullRequestId = text; -type BountyAmmount = nat; +type BountyAmount = nat32; // Bounty Service @@ -110,7 +110,7 @@ type UnRegisterIssueErr = variant { type UnRegisterIssueReceipt = opt UnRegisterIssueErr; service : (authority: principal) -> { - + // GitHub Service "get_issue": (GithubToken) -> (IssueReceipt); "get_fixed_by": (GithubToken) -> (FixedByReceipt); @@ -121,6 +121,6 @@ service : (authority: principal) -> { "healthcheck": () -> (text); "accept": (Contributor, GithubIssueId, GithubPullRequestId) -> (AcceptReceipt); "deposit": () -> (DepositReceipt); - "register_issue": (Contributor, GithubIssueId, BountyAmmount) -> (RegisterIssueReceipt); + "register_issue": (Contributor, GithubIssueId, BountyAmount) -> (RegisterIssueReceipt); "unregister_issue": (GithubIssueId) -> (UnRegisterIssueReceipt); } \ No newline at end of file diff --git a/backend/src/bounty/api/register_issue.rs b/backend/src/bounty/api/register_issue.rs index f59c1dd..505285e 100644 --- a/backend/src/bounty/api/register_issue.rs +++ b/backend/src/bounty/api/register_issue.rs @@ -1,6 +1,7 @@ use super::state::{Bounty, Contributor, Issue, PullRequest, BOUNTY_STATE}; use candid::CandidType; +use candid::Nat; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, CandidType)] @@ -10,7 +11,11 @@ pub enum RegisterIssueError { pub type RegisterIssueReceipt = Option; -pub fn register_issue_impl(github_issue: Issue) -> RegisterIssueReceipt { +pub fn register_issue_impl( + contributor: Contributor, + github_issue_id: String, + amount: Nat, +) -> RegisterIssueReceipt { print!("Registering issue: {:?}", github_issue); return BOUNTY_STATE.with(|state| { if let Some(ref mut bounty_canister) = *state.borrow_mut() { @@ -38,66 +43,52 @@ pub fn register_issue_impl(github_issue: Issue) -> RegisterIssueReceipt { mod test_register_issue { use super::*; use crate::bounty::api::init::init_impl; - use candid::Principal; + use candid::{Nat, Principal}; + use num_bigint::BigUint; #[test] fn test_register_issue() { - let authority = - Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe") - .unwrap(); + let authority = Principal::anonymous(); + init_impl(authority); + let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); - let r: Option = register_issue_impl(Issue { - id: github_issue_id, - maintainer: Contributor { - address: Principal::anonymous(), - crypto_address: "0x1234".to_string(), - }, - bounty: Bounty { - amount: 100, - winner: None, - accepted_prs: Default::default(), - }, - }); + + let contributor = Contributor { + address: Principal::anonymous(), + crypto_address: "0x1234".to_string(), + }; + + let bounty_amount: Nat = Nat(BigUint::from(100u32)); + + let r: Option = + register_issue_impl(contributor, github_issue_id.clone(), bounty_amount); + assert!(r.is_none()); } #[test] fn test_cant_register_issue_twice() { - let authority = - Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe") - .unwrap(); + let authority = Principal::anonymous(); + init_impl(authority); + let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); - let r1: Option = register_issue_impl(Issue { - id: github_issue_id.clone(), - maintainer: Contributor { - address: Principal::anonymous(), - crypto_address: "0x1234".to_string(), - }, - bounty: Bounty { - amount: 100, - winner: None, - accepted_prs: Default::default(), - }, - }); - assert!(r1.is_none()); - let r2: Option = register_issue_impl(Issue { - id: github_issue_id.clone(), - maintainer: Contributor { - address: Principal::anonymous(), - crypto_address: "0x1234".to_string(), - }, - bounty: Bounty { - amount: 100, - winner: None, - //not sure if next line is correct - accepted_prs: Default::default(), - }, - }); - assert!(r2.is_some()); - assert!(matches!( - r2, - Some(RegisterIssueError::CantRegisterIssueTwice) - )); + + let contributor = Contributor { + address: Principal::anonymous(), + crypto_address: "0x1234".to_string(), + }; + + let bounty_amount: Nat = Nat(BigUint::from(100u32)); + + let r: Option = + register_issue_impl(contributor, github_issue_id.clone(), bounty_amount); + + assert!(r.is_none()); + + let r2: Option = + register_issue_impl(contributor, github_issue_id.clone(), bounty_amount); + + assert!(r2.is_none()); } } From 1a60657192e7e5bdb1641e654d22b411aa12d4a7 Mon Sep 17 00:00:00 2001 From: Daguis Date: Fri, 10 May 2024 16:43:35 -0300 Subject: [PATCH 5/9] Aplying reviews on Register Issue --- backend/src/bounty/api/register_issue.rs | 50 ++++++++++++++---------- backend/src/bounty/api/state.rs | 4 +- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/backend/src/bounty/api/register_issue.rs b/backend/src/bounty/api/register_issue.rs index 505285e..fa13d0a 100644 --- a/backend/src/bounty/api/register_issue.rs +++ b/backend/src/bounty/api/register_issue.rs @@ -1,38 +1,40 @@ +use std::collections::HashMap; + +use super::state::IssueId; use super::state::{Bounty, Contributor, Issue, PullRequest, BOUNTY_STATE}; use candid::CandidType; use candid::Nat; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize, CandidType)] -pub enum RegisterIssueError { - CantRegisterIssueTwice, -} +pub type RegisterIssueError = (); pub type RegisterIssueReceipt = Option; pub fn register_issue_impl( contributor: Contributor, - github_issue_id: String, + github_issue_id: IssueId, amount: Nat, ) -> RegisterIssueReceipt { - print!("Registering issue: {:?}", github_issue); return BOUNTY_STATE.with(|state| { if let Some(ref mut bounty_canister) = *state.borrow_mut() { - let mut issue_exists = false; - - if let Some(ref mut issue) = bounty_canister.github_issues.get_mut(&github_issue.id) { - issue_exists = true; - } - - if issue_exists { - Some(RegisterIssueError::CantRegisterIssueTwice) - } else { + let issue_exists = bounty_canister.github_issues.contains_key(&github_issue_id); + if !issue_exists { + let github_issue = Issue { + id: github_issue_id.clone(), + maintainer: contributor, + bounty: Bounty { + amount: amount, + winner: None, + accepted_prs: HashMap::new(), + }, + }; + // TODO: Check contributor it's registered and github_issue_id exists on github bounty_canister .github_issues - .insert(github_issue.id.clone(), github_issue); - None + .insert(github_issue_id.clone(), github_issue); } + None } else { panic!("Bounty canister state not initialized") } @@ -81,13 +83,19 @@ mod test_register_issue { let bounty_amount: Nat = Nat(BigUint::from(100u32)); - let r: Option = - register_issue_impl(contributor, github_issue_id.clone(), bounty_amount); + let r: Option = register_issue_impl( + contributor.clone(), + github_issue_id.clone(), + bounty_amount.clone(), + ); assert!(r.is_none()); - let r2: Option = - register_issue_impl(contributor, github_issue_id.clone(), bounty_amount); + let r2: Option = register_issue_impl( + contributor.clone(), + github_issue_id.clone(), + bounty_amount.clone(), + ); assert!(r2.is_none()); } diff --git a/backend/src/bounty/api/state.rs b/backend/src/bounty/api/state.rs index b705fec..38c4ecb 100644 --- a/backend/src/bounty/api/state.rs +++ b/backend/src/bounty/api/state.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use candid::{CandidType, Principal}; +use candid::{CandidType, Nat, Principal}; use serde::{Deserialize, Serialize}; pub type IssueId = String; @@ -20,7 +20,7 @@ pub struct PullRequest { #[derive(Debug, Serialize, Deserialize, CandidType, Clone, Builder)] pub struct Bounty { - pub amount: i32, + pub amount: Nat, pub winner: Option, pub accepted_prs: HashMap, } From 68bae29b92522ca36ebf3724c8f75e161f2ecceb Mon Sep 17 00:00:00 2001 From: Marcelo Manuel Baraschi Date: Fri, 10 May 2024 17:05:31 -0300 Subject: [PATCH 6/9] Finish Register Issue --- backend/src/bounty/api/register_issue.rs | 16 ++++- backend/src/bounty/api/unregister_issue.rs | 81 +++++++++++++++++----- backend/src/lib.rs | 18 +++-- 3 files changed, 87 insertions(+), 28 deletions(-) diff --git a/backend/src/bounty/api/register_issue.rs b/backend/src/bounty/api/register_issue.rs index fa13d0a..1f03f91 100644 --- a/backend/src/bounty/api/register_issue.rs +++ b/backend/src/bounty/api/register_issue.rs @@ -1,11 +1,9 @@ use std::collections::HashMap; use super::state::IssueId; -use super::state::{Bounty, Contributor, Issue, PullRequest, BOUNTY_STATE}; +use super::state::{Bounty, Contributor, Issue, BOUNTY_STATE}; -use candid::CandidType; use candid::Nat; -use serde::{Deserialize, Serialize}; pub type RegisterIssueError = (); @@ -67,6 +65,18 @@ mod test_register_issue { register_issue_impl(contributor, github_issue_id.clone(), bounty_amount); assert!(r.is_none()); + + BOUNTY_STATE.with(|state| { + let bounty_canister = state.borrow(); + if let Some(ref bounty_canister) = *bounty_canister { + assert!(bounty_canister + .github_issues + .get(&github_issue_id) + .is_some()); + } else { + panic!("Bounty canister state not initialized"); + } + }); } #[test] fn test_cant_register_issue_twice() { diff --git a/backend/src/bounty/api/unregister_issue.rs b/backend/src/bounty/api/unregister_issue.rs index e0251a8..8e694db 100644 --- a/backend/src/bounty/api/unregister_issue.rs +++ b/backend/src/bounty/api/unregister_issue.rs @@ -3,7 +3,10 @@ use super::state::{Bounty, Contributor, Issue, PullRequest, BOUNTY_STATE}; use crate::bounty::api::register_issue::RegisterIssueError; use crate::register_issue_impl; use candid::CandidType; +use candid::Nat; +use num_bigint::BigUint; use serde::{Deserialize, Serialize}; + #[derive(Debug, Serialize, Deserialize, CandidType)] pub enum UnRegisterIssueError { IssueNotFound, @@ -41,35 +44,77 @@ mod test_unregister_issue { #[test] fn test_unregister_issue() { - let authority = - Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe") - .unwrap(); + let authority = Principal::anonymous(); + init_impl(authority); + let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); - let r1: Option = register_issue_impl(Issue { - id: github_issue_id.clone(), - maintainer: Contributor { - address: Principal::anonymous(), - crypto_address: "0x1234".to_string(), - }, - bounty: Bounty { - amount: 100, - winner: None, - accepted_prs: Default::default(), - }, - }); + + let contributor = Contributor { + address: Principal::anonymous(), + crypto_address: "0x1234".to_string(), + }; + + let bounty_amount: Nat = Nat(BigUint::from(100u32)); + + let r: Option = register_issue_impl( + contributor.clone(), + github_issue_id.clone(), + bounty_amount.clone(), + ); + + assert!(r.is_none()); let r2: Option = unregister_issue_impl(github_issue_id.clone()); assert!(r2.is_none()); + + BOUNTY_STATE.with(|state| { + let bounty_canister = state.borrow(); + if let Some(ref bounty_canister) = *bounty_canister { + assert!(bounty_canister + .github_issues + .get(&github_issue_id) + .is_none()); + } else { + panic!("Bounty canister state not initialized"); + } + }); } #[test] fn test_cant_unregister_a_non_existent_issue() { - let authority = - Principal::from_text("t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe") - .unwrap(); + let authority = Principal::anonymous(); init_impl(authority); let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); let r: Option = unregister_issue_impl(github_issue_id.clone()); assert!(r.is_some()); assert!(matches!(r, Some(UnRegisterIssueError::IssueNotFound))); } + + #[test] + fn test_unregister_issue_twice() { + let authority = Principal::anonymous(); + + init_impl(authority); + + let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); + + let contributor = Contributor { + address: Principal::anonymous(), + crypto_address: "0x1234".to_string(), + }; + + let bounty_amount: Nat = Nat(BigUint::from(100u32)); + + let r: Option = register_issue_impl( + contributor.clone(), + github_issue_id.clone(), + bounty_amount.clone(), + ); + + assert!(r.is_none()); + let r2: Option = unregister_issue_impl(github_issue_id.clone()); + assert!(r2.is_none()); + + let r3: Option = unregister_issue_impl(github_issue_id.clone()); + assert!(r3.is_none()); + } } diff --git a/backend/src/lib.rs b/backend/src/lib.rs index e4488ab..1108586 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate derive_builder; -use candid::Principal; +use candid::{Nat, Principal}; pub mod provider { pub mod github { @@ -38,7 +38,7 @@ use bounty::api::accept::{accept_impl, AcceptReceipt}; use bounty::api::deposit::{deposit_impl, DepositReceipt}; use bounty::api::init::init_impl; use bounty::api::register_issue::{register_issue_impl, RegisterIssueReceipt}; -use bounty::api::state::{Contributor, Issue}; +use bounty::api::state::{Contributor, IssueId, PullRequestId}; use bounty::api::unregister_issue::{unregister_issue_impl, UnRegisterIssueReceipt}; // GITHUB SERVICE @@ -103,8 +103,8 @@ fn init(authority: Principal) -> () { #[ic_cdk::update] fn accept( contributor: Contributor, - github_issue_id: String, - github_pr_id: String, + github_issue_id: IssueId, + github_pr_id: PullRequestId, ) -> AcceptReceipt { return accept_impl(contributor, github_issue_id, github_pr_id); } @@ -120,11 +120,15 @@ async fn healthcheck() -> String { } #[ic_cdk::update] -fn register_issue(github_issue: Issue) -> RegisterIssueReceipt { - return register_issue_impl(github_issue); +fn register_issue( + contributor: Contributor, + github_issue_id: IssueId, + amount: Nat, +) -> RegisterIssueReceipt { + return register_issue_impl(contributor, github_issue_id, amount); } #[ic_cdk::update] -fn unregister_issue(github_issue_id: String) -> UnRegisterIssueReceipt { +fn unregister_issue(github_issue_id: IssueId) -> UnRegisterIssueReceipt { return unregister_issue_impl(github_issue_id); } From c68920df0e5a2f3d160fd9b3e277da680979c42d Mon Sep 17 00:00:00 2001 From: Marcelo Manuel Baraschi Date: Fri, 10 May 2024 17:50:54 -0300 Subject: [PATCH 7/9] fixed Unregister issue + fixed tests --- backend/backend.did | 4 +- backend/src/bounty/api/claim.rs | 2 +- backend/src/bounty/api/unregister_issue.rs | 47 ++++++---------------- 3 files changed, 15 insertions(+), 38 deletions(-) diff --git a/backend/backend.did b/backend/backend.did index da33a05..60af4b3 100644 --- a/backend/backend.did +++ b/backend/backend.did @@ -103,9 +103,7 @@ type RegisterIssueErr = () type RegisterIssueReceipt = opt RegisterIssueErr; -type UnRegisterIssueErr = variant { - IssueNotFound; -}; +type UnRegisterIssueErr = () type UnRegisterIssueReceipt = opt UnRegisterIssueErr; diff --git a/backend/src/bounty/api/claim.rs b/backend/src/bounty/api/claim.rs index c644053..bd22682 100644 --- a/backend/src/bounty/api/claim.rs +++ b/backend/src/bounty/api/claim.rs @@ -184,7 +184,7 @@ fn extract_regex(regex: &str, str: &str) -> Option { let error_message = format!("Error (regex): {}", err); print!("{}", error_message); None - }, + } Ok(re) => { if let Some(captures) = re.captures(str) { if let Some(number) = captures.get(1) { diff --git a/backend/src/bounty/api/unregister_issue.rs b/backend/src/bounty/api/unregister_issue.rs index 8e694db..0bd621c 100644 --- a/backend/src/bounty/api/unregister_issue.rs +++ b/backend/src/bounty/api/unregister_issue.rs @@ -1,35 +1,19 @@ -use super::state::{Bounty, Contributor, Issue, PullRequest, BOUNTY_STATE}; - -use crate::bounty::api::register_issue::RegisterIssueError; -use crate::register_issue_impl; -use candid::CandidType; -use candid::Nat; -use num_bigint::BigUint; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, CandidType)] -pub enum UnRegisterIssueError { - IssueNotFound, -} +use super::state::BOUNTY_STATE; +use crate::IssueId; +pub type UnRegisterIssueError = (); pub type UnRegisterIssueReceipt = Option; -pub fn unregister_issue_impl(github_issue: String) -> UnRegisterIssueReceipt { - print!("Unregistering issue: {:?}", github_issue); +pub fn unregister_issue_impl(github_issue_id: IssueId) -> UnRegisterIssueReceipt { return BOUNTY_STATE.with(|state| { if let Some(ref mut bounty_canister) = *state.borrow_mut() { - let mut issue_exists = false; - - if let Some(ref mut issue) = bounty_canister.github_issues.get_mut(&github_issue) { - issue_exists = true; - } + let issue_exists = bounty_canister.github_issues.contains_key(&github_issue_id); if issue_exists { - bounty_canister.github_issues.remove(&github_issue); - None - } else { - Some(UnRegisterIssueError::IssueNotFound) + // TODO: Check contributor it's registered and github_issue_id exists on github + bounty_canister.github_issues.remove(&github_issue_id); } + None } else { panic!("Bounty canister state not initialized") } @@ -40,7 +24,11 @@ pub fn unregister_issue_impl(github_issue: String) -> UnRegisterIssueReceipt { mod test_unregister_issue { use super::*; use crate::bounty::api::init::init_impl; - use candid::Principal; + use crate::bounty::api::register_issue::RegisterIssueError; + use crate::bounty::api::state::{Contributor, BOUNTY_STATE}; + use crate::register_issue_impl; + use candid::{Nat, Principal}; + use num_bigint::BigUint; #[test] fn test_unregister_issue() { @@ -79,15 +67,6 @@ mod test_unregister_issue { } }); } - #[test] - fn test_cant_unregister_a_non_existent_issue() { - let authority = Principal::anonymous(); - init_impl(authority); - let github_issue_id = "input-output-hk/hydra/issues/1370".to_string(); - let r: Option = unregister_issue_impl(github_issue_id.clone()); - assert!(r.is_some()); - assert!(matches!(r, Some(UnRegisterIssueError::IssueNotFound))); - } #[test] fn test_unregister_issue_twice() { From 36536e679c745ad86a7d31eafb2645b89a5f879d Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Sat, 11 May 2024 00:43:09 +0200 Subject: [PATCH 8/9] Fix candid --- backend/backend.did | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/backend.did b/backend/backend.did index 60af4b3..d11b7fe 100644 --- a/backend/backend.did +++ b/backend/backend.did @@ -99,11 +99,11 @@ type AcceptErr = variant { type AcceptReceipt = opt AcceptErr; -type RegisterIssueErr = () +type RegisterIssueErr = null; type RegisterIssueReceipt = opt RegisterIssueErr; -type UnRegisterIssueErr = () +type UnRegisterIssueErr = null; type UnRegisterIssueReceipt = opt UnRegisterIssueErr; From c60e1851fc79a7f1134c8a0a0c341e16ef7dd432 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Sat, 11 May 2024 00:48:49 +0200 Subject: [PATCH 9/9] Fix CI --- .github/workflows/ci-backend.yml | 1 + .github/workflows/provision-darwin.sh | 23 ++++++++++++++++++----- .github/workflows/provision-linux.sh | 22 +++++++++++++++++----- .ic_commit | 1 + Makefile | 6 +++--- creates | 0 package.json | 2 +- 7 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 .ic_commit delete mode 100644 creates diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml index 511a967..390e64a 100644 --- a/.github/workflows/ci-backend.yml +++ b/.github/workflows/ci-backend.yml @@ -10,6 +10,7 @@ on: - .github/workflows/provision-darwin.sh - .github/workflows/provision-linux.sh - .github/workflows/backend.yaml + - .ic-commit concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/provision-darwin.sh b/.github/workflows/provision-darwin.sh index a29b42b..220d769 100755 --- a/.github/workflows/provision-darwin.sh +++ b/.github/workflows/provision-darwin.sh @@ -11,21 +11,34 @@ bash install-brew.sh rm install-brew.sh # Install Node. -version=${NODE_VERSION:=14.15.4} +version=${NODE_VERSION:=20.12.2} curl --location --output node.pkg "https://nodejs.org/dist/v$version/node-v$version.pkg" sudo installer -pkg node.pkg -store -target / rm node.pkg # Install DFINITY SDK. -curl --location --output install-dfx.sh "https://raw.githubusercontent.com/dfinity/sdk/dfxvm-install-script/install.sh" -DFX_VERSION=${DFX_VERSION:=0.19.0} DFXVM_INIT_YES=true bash install-dfx.sh +curl --location --output install-dfx.sh "https://raw.githubusercontent.com/dfinity/sdk/master/public/install-dfxvm.sh" +DFX_VERSION=${DFX_VERSION:=0.20.0} DFXVM_INIT_YES=true bash install-dfx.sh rm install-dfx.sh echo "$HOME/Library/Application Support/org.dfinity.dfx/bin" >> $GITHUB_PATH source "$HOME/Library/Application Support/org.dfinity.dfx/env" dfx cache install +# check the current ic-commit found in the main branch, check if it differs from the one in this PR branch +# if so, update the dfx cache with the latest ic artifacts +if [ -f "${GITHUB_WORKSPACE}/.ic-commit" ]; then + stable_sha=$(curl https://raw.githubusercontent.com/dfinity/examples/master/.ic-commit) + current_sha=$(sed <"$GITHUB_WORKSPACE/.ic-commit" 's/#.*$//' | sed '/^$/d') + arch="x86_64-darwin" + if [ "$current_sha" != "$stable_sha" ]; then + export current_sha + export arch + sh "$GITHUB_WORKSPACE/.github/workflows/update-dfx-cache.sh" + fi +fi + # Install ic-repl -version=0.1.2 +version=0.7.0 curl --location --output ic-repl "https://github.com/chenyan2002/ic-repl/releases/download/$version/ic-repl-macos" mv ./ic-repl /usr/local/bin/ic-repl chmod a+x /usr/local/bin/ic-repl @@ -54,4 +67,4 @@ mv "${HOME}/bin/wasmtime-v${wasmtime_version}-x86_64-macos/wasmtime" "${HOME}/bi rm "wasmtime-v${wasmtime_version}-x86_64-macos.tar.xz" # Exit temporary directory. -popd +popd \ No newline at end of file diff --git a/.github/workflows/provision-linux.sh b/.github/workflows/provision-linux.sh index 507527f..f4eadac 100755 --- a/.github/workflows/provision-linux.sh +++ b/.github/workflows/provision-linux.sh @@ -6,21 +6,33 @@ set -ex pushd /tmp # Install Node. -wget --output-document install-node.sh "https://deb.nodesource.com/setup_14.x" +wget --output-document install-node.sh "https://deb.nodesource.com/setup_20.x" sudo bash install-node.sh sudo apt-get install --yes nodejs rm install-node.sh # Install DFINITY SDK. -wget --output-document install-dfx.sh "https://raw.githubusercontent.com/dfinity/sdk/dfxvm-install-script/install.sh" -DFX_VERSION=${DFX_VERSION:=0.19.0} DFXVM_INIT_YES=true bash install-dfx.sh +wget --output-document install-dfx.sh "https://raw.githubusercontent.com/dfinity/sdk/master/public/install-dfxvm.sh" +DFX_VERSION=${DFX_VERSION:=0.20.0} DFXVM_INIT_YES=true bash install-dfx.sh rm install-dfx.sh echo "$HOME/.local/share/dfx/bin" >> $GITHUB_PATH source "$HOME/.local/share/dfx/env" dfx cache install +# check the current ic-commit found in the main branch, check if it differs from the one in this PR branch +# if so, update the dfx cache with the latest ic artifacts +if [ -f "${GITHUB_WORKSPACE}/.ic-commit" ]; then + stable_sha=$(curl https://raw.githubusercontent.com/dfinity/examples/master/.ic-commit) + current_sha=$(sed <"$GITHUB_WORKSPACE/.ic-commit" 's/#.*$//' | sed '/^$/d') + arch="x86_64-linux" + if [ "$current_sha" != "$stable_sha" ]; then + export current_sha + export arch + sh "$GITHUB_WORKSPACE/.github/workflows/update-dfx-cache.sh" + fi +fi # Install ic-repl -version=0.1.2 +version=0.7.0 curl --location --output ic-repl "https://github.com/chenyan2002/ic-repl/releases/download/$version/ic-repl-linux64" mv ./ic-repl /usr/local/bin/ic-repl chmod a+x /usr/local/bin/ic-repl @@ -53,4 +65,4 @@ echo "$HOME/bin" >> $GITHUB_PATH echo "$HOME/.cargo/bin" >> $GITHUB_PATH # Exit temporary directory. -popd +popd \ No newline at end of file diff --git a/.ic_commit b/.ic_commit new file mode 100644 index 0000000..8b38a12 --- /dev/null +++ b/.ic_commit @@ -0,0 +1 @@ +63acf4f88b20ec0c6384f4e18f0f6f69fc5d9b9f \ No newline at end of file diff --git a/Makefile b/Makefile index e2684d7..fdf0e81 100644 --- a/Makefile +++ b/Makefile @@ -97,9 +97,9 @@ test-4: # install cat $$TMP_FILE; \ rm -f $$TMP_FILE -.PHONY: test-a -.SILENT: test-a -test-a: # install +.PHONY: test +.SILENT: test +test: install # Call the backend canister for healthcheck and capture the output @echo "Calling healthcheck on backend canister..." @TMP_FILE=$$(mktemp); \ diff --git a/creates b/creates deleted file mode 100644 index e69de29..0000000 diff --git a/package.json b/package.json index de8412f..5a37812 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "webpack-dev-server": "^4.8.1" }, "engines": { - "node": "^12 || ^14 || ^16 || ^18" + "node": "16 || ^18 || ^20" }, "browserslist": [ "last 2 chrome version",