From 3988a3b0231661fa62c6fd2e06ae98a6835ed9f1 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 10 Aug 2024 15:02:57 +0200 Subject: [PATCH] feat: admin_withdraw_icp method --- .../src/client/ekoke_liquidity_pool.rs | 16 ++++- .../tests/inspect/ekoke_liquidity_pool.rs | 33 +++++++++ integration-tests/tests/inspect/mod.rs | 1 + integration-tests/tests/use_case/mod.rs | 1 + .../use_case/withdraw_liquidity_pool_icp.rs | 34 +++++++++ .../ekoke-liquidity-pool.did | 9 +-- .../ekoke-liquidity-pool.did.d.ts | 11 +-- .../ekoke-liquidity-pool.did.js | 71 ++++++++++--------- .../ekoke-liquidity-pool.did | 9 +-- src/ekoke_liquidity_pool/src/app.rs | 9 +++ src/ekoke_liquidity_pool/src/lib.rs | 8 ++- 11 files changed, 151 insertions(+), 51 deletions(-) create mode 100644 integration-tests/tests/inspect/ekoke_liquidity_pool.rs create mode 100644 integration-tests/tests/use_case/withdraw_liquidity_pool_icp.rs diff --git a/integration-tests/src/client/ekoke_liquidity_pool.rs b/integration-tests/src/client/ekoke_liquidity_pool.rs index c982e97..ebbb6d9 100644 --- a/integration-tests/src/client/ekoke_liquidity_pool.rs +++ b/integration-tests/src/client/ekoke_liquidity_pool.rs @@ -1,7 +1,8 @@ -use candid::{Encode, Principal}; +use candid::{Encode, Nat, Principal}; use did::ekoke_liquidity_pool::WithdrawError; -use icrc::icrc1::account::Subaccount; +use icrc::icrc1::account::{Account, Subaccount}; +use crate::actor::admin; use crate::TestEnv; pub struct EkokeLiquidityPoolClient<'a> { @@ -33,4 +34,15 @@ impl<'a> EkokeLiquidityPoolClient<'a> { ) .unwrap() } + + pub fn admin_withdraw_icp(&self, to: Account, amount: Nat) -> Result<(), WithdrawError> { + self.env + .update( + self.env.ekoke_liquidity_pool_id, + admin(), + "admin_withdraw_icp", + Encode!(&to, &amount).unwrap(), + ) + .unwrap() + } } diff --git a/integration-tests/tests/inspect/ekoke_liquidity_pool.rs b/integration-tests/tests/inspect/ekoke_liquidity_pool.rs new file mode 100644 index 0000000..ba24add --- /dev/null +++ b/integration-tests/tests/inspect/ekoke_liquidity_pool.rs @@ -0,0 +1,33 @@ +use candid::{Encode, Nat}; +use integration_tests::actor::{admin, bob}; +use integration_tests::TestEnv; + +#[test] +#[serial_test::serial] +fn test_should_inspect_is_admin() { + let env = TestEnv::init(); + + assert!(env + .update::( + env.ekoke_liquidity_pool_id, + admin(), + "admin_cycles", + Encode!().unwrap(), + ) + .is_ok()); +} + +#[test] +#[serial_test::serial] +fn test_should_fail_inspect_admin() { + let env = TestEnv::init(); + // not an admin + assert!(env + .update::( + env.ekoke_liquidity_pool_id, + bob(), + "admin_cycles", + Encode!().unwrap(), + ) + .is_err()); +} diff --git a/integration-tests/tests/inspect/mod.rs b/integration-tests/tests/inspect/mod.rs index 8d37c9d..ff4e040 100644 --- a/integration-tests/tests/inspect/mod.rs +++ b/integration-tests/tests/inspect/mod.rs @@ -1,4 +1,5 @@ mod deferred; mod ekoke_erc20_swap; +mod ekoke_liquidity_pool; mod ekoke_reward_pool; mod marketplace; diff --git a/integration-tests/tests/use_case/mod.rs b/integration-tests/tests/use_case/mod.rs index 7aa2812..76fff24 100644 --- a/integration-tests/tests/use_case/mod.rs +++ b/integration-tests/tests/use_case/mod.rs @@ -9,3 +9,4 @@ mod register_sell_contract; mod reserve_reward_pool; mod update_contract_property; mod withdraw_contract_deposit; +mod withdraw_liquidity_pool_icp; diff --git a/integration-tests/tests/use_case/withdraw_liquidity_pool_icp.rs b/integration-tests/tests/use_case/withdraw_liquidity_pool_icp.rs new file mode 100644 index 0000000..179c00f --- /dev/null +++ b/integration-tests/tests/use_case/withdraw_liquidity_pool_icp.rs @@ -0,0 +1,34 @@ +use candid::Nat; +use integration_tests::actor::bob_account; +use integration_tests::client::{EkokeLiquidityPoolClient, IcrcLedgerClient}; +use integration_tests::TestEnv; + +#[test] +#[serial_test::serial] +fn test_as_seller_i_should_withdraw_contract_deposit_after_being_paid() { + let env = TestEnv::init(); + let icp_ledger_client = IcrcLedgerClient::new(env.icp_ledger_id, &env); + let ekoke_liquidity_pool_client = EkokeLiquidityPoolClient::from(&env); + + // withdraw icp to bob + let current_bob_balance = icp_ledger_client.icrc1_balance_of(bob_account()); + let current_pool_balance = + icp_ledger_client.icrc1_balance_of(env.ekoke_liquidity_pool_id.into()); + + let icp_fee = icp_ledger_client.icrc1_fee(); + + let withdraw_amount = Nat::from(1_000_000_000u64); + + let expected_bob_balance = current_bob_balance + withdraw_amount.clone(); + let expected_pool_balance = current_pool_balance - withdraw_amount.clone() - icp_fee; + + assert!(ekoke_liquidity_pool_client + .admin_withdraw_icp(bob_account(), withdraw_amount) + .is_ok()); + + let new_bob_balance = icp_ledger_client.icrc1_balance_of(bob_account()); + let new_pool_balance = icp_ledger_client.icrc1_balance_of(env.ekoke_liquidity_pool_id.into()); + + assert_eq!(new_bob_balance, expected_bob_balance); + assert_eq!(new_pool_balance, expected_pool_balance); +} diff --git a/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did b/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did index 23ea558..d8358e0 100644 --- a/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did +++ b/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did @@ -70,8 +70,8 @@ type RejectionCode = variant { SysFatal; CanisterReject; }; -type Result = variant { Ok : LiquidityPoolBalance; Err : EkokeError }; -type Result_1 = variant { Ok; Err : WithdrawError }; +type Result = variant { Ok; Err : WithdrawError }; +type Result_1 = variant { Ok : LiquidityPoolBalance; Err : EkokeError }; type TransferError = variant { GenericError : record { message : text; error_code : nat }; TemporarilyUnavailable; @@ -103,9 +103,10 @@ service : (EkokeLiquidityPoolInitData) -> { admin_set_admins : (vec principal) -> (); admin_set_deferred_canister : (principal) -> (); admin_set_icp_ledger_canister : (principal) -> (); + admin_withdraw_icp : (Account, nat) -> (Result); create_refunds : (vec record { principal; nat }) -> (); http_request : (HttpRequest) -> (HttpResponse) query; liquidity_pool_accounts : () -> (LiquidityPoolAccounts) query; - liquidity_pool_balance : () -> (Result) query; - withdraw_refund : (opt blob) -> (Result_1); + liquidity_pool_balance : () -> (Result_1) query; + withdraw_refund : (opt blob) -> (Result); } \ No newline at end of file diff --git a/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did.d.ts b/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did.d.ts index a227030..ca4a741 100644 --- a/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did.d.ts +++ b/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did.d.ts @@ -72,10 +72,10 @@ export type RejectionCode = { 'NoError' : null } | { 'Unknown' : null } | { 'SysFatal' : null } | { 'CanisterReject' : null }; -export type Result = { 'Ok' : LiquidityPoolBalance } | - { 'Err' : EkokeError }; -export type Result_1 = { 'Ok' : null } | +export type Result = { 'Ok' : null } | { 'Err' : WithdrawError }; +export type Result_1 = { 'Ok' : LiquidityPoolBalance } | + { 'Err' : EkokeError }; export type TransferError = { 'GenericError' : { 'message' : string, 'error_code' : bigint } } | @@ -105,11 +105,12 @@ export interface _SERVICE { 'admin_set_admins' : ActorMethod<[Array], undefined>, 'admin_set_deferred_canister' : ActorMethod<[Principal], undefined>, 'admin_set_icp_ledger_canister' : ActorMethod<[Principal], undefined>, + 'admin_withdraw_icp' : ActorMethod<[Account, bigint], Result>, 'create_refunds' : ActorMethod<[Array<[Principal, bigint]>], undefined>, 'http_request' : ActorMethod<[HttpRequest], HttpResponse>, 'liquidity_pool_accounts' : ActorMethod<[], LiquidityPoolAccounts>, - 'liquidity_pool_balance' : ActorMethod<[], Result>, - 'withdraw_refund' : ActorMethod<[[] | [Uint8Array | number[]]], Result_1>, + 'liquidity_pool_balance' : ActorMethod<[], Result_1>, + 'withdraw_refund' : ActorMethod<[[] | [Uint8Array | number[]]], Result>, } export declare const idlFactory: IDL.InterfaceFactory; export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did.js b/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did.js index 9790c94..f821b4a 100644 --- a/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did.js +++ b/src/declarations/ekoke-liquidity-pool/ekoke-liquidity-pool.did.js @@ -4,6 +4,38 @@ export const idlFactory = ({ IDL }) => { 'icp_ledger_canister' : IDL.Principal, 'admins' : IDL.Vec(IDL.Principal), }); + const Account = IDL.Record({ + 'owner' : IDL.Principal, + 'subaccount' : IDL.Opt(IDL.Vec(IDL.Nat8)), + }); + const TransferError = IDL.Variant({ + 'GenericError' : IDL.Record({ + 'message' : IDL.Text, + 'error_code' : IDL.Nat, + }), + 'TemporarilyUnavailable' : IDL.Null, + 'BadBurn' : IDL.Record({ 'min_burn_amount' : IDL.Nat }), + 'Duplicate' : IDL.Record({ 'duplicate_of' : IDL.Nat }), + 'BadFee' : IDL.Record({ 'expected_fee' : IDL.Nat }), + 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }), + 'TooOld' : IDL.Null, + 'InsufficientFunds' : IDL.Record({ 'balance' : IDL.Nat }), + }); + const RejectionCode = IDL.Variant({ + 'NoError' : IDL.Null, + 'CanisterError' : IDL.Null, + 'SysTransient' : IDL.Null, + 'DestinationInvalid' : IDL.Null, + 'Unknown' : IDL.Null, + 'SysFatal' : IDL.Null, + 'CanisterReject' : IDL.Null, + }); + const WithdrawError = IDL.Variant({ + 'NothingToWithdraw' : IDL.Principal, + 'Transfer' : TransferError, + 'CanisterCall' : IDL.Tuple(RejectionCode, IDL.Text), + }); + const Result = IDL.Variant({ 'Ok' : IDL.Null, 'Err' : WithdrawError }); const HttpRequest = IDL.Record({ 'url' : IDL.Text, 'method' : IDL.Text, @@ -16,10 +48,6 @@ export const idlFactory = ({ IDL }) => { 'upgrade' : IDL.Opt(IDL.Bool), 'status_code' : IDL.Nat16, }); - const Account = IDL.Record({ - 'owner' : IDL.Principal, - 'subaccount' : IDL.Opt(IDL.Vec(IDL.Nat8)), - }); const LiquidityPoolAccounts = IDL.Record({ 'icp' : Account }); const LiquidityPoolBalance = IDL.Record({ 'icp' : IDL.Nat }); const ConfigurationError = IDL.Variant({ @@ -40,19 +68,6 @@ export const idlFactory = ({ IDL }) => { 'Expired' : IDL.Record({ 'ledger_time' : IDL.Nat64 }), 'InsufficientFunds' : IDL.Record({ 'balance' : IDL.Nat }), }); - const TransferError = IDL.Variant({ - 'GenericError' : IDL.Record({ - 'message' : IDL.Text, - 'error_code' : IDL.Nat, - }), - 'TemporarilyUnavailable' : IDL.Null, - 'BadBurn' : IDL.Record({ 'min_burn_amount' : IDL.Nat }), - 'Duplicate' : IDL.Record({ 'duplicate_of' : IDL.Nat }), - 'BadFee' : IDL.Record({ 'expected_fee' : IDL.Nat }), - 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }), - 'TooOld' : IDL.Null, - 'InsufficientFunds' : IDL.Record({ 'balance' : IDL.Nat }), - }); const PoolError = IDL.Variant({ 'PoolNotFound' : IDL.Nat, 'NotEnoughTokens' : IDL.Null, @@ -66,15 +81,6 @@ export const idlFactory = ({ IDL }) => { 'InsufficientFunds' : IDL.Null, }); const RegisterError = IDL.Variant({ 'TransactionNotFound' : IDL.Null }); - const RejectionCode = IDL.Variant({ - 'NoError' : IDL.Null, - 'CanisterError' : IDL.Null, - 'SysTransient' : IDL.Null, - 'DestinationInvalid' : IDL.Null, - 'Unknown' : IDL.Null, - 'SysFatal' : IDL.Null, - 'CanisterReject' : IDL.Null, - }); const BalanceError = IDL.Variant({ 'AccountNotFound' : IDL.Null, 'InsufficientBalance' : IDL.Null, @@ -113,21 +119,16 @@ export const idlFactory = ({ IDL }) => { 'Icrc2Transfer' : TransferFromError, 'Ecdsa' : EcdsaError, }); - const Result = IDL.Variant({ + const Result_1 = IDL.Variant({ 'Ok' : LiquidityPoolBalance, 'Err' : EkokeError, }); - const WithdrawError = IDL.Variant({ - 'NothingToWithdraw' : IDL.Principal, - 'Transfer' : TransferError, - 'CanisterCall' : IDL.Tuple(RejectionCode, IDL.Text), - }); - const Result_1 = IDL.Variant({ 'Ok' : IDL.Null, 'Err' : WithdrawError }); return IDL.Service({ 'admin_cycles' : IDL.Func([], [IDL.Nat], ['query']), 'admin_set_admins' : IDL.Func([IDL.Vec(IDL.Principal)], [], []), 'admin_set_deferred_canister' : IDL.Func([IDL.Principal], [], []), 'admin_set_icp_ledger_canister' : IDL.Func([IDL.Principal], [], []), + 'admin_withdraw_icp' : IDL.Func([Account, IDL.Nat], [Result], []), 'create_refunds' : IDL.Func( [IDL.Vec(IDL.Tuple(IDL.Principal, IDL.Nat))], [], @@ -139,8 +140,8 @@ export const idlFactory = ({ IDL }) => { [LiquidityPoolAccounts], ['query'], ), - 'liquidity_pool_balance' : IDL.Func([], [Result], ['query']), - 'withdraw_refund' : IDL.Func([IDL.Opt(IDL.Vec(IDL.Nat8))], [Result_1], []), + 'liquidity_pool_balance' : IDL.Func([], [Result_1], ['query']), + 'withdraw_refund' : IDL.Func([IDL.Opt(IDL.Vec(IDL.Nat8))], [Result], []), }); }; export const init = ({ IDL }) => { diff --git a/src/ekoke_liquidity_pool/ekoke-liquidity-pool.did b/src/ekoke_liquidity_pool/ekoke-liquidity-pool.did index 23ea558..d8358e0 100644 --- a/src/ekoke_liquidity_pool/ekoke-liquidity-pool.did +++ b/src/ekoke_liquidity_pool/ekoke-liquidity-pool.did @@ -70,8 +70,8 @@ type RejectionCode = variant { SysFatal; CanisterReject; }; -type Result = variant { Ok : LiquidityPoolBalance; Err : EkokeError }; -type Result_1 = variant { Ok; Err : WithdrawError }; +type Result = variant { Ok; Err : WithdrawError }; +type Result_1 = variant { Ok : LiquidityPoolBalance; Err : EkokeError }; type TransferError = variant { GenericError : record { message : text; error_code : nat }; TemporarilyUnavailable; @@ -103,9 +103,10 @@ service : (EkokeLiquidityPoolInitData) -> { admin_set_admins : (vec principal) -> (); admin_set_deferred_canister : (principal) -> (); admin_set_icp_ledger_canister : (principal) -> (); + admin_withdraw_icp : (Account, nat) -> (Result); create_refunds : (vec record { principal; nat }) -> (); http_request : (HttpRequest) -> (HttpResponse) query; liquidity_pool_accounts : () -> (LiquidityPoolAccounts) query; - liquidity_pool_balance : () -> (Result) query; - withdraw_refund : (opt blob) -> (Result_1); + liquidity_pool_balance : () -> (Result_1) query; + withdraw_refund : (opt blob) -> (Result); } \ No newline at end of file diff --git a/src/ekoke_liquidity_pool/src/app.rs b/src/ekoke_liquidity_pool/src/app.rs index 380853a..474d306 100644 --- a/src/ekoke_liquidity_pool/src/app.rs +++ b/src/ekoke_liquidity_pool/src/app.rs @@ -73,6 +73,15 @@ impl EkokeLiquidityPoolCanister { Ok(()) } + /// Withdraw icp to an account + pub async fn admin_withdraw_icp(to: Account, amount: Nat) -> Result<(), WithdrawError> { + if !Inspect::inspect_is_admin(utils::caller()) { + ic_cdk::trap("Unauthorized"); + } + + LiquidityPool::withdraw_icp(to, amount).await + } + /// Returns cycles pub fn admin_cycles() -> Nat { if !Inspect::inspect_is_admin(utils::caller()) { diff --git a/src/ekoke_liquidity_pool/src/lib.rs b/src/ekoke_liquidity_pool/src/lib.rs index 299d558..8c34d5c 100644 --- a/src/ekoke_liquidity_pool/src/lib.rs +++ b/src/ekoke_liquidity_pool/src/lib.rs @@ -16,7 +16,7 @@ use did::ekoke_liquidity_pool::{ EkokeLiquidityPoolInitData, LiquidityPoolAccounts, LiquidityPoolBalance, WithdrawError, }; use ic_cdk_macros::{init, query, update}; -use icrc::icrc1::account::Subaccount; +use icrc::icrc1::account::{Account, Subaccount}; use self::app::EkokeLiquidityPoolCanister; @@ -49,6 +49,12 @@ pub async fn withdraw_refund(subaccount: Option) -> Result<(), Withd EkokeLiquidityPoolCanister::withdraw_refund(subaccount).await } +#[update] +#[candid_method(update)] +pub async fn admin_withdraw_icp(to: Account, amount: Nat) -> Result<(), WithdrawError> { + EkokeLiquidityPoolCanister::admin_withdraw_icp(to, amount).await +} + #[query] #[candid_method(query)] pub fn admin_cycles() -> Nat {