Skip to content

Commit

Permalink
feat: withdraw payments (#725)
Browse files Browse the repository at this point in the history
* feat: withdraw payments

Signed-off-by: David Dal Busco <[email protected]>

* feat: generate did

Signed-off-by: David Dal Busco <[email protected]>

* docs: withdraw balance

Signed-off-by: David Dal Busco <[email protected]>

* feat: script to widthdraw payments

Signed-off-by: David Dal Busco <[email protected]>

* test: should throw

Signed-off-by: David Dal Busco <[email protected]>

---------

Signed-off-by: David Dal Busco <[email protected]>
  • Loading branch information
peterpeterparker authored Oct 9, 2024
1 parent 10835e5 commit edd0eec
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 8 deletions.
13 changes: 13 additions & 0 deletions scripts/console.withdraw.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env node

import { consoleActorLocal } from './actor.mjs';

try {
const { withdraw_payments } = await consoleActorLocal();

await withdraw_payments();

console.log('✅ Payments successfully withdrawn.');
} catch (error) {
console.error('❌ Payments cannot be withdrawn', error);
}
1 change: 1 addition & 0 deletions src/console/console.did
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,5 @@ service : () -> {
update_rate_config : (SegmentType, RateConfig) -> ();
upload_asset_chunk : (UploadChunk) -> (UploadChunkResult);
version : () -> (text) query;
withdraw_payments : () -> (nat64);
}
9 changes: 8 additions & 1 deletion src/console/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod impls;
mod memory;
mod metadata;
mod msg;
mod payments;
mod proposals;
mod storage;
mod store;
Expand All @@ -17,6 +18,7 @@ use crate::factory::orbiter::create_orbiter as create_orbiter_console;
use crate::factory::satellite::create_satellite as create_satellite_console;
use crate::guards::{caller_is_admin_controller, caller_is_observatory};
use crate::memory::{init_storage_heap_state, STATE};
use crate::payments::payments::withdraw_balance;
use crate::proposals::{
commit_proposal as make_commit_proposal,
delete_proposal_assets as delete_proposal_assets_proposal, init_proposal as make_init_proposal,
Expand Down Expand Up @@ -51,7 +53,7 @@ use ic_cdk::api::call::ManualReply;
use ic_cdk::api::caller;
use ic_cdk::{id, trap};
use ic_cdk_macros::{export_candid, init, post_upgrade, pre_upgrade, query, update};
use ic_ledger_types::Tokens;
use ic_ledger_types::{BlockIndex, Tokens};
use junobuild_collections::types::core::CollectionKey;
use junobuild_shared::controllers::init_controllers;
use junobuild_shared::types::core::DomainName;
Expand Down Expand Up @@ -171,6 +173,11 @@ fn list_payments() -> Payments {
list_payments_state()
}

#[update(guard = "caller_is_admin_controller")]
async fn withdraw_payments() -> BlockIndex {
withdraw_balance().await.unwrap_or_else(|e| trap(&e))
}

/// Satellites

#[update]
Expand Down
1 change: 1 addition & 0 deletions src/console/src/payments/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod payments;
67 changes: 67 additions & 0 deletions src/console/src/payments/payments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use candid::Principal;
use ic_cdk::id;
use ic_ledger_types::{
account_balance, AccountBalanceArgs, AccountIdentifier, BlockIndex, Memo, Tokens,
};
use junobuild_shared::constants::IC_TRANSACTION_FEE_ICP;
use junobuild_shared::env::LEDGER;
use junobuild_shared::ledger::{principal_to_account_identifier, transfer_token, SUB_ACCOUNT};

/// Withdraws the entire balance of the Console — i.e., withdraws the payments for the additional
/// Satellites and Orbiters that have been made.
///
/// The destination account for the withdrawal is one of mine (David here).
///
/// # Returns
/// - `Ok(BlockIndex)`: If the transfer was successful, it returns the block index of the transaction.
/// - `Err(String)`: If an error occurs during the process, it returns a descriptive error message.
///
/// # Errors
/// This function can return errors in the following cases:
/// - If the account balance retrieval fails.
/// - If the transfer to the ledger fails due to insufficient balance or other issues.
///
/// # Example
/// ```rust
/// let result = withdraw_balance().await;
/// match result {
/// Ok(block_index) => println!("Withdrawal successful! Block index: {}", block_index),
/// Err(e) => println!("Error during withdrawal: {}", e),
/// }
/// ```
pub async fn withdraw_balance() -> Result<BlockIndex, String> {
let account_identifier: AccountIdentifier = AccountIdentifier::from_hex(
"e4aaed31b1cbf2dfaaca8ef9862a51b04fc4a314e2c054bae8f28d501c57068b",
)?;

let balance = console_balance().await?;

let block_index = transfer_token(
account_identifier,
Memo(0),
balance - IC_TRANSACTION_FEE_ICP,
IC_TRANSACTION_FEE_ICP,
)
.await
.map_err(|e| format!("failed to call ledger: {:?}", e))?
.map_err(|e| format!("ledger transfer error {:?}", e))?;

Ok(block_index)
}

async fn console_balance() -> Result<Tokens, String> {
let ledger = Principal::from_text(LEDGER).unwrap();

let console_account_identifier: AccountIdentifier =
principal_to_account_identifier(&id(), &SUB_ACCOUNT);

let args: AccountBalanceArgs = AccountBalanceArgs {
account: console_account_identifier,
};

let tokens = account_balance(ledger, args)
.await
.map_err(|e| format!("failed to call ledger balance: {:?}", e))?;

Ok(tokens)
}
1 change: 1 addition & 0 deletions src/declarations/console/console.did.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export interface _SERVICE {
update_rate_config: ActorMethod<[SegmentType, RateConfig], undefined>;
upload_asset_chunk: ActorMethod<[UploadChunk], UploadChunkResult>;
version: ActorMethod<[], string>;
withdraw_payments: ActorMethod<[], bigint>;
}
export declare const idlFactory: IDL.InterfaceFactory;
export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[];
3 changes: 2 additions & 1 deletion src/declarations/console/console.factory.did.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ export const idlFactory = ({ IDL }) => {
submit_proposal: IDL.Func([IDL.Nat], [IDL.Nat, Proposal], []),
update_rate_config: IDL.Func([SegmentType, RateConfig], [], []),
upload_asset_chunk: IDL.Func([UploadChunk], [UploadChunkResult], []),
version: IDL.Func([], [IDL.Text], ['query'])
version: IDL.Func([], [IDL.Text], ['query']),
withdraw_payments: IDL.Func([], [IDL.Nat64], [])
});
};
// @ts-ignore
Expand Down
3 changes: 2 additions & 1 deletion src/declarations/console/console.factory.did.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ export const idlFactory = ({ IDL }) => {
submit_proposal: IDL.Func([IDL.Nat], [IDL.Nat, Proposal], []),
update_rate_config: IDL.Func([SegmentType, RateConfig], [], []),
upload_asset_chunk: IDL.Func([UploadChunk], [UploadChunkResult], []),
version: IDL.Func([], [IDL.Text], ['query'])
version: IDL.Func([], [IDL.Text], ['query']),
withdraw_payments: IDL.Func([], [IDL.Nat64], [])
});
};
// @ts-ignore
Expand Down
23 changes: 22 additions & 1 deletion src/libs/shared/src/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,34 @@ pub async fn transfer_payment(
memo: Memo,
amount: Tokens,
fee: Tokens,
) -> CallResult<TransferResult> {
let account_identifier: AccountIdentifier = principal_to_account_identifier(to, to_sub_account);

transfer_token(account_identifier, memo, amount, fee).await
}

/// Transfers tokens to a specified account identified.
///
/// # Arguments
/// * `account_identifier` - The account identifier of the destination.
/// * `memo` - A memo for the transaction.
/// * `amount` - The amount of tokens to transfer.
/// * `fee` - The transaction fee.
///
/// # Returns
/// A result containing the transfer result or an error message.
pub async fn transfer_token(
account_identifier: AccountIdentifier,
memo: Memo,
amount: Tokens,
fee: Tokens,
) -> CallResult<TransferResult> {
let args = TransferArgs {
memo,
amount,
fee,
from_subaccount: Some(SUB_ACCOUNT),
to: principal_to_account_identifier(to, to_sub_account),
to: account_identifier,
created_at_time: None,
};

Expand Down
50 changes: 46 additions & 4 deletions src/tests/console.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { _SERVICE as ConsoleActor } from '$declarations/console/console.did';
import { idlFactory as idlFactorConsole } from '$declarations/console/console.factory.did';
import { AnonymousIdentity } from '@dfinity/agent';
import { Ed25519KeyIdentity } from '@dfinity/identity';
import { PocketIc, type Actor } from '@hadronous/pic';
import { afterEach, beforeEach, describe, expect, inject } from 'vitest';
import { CONTROLLER_ERROR_MSG } from './constants/console-tests.constants';
import { deploySegments, initMissionControls } from './utils/console-tests.utils';
import { CONSOLE_WASM_PATH } from './utils/setup-tests.utils';

Expand Down Expand Up @@ -31,9 +33,49 @@ describe('Console', () => {
await pic?.tearDown();
});

it('should throw errors if too many users are created quickly', async () => {
await expect(
async () => await initMissionControls({ actor, pic, length: 2 })
).rejects.toThrowError(new RegExp('Rate limit reached, try again later', 'i'));
describe('owner', () => {
it('should throw errors if too many users are created quickly', async () => {
await expect(
async () => await initMissionControls({ actor, pic, length: 2 })
).rejects.toThrowError(new RegExp('Rate limit reached, try again later', 'i'));
});
});

describe('anonymous', () => {
beforeEach(() => {
actor.setIdentity(new AnonymousIdentity());
});

it('should throw errors on list payments', async () => {
const { list_payments } = actor;

await expect(list_payments()).rejects.toThrow(CONTROLLER_ERROR_MSG);
});

it('should throw errors on withdraw payments', async () => {
const { withdraw_payments } = actor;

await expect(withdraw_payments()).rejects.toThrow(CONTROLLER_ERROR_MSG);
});
});

describe('random', () => {
const randomCaller = Ed25519KeyIdentity.generate();

beforeEach(() => {
actor.setIdentity(randomCaller);
});

it('should throw errors on list payments', async () => {
const { list_payments } = actor;

await expect(list_payments()).rejects.toThrow(CONTROLLER_ERROR_MSG);
});

it('should throw errors on withdraw payments', async () => {
const { withdraw_payments } = actor;

await expect(withdraw_payments()).rejects.toThrow(CONTROLLER_ERROR_MSG);
});
});
});

0 comments on commit edd0eec

Please sign in to comment.