COOPENOMICS  v1
Кооперативная Экономика
Действия ledger2\ingroup public_ledger2

Функции

void ledger2::apply (eosio::name coopname, eosio::name initiator, eosio::name operation_code, eosio::name process_type, eosio::asset amount, eosio::name username, eosio::checksum256 process_hash, std::string memo)
 Единая точка входа финансовых движений ledger2 (orchestrator). Подробнее...
 
void ledger2::walletop (eosio::name coopname, uint8_t op_code, eosio::name wallet_from, eosio::name wallet_to, eosio::name username, eosio::asset amount, eosio::checksum256 process_hash, std::string memo)
 Атомарная операция по кошельку (issue/transfer/block/unblock). Подробнее...
 
void ledger2::debit (eosio::name coopname, uint64_t account_id, eosio::asset amount, eosio::checksum256 process_hash, std::string memo)
 Атомарная дебетовая проводка на счёт + пересчёт сальдо. Подробнее...
 
void ledger2::credit (eosio::name coopname, uint64_t account_id, eosio::asset amount, eosio::checksum256 process_hash, std::string memo)
 Атомарная кредитовая проводка на счёт + пересчёт сальдо. Подробнее...
 
void ledger2::migrate ()
 Универсальное миграционное действие — точка расширения для разовых исправлений состояния, которые можно провести автоматически после деплоя контракта. Подробнее...
 
void ledger2::migrate3 (eosio::name coopname, eosio::name wallet_name, eosio::name username, eosio::asset available, eosio::asset blocked)
 Идемпотентная per-record миграция L3-балансов (Phase 1/2; ADR-008). Подробнее...
 
void ledger2::walmove (eosio::name coopname, eosio::name initiator, eosio::name username, eosio::name from_wallet, eosio::name to_wallet, eosio::asset amount, eosio::checksum256 process_hash, std::string memo)
 Перевод между кошельками внутри одного бух.счёта (operation o.adj.walmove). Подробнее...
 
void ledger2::revert (eosio::name coopname, eosio::name initiator, uint64_t original_operation_id, eosio::name original_operation_code, eosio::name username, eosio::asset amount, uint8_t mirror_wallet_op, eosio::name mirror_wallet_from, eosio::name mirror_wallet_to, uint64_t mirror_debit_account_id, uint64_t mirror_credit_account_id, eosio::checksum256 process_hash, std::string memo)
 Откат ранее проведённой операции (operation o.adj.rev). Подробнее...
 

Подробное описание

Функции

◆ apply()

void ledger2::apply ( eosio::name  coopname,
eosio::name  initiator,
eosio::name  operation_code,
eosio::name  process_type,
eosio::asset  amount,
eosio::name  username,
eosio::checksum256  process_hash,
std::string  memo 
)

Единая точка входа финансовых движений ledger2 (orchestrator).

Единая точка входа ledger2 для финансовых движений (orchestrator).

Не пишет в state напрямую: рассылает 3 atomic inline action (walletop, debit, credit), связанных общим process_hash.

process_type — имя нитки процесса, к которой относится операция: его называет контракт-инициатор, поэтому одна и та же операция может идти в разных процессах (членский взнос КУ зачисляется и внутри поставки, и внутри гарантийного возврата). См. processes.hpp.

Пересмотр 2026-04-18 (Epic 1 addendum): apply ничего не пишет в state напрямую. Это orchestrator, который рассылает 3 атомарных inline action:

  1. ledger2::walletop — issue/transfer/block/unblock на wallets2
  2. ledger2::debit — +debit_balance на accounts2[Dr] + пересчёт сальдо
  3. ledger2::credit — +credit_balance на accounts2[Cr] + пересчёт сальдо

Все три inline-actions передают единый process_hash, что позволяет бэкенду собрать «тройку» из blockchain_actions: WHERE account = 'ledger2' AND data->>'process_hash' = X

История проводок не хранится в RAM-таблицах — она целиком восстанавливается на бэкенде из blockchain_actions (для apply/walletop/debit/credit) и blockchain_deltas (для accounts2 и wallets2). Это даёт:

  • дешевле RAM (O(1) state на счёт + кошелёк, не O(N) проводок);
  • проще контракт (нет emplace/backfill в журналы);
  • лучше observability (каждое движение — отдельная action в трейсах).

◆ credit()

void ledger2::credit ( eosio::name  coopname,
uint64_t  account_id,
eosio::asset  amount,
eosio::checksum256  process_hash,
std::string  memo 
)

Атомарная кредитовая проводка на счёт + пересчёт сальдо.

Атомарная кредитовая проводка на счёт ledger2.

Внутренний action — вызывается только через inline из apply().

Внутренний action ledger2 — вызывается только через inline из apply(). Auth: только сам ledger2 (require_auth(get_self())).

Прибавляет amount к accounts2[account_id].credit_balance, затем пересчитывает balance согласно account_type. Парная debit-проводка приходит отдельным inline (action debit) с тем же process_hash — бэкенд связывает их в пару.

TODO(payer, 2026-04-18): payer = get_self() → RAM ledger2. Перевести на coopname при общем переходе на coopname-eosio.code permissions. См. Decision #D2 в code review Epic 1.

◆ debit()

void ledger2::debit ( eosio::name  coopname,
uint64_t  account_id,
eosio::asset  amount,
eosio::checksum256  process_hash,
std::string  memo 
)

Атомарная дебетовая проводка на счёт + пересчёт сальдо.

Атомарная дебетовая проводка на счёт ledger2.

Внутренний action — вызывается только через inline из apply().

Внутренний action ledger2 — вызывается только через inline из apply(). Auth: только сам ledger2 (require_auth(get_self())).

Прибавляет amount к accounts2[account_id].debit_balance, затем пересчитывает balance согласно account_type. Парная credit-проводка приходит отдельным inline (action credit) с тем же process_hash — бэкенд связывает их в пару.

TODO(payer, 2026-04-18): payer = get_self() → RAM ledger2. Перевести на coopname при общем переходе на coopname-eosio.code permissions. См. Decision #D2 в code review Epic 1.

◆ migrate()

void ledger2::migrate ( )

Универсальное миграционное действие — точка расширения для разовых исправлений состояния, которые можно провести автоматически после деплоя контракта.

Универсальное миграционное действие контракта ledger2 — точка расширения для разовых исправлений состояния, которые можно провести автоматически после деплоя.

Тело периодически переписывается под текущую задачу миграции; после прогона на проде очищается до пустого require_auth(get_self()). История прошлых миграций — в git-истории ledger2/src/migrate/migrate.cpp.

Текущая задача — см. doxygen-блок в migrate.cpp.

Заметки
Авторизация требуется от аккаунта: ledger2 (get_self()).

Содержимое периодически переписывается под текущую задачу миграции, а после её прогона на проде тело очищается до пустого require_auth(get_self()) (как в capital::migrate).

Текущая задача (2026-05-24): свёртка blocked → available по ВСЕМ коопам.

Контекст: механика «заблокированного» баланса упразднена (см. lib/core/ledger2/operations.hpp — удалены WalletOp BLOCK/UNBLOCK/BURN_BLOCKED; резерв возврата паевого теперь выражается переводом на кошелёк-резерв w.wal.wpend). Поле blocked остаётся в таблицах wallets2/userwallets как deprecated (физическое удаление поля = небезопасная смена layout таблицы на живых коопах, выносится в отдельный cleanup-деплой). Перед тем как поле перестанет поддерживаться кодом, накопленные blocked-остатки нужно вернуть в available, чтобы средства не «зависли» на упразднённом субсчёте.

Действие: пройти всех кооперативов (cooperatives2 в scope registrator) и для каждого свернуть blocked → available на уровнях L2 (wallets2) и L3 (userwallets): available += blocked; blocked = 0. Сумма средств на кошельке не меняется — только субсчёт.

Идемпотентно: после свёртки blocked == 0, повторный прогон — no-op.

Сигнатура без аргументов — действие вызывается автоматически при деплое контракта (как и прочие задачи migrate); проходит по всем кооперативам сам.

ПРЕДУСЛОВИЕ (операционное): на момент прогона не должно быть заявок на возврат «в полёте» (статусы pending/authorized в wallet::withdraws) — их blocked относится к старой механике и при свёртке в available вернётся пайщику как свободные средства, а последующий completewthd (BURN с w.wal.wpend) не найдёт резерва. Незавершённые заявки нужно довести (complete/decline) ДО деплоя с этой миграцией.

Заметки
Авторизация требуется от аккаунта: ledger2 (get_self()).

◆ migrate3()

void ledger2::migrate3 ( eosio::name  coopname,
eosio::name  wallet_name,
eosio::name  username,
eosio::asset  available,
eosio::asset  blocked 
)

Идемпотентная per-record миграция L3-балансов (Phase 1/2; ADR-008).

Идемпотентная per-record миграция L3-балансов (Phase 1/Phase 2; ADR-008).

Заполняет userwallets[coopname][wallet_name, username] переданными значениями. Идемпотентно: значения УСТАНАВЛИВАЮТСЯ (не инкрементируются), повторный вызов с теми же параметрами = no-op.

Без бух-проводок (это инициализация состояния, не операция). Без сверки с wallet::users.programs[] — миграция может заполнять L3 ДО переезда соглашений в wallet::users (порядок миграции — на уровне rollout-окна).

Auth: coopname@active.

НЕ путать с зарезервированным ledger2::migrate (legacy ledger → ledger2).

Аргументы
coopnameкооператив (auth + payer)
wallet_nameUSER_SHARED-кошелёк (см. LEDGER2_WALLET_REGISTRY)
usernameпайщик
availableзначение available (asset)
blockedзначение blocked (asset)

Заполняет userwallets[coopname][wallet_name, username] переданными значениями. Идемпотентно: при повторном вызове с теми же параметрами state приводится к тому же значению (overwrite, не инкремент).

Без бух-проводок: заполнение L3 — инициализация состояния, не финансовая операция (Σ L3 ↔ Σ L2 уже сходится после migrate.cpp Эпика 1, либо заполняется до согласования инварианта; cross-contract проверки введёт Story 3.2 на нормальном пути walletop, не на migrate3).

Без сверки с wallet::users.programs[]: миграция допускается до того, как программные соглашения переедут в wallet::users. Порядок миграции определяется rollout-окном.

Auto-delete: если переданы (available=0, blocked=0) — запись удаляется.

Auth: ledger2@active (через get_self()), payer: ledger2. Так миграция работает для ЛЮБОГО кооператива без ключей от coopname — у нас по контракту есть только своя подпись. RAM, расход на которую — это сам контракт.

◆ revert()

void ledger2::revert ( eosio::name  coopname,
eosio::name  initiator,
uint64_t  original_operation_id,
eosio::name  original_operation_code,
eosio::name  username,
eosio::asset  amount,
uint8_t  mirror_wallet_op,
eosio::name  mirror_wallet_from,
eosio::name  mirror_wallet_to,
uint64_t  mirror_debit_account_id,
uint64_t  mirror_credit_account_id,
eosio::checksum256  process_hash,
std::string  memo 
)

Откат ранее проведённой операции (operation o.adj.rev).

Создаёт зеркальную проводку по операции original_operation_id: меняет местами Dr/Cr счета и (для wallet_op) wallet_from/wallet_to. Для исходного ISSUE используется WalletOp::BURN (изъятие с wallet_from без увеличения куда-либо). Различие «штатное сжигание» vs «зеркало revert» делается через operation_code (o.adj.rev).

Параметры зеркала готовит backend из своей БД (по записи оригинала в blockchain_actions/state) — контракт не имеет доступа к истории операций.

Запрещено откатывать миграционные операции (operation_code starts with o.mig.). Повторный откат отката (revert от revert) разрешён.

Auth: coopname@active (председатель).

Аргументы
coopnameкооператив (payer auth)
initiatorинициатор отката (для аудита)
original_operation_idid оригинальной записи в blockchain_actions
original_operation_codeoperation_code оригинала (для запрета o.mig.*)
usernameusername оригинала (для аналитики)
amountсумма (как в оригинале)
mirror_wallet_opтип wallet-операции зеркала (TRANSFER/BURN)
mirror_wallet_fromкошелёк-источник зеркала
mirror_wallet_toкошелёк-получатель зеркала (пустое имя для BURN)
mirror_debit_account_idDr-счёт зеркала (0 если оригинал был без бухпроводок)
mirror_credit_account_idCr-счёт зеркала (0 если оригинал был без бухпроводок)
process_hashуникальный хэш для зеркальной операции
memoобязательное обоснование (длина не ограничена)

Contract-only: top-level вызов председателем (coopname@active) запрещён. Action принимает только подпись от whitelisted контрактов (contracts_whitelist) — то есть зеркальная проводка возможна только когда её инициирует другой контракт-инициатор (registrator/wallet/capital/...), который параллельно откатывает свой собственный state.

Why this restriction: ledger2 — учётный слой; зеркальная проводка не трогает сущности контрактов-инициаторов (participants/deposits/contributors). Top-level откат председателем рассинхронизирует учёт и состояние домена (например, participant остаётся accepted, а минимальный паевой «вернулся»).

Контракт не имеет доступа к истории blockchain_actions — параметры зеркала собирает контракт-инициатор (он знает свой исходный operation_code и соответствующие Dr/Cr/wallet через operations.hpp registry).

Запрет на откат миграционных операций (o.mig.*) сохранён. Повторный откат отката (revert от revert) разрешён.

◆ walletop()

void ledger2::walletop ( eosio::name  coopname,
uint8_t  op_code,
eosio::name  wallet_from,
eosio::name  wallet_to,
eosio::name  username,
eosio::asset  amount,
eosio::checksum256  process_hash,
std::string  memo 
)

Атомарная операция по кошельку (issue/transfer/block/unblock).

Атомарная операция по кошельку (issue/transfer/burn).

Внутренний action — вызывается только через inline из apply().

Внутренний action ledger2 — вызывается только через inline из apply(). Auth: только сам ledger2 (require_auth(get_self())).

Уровни учёта (ADR-002, ADR-010): L2 — wallets2[coopname][wallet_name] — всегда мутируется. L3 — userwallets[coopname][wallet_name, user] — для USER_SHARED-кошельков.

Story 3.1: вводится L3 на USER_SHARED-кошельках с auto-create/auto-delete.

  • USER_SHARED-сторона требует непустой username.
  • COOPERATIVE-сторона username игнорирует.
  • L3-cleanup при обнулении (available + blocked == 0).

НЕ входит в Story 3.1 (вынесено в Story 3.2):

  • cross-contract check wallet::users.programs[];
  • post-mutation assert Σ L3 == L2.

История этого вызова автоматически попадает в blockchain_actions с полями (op_code, wallet_from, wallet_to, username, amount, process_hash, memo) — этого достаточно бэкенду для восстановления wjournal-эквивалента.

TODO(payer, 2026-04-18): сейчас payer = get_self() (ledger2), что даёт неограниченный рост RAM контракта. Перевести на payer = coopname, когда все caller-контракты возьмут у coopname разрешение eosio.codeledger2 через linkauth. Решение по code review Decision #D2.

◆ walmove()

void ledger2::walmove ( eosio::name  coopname,
eosio::name  initiator,
eosio::name  username,
eosio::name  from_wallet,
eosio::name  to_wallet,
eosio::asset  amount,
eosio::checksum256  process_hash,
std::string  memo 
)

Перевод между кошельками внутри одного бух.счёта (operation o.adj.walmove).

Ручная корректировка председателя: переносит amount с from_wallet на to_wallet БЕЗ движения по бух.счетам (debit/credit не вызываются). Применение: разнесение по аналитическим кошелькам после криво легшей миграции (типичный кейс voskhod), мелкие исправления в рамках одного фонда.

Auth: coopname@active (председатель). Audit: action+inline walletop попадают в blockchain_actions с общим process_hash, бэкенд видит как один процесс processes::adjustment::CORRECTION (p.adj.fix).

Валидация: from_wallet ≠ to_wallet, оба в LEDGER2_WALLET_REGISTRY, memo не пуст. Соответствие account_id для двух кошельков обеспечивает UI/backend (контракт не хранит wallet→account mapping).

Аргументы
coopnameкооператив (одновременно payer auth)
initiatorинициатор корректировки (для аудита; обычно совпадает с coopname)
usernameвладелец кошельков (для аналитики; обычно coopname для коллективных кошельков)
from_walletкошелёк-источник
to_walletкошелёк-получатель
amountсумма в _root_govern_symbol
process_hashуникальный хэш корректировки (генерирует backend)
memoобязательное обоснование (длина не ограничена)

Top-level action — председатель подписывает сам, никаких caller-контрактов. Делает один inline walletop с op_code = TRANSFER без бухпроводок — корректировка между кошельками одного бух.счёта не меняет балансы самих счетов (accounts2.balance), только их аналитику. «Без бухпроводок» здесь выражается отсутствием inline-actions debit/credit на уровне самого walmove (ADR-003: семантика без проводок задаётся парой нулевых account_id записи OPERATION_REGISTRY; для walmove это технический action вне реестра).

Связь wallet→account не хранится в LEDGER2_WALLET_REGISTRY (она выводится из OPERATION_REGISTRY по месту использования и может теоретически быть многозначной), поэтому соответствие двух кошельков одному account_id проверяется на стороне backend (резолвер walmoveWallets смотрит Ledger2.LEDGER2_OPERATION_REGISTRY и отказывает на разные account_id).