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

feat(station): display station and upgrader cycle balance on Administration page #457

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
70 changes: 52 additions & 18 deletions apps/wallet/src/components/settings/StationInfoCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,23 +126,56 @@
</VListItemSubtitle>
</VListItem>
<AuthCheck :privileges="[Privilege.SystemInfo]">
<VListItem v-if="!loadingSystemInfo" class="px-0">
<tempalte v-if="!loadingSystemInfo">
<VListItem class="px-0">
<VListItemTitle class="font-weight-bold">{{
$t(`terms.upgrader_id`)
}}</VListItemTitle>
<VListItemSubtitle v-if="upgraderId"
>{{ upgraderId }}
<VBtn
size="x-small"
variant="text"
:icon="mdiContentCopy"
@click="
copyToClipboard({
textToCopy: upgraderId,
sendNotification: true,
})
"
/>
</VListItemSubtitle>
</VListItem>

<VListItem class="px-0">
<VListItemTitle class="font-weight-bold">{{
$t(`pages.administration.cycle_balances`)
}}</VListItemTitle>
<VListItemSubtitle>
<table style="min-width: 200px">
<tr>
<td>{{ $t('terms.station') }}:</td>
<td class="text-right">
{{ systemInfo ? formatCycles(systemInfo.cycles) : '-' }}
</td>
</tr>
<tr>
<td>{{ $t('terms.upgrader') }}:</td>
<td class="text-right">
{{
systemInfo?.upgrader_cycles?.[0]
? formatCycles(systemInfo.upgrader_cycles[0])
: '-'
}}
</td>
</tr>
</table>
</VListItemSubtitle>
</VListItem>
</tempalte>
<VListItem class="px-0" v-else-if="loadingSystemInfoError">
<VListItemTitle class="font-weight-bold">{{ $t(`terms.upgrader_id`) }}</VListItemTitle>
<VListItemSubtitle v-if="upgraderId"
>{{ upgraderId }}
<VBtn
size="x-small"
variant="text"
:icon="mdiContentCopy"
@click="
copyToClipboard({
textToCopy: upgraderId,
sendNotification: true,
})
"
/>
</VListItemSubtitle>
<VListItemSubtitle v-else-if="loadingSystemInfoError">
<VListItemSubtitle>
<VAlert type="error" variant="tonal" density="compact" class="mb-4 mt-2">
{{ $t('pages.administration.system_info_error') }}
</VAlert>
Expand Down Expand Up @@ -238,6 +271,7 @@ import {
Request,
SystemInfo,
} from '~/generated/station/station.did';
import { formatCycles } from '~/mappers/cycles.mapper';
import { storeUserStationToUserStation } from '~/mappers/stations.mapper';
import { i18n } from '~/plugins/i18n.plugin';
import { services } from '~/plugins/services.plugin';
Expand All @@ -246,9 +280,9 @@ import { useSessionStore } from '~/stores/session.store';
import { useStationStore } from '~/stores/station.store';
import { Privilege } from '~/types/auth.types';
import { copyToClipboard } from '~/utils/app.utils';
import StationInfoForm, { StationInfoModel } from './StationInfoForm.vue';
import { unreachable, variantIs } from '~/utils/helper.utils';
import { hasRequiredPrivilege } from '~/utils/auth.utils';
import { unreachable, variantIs } from '~/utils/helper.utils';
import StationInfoForm, { StationInfoModel } from './StationInfoForm.vue';

const station = useStationStore();
const session = useSessionStore();
Expand Down
6 changes: 4 additions & 2 deletions apps/wallet/src/generated/station/station.did
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ type MonitorExternalCanisterStartInput = record {
// The strategy for funding the canister.
funding_strategy : MonitorExternalCanisterStrategyInput;
// The strategy for obtaining cycles for the funding operation.
cycle_obtain_strategy: opt CycleObtainStrategyInput;
cycle_obtain_strategy : opt CycleObtainStrategyInput;
};

// The operation kind for monitoring an external canister in the station.
Expand Down Expand Up @@ -2033,8 +2033,10 @@ type SystemInfo = record {
version : text;
// The upgrader principal id.
upgrader_id : principal;
// Cycle balance of the canister.
// Cycle balance of the station.
cycles : nat64;
// Cycle balance of the canister.
upgrader_cycles : opt nat64;
// The time at which the canister was last upgraded.
last_upgrade_timestamp : TimestampRFC3339;
// Did the canister successfully fetched randomness from the management canister.
Expand Down
1 change: 1 addition & 0 deletions apps/wallet/src/generated/station/station.did.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,7 @@ export interface SupportedBlockchain {
}
export interface SystemInfo {
'disaster_recovery' : [] | [DisasterRecovery],
'upgrader_cycles' : [] | [bigint],
'name' : string,
'last_upgrade_timestamp' : TimestampRFC3339,
'raw_rand_successful' : boolean,
Expand Down
1 change: 1 addition & 0 deletions apps/wallet/src/generated/station/station.did.js
Original file line number Diff line number Diff line change
Expand Up @@ -1571,6 +1571,7 @@ export const idlFactory = ({ IDL }) => {
});
const SystemInfo = IDL.Record({
'disaster_recovery' : IDL.Opt(DisasterRecovery),
'upgrader_cycles' : IDL.Opt(IDL.Nat64),
'name' : IDL.Text,
'last_upgrade_timestamp' : TimestampRFC3339,
'raw_rand_successful' : IDL.Bool,
Expand Down
1 change: 1 addition & 0 deletions apps/wallet/src/locales/en.locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,7 @@ export default {
cycle_obtain_strategy_disabled:
'WARNING: Station cycle balance top-up disabled. Your station may run out of cycles.',
cycle_obtain_strategy_mint_from_native_token: 'Mint from ICP account',
cycle_balances: 'Cycle Balances',
},
users: {
title: 'Users',
Expand Down
1 change: 1 addition & 0 deletions apps/wallet/src/locales/fr.locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ export default {
system_info_error: 'Erreur lors du chargement des informations système, veuillez réessayer.',
cycle_obtain_strategy_disabled: 'Stratégie de recharge des cycles non définie',
cycle_obtain_strategy_mint_from_native_token: 'Mint depuis le compte ICP',
cycle_balances: 'Solde de Cycles',
},
users: {
title: 'Usagers',
Expand Down
1 change: 1 addition & 0 deletions apps/wallet/src/locales/pt.locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,7 @@ export default {
'Erro ao carregar as informações do sistema da carteira, por favor, tente novamente.',
cycle_obtain_strategy_disabled: 'AVISO: Recarga de saldo de ciclos da carteira desativada.',
cycle_obtain_strategy_mint_from_native_token: 'Recarregar a partir da conta de ICP',
cycle_balances: 'Saldo de ciclos',
},
user_groups: {
title: 'Grupos de usuários',
Expand Down
28 changes: 27 additions & 1 deletion apps/wallet/src/mappers/cycles.mapper.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { describe, expect, it } from 'vitest';
import { fromCyclesUnit, toCyclesUnit } from '~/mappers/cycles.mapper';
import {
cyclesUnitFromNumber,
formatCycles,
fromCyclesUnit,
toCyclesUnit,
} from '~/mappers/cycles.mapper';
import { CyclesUnit } from '~/types/app.types';

describe('toCyclesUnit', () => {
Expand Down Expand Up @@ -69,3 +74,24 @@ describe('fromCyclesUnit', () => {
expect(() => fromCyclesUnit(1, 'unknown' as CyclesUnit)).toThrow();
});
});

describe('cyclesUnitFromNumber', () => {
it('should return the correct unit based on the number provided', () => {
expect(cyclesUnitFromNumber(1_000_000_000_000n)).toBe(CyclesUnit.Trillion);
expect(cyclesUnitFromNumber(1_000_000_000n)).toBe(CyclesUnit.Billion);
expect(cyclesUnitFromNumber(1_000_000n)).toBe(CyclesUnit.Million);
expect(cyclesUnitFromNumber(1n)).toBe(CyclesUnit.Smallest);
});
});

describe('formatCycles', () => {
it('should return the formatted cycles amount from a value', () => {
expect(formatCycles(1_000_000_000_000n)).toBe(`1 TC`);
expect(formatCycles(1_100_000_000_000n)).toBe(`1.1 TC`);
expect(formatCycles(1_000_000_000n)).toBe(`1 BC`);
expect(formatCycles(1_100_000_000n)).toBe(`1.1 BC`);
expect(formatCycles(1_000_000n)).toBe(`1 MC`);
expect(formatCycles(1_100_000n)).toBe(`1.1 MC`);
expect(formatCycles(1n)).toBe(`1 Cycles`);
});
});
18 changes: 18 additions & 0 deletions apps/wallet/src/mappers/cycles.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,21 @@ export const fromCyclesUnit = (cycles: number, unit: CyclesUnit): bigint => {
return unreachable(unit);
}
};

export const cyclesUnitFromNumber = (cycles: bigint): CyclesUnit => {
if (cycles >= 1_000_000_000_000) {
return CyclesUnit.Trillion;
}
if (cycles >= 1_000_000_000) {
return CyclesUnit.Billion;
}
if (cycles >= 1_000_000) {
return CyclesUnit.Million;
}
return CyclesUnit.Smallest;
};

export const formatCycles = (cycles: bigint): string => {
const unit = cyclesUnitFromNumber(cycles);
return `${toCyclesUnit(cycles, unit)} ${unit}`;
};
2 changes: 1 addition & 1 deletion apps/wallet/src/types/app.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export enum CyclesUnit {
Billion = 'BC',
Million = 'MC',
// Can be used to display cycles in the smallest unit.
Smallest = 'e8s',
Smallest = 'Cycles',
}

export enum TimeUnit {
Expand Down
6 changes: 4 additions & 2 deletions core/station/api/spec.did
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ type MonitorExternalCanisterStartInput = record {
// The strategy for funding the canister.
funding_strategy : MonitorExternalCanisterStrategyInput;
// The strategy for obtaining cycles for the funding operation.
cycle_obtain_strategy: opt CycleObtainStrategyInput;
cycle_obtain_strategy : opt CycleObtainStrategyInput;
};

// The operation kind for monitoring an external canister in the station.
Expand Down Expand Up @@ -2033,8 +2033,10 @@ type SystemInfo = record {
version : text;
// The upgrader principal id.
upgrader_id : principal;
// Cycle balance of the canister.
// Cycle balance of the station.
cycles : nat64;
// Cycle balance of the canister.
upgrader_cycles : opt nat64;
// The time at which the canister was last upgraded.
last_upgrade_timestamp : TimestampRFC3339;
// Did the canister successfully fetched randomness from the management canister.
Expand Down
1 change: 1 addition & 0 deletions core/station/api/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct SystemInfoDTO {
pub version: String,
pub upgrader_id: Principal,
pub cycles: u64,
pub upgrader_cycles: Option<u64>,
pub last_upgrade_timestamp: TimestampRfc3339,
pub raw_rand_successful: bool,
pub disaster_recovery: Option<DisasterRecoveryDTO>,
Expand Down
14 changes: 12 additions & 2 deletions core/station/impl/src/controllers/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
errors::AuthorizationError,
migration,
models::resource::{Resource, SystemResourceAction},
services::{SystemService, INITIALIZING, SYSTEM_SERVICE},
services::{SystemService, CYCLE_MANAGER, INITIALIZING, SYSTEM_SERVICE},
SYSTEM_VERSION,
};
use ic_cdk_macros::{post_upgrade, query, update};
Expand Down Expand Up @@ -137,9 +137,19 @@ impl SystemController {
async fn system_info(&self) -> ApiResult<SystemInfoResponse> {
let system_info = self.system_service.get_system_info();
let cycles = canister_balance();
let upgrader_balance = CYCLE_MANAGER.get_canister(system_info.get_upgrader_canister_id());

Ok(SystemInfoResponse {
system: system_info.to_dto(&cycles, SYSTEM_VERSION),
system: system_info.to_dto(
&cycles,
SYSTEM_VERSION,
upgrader_balance.and_then(|record| {
record
.get_cycles()
.as_ref()
.map(|cycles_balance| cycles_balance.amount as u64)
}),
),
})
}

Expand Down
8 changes: 7 additions & 1 deletion core/station/impl/src/mappers/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ use orbit_essentials::{
use station_api::DisasterRecoveryDTO;

impl SystemInfo {
pub fn to_dto(&self, cycles: &u64, version: &str) -> station_api::SystemInfoDTO {
pub fn to_dto(
&self,
cycles: &u64,
version: &str,
upgrader_cycles: Option<u64>,
) -> station_api::SystemInfoDTO {
station_api::SystemInfoDTO {
name: self.get_name().to_string(),
last_upgrade_timestamp: timestamp_to_rfc3339(&self.get_last_upgrade_timestamp()),
Expand All @@ -23,6 +28,7 @@ impl SystemInfo {
}
}),
cycle_obtain_strategy: (*self.get_cycle_obtain_strategy()).into(),
upgrader_cycles,
}
}
}
5 changes: 5 additions & 0 deletions core/station/impl/src/services/cycle_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::repositories::ACCOUNT_REPOSITORY;
use canfund::api::cmc::IcCyclesMintingCanister;
use canfund::api::ledger::IcLedgerCanister;
use canfund::manager::options::{FundManagerOptions, ObtainCyclesOptions};
use canfund::manager::record::CanisterRecord;
use canfund::manager::RegisterOpts;
use canfund::operations::obtain::MintCycles;
use canfund::FundManager;
Expand Down Expand Up @@ -65,6 +66,10 @@ impl CycleManager {
));
}

pub fn get_canister(&self, canister_id: &CanisterId) -> Option<CanisterRecord> {
FUND_MANAGER.with(|manager| manager.borrow().get_canisters().get(canister_id).cloned())
}

pub fn remove_canister(&self, canister_id: CanisterId) {
FUND_MANAGER.with(|manager| {
manager.borrow_mut().unregister(canister_id);
Expand Down
1 change: 1 addition & 0 deletions core/station/impl/src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub use external_canister::*;
pub mod permission;

mod cycle_manager;
pub use cycle_manager::*;

mod disaster_recovery;
pub use disaster_recovery::*;
Expand Down
Loading