Стандарт: SOLPDT 1.1 — Agentic Trust Layer Extension
Настоящее приложение содержит референсную имплементацию механизмов амортизации нематериальных активов (НМА) в рамках стандарта SOLPDT 1.1. Документ предназначен для:
В документе приведены математические формулы, примеры кода на Rust (для смарт-контрактов Solana) и JavaScript (для off-chain вычислений), а также правила интеграции с другими компонентами SOLPDT.
Стандарт SOLPDT 1.1 поддерживает три метода начисления амортизации, определённые в поле amortization_method блока accounting_asset_details (см. Приложение D).
Применяется по умолчанию. Стоимость актива равномерно списывается в течение всего срока полезного использования.
Остаточная стоимость через N месяцев:
Ускоренная амортизация, при которой в первые периоды списывается большая сумма.
Амортизация начисляется исходя из фактического использования актива.
В сети Solana время измеряется в Unix-секундах, доступных через системный вызов Clock::get()?.unix_timestamp. Использование временных меток (вместо номера слота) гарантирует детерминированный расчёт амортизации независимо от пропусков слотов или форков.
Важные константы:
| Константа | Значение (секунды) | Примечание |
|---|---|---|
SECONDS_PER_MINUTE | 60 | |
SECONDS_PER_HOUR | 3 600 | |
SECONDS_PER_DAY | 86 400 | |
SECONDS_PER_MONTH | 2 629 743 | 30.44 дня (365.25 / 12) |
SECONDS_PER_YEAR | 31 556 926 | 365.25 дней |
use anchor_lang::prelude::*;
// Константы времени
pub const SECONDS_PER_MONTH: i64 = 2_629_743; // 30.44 дня
pub const SECONDS_PER_YEAR: i64 = 31_556_926; // 365.25 дней
// Методы амортизации
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq)]
pub enum AmortizationMethod {
StraightLine,
DecliningBalance { acceleration_factor: u8 }, // 1-3, типично 2
UnitsOfProduction,
}
// Структура учёта амортизации
#[account]
#[derive(Default)]
pub struct AmortizationAccount {
pub initial_cost: u64, // Первоначальная стоимость (в минимальных единицах)
pub residual_value: u64, // Ликвидационная стоимость
pub useful_life_months: u64, // СПИ в месяцах
pub method: AmortizationMethod, // Метод амортизации
pub start_timestamp: i64, // Unix-время начала амортизации
pub last_update_timestamp: i64, // Время последнего обновления
pub accumulated_amortization: u64, // Накопленная амортизация
pub current_nbv: u64, // Текущая остаточная стоимость
pub total_units_expected: u64, // Ожидаемый объём (для units_of_production)
pub total_units_processed: u64, // Обработанный объём
} /// Рассчитывает текущую остаточную стоимость по линейному методу.
///
/// # Аргументы
/// * `initial_cost` - первоначальная стоимость
/// * `residual_value` - ликвидационная стоимость
/// * `useful_life_months` - срок полезного использования в месяцах
/// * `start_timestamp` - Unix-время начала амортизации
/// * `current_timestamp` - текущее Unix-время
///
/// # Возвращает
/// Текущую остаточную стоимость (Net Book Value)
pub fn calculate_straight_line_nbv(
initial_cost: u64,
residual_value: u64,
useful_life_months: u64,
start_timestamp: i64,
current_timestamp: i64,
) -> u64 {
// Проверка входных данных
if current_timestamp <= start_timestamp {
return initial_cost;
}
let total_life_seconds: u64 = useful_life_months
.checked_mul(SECONDS_PER_MONTH as u64)
.unwrap_or(u64::MAX);
let elapsed_seconds = (current_timestamp - start_timestamp) as u64;
// Если срок истёк — возвращаем ликвидационную стоимость
if elapsed_seconds >= total_life_seconds {
return residual_value;
}
// Расчёт накопленной амортизации
let depreciable_amount = initial_cost
.checked_sub(residual_value)
.unwrap_or(0);
// Используем u128 для предотвращения переполнения при умножении
let amortization = (depreciable_amount as u128 * elapsed_seconds as u128)
.checked_div(total_life_seconds as u128)
.unwrap_or(0) as u64;
initial_cost.checked_sub(amortization).unwrap_or(residual_value)
} /// Рассчитывает текущую остаточную стоимость по методу уменьшаемого остатка.
///
/// ВНИМАНИЕ: Данный метод требует хранения истории начислений.
/// Функция возвращает амортизацию за ОДИН ПЕРИОД (год).
pub fn calculate_declining_balance_annual_amortization(
current_nbv: u64,
residual_value: u64,
useful_life_years: u64,
acceleration_factor: u8,
) -> u64 {
if current_nbv <= residual_value {
return 0;
}
// Годовая норма амортизации
let base_rate: u128 = 1_000_000_000; // 100% в формате с 9 знаками после запятой
let annual_rate = base_rate
.checked_div(useful_life_years as u128)
.unwrap_or(0)
.checked_mul(acceleration_factor as u128)
.unwrap_or(0);
// Годовая сумма амортизации
let depreciable_base = current_nbv.checked_sub(residual_value).unwrap_or(0);
((depreciable_base as u128 * annual_rate) / base_rate) as u64
} /// Обновляет учётные данные амортизации.
/// Вызывается периодически или при каждом обращении к активу.
pub fn update_amortization(ctx: Context<UpdateAmortization>) -> Result<()> {
let account = &mut ctx.accounts.amortization_account;
let clock = Clock::get()?;
let current_timestamp = clock.unix_timestamp;
// Если время не изменилось — выходим
if current_timestamp <= account.last_update_timestamp {
return Ok(());
}
// Рассчитываем новую остаточную стоимость в зависимости от метода
let new_nbv = match account.method {
AmortizationMethod::StraightLine => {
calculate_straight_line_nbv(
account.initial_cost,
account.residual_value,
account.useful_life_months,
account.start_timestamp,
current_timestamp,
)
}
AmortizationMethod::DecliningBalance { acceleration_factor } => {
// Для упрощения примера — используем упрощённую логику
let years_elapsed = ((current_timestamp - account.start_timestamp) as u64)
.checked_div(SECONDS_PER_YEAR as u64)
.unwrap_or(0);
let mut nbv = account.initial_cost;
for _ in 0..years_elapsed {
let amort = calculate_declining_balance_annual_amortization(
nbv,
account.residual_value,
account.useful_life_months / 12,
acceleration_factor,
);
nbv = nbv.checked_sub(amort).unwrap_or(account.residual_value);
if nbv <= account.residual_value {
nbv = account.residual_value;
break;
}
}
nbv
}
AmortizationMethod::UnitsOfProduction => {
account.current_nbv
}
};
// Обновляем накопленную амортизацию
account.accumulated_amortization = account.initial_cost
.checked_sub(new_nbv)
.unwrap_or(0);
account.current_nbv = new_nbv;
account.last_update_timestamp = current_timestamp;
Ok(())
} // Константы времени (секунды)
export const SECONDS_PER_MONTH = 2629743; // 30.44 дня
export const SECONDS_PER_YEAR = 31556926; // 365.25 дней
// Методы амортизации
export enum AmortizationMethod {
STRAIGHT_LINE = 'straight_line',
DECLINING_BALANCE = 'declining_balance',
UNITS_OF_PRODUCTION = 'units_of_production',
}
// Интерфейс данных амортизации
export interface AmortizationData {
initialCost: number;
residualValue: number;
usefulLifeMonths: number;
method: AmortizationMethod;
startDate: Date;
accelerationFactor?: number; // для declining_balance
totalUnitsExpected?: number; // для units_of_production
unitsProcessed?: number; // для units_of_production
} /**
* Рассчитывает текущую остаточную стоимость по линейному методу.
*/
export function calculateStraightLineNBV(
data: AmortizationData,
currentDate: Date = new Date()
): number {
const { initialCost, residualValue, usefulLifeMonths, startDate } = data;
const totalLifeSeconds = usefulLifeMonths * SECONDS_PER_MONTH;
const elapsedSeconds = Math.floor((currentDate.getTime() - startDate.getTime()) / 1000);
// Срок истёк
if (elapsedSeconds >= totalLifeSeconds) {
return residualValue;
}
// Срок ещё не начался
if (elapsedSeconds <= 0) {
return initialCost;
}
const depreciableAmount = initialCost - residualValue;
const amortization = (depreciableAmount * elapsedSeconds) / totalLifeSeconds;
return initialCost - amortization;
} /**
* Рассчитывает амортизацию за один год по методу уменьшаемого остатка.
*/
export function calculateDecliningBalanceAnnualAmortization(
currentNBV: number,
residualValue: number,
usefulLifeYears: number,
accelerationFactor: number = 2.0
): number {
if (currentNBV <= residualValue) {
return 0;
}
const annualRate = (1 / usefulLifeYears) * accelerationFactor;
const depreciableBase = currentNBV - residualValue;
return Math.min(depreciableBase, depreciableBase * annualRate);
}
/**
* Рассчитывает остаточную стоимость после N лет по методу уменьшаемого остатка.
*/
export function calculateDecliningBalanceNBV(
data: AmortizationData,
currentDate: Date = new Date()
): number {
const { initialCost, residualValue, usefulLifeMonths, startDate, accelerationFactor = 2.0 } = data;
const usefulLifeYears = usefulLifeMonths / 12;
const elapsedYears = (currentDate.getTime() - startDate.getTime()) /
(SECONDS_PER_YEAR * 1000);
let nbv = initialCost;
const fullYears = Math.floor(elapsedYears);
for (let i = 0; i < fullYears; i++) {
const amort = calculateDecliningBalanceAnnualAmortization(
nbv, residualValue, usefulLifeYears, accelerationFactor
);
nbv = Math.max(residualValue, nbv - amort);
}
return nbv;
} /**
* Рассчитывает остаточную стоимость по методу пропорционально объёму.
*/
export function calculateUnitsOfProductionNBV(
data: AmortizationData
): number {
const { initialCost, residualValue, totalUnitsExpected = 0, unitsProcessed = 0 } = data;
if (totalUnitsExpected === 0) {
return initialCost;
}
const depreciableAmount = initialCost - residualValue;
const amortization = (depreciableAmount * unitsProcessed) / totalUnitsExpected;
return Math.max(residualValue, initialCost - amortization);
} /**
* Универсальная функция расчёта остаточной стоимости.
*/
export function calculateCurrentNBV(
data: AmortizationData,
currentDate: Date = new Date()
): number {
switch (data.method) {
case AmortizationMethod.STRAIGHT_LINE:
return calculateStraightLineNBV(data, currentDate);
case AmortizationMethod.DECLINING_BALANCE:
return calculateDecliningBalanceNBV(data, currentDate);
case AmortizationMethod.UNITS_OF_PRODUCTION:
return calculateUnitsOfProductionNBV(data);
default:
throw new Error(`Unsupported amortization method: ${data.method}`);
}
}
/**
* Рассчитывает ежемесячную сумму амортизации (для линейного метода).
*/
export function getMonthlyAmortization(data: AmortizationData): number {
if (data.method !== AmortizationMethod.STRAIGHT_LINE) {
throw new Error('Monthly amortization is only defined for straight-line method');
}
return (data.initialCost - data.residualValue) / data.usefulLifeMonths;
} // Пример: ИИ-агент с первоначальной стоимостью 150 000 RUB
const agentAsset: AmortizationData = {
initialCost: 150000,
residualValue: 0,
usefulLifeMonths: 24,
method: AmortizationMethod.STRAIGHT_LINE,
startDate: new Date('2026-04-09T10:30:00Z'),
};
// Расчёт через 8 месяцев
const eightMonthsLater = new Date('2026-12-09T10:30:00Z');
const nbv = calculateCurrentNBV(agentAsset, eightMonthsLater);
console.log(`Остаточная стоимость: ${nbv.toFixed(2)} RUB`); // 100000.00 RUB
console.log(`Ежемесячная амортизация: ${getMonthlyAmortization(agentAsset).toFixed(2)} RUB`); // 6250.00 RUB
// Накопленная амортизация
const accumulated = agentAsset.initialCost - nbv;
console.log(`Накопленная амортизация: ${accumulated.toFixed(2)} RUB`); // 50000.00 RUB | Месяц | Остаточная стоимость | Амортизация за месяц | Накопленная амортизация |
|---|---|---|---|
| 0 | 150 000,00 ₽ | — | 0,00 ₽ |
| 1 | 143 750,00 ₽ | 6 250,00 ₽ | 6 250,00 ₽ |
| 6 | 112 500,00 ₽ | 6 250,00 ₽ | 37 500,00 ₽ |
| 12 | 75 000,00 ₽ | 6 250,00 ₽ | 75 000,00 ₽ |
| 18 | 37 500,00 ₽ | 6 250,00 ₽ | 112 500,00 ₽ |
| 24 | 0,00 ₽ | 6 250,00 ₽ | 150 000,00 ₽ |
| Год | Остаточная стоимость на начало | Амортизация за год | Остаточная стоимость на конец |
|---|---|---|---|
| 0 | 150 000,00 ₽ | — | 150 000,00 ₽ |
| 1 | 150 000,00 ₽ | 150 000,00 ₽ | 0,00 ₽ |
Поле amortization_policy в блоке accounting_asset_details (Приложение D) содержит все параметры, необходимые для расчёта:
"amortization_policy": {
"useful_life_months": 24,
"amortization_method": "straight_line",
"residual_value": 0.00,
"start_date_trigger": "first_settlement_timestamp",
"start_date": "2026-04-09T10:30:00Z"
} Функция calculateCurrentNBV может напрямую использовать эти данные.
При внесении агента в TRL:
impairment_test.last_test_result записывается "written_off"Стандарт SOLPDT 1.1 предусматривает автоматический тест на обесценение при падении PDT-Score ниже 0.5 в течение 30 дней. В этом случае:
| Параметр | Значение |
|---|---|
| initialCost | 100 000 |
| residualValue | 0 |
| usefulLifeMonths | 12 |
| startDate | 2026-01-01T00:00:00Z |
| currentDate | 2026-07-01T00:00:00Z (ровно 6 месяцев) |
| Параметр | Значение |
|---|---|
| initialCost | 120 000 |
| residualValue | 0 |
| usefulLifeMonths | 12 |
| startDate | 2026-01-01T00:00:00Z |
| currentDate | 2026-01-16T00:00:00Z (15 дней = ~0.5 месяца) |
| Параметр | Значение |
|---|---|
| initialCost | 50 000 |
| residualValue | 5 000 |
| usefulLifeMonths | 24 |
| startDate | 2024-01-01T00:00:00Z |
| currentDate | 2026-01-01T00:00:00Z (24+ месяцев) |
update_amortization должна вызываться не чаще одного раза в суткиcurrent_nbv в аккаунте и обновляйте только при изменении временного периодаchecked_* операции для безопасной арифметикиКОРНЕВОЙ УЗЕЛ (СОЗДАТЕЛЬ СТАНДАРТА): Юрий Соколов (SOL Trust Network)
КОНТАКТЫ: standards@solpdt.com