|
COOPENOMICS
v1
Кооперативная Экономика
|
Функции | |
| 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). Подробнее... | |
| 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:
Все три 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). Это даёт:
| 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.
| 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.
| 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()). | 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_name | USER_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, расход на которую — это сам контракт.
| 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_id | id оригинальной записи в blockchain_actions |
| original_operation_code | operation_code оригинала (для запрета o.mig.*) |
| username | username оригинала (для аналитики) |
| amount | сумма (как в оригинале) |
| mirror_wallet_op | тип wallet-операции зеркала (TRANSFER/BURN) |
| mirror_wallet_from | кошелёк-источник зеркала |
| mirror_wallet_to | кошелёк-получатель зеркала (пустое имя для BURN) |
| mirror_debit_account_id | Dr-счёт зеркала (0 если оригинал был без бухпроводок) |
| mirror_credit_account_id | Cr-счёт зеркала (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) разрешён.
| 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.
username.username игнорирует.НЕ входит в Story 3.1 (вынесено в Story 3.2):
wallet::users.programs[];Σ 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.code → ledger2 через linkauth. Решение по code review Decision #D2.
| 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).