common capital tests

This commit is contained in:
Alex Ant
2025-09-01 11:37:54 +05:00
parent e0bf815377
commit 7a5eab104e
59 changed files with 2055 additions and 1063 deletions
+22
View File
@@ -0,0 +1,22 @@
{
"configurations": [
{
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/lib/**",
"${workspaceFolder}/lib/eosiolib/contracts/**",
"${workspaceFolder}/lib/eosiolib/core/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE",
"int128_t=__int128_t",
"uint128_t=__uint128_t"
],
"cStandard": "c17",
"cppStandard": "c++20"
}
],
"version": 4
}
+68
View File
@@ -0,0 +1,68 @@
{
// Enable the ESlint flat config support
"prettier.enable": false,
"editor.formatOnSave": true,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
// "source.fixAll": "explicit",
// "source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{
"rule": "style/*",
"severity": "off"
},
{
"rule": "*-indent",
"severity": "off"
},
{
"rule": "*-spacing",
"severity": "off"
},
{
"rule": "*-spaces",
"severity": "off"
},
{
"rule": "*-order",
"severity": "off"
},
{
"rule": "*-dangle",
"severity": "off"
},
{
"rule": "*-newline",
"severity": "off"
},
{
"rule": "*quotes",
"severity": "off"
},
{
"rule": "*semi",
"severity": "off"
}
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml"
],
// "[cpp]": {
// "editor.defaultFormatter": "ms-vscode.cpptools",
// },
// "C_Cpp.vcFormat.indent.withinParentheses": "indent",
"C_Cpp.errorSquiggles": "disabled"
}
+32
View File
@@ -15,6 +15,7 @@ import EosApi from 'eosjs-api'
// @ts-expect-error заменить на antelope
import ecc from 'eosjs-ecc'
import config from '../configs'
import { consoleIt } from '../tests/shared/consoleIt'
import type { Contract, Feature, Keys, Network } from '../types'
@@ -987,4 +988,35 @@ export default class Blockchain {
console.log('Программа установлена: ', params)
}
/**
* Выполняет транзакцию и автоматически выводит консоль логи через consoleIt
* @param actions - массив действий для транзакции
* @param options - опции транзакции (по умолчанию стандартные)
* @param options.blocksBehind - количество блоков позади для подтверждения
* @param options.expireSeconds - время жизни транзакции в секундах
* @returns результат транзакции
*/
async transactWithLogs(
actions: any[],
options: { blocksBehind?: number, expireSeconds?: number } = {},
): Promise<any> {
await this.update_pass_instance()
const defaultOptions = {
blocksBehind: 3,
expireSeconds: 30,
...options,
}
const result = await this.api.transact(
{ actions },
defaultOptions,
)
// Автоматически выводим консоль логи
consoleIt(result)
return result
}
}
File diff suppressed because it is too large Load Diff
@@ -173,6 +173,7 @@ export async function commitToResult(
commitHash,
commit: blockchainCommit,
transactionId: approveCommitResult.transaction_id,
transaction: approveCommitResult,
finalProject,
finalContributor,
}
@@ -0,0 +1,27 @@
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
/**
* Получает кошелек капитализации пользователя
* @param blockchain экземпляр блокчейна
* @param coopname название кооператива
* @param username имя пользователя
* @returns кошелек капитализации или null если не найден
*/
export async function getCapitalWallet(
blockchain: Blockchain,
coopname: string,
username: string,
) {
const capitalWallets = await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'capwallets',
100,
username,
username,
2, // by_username index
)
return capitalWallets[0] || null
}
@@ -0,0 +1,18 @@
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
export async function getProject(blockchain: Blockchain, coopname: string, project_hash: string): Promise<CapitalContract.Tables.Projects.IProject> {
const rows = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
CapitalContract.Tables.Projects.tableName,
1000,
project_hash,
project_hash,
3,
'sha256',
))
return rows[0]
}
@@ -0,0 +1,30 @@
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
/**
* Получает кошелек проекта пользователя
* @param blockchain экземпляр блокчейна
* @param coopname название кооператива
* @param projectHash хеш проекта
* @param username имя пользователя
* @returns кошелек проекта или null если не найден
*/
export async function getProjectWallet(
blockchain: Blockchain,
coopname: string,
projectHash: string,
username: string,
) {
const projectWallets = await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projwallets',
100,
username,
username,
3, // by_username index
)
// Ищем кошелек для конкретного проекта
return projectWallets.find((wallet: any) => wallet.project_hash === projectHash) || null
}
@@ -127,8 +127,8 @@ export async function investInProject(
console.log('▶ Кошелек программы:', finalProgramWallet)
// Проверка изменения балансов
expect(parseFloat(finalUserWallet.blocked)).toBe(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount))
expect(parseFloat(finalProgramWallet.blocked)).toBe(parseFloat(prevProgramWallet.blocked) + parseFloat(investAmount))
expect(parseFloat(finalUserWallet.blocked)).toBeCloseTo(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount), 1)
expect(parseFloat(finalProgramWallet.blocked)).toBeCloseTo(parseFloat(prevProgramWallet.blocked) + parseFloat(investAmount), 1)
console.log(`\n✅ Инвестирование на ${investAmount} завершено успешно!`)
@@ -0,0 +1,53 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getSegment } from './getSegment'
export async function processAddContributor(
blockchain: Blockchain,
coopname: string,
project_hash: string,
username: string,
) {
// Состояние сегмента до добавления (может не существовать)
let segmentBefore: any = null
try {
segmentBefore = await getSegment(blockchain, coopname, project_hash, username)
}
catch (error) {
// Сегмент может не существовать - это нормально
}
const data: CapitalContract.Actions.AddContributor.IAddContributor = {
coopname,
project_hash,
username,
}
// Выполняем добавление участника в проект
const txAdd = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.AddContributor.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txAdd)
expect(txAdd.transaction_id).toBeDefined()
// Состояние сегмента после добавления
const segmentAfter = await getSegment(blockchain, coopname, project_hash, username)
return {
transactionId: txAdd.transaction_id,
segmentBefore,
segmentAfter,
}
}
@@ -4,7 +4,11 @@ import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { generateRandomSHA256 } from '../../utils/randomHash'
import { fakeDocument } from '../shared/fakeDocument'
import type Blockchain from '../../blockchain'
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
import { getSegment } from './getSegment'
import { getProjectWallet } from './getProjectWallet'
import { capitalProgramId, walletProgramId } from './consts'
import { getProject } from './getProject'
/**
* Функция для конвертации сегмента участника
@@ -32,6 +36,20 @@ export async function processConvertSegment(
const segmentBefore = await getSegment(blockchain, coopname, projectHash, username)
console.log('📊 Сегмент до конвертации:', segmentBefore)
// Получаем кошельки до конвертации
const mainWalletBefore = await getCoopProgramWallet(blockchain, coopname, walletProgramId)
const capitalWalletBefore = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
const userCapitalWalletBefore = await getUserProgramWallet(blockchain, coopname, username, capitalProgramId)
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, username)
const projectBefore = await getProject(blockchain, coopname, projectHash)
console.log('💰 Кошельки до конвертации:')
console.log(' ▶ Главный кошелек программы:', mainWalletBefore)
console.log(' ▶ Глобальный кошелек программы капитализации:', capitalWalletBefore)
console.log(' ▶ Кошелек пользователя в программе капитализации:', userCapitalWalletBefore)
console.log(' ▶ Кошелек проекта:', projectWalletBefore)
console.log(' ▶ Проект до конвертации: ', projectBefore)
// 1. Конвертируем сегмент
const convertSegmentData: CapitalContract.Actions.ConvertSegment.IConvertSegment = {
coopname,
@@ -70,16 +88,71 @@ export async function processConvertSegment(
expect(convertResult.transaction_id).toBeDefined()
console.log('✅ Сегмент конвертирован:', convertHash)
// Получаем сегмент после конвертации
// Получаем сегмент после конвертации (должен быть удален)
const segmentAfter = await getSegment(blockchain, coopname, projectHash, username)
// Проверяем изменение статуса сегмента на COMPLETED
expect(segmentAfter.status).toBe('completed')
console.log('🔍 Статус сегмента после конвертации:', segmentAfter.status)
// Получаем кошельки после конвертации
const mainWalletAfter = await getCoopProgramWallet(blockchain, coopname, walletProgramId)
const capitalWalletAfter = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
const userCapitalWalletAfter = await getUserProgramWallet(blockchain, coopname, username, capitalProgramId)
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, username)
console.log('💰 Кошельки после конвертации:')
console.log(' ▶ Главный кошелек программы:', mainWalletAfter)
console.log(' ▶ Глобальный кошелек программы капитализации:', capitalWalletAfter)
console.log(' ▶ Кошелек пользователя в программе капитализации:', userCapitalWalletAfter)
console.log(' ▶ Кошелек проекта:', projectWalletAfter)
// Проверяем что сегмент удален после конвертации
expect(segmentAfter).toBeUndefined()
console.log('🔍 Сегмент после конвертации: ', segmentAfter)
// Проверяем изменения в кошельках
console.log('\n🔍 Проверка изменений в кошельках:')
// Проверяем главный кошелек программы (wallet_amount)
if (walletAmount !== '0.0000 RUB') {
const walletAmountValue = parseFloat(walletAmount.split(' ')[0])
const beforeAvailable = mainWalletBefore ? parseFloat(mainWalletBefore.available.split(' ')[0]) : 0
const afterAvailable = mainWalletAfter ? parseFloat(mainWalletAfter.available.split(' ')[0]) : 0
const expectedIncrease = beforeAvailable + walletAmountValue
console.log(`✅ Главный кошелек программы: ${beforeAvailable}${afterAvailable} (+${walletAmountValue})`)
expect(afterAvailable).toBeCloseTo(expectedIncrease, 1)
}
// Проверяем глобальный кошелек программы капитализации (capital_amount)
if (capitalAmount !== '0.0000 RUB') {
const capitalAmountValue = parseFloat(capitalAmount.split(' ')[0])
const beforeBlocked = capitalWalletBefore ? parseFloat(capitalWalletBefore.blocked.split(' ')[0]) : 0
const afterBlocked = capitalWalletAfter ? parseFloat(capitalWalletAfter.blocked.split(' ')[0]) : 0
const expectedIncrease = beforeBlocked + capitalAmountValue
console.log(`✅ Глобальный кошелек программы капитализации: ${beforeBlocked}${afterBlocked} (+${capitalAmountValue})`)
expect(afterBlocked).toBeCloseTo(expectedIncrease, 1)
}
// Проверяем кошелек пользователя в программе капитализации (capital_amount)
if (capitalAmount !== '0.0000 RUB') {
const capitalAmountValue = parseFloat(capitalAmount.split(' ')[0])
const beforeBlocked = userCapitalWalletBefore ? parseFloat(userCapitalWalletBefore.blocked.split(' ')[0]) : 0
const afterBlocked = userCapitalWalletAfter ? parseFloat(userCapitalWalletAfter.blocked.split(' ')[0]) : 0
const expectedIncrease = beforeBlocked + capitalAmountValue
console.log(`✅ Кошелек пользователя в программе капитализации: ${beforeBlocked}${afterBlocked} (+${capitalAmountValue})`)
expect(afterBlocked).toBeCloseTo(expectedIncrease, 1)
}
// Проверяем кошелек проекта (project_amount)
if (projectAmount !== '0.0000 RUB' && projectBefore.parent_hash.includes('0000000000')) {
const projectAmountValue = parseFloat(projectAmount.split(' ')[0])
const beforeShares = projectWalletBefore ? parseFloat(projectWalletBefore.shares.split(' ')[0]) : 0
const afterShares = projectWalletAfter ? parseFloat(projectWalletAfter.shares.split(' ')[0]) : 0
const expectedIncrease = beforeShares + projectAmountValue
console.log(`✅ Кошелек проекта: ${beforeShares}${afterShares} (+${projectAmountValue})`)
expect(afterShares).toBeCloseTo(expectedIncrease, 1)
}
console.log('\n📊 Результаты после конвертации сегмента:')
console.log('▶ Сегмент до:', segmentBefore)
console.log('▶ Сегмент после:', segmentAfter)
console.log('▶ Сегмент после удалён: ', segmentAfter)
console.log(`\n✅ Процесс конвертации сегмента завершен успешно!`)
@@ -87,6 +160,14 @@ export async function processConvertSegment(
convertHash,
segmentBefore,
segmentAfter,
mainWalletBefore,
mainWalletAfter,
capitalWalletBefore,
capitalWalletAfter,
userCapitalWalletBefore,
userCapitalWalletAfter,
projectWalletBefore,
projectWalletAfter,
transactionId: convertResult.transaction_id,
}
}
@@ -0,0 +1,62 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import type Blockchain from '../../blockchain'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
export async function processDeleteProject(
blockchain: Blockchain,
coopname: string,
project_hash: string,
) {
// Состояние проекта до удаления
const projectBefore = (await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
))[0]
const data: CapitalContract.Actions.DeleteProject.IDeleteProject = {
coopname,
project_hash,
}
// Выполняем удаление проекта
const txDelete = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.DeleteProject.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
},
{ blocksBehind: 3, expireSeconds: 30 },
)
getTotalRamUsage(txDelete)
expect(txDelete.transaction_id).toBeDefined()
// Проверяем что проект удален
const projectsAfter = await blockchain.getTableRows(
CapitalContract.contractName.production,
coopname,
'projects',
1,
project_hash,
project_hash,
3,
'sha256',
)
return {
transactionId: txDelete.transaction_id,
projectBefore,
projectAfter: projectsAfter[0] || null, // null если удален
}
}
@@ -0,0 +1,61 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getCapitalWallet } from './getCapitalWallet'
export async function processFundProgram(
blockchain: any,
coopname: string,
amount: string,
memo: string = 'Тестовое финансирование программы',
) {
console.log(`\n🔹 Начало финансирования программы капитализации: сумма=${amount}, memo=${memo}`)
// Получение данных ДО финансирования
const programWalletBefore = await getCapitalWallet(blockchain, coopname, coopname)
console.log('📊 Состояние программы ДО финансирования:')
console.log('▶ Кошелек программы:', programWalletBefore)
// Финансирование программы капитализации
const fundData: CapitalContract.Actions.FundProgram.IFundProgram = {
coopname,
amount,
memo,
}
const fundResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.FundProgram.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: fundData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(fundResult)
expect(fundResult.transaction_id).toBeDefined()
// Получение данных ПОСЛЕ финансирования
const programWalletAfter = await getCapitalWallet(blockchain, coopname, coopname)
console.log('📊 Состояние программы ПОСЛЕ финансирования:')
console.log('▶ Кошелек программы:', programWalletAfter)
console.log(`\n✅ Финансирование программы капитализации завершено успешно!`)
return {
transactionId: fundResult.transaction_id,
transaction: fundResult,
programWalletBefore,
programWalletAfter,
}
}
@@ -0,0 +1,63 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getProjectWallet } from './getProjectWallet'
export async function processFundProject(
blockchain: any,
coopname: string,
projectHash: string,
amount: string,
memo: string = 'Тестовое финансирование проекта',
) {
console.log(`\n🔹 Начало финансирования проекта: проект=${projectHash}, сумма=${amount}, memo=${memo}`)
// Получение данных ДО финансирования
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, coopname)
console.log('📊 Состояние проекта ДО финансирования:')
console.log('▶ Кошелек проекта:', projectWalletBefore)
// Финансирование проекта
const fundData: CapitalContract.Actions.FundProject.IFundProject = {
coopname,
project_hash: projectHash,
amount,
memo,
}
const fundResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.FundProject.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: fundData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(fundResult)
expect(fundResult.transaction_id).toBeDefined()
// Получение данных ПОСЛЕ финансирования
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, coopname)
console.log('📊 Состояние проекта ПОСЛЕ финансирования:')
console.log('▶ Кошелек проекта:', projectWalletAfter)
console.log(`\n✅ Финансирование проекта завершено успешно!`)
return {
transactionId: fundResult.transaction_id,
transaction: fundResult,
projectWalletBefore,
projectWalletAfter,
}
}
@@ -14,22 +14,14 @@ export async function processMasterApprove(
commit_hash: commitHash,
}
const result = await blockchain.api.transact(
const result = await blockchain.transactWithLogs([
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CommitApprove.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
],
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.CommitApprove.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data,
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
])
getTotalRamUsage(result)
expect(result.transaction_id).toBeDefined()
@@ -2,6 +2,7 @@ import { expect } from 'vitest'
import { CapitalContract, SovietContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { generateRandomSHA256 } from '../../utils/randomHash'
import { setDocumentSignatures } from '../../utils/setDocumentSignatures'
import { processLastDecision } from '../soviet/processLastDecision'
import { fakeDocument } from '../shared/fakeDocument'
import type Blockchain from '../../blockchain'
@@ -34,7 +35,8 @@ export async function processPushResult(
const segmentBefore = await getSegment(blockchain, coopname, projectHash, username)
console.log('📊 Сегмент до внесения результата:', segmentBefore)
fakeDocument.signatures[0].signer = username
// Устанавливаем подписи документа
const documentWithSignatures = setDocumentSignatures([username])
// 1. Вносим результат
const pushResultData: CapitalContract.Actions.PushResult.IPushResult = {
@@ -44,7 +46,7 @@ export async function processPushResult(
result_hash: resultHash,
contribution_amount: contributionAmount,
debt_amount: debtAmount,
statement: fakeDocument,
statement: documentWithSignatures,
debt_hashes: debtHashes,
}
@@ -95,12 +97,13 @@ export async function processPushResult(
await processLastDecision(blockchain, coopname)
console.log('✅ Решение совета принято')
console.log('🔍 Подписываем акт №1 вкладчиком', documentWithSignatures)
// 4. Подписываем акт №1 вкладчиком
const signAct1Data: CapitalContract.Actions.SignAct1.ISignAct1 = {
coopname,
username,
result_hash: resultHash,
act: fakeDocument,
act: documentWithSignatures,
}
console.log(`\n✅ Подписание акта №1 вкладчиком ${username}`)
@@ -125,18 +128,18 @@ export async function processPushResult(
expect(signAct1Result.transaction_id).toBeDefined()
console.log('✅ Акт №1 подписан вкладчиком')
fakeDocument.signatures[1] = fakeDocument.signatures[0]
fakeDocument.signatures[1].signer = 'ant'
// Создаем документ с подписями вкладчика и председателя
const documentWithChairmanSignature = setDocumentSignatures([username, 'ant'])
// 5. Подписываем акт №2 председателем
const signAct2Data: CapitalContract.Actions.SignAct2.ISignAct2 = {
coopname,
result_hash: resultHash,
act: fakeDocument,
username: 'ant',
act: documentWithChairmanSignature,
chairman: 'ant',
}
console.log(`\n✅ Подписание акта №2 председателем`)
console.log(`\n✅ Подписание акта №2 председателем`, documentWithChairmanSignature)
const signAct2Result = await blockchain.api.transact(
{
actions: [
@@ -0,0 +1,59 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getCapitalWallet } from './getCapitalWallet'
export async function processRefreshProg(
blockchain: any,
coopname: string,
username: string,
) {
console.log(`\n🔹 Начало обновления CRPS пайщика в программе капитализации: пользователь=${username}`)
// Получение данных ДО обновления
const programWalletBefore = await getCapitalWallet(blockchain, coopname, username)
console.log('📊 Состояние пайщика ДО обновления CRPS:')
console.log('▶ Кошелек пайщика:', programWalletBefore)
// Обновление CRPS пайщика в программе капитализации
const refreshData: CapitalContract.Actions.RefreshProgram.IRefreshProgram = {
coopname,
username,
}
const refreshResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RefreshProgram.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: refreshData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(refreshResult)
expect(refreshResult.transaction_id).toBeDefined()
// Получение данных ПОСЛЕ обновления
const programWalletAfter = await getCapitalWallet(blockchain, coopname, username)
console.log('📊 Состояние пайщика ПОСЛЕ обновления CRPS:')
console.log('▶ Кошелек пайщика:', programWalletAfter)
console.log(`\n✅ Обновление CRPS пайщика в программе капитализации завершено успешно!`)
return {
transactionId: refreshResult.transaction_id,
transaction: refreshResult,
programWalletBefore,
programWalletAfter,
}
}
@@ -0,0 +1,61 @@
import { expect } from 'vitest'
import { CapitalContract } from 'cooptypes'
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
import { getProjectWallet } from './getProjectWallet'
export async function processRefreshProj(
blockchain: any,
coopname: string,
projectHash: string,
username: string,
) {
console.log(`\n🔹 Начало обновления CRPS пайщика в проекте: проект=${projectHash}, пользователь=${username}`)
// Получение данных ДО обновления
const projectWalletBefore = await getProjectWallet(blockchain, coopname, projectHash, username)
console.log('📊 Состояние кошелька проекта ДО обновления CRPS:')
console.log('▶ Кошелек проекта:', projectWalletBefore)
// Обновление CRPS пайщика в проекте
const refreshData: CapitalContract.Actions.RefreshProject.IRefreshProject = {
coopname,
project_hash: projectHash,
username,
}
const refreshResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RefreshProject.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: refreshData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
)
getTotalRamUsage(refreshResult)
expect(refreshResult.transaction_id).toBeDefined()
// Получение данных ПОСЛЕ обновления
const projectWalletAfter = await getProjectWallet(blockchain, coopname, projectHash, username)
console.log('📊 Состояние кошелька проекта ПОСЛЕ обновления CRPS:')
console.log('▶ Кошелек проекта:', projectWalletAfter)
console.log(`\n✅ Обновление CRPS пайщика в проекте завершено успешно!`)
return {
transactionId: refreshResult.transaction_id,
transaction: refreshResult,
projectWalletBefore,
projectWalletAfter,
}
}
@@ -28,21 +28,15 @@ export async function refreshSegment(
username,
}
const refreshResult = await blockchain.api.transact(
{
actions: [
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RefreshSegment.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: refreshSegmentData,
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
},
const refreshResult = await blockchain.transactWithLogs(
[
{
account: CapitalContract.contractName.production,
name: CapitalContract.Actions.RefreshSegment.actionName,
authorization: [{ actor: coopname, permission: 'active' }],
data: refreshSegmentData,
},
],
)
getTotalRamUsage(refreshResult)
@@ -0,0 +1,84 @@
interface ActionTrace {
act?: {
account: string
name: string
authorization?: { actor: string }[]
}
console?: string
inline_traces?: ActionTrace[]
}
interface TransactionResult {
processed?: {
action_traces?: ActionTrace[]
}
}
/**
* Извлекает и печатает все консоль логи из результата транзакции
* @param transaction - результат транзакции blockchain.api.transact
*/
export function consoleIt(transaction: TransactionResult): void {
if (!transaction?.processed?.action_traces) {
console.log('❌ Нет данных о транзакции или action traces')
return
}
console.log('\n📋 КОНСОЛЬ ЛОГИ ТРАНЗАКЦИИ:')
console.log('='.repeat(50))
let hasConsoleLogs = false
function processActionTrace(trace: ActionTrace, depth = 0): void {
const indent = ' '.repeat(depth)
// Получаем информацию о действии
const account = trace.act?.account || 'unknown'
const action = trace.act?.name || 'unknown'
const actor = trace.act?.authorization?.[0]?.actor || 'unknown'
// Печатаем заголовок действия
console.log(`${indent}🔧 ${account}:${action} (actor: ${actor})`)
// Если есть консоль логи, печатаем их
if (trace.console) {
hasConsoleLogs = true
const logs = trace.console.split('\n').filter(log => log.trim())
if (logs.length > 0) {
console.log(`${indent}📝 Console output:`)
logs.forEach((log) => {
console.log(`${indent} ${log}`)
})
}
}
else {
console.log(`${indent} (нет консоль логов)`)
}
// Обрабатываем inline traces рекурсивно
if (trace.inline_traces?.length) {
console.log(`${indent} ↳ Inline actions:`)
trace.inline_traces.forEach((inlineTrace) => {
processActionTrace(inlineTrace, depth + 1)
})
}
if (depth === 0) {
console.log('') // Пустая строка между действиями верхнего уровня
}
}
// Обрабатываем все action traces
transaction.processed.action_traces.forEach((trace, index) => {
console.log(`\n📍 Action ${index + 1}:`)
processActionTrace(trace, 0)
})
if (!hasConsoleLogs) {
console.log('ℹ️ В транзакции не найдено консоль логов')
}
console.log('='.repeat(50))
console.log('🏁 Конец логов транзакции\n')
}
@@ -0,0 +1,5 @@
export * from './combinedKeys'
export * from './consoleIt'
export * from './fakeDocument'
export * from './fakeVote'
export * from './formatDateToISOString'
@@ -17,6 +17,11 @@ export async function getUserProgramWallet(blockchain: any, coopname: string, us
return wallets.find((el: any) => el.program_id === program_id)
}
export async function getUserProgramWalletAmount(blockchain: any, coopname: string, username: string, program_id: number) {
const wallet = await getUserProgramWallet(blockchain, coopname, username, program_id)
return wallet ? `${(parseFloat(wallet.available) + parseFloat(wallet.blocked)).toFixed(4)} RUB` : '0.0000 RUB'
}
export async function getCoopProgramWallet(blockchain: any, coopname: string, program_id: number) {
const program = await blockchain.getTableRows(
SovietContract.contractName.production,
@@ -76,5 +81,5 @@ export function compareTokenAmounts(prevAmount: string, currentAmount: string, e
const current = parseFloat(currentAmount.split(' ')[0])
const expected = prev + expectedIncrease
expect(current).toBe(expected)
expect(current).toBeCloseTo(expected, 4)
}
@@ -0,0 +1,29 @@
import { fakeDocument } from '../tests/shared/fakeDocument'
/**
* Устанавливает подписи документа на основе массива имен аккаунтов
* @param usernames массив имен аккаунтов для подписи документа
* @returns копия fakeDocument с установленными подписями
*/
export function setDocumentSignatures(usernames: string[]) {
const document = JSON.parse(JSON.stringify(fakeDocument))
// Очищаем существующие подписи
document.signatures = []
// Создаем подписи для каждого пользователя
usernames.forEach((username, index) => {
const signature = {
id: index + 1,
signed_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
signer: username,
public_key: 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J',
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
signed_at: '2025-05-14T12:22:26',
meta: '{}',
}
document.signatures.push(signature)
})
return document
}
@@ -29,7 +29,6 @@ void capital::createdebt(name coopname, name username, checksum256 project_hash,
// Проверяем что участник существует в проекте
auto exist_contributor = Capital::Contributors::get_active_contributor_with_appendix_or_fail(coopname, project_hash, username);
eosio::check(exist_contributor.has_value(), "Договор УХД с пайщиком не найден");
// Проверяем что сегмент существует и обновлен
auto exist_segment = Capital::Segments::get_segment(coopname, project_hash, username);
@@ -21,6 +21,10 @@ void capital::debtpaycnfrm(name coopname, checksum256 debt_hash) {
// Обновляем статус долга на PAID
Capital::Debts::update_debt_status(coopname, debt_hash, Capital::Debts::Status::PAID, _gateway);
// Увеличиваем значение счёта выданных ссуд
auto memo = Capital::Memo::get_debt_memo(exist_debt.username);
Ledger::add(_capital, coopname, Ledger::accounts::LONG_TERM_LOANS, exist_debt.amount, memo);
// Увеличиваем долг contributor (теперь долг активен и должен быть погашен через внесение результата)
Capital::Contributors::increase_debt_amount(coopname, exist_debt.username, exist_debt.amount);
};
@@ -12,7 +12,8 @@
* @note Авторизация требуется от аккаунта: @p _soviet или @p _gateway
*/
void capital::fundprog(eosio::name coopname, asset amount, std::string memo) {
auto payer = check_auth_and_get_payer_or_fail({_soviet, _gateway});
require_auth(coopname);
// auto payer = check_auth_and_get_payer_or_fail({_soviet, _gateway});
Wallet::validate_asset(amount);
@@ -15,7 +15,8 @@
* @note Авторизация требуется от аккаунта: @p _soviet или @p _gateway
*/
void capital::fundproj(eosio::name coopname, checksum256 project_hash, asset amount, std::string memo) {
auto payer = check_auth_and_get_payer_or_fail({ _soviet, _gateway });
require_auth(coopname);
// auto payer = check_auth_and_get_payer_or_fail({ _soviet, _gateway });
Wallet::validate_asset(amount);
auto exist_project = Capital::Projects::get_project(coopname, project_hash);
@@ -9,10 +9,10 @@
* @ingroup public_actions
* @ingroup public_capital_actions
* @note Авторизация требуется от аккаунта: @p username
* @note Авторизация требуется от аккаунта: @p coopname
*/
[[eosio::action]] void capital::refreshproj(name coopname, checksum256 project_hash, name username) {
require_auth(username);
require_auth(coopname);
// Проверяем существование проекта
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
@@ -34,7 +34,10 @@ void capital::approvecmmt(eosio::name coopname, eosio::name master, checksum256
Capital::Core::increment_authors_crps_in_project(coopname, commit.project_hash,
commit.amounts.authors_base_pool,
commit.amounts.authors_bonus_pool);
// Распределяем премии вкладчиков между всеми вкладчиками проекта через CRPS
Capital::Core::increment_contributors_crps_in_project(coopname, commit.project_hash, commit.amounts.contributors_bonus_pool);
// Удаляем коммит после обработки
Capital::Commits::delete_commit(coopname, commit_hash);
};
@@ -13,15 +13,21 @@
* @note Авторизация требуется от аккаунта: @p username
*/
void capital::addcontrib(eosio::name coopname, checksum256 project_hash, eosio::name username) {
require_auth(username);
require_auth(coopname);
// Проверяем существование проекта
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
auto check_appendix = Capital::Contributors::get_active_contributor_with_appendix_or_fail(coopname, project_hash, username);
// Проверяем наличие сегмента
auto exist_segment = Capital::Segments::get_segment(coopname, project_hash, username);
// Добавляем вкладчика через CRPS систему
// Функция сама выполнит все необходимые проверки:
// - наличие активного договора УХД
// - наличие подписанного приложения к проекту
// - положительный баланс в программе капитализации
if (exist_segment.has_value()) {
// Проверяем что пользователь не зарегистрирован в проекте как вкладчик
eosio::check(!exist_segment -> is_contributor, "Вкладчик уже зарегистрирован в проекте");
}
// Добавляем вкладчика
Capital::Core::upsert_contributor_segment(coopname, project_hash, username);
}
@@ -2,8 +2,10 @@
* @brief Удаляет проект
* Удаляет проект из системы кооператива:
* - Проверяет существование проекта
* - Валидирует что проект в статусе voting
* - Удаляет проект и все связанные с ним данные
* - Валидирует что проект в статусе completed
* - Проверяет что все сегменты сконвертированы (удалены)
* - Проверяет что в проектном кошельке 0 total_shares
* - Удаляет проект
* @param coopname Наименование кооператива
* @param project_hash Хеш проекта для удаления
* @ingroup public_actions
@@ -15,7 +17,16 @@ void capital::delproject(name coopname, checksum256 project_hash) {
require_auth(coopname);
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
eosio::check(project.status == Capital::Projects::Status::VOTING, "Проект должен быть в статусе 'voting'");
eosio::check(project.status == Capital::Projects::Status::COMPLETED, "Проект должен быть в статусе 'completed'");
//TODO: delete project with check that we are delete everything related to it
// Проверяем что все сегменты сконвертированы (удалены)
eosio::check(!Capital::Segments::has_project_segments(coopname, project_hash),
"Не все сегменты сконвертированы. Сначала конвертируйте все сегменты");
// Проверяем что в проектном кошельке 0 total_shares
eosio::check(project.membership.total_shares.amount == 0,
"В проектном кошельке есть доли. Сначала выведите все доли");
// Удаляем проект
Capital::Projects::delete_project(coopname, project_hash);
}
@@ -2,21 +2,21 @@
* @defgroup public_capital_project_deletion_process Удалить проект
* @ingroup public_capital_processes
*
* Процесс полного удаления проекта и всех связанных данных из блокчейна
* в случаях, когда хранение данных более не целесообразно.
* Процесс удаления проекта из блокчейна после завершения всех финансовых операций
* и конвертации вкладов участников.
*
* ## Назначение процесса
*
* Удаление проекта обеспечивает:
* - Освобождение ресурсов блокчейна от неактуальных данных
* - Удаление проектов, не предполагавших получение членских взносов
* - Очистку системы от завершенных экспериментальных проектов
* - Удаление проектов, все вклады в которых сконвертированы
* - Очистку системы от завершенных проектов без активных участников
* - Оптимизацию использования оперативной памяти блокчейна
*
* @pre
* - Все вклады участников должны быть сконвертированы
* - Нет неразрешенных финансовых обязательств
* - Проект изначально не предполагал получение членских взносов
* - Проект должен быть в статусе COMPLETED
* - Все сегменты участников должны быть сконвертированы (удалены)
* - В проектном кошельке должно быть 0 total_shares
*
* \htmlonly
* <div class="mermaid">
@@ -24,17 +24,21 @@
* participant К as Кооператив
* participant ПК as Контракт Capital
* participant П as Проект
* participant С as Сегменты
*
* К->>ПК: 1. Инициация удаления проекта
* Note over ПК: Проверка статуса проекта
* ПК->>П: 2. Проверка готовности к удалению
* alt Проект готов к удалению
* П->>ПК: 3. Подтверждение готовности
* ПК->>ПК: 4. Валидация конвертации вкладов
* ПК->>ПК: 5. Удаление связанных данных
* ПК->>П: 6. Удаление записи проекта
* else Проект не готов
* ПК->>К: Ошибка: проект не готов к удалению
* ПК->>П: 2. Проверка статуса COMPLETED
* ПК->>С: 3. Проверка наличия сегментов
* alt Сегменты отсутствуют
* ПК->>П: 4. Проверка total_shares = 0
* alt total_shares = 0
* ПК->>П: 5. Удаление проекта
* else total_shares > 0
* ПК->>К: Ошибка: есть доли в кошельке
* end
* else Сегменты есть
* ПК->>К: Ошибка: не все сегменты сконвертированы
* end
* </div>
* \endhtmlonly
@@ -52,42 +56,35 @@
* ### Шаг 2: Проверка статуса проекта
* <b>Действие:</b> Валидация состояния
*
* Система проверяет, что проект находится в корректном статусе
* для удаления. В текущей реализации требуется статус VOTING,
* но в продакшене логично требовать CLOSED статус.
* Система проверяет, что проект находится в статусе COMPLETED.
* Только завершенные проекты могут быть удалены.
*
* @post Подтвержден корректный статус проекта
* @post Подтвержден статус COMPLETED проекта
*
* ### Шаг 3: Валидация конвертации вкладов
* <b>Действие:</b> Проверка завершенности финансовых операций
* ### Шаг 3: Проверка конвертации сегментов
* <b>Действие:</b> Валидация завершенности конвертации
*
* Перед удалением система должна проверить, что:
* - Все сегменты участников сконвертированы
* - Нет неразрешенных финансовых обязательств
* - Все средства переведены в целевые кошельки или проекты
* Система проверяет, что все сегменты участников сконвертированы
* и удалены из системы. Если сегменты остались, удаление невозможно.
*
* @post Проверка завершенности всех финансовых операций
* @post Подтверждено отсутствие сегментов проекта
*
* ### Шаг 4: Удаление связанных данных
* <b>Действие:</b> Очистка всех таблиц проекта
* ### Шаг 4: Проверка проектного кошелька
* <b>Действие:</b> Валидация финансового состояния
*
* Система должна удалить все данные, связанные с проектом:
* - Записи участников и их сегменты
* - Данные голосования и результаты
* - Инвестиции и финансовые операции
* - Коммиты и трудозатраты
* - Расходы и ссуды
* Система проверяет, что в проектном кошельке нет активных долей
* (total_shares = 0). Если доли остались, их необходимо вывести.
*
* @post Удаление всех связанных записей из таблиц блокчейна
* @post Подтверждено отсутствие долей в проектном кошельке
*
* ### Шаг 5: Удаление записи проекта
* ### Шаг 5: Удаление проекта
* <b>Действие:</b> Финальная очистка
*
* После удаления всех связанных данных система удаляет
* основную запись проекта из таблицы проектов.
* После всех проверок система удаляет запись проекта из таблицы проектов.
*
* @post Полное удаление проекта из блокчейна
* @post Освобождение ресурсов оперативной памяти
*
* @warning Текущая реализация не завершена и находится в разработке.
* @note Проект может быть удален только после полной конвертации всех вкладов
* участников и вывода всех долей из проектного кошелька.
*/
@@ -33,70 +33,44 @@ void capital::convertsegm(eosio::name coopname, eosio::name username,
"Сегмент пайщика не найден");
// Проверяем статус сегмента - результат должен быть внесён после pushrslt
eosio::check(segment.status == Capital::Segments::Status::CONTRIBUTED,
"Результат не внесён. Сначала внесите результат через pushrslt");
eosio::check(segment.status == Capital::Segments::Status::ACCEPTED, "Результат не внесён. Сначала внесите результат через pushrslt");
// Проверяем актуальность сегмента (включая синхронизацию с инвестициями)
Capital::Segments::check_segment_is_updated(coopname, project_hash, username,
"Сегмент не обновлен. Выполните rfrshsegment перед конвертацией");
Capital::Segments::check_segment_is_updated(coopname, project_hash, username, "Сегмент не обновлен. Выполните rfrshsegment перед конвертацией");
// === ФАЗА 1: БАЗОВЫЕ ПРОВЕРКИ ===
Wallet::validate_asset(wallet_amount);
Wallet::validate_asset(capital_amount);
Wallet::validate_asset(project_amount);
// Проверяем, что у пользователя есть что конвертировать
eosio::check(segment.total_segment_cost.amount > 0,
"У участника нет средств для конвертации (общая стоимость сегмента равна нулю)");
print("wallet_amount: ", wallet_amount.to_string());
print("capital_amount: ", capital_amount.to_string());
print("project_amount: ", project_amount.to_string());
// === ФАЗА 2: РАСЧЕТ ДОСТУПНЫХ СУММ ===
// Доступная сумма для конвертации в кошелек
eosio::asset available_for_wallet = segment.provisional_amount - segment.debt_amount;
print("available_for_wallet: ", available_for_wallet.to_string());
eosio::check(wallet_amount <= available_for_wallet, "Сумма конвертации в кошелек превышает доступную и обеспеченную суммы. Для конвертации доступна обеспеченная сумма за вычетом суммы выданных ссуд (provisional_amount - debt_amount)");
// Проверяем что долг уже был погашен после pushrslt
eosio::check(segment.debt_settled >= segment.debt_amount,
"Долг не был погашен. Сначала внесите результат через pushrslt");
// Доступная сумма для конвертации в проект (все базовые суммы всех ролей за вычетом выданных ссуд)
eosio::asset available_for_project = segment.creator_base + segment.author_base + segment.coordinator_base + segment.investor_base + segment.property_base - segment.debt_amount;
print("available_for_project: ", available_for_project.to_string());
eosio::check(project_amount <= available_for_project, "Сумма конвертации в проект превышает себестоимость вкладов за вычетом суммы выданных ссуд");
// Суммы доступные для конвертации в проект (все базовые суммы всех ролей)
eosio::asset available_for_project = segment.creator_base + segment.author_base +
segment.coordinator_base + segment.investor_base + segment.property_base;
// Суммы доступные для конвертации в кошелек (только creator_base + author_base + coordinator_base)
// уменьшенные на погашенный долг, но не больше provisional_amount
eosio::asset base_for_wallet = segment.creator_base + segment.author_base + segment.coordinator_base;
eosio::asset wallet_base_after_debt = base_for_wallet - segment.debt_amount;
// Долг не может превышать базовые суммы создателя/автора/координатора
eosio::check(wallet_base_after_debt.amount >= 0,
"Долг превышает базовые суммы создателя/автора/координатора");
eosio::asset available_for_wallet = (wallet_base_after_debt < segment.provisional_amount) ?
wallet_base_after_debt : segment.provisional_amount;
// Все взносы должны конвертироваться в капитализацию, если не идут в проект или кошелек
// Вычитаем погашенный долг из общей стоимости сегмента
eosio::asset total_segment_available = segment.total_segment_cost - segment.debt_amount;
// === ФАЗА 3: ПРОВЕРКИ ОГРАНИЧЕНИЙ ПО НАПРАВЛЕНИЯМ ===
// Проверка 3.1: Ограничения конвертации в проект
eosio::check(project_amount <= available_for_project,
"Сумма конвертации в проект превышает доступные базовые суммы всех ролей (creator_base + author_base + coordinator_base + investor_base + property_base)");
// Проверка 3.2: Ограничения конвертации в кошелек
eosio::check(wallet_amount <= available_for_wallet,
"Сумма конвертации в кошелек превышает доступные суммы (только creator_base + author_base + coordinator_base и не больше provisional_amount)");
// Проверка 3.3: Ограничения конвертации в капитализацию
// Доступная сумма для конвертации в программу
eosio::asset available_for_program = segment.total_segment_cost - segment.debt_amount;
print("available_for_program: ", available_for_program.to_string());
// Проверяем что общая сумма конвертации равна доступной сумме для конвертации в программу
eosio::asset total_convert = wallet_amount + capital_amount + project_amount;
eosio::check(capital_amount == (total_segment_available - wallet_amount - project_amount),
"Сумма конвертации в капитал должна равняться: total_segment_cost - wallet_amount - project_amount");
// === ФАЗА 4: ФИНАЛЬНАЯ ПРОВЕРКА ===
// Проверяем что общая сумма конвертации соответствует всем взносам сегмента
eosio::check(total_convert == total_segment_available,
"Общая сумма конвертации должна равняться общей стоимости сегмента (total_segment_cost)");
Wallet::sub_blocked_funds(_capital, coopname, username, total_convert - project_amount, _source_program,
Capital::Memo::get_convert_segment_to_wallet_memo(convert_hash));
print("total_convert: ", total_convert.to_string());
eosio::check(total_convert == available_for_program, "Общая сумма конвертации должна равняться сумме всех себестоимостей и премий за вычетом суммы выданных ссуд");
eosio::check(capital_amount <= available_for_program, "Сумма конвертации в программу превышает сумму себестоимости и премий за вычетом суммы выданных ссуд");
eosio::check(capital_amount == (available_for_program - wallet_amount - project_amount), "В программу должно быть сконвертировано всё доступное, что не конвертируется в проект или кошелёк");
// Списываем средства с кошелька генерации, которые уходят из проекта
Wallet::sub_blocked_funds(_capital, coopname, username, total_convert - project_amount, _source_program, Capital::Memo::get_convert_segment_to_wallet_memo(convert_hash));
// Выполняем операции с балансами
if (wallet_amount.amount > 0) {
@@ -114,6 +88,7 @@ void capital::convertsegm(eosio::name coopname, eosio::name username,
if (project_amount.amount > 0) {
// Определяем целевой проект для конвертации
auto current_project = Capital::Projects::get_project_or_fail(coopname, project_hash);
checksum256 target_project_hash = project_hash;
checksum256 empty_hash = checksum256();
@@ -127,19 +102,17 @@ void capital::convertsegm(eosio::name coopname, eosio::name username,
eosio::check(target_project.can_convert_to_project,
"Конвертация в кошелек проекта запрещена для данного проекта");
auto check_appendix = Capital::Contributors::get_active_contributor_with_appendix_or_fail(coopname, target_project_hash, username);
// Создаем или обновляем кошелек целевого проекта с долями участника
Capital::Wallets::upsert_project_wallet(coopname, target_project_hash, username, project_amount);
// Добавляем сконвертированные средства в глобальный пул программных инвестиций
Capital::Core::add_program_investment_funds(coopname, project_amount);
Capital::Projects::add_project_membership_shares(coopname, target_project_hash, project_amount);
Capital::Projects::add_project_converted_funds(coopname, target_project_hash, project_amount);
}
// Обновляем сегмент (включая конвертированные суммы)
Capital::Segments::update_segment_conversion(coopname, project_hash, username,
wallet_amount, capital_amount, project_amount);
// Удаляем сегмент после конвертации
Capital::Segments::remove_segment(coopname, project_hash, username);
}
@@ -15,7 +15,7 @@
* @note Авторизация требуется от аккаунта: @p coopname
*/
void capital::approverslt(eosio::name coopname, checksum256 result_hash, document2 approved_statement){
require_auth(coopname);
require_auth(_soviet);
// Проверяем заявление
verify_document_or_fail(approved_statement);
@@ -26,7 +26,7 @@ void capital::approverslt(eosio::name coopname, checksum256 result_hash, documen
eosio::check(exist_result->status == Capital::Results::Status::CREATED, "Неверный статус результата");
// Устанавливаем одобренное заявление
Capital::Results::set_result_approved_statement(coopname, result_hash, approved_statement, coopname);
Capital::Results::set_result_approved_statement(coopname, result_hash, approved_statement);
// Обновляем статус
Capital::Results::update_result_status(coopname, result_hash, Capital::Results::Status::APPROVED);
@@ -1,31 +0,0 @@
/**
* @brief Отклоняет одобрение результата участника
* Отклоняет одобрение результата участника и возвращает статус сегмента:
* - Проверяет подлинность документа причины отклонения
* - Валидирует статус результата (должен быть created)
* - Удаляет объект результата и возвращает статус сегмента в ready
* @param coopname Наименование кооператива
* @param result_hash Хеш результата для отклонения одобрения
* @param decline_reason Документ причины отклонения
* @ingroup public_actions
* @ingroup public_capital_actions
* @note Авторизация требуется от аккаунта: @p coopname
*/
void capital::declineapprv(eosio::name coopname, checksum256 result_hash, document2 decline_reason) {
require_auth(coopname);
// Проверяем документ причины отклонения
verify_document_or_fail(decline_reason);
// Получаем результат и проверяем статус
auto exist_result = Capital::Results::get_result(coopname, result_hash);
eosio::check(exist_result.has_value(), "Результат не найден");
eosio::check(exist_result->status == Capital::Results::Status::CREATED, "Неверный статус результата");
// Удаляем объект результата и возвращаем статус сегмента в GENERATION
Capital::Results::delete_result_and_reset_segment(coopname, exist_result->project_hash, exist_result->username);
// Возвращаем статус сегмента в GENERATION
Capital::Segments::update_segment_status(coopname, exist_result->project_hash, exist_result->username, Capital::Segments::Status::GENERATION);
};
@@ -6,25 +6,22 @@
* - Удаляет объект результата и возвращает статус сегмента в ready
* @param coopname Наименование кооператива
* @param result_hash Хеш результата для отклонения
* @param decision Документ решения совета
* @param reason Причина отклонения
* @ingroup public_actions
* @ingroup public_capital_actions
* @note Авторизация требуется от аккаунта: @p _soviet
*/
void capital::declrslt(eosio::name coopname, checksum256 result_hash, document2 decision) {
void capital::declrslt(eosio::name coopname, checksum256 result_hash, std::string reason) {
require_auth(_soviet);
// Проверяем документ решения
verify_document_or_fail(decision);
// Получаем результат и проверяем статус
auto exist_result = Capital::Results::get_result(coopname, result_hash);
eosio::check(exist_result.has_value(), "Объект результата не найден");
eosio::check(exist_result->status == Capital::Results::Status::APPROVED, "Неверный статус результата");
eosio::check(exist_result->status == Capital::Results::Status::APPROVED || exist_result -> status == Capital::Results::Status::CREATED, "Неверный статус результата");
// Удаляем объект результата и возвращаем статус сегмента в GENERATION
Capital::Results::delete_result_and_reset_segment(coopname, exist_result->project_hash, exist_result->username);
// Удаляем объект результата
Capital::Results::delete_result(coopname, exist_result->project_hash, exist_result->username);
// Возвращаем статус сегмента в GENERATION
Capital::Segments::update_segment_status(coopname, exist_result->project_hash, exist_result->username, Capital::Segments::Status::GENERATION);
@@ -113,11 +113,13 @@ void capital::pushrslt(name coopname, name username, checksum256 project_hash, c
eosio::check(debt_hashes.empty(), "Если нет суммы долга, вектор хэшей долгов должен быть пустым");
}
// Обновляем сегмент после принятия результата и пересчитываем доли - объединенная операция
// для избежания двойного обновления одной записи
Capital::Segments::update_segment_after_result_contribution_with_shares(coopname, project_hash, username,
// Обновляем сегмент
Capital::Segments::update_segment_after_result_contribution(coopname, project_hash, username,
debt_amount);
// Обновляем накопительные показатели контрибьютора на основе его ролей в сегменте
Capital::Contributors::update_contributor_ratings_from_segment(coopname, segment);
// Отправляем результат на одобрение председателем
Capital::Results::send_result_for_approval(coopname, username, result_hash, statement);
}
@@ -16,13 +16,13 @@
* @note Авторизация требуется от аккаунта: @p coopname
*/
void capital::signact2(eosio::name coopname, eosio::name username, checksum256 result_hash, document2 act) {
void capital::signact2(eosio::name coopname, eosio::name chairman, checksum256 result_hash, document2 act) {
require_auth(coopname);
auto soviet = get_board_by_type_or_fail(coopname, "soviet"_n);
auto chairman = soviet.get_chairman();
auto real_chairman = soviet.get_chairman();
eosio::check(username == chairman,
eosio::check(real_chairman == chairman,
"Только председатель может принять имущество");
// Проверяем результат и права
@@ -31,12 +31,12 @@ void capital::signact2(eosio::name coopname, eosio::name username, checksum256 r
eosio::check(result->status == Capital::Results::Status::ACT1, "Неверный статус. Первый акт должен быть подписан");
// Проверяем документ
verify_document_or_fail(act, { result->username, username });
verify_document_or_fail(act, { result->username, real_chairman });
// Устанавливаем второй акт
Capital::Results::set_result_act2(coopname, result_hash, act);
auto contributor = Capital::Contributors::get_active_contributor_or_fail(coopname, username);
auto contributor = Capital::Contributors::get_active_contributor_or_fail(coopname, result -> username);
auto memo = Capital::Memo::get_push_result_memo(contributor -> id);
// Начисляем заблокированные средства в кошелек программы генерации
@@ -90,7 +90,6 @@
#include "app/result_submission/convert_segment/convertsegm.cpp"
#include "app/result_submission/push_result/approverslt.cpp"
#include "app/result_submission/push_result/authrslt.cpp"
#include "app/result_submission/push_result/declineapprv.cpp"
#include "app/result_submission/push_result/declrslt.cpp"
#include "app/result_submission/push_result/pushrslt.cpp"
#include "app/result_submission/push_result/signact1.cpp"
+2 -5
View File
@@ -153,16 +153,13 @@ public:
void approverslt(eosio::name coopname, checksum256 result_hash, document2 approved_statement);
[[eosio::action]]
void declineapprv(eosio::name coopname, checksum256 result_hash, document2 decline_reason);
[[eosio::action]]
void declrslt(eosio::name coopname, checksum256 result_hash, document2 decision);
void declrslt(eosio::name coopname, checksum256 result_hash, std::string reason);
[[eosio::action]]
void signact1(eosio::name coopname, eosio::name username, checksum256 result_hash, document2 act);
[[eosio::action]]
void signact2(eosio::name coopname, eosio::name username, checksum256 result_hash, document2 act);
void signact2(eosio::name coopname, eosio::name chairman, checksum256 result_hash, document2 act);
// конвертация
@@ -71,23 +71,23 @@ namespace Capital::Core {
segments.modify(segment_it, _capital, [&](auto &s) {
if (s.is_author) {
// Обновляем базовые авторские награды
int64_t pending_base_reward = 1 *
(project.crps.author_base_cumulative_reward_per_share - s.last_author_base_reward_per_share);
if (pending_base_reward > 0) {
eosio::asset base_reward_asset = eosio::asset(pending_base_reward, _root_govern_symbol);
s.author_base += base_reward_asset;
s.last_author_base_reward_per_share = project.crps.author_base_cumulative_reward_per_share;
double base_delta = project.crps.author_base_cumulative_reward_per_share - s.last_author_base_reward_per_share;
if (base_delta > 0.0) {
int64_t pending_base_reward = static_cast<int64_t>(base_delta);
if (pending_base_reward > 0) {
s.author_base += eosio::asset(pending_base_reward, _root_govern_symbol);
s.last_author_base_reward_per_share = project.crps.author_base_cumulative_reward_per_share;
}
}
// Обновляем бонусные авторские награды
int64_t pending_bonus_reward = 1 *
(project.crps.author_bonus_cumulative_reward_per_share - s.last_author_bonus_reward_per_share);
if (pending_bonus_reward > 0) {
s.author_bonus += eosio::asset(pending_bonus_reward, _root_govern_symbol);
s.last_author_bonus_reward_per_share = project.crps.author_bonus_cumulative_reward_per_share;
double bonus_delta = project.crps.author_bonus_cumulative_reward_per_share - s.last_author_bonus_reward_per_share;
if (bonus_delta > 0.0) {
int64_t pending_bonus_reward = static_cast<int64_t>(bonus_delta);
if (pending_bonus_reward > 0) {
s.author_bonus += eosio::asset(pending_bonus_reward, _root_govern_symbol);
s.last_author_bonus_reward_per_share = project.crps.author_bonus_cumulative_reward_per_share;
}
}
}
});
@@ -106,13 +106,13 @@ namespace Capital::Core {
if (p.counts.total_authors > 0) {
// Обновляем базовые авторские награды
if (base_reward.amount > 0) {
int64_t base_delta = base_reward.amount / p.counts.total_authors;
double base_delta = static_cast<double>(base_reward.amount) / static_cast<double>(p.counts.total_authors);
p.crps.author_base_cumulative_reward_per_share += base_delta;
}
// Обновляем бонусные авторские награды
if (bonus_reward.amount > 0) {
int64_t bonus_delta = bonus_reward.amount / p.counts.total_authors;
double bonus_delta = static_cast<double>(bonus_reward.amount) / static_cast<double>(p.counts.total_authors);
p.crps.author_bonus_cumulative_reward_per_share += bonus_delta;
}
}
@@ -6,15 +6,24 @@ namespace Capital::Core {
/**
* @brief Обновляет CRPS поля в проекте для вкладчиков при добавлении наград
*/
void update_contributor_crps(eosio::name coopname, const checksum256 &project_hash, const eosio::asset &reward_amount) {
void increment_contributors_crps_in_project(eosio::name coopname, const checksum256 &project_hash, const eosio::asset &reward_amount) {
Capital::project_index projects(_capital, coopname.value);
auto project = projects.find(Capital::Projects::get_project_or_fail(coopname, project_hash).id);
print("DEBUG increment_contributors_crps: reward_amount=", reward_amount);
print(" total_shares=", project->crps.total_capital_contributors_shares);
print(" old_crps=", project->crps.contributor_cumulative_reward_per_share);
projects.modify(project, _capital, [&](auto &p) {
// Проверяем что есть зарегистрированные доли для распределения
if (p.crps.total_capital_contributors_shares.amount > 0) {
// Рассчитываем дельту reward per share (награда на долю в базовых единицах)
int64_t delta = reward_amount.amount / p.crps.total_capital_contributors_shares.amount;
p.crps.contributor_cumulative_reward_per_share += delta;
// Простой расчет награды на долю
double reward_per_share = static_cast<double>(reward_amount.amount) / static_cast<double>(p.crps.total_capital_contributors_shares.amount);
p.crps.contributor_cumulative_reward_per_share += reward_per_share;
print(" reward_per_share=", reward_per_share);
print(" new_crps=", p.crps.contributor_cumulative_reward_per_share);
} else {
print(" NO SHARES REGISTERED!");
}
});
}
@@ -51,6 +60,7 @@ void upsert_contributor_segment(eosio::name coopname, const checksum256 &project
});
// Увеличиваем счетчики вкладчиков
print("DEBUG upsert_contributor: new contributor ", username, " shares=", user_shares);
Capital::Projects::increment_total_contributors(coopname, project_hash);
Capital::Projects::increment_total_contributor_shares(coopname, project_hash, user_shares);
} else {
@@ -81,6 +91,7 @@ void upsert_contributor_segment(eosio::name coopname, const checksum256 &project
if (became_contributor) {
// Увеличиваем счетчик зарегистрированных вкладчиков
print("DEBUG upsert_contributor: existing user ", username, " became contributor, shares=", user_shares);
Capital::Projects::increment_total_contributors(coopname, project_hash);
// Увеличиваем счетчик долей для нового вкладчика
Capital::Projects::increment_total_contributor_shares(coopname, project_hash, user_shares);
@@ -96,44 +107,39 @@ void upsert_contributor_segment(eosio::name coopname, const checksum256 &project
auto segment_opt = Segments::get_segment(coopname, project_hash, username);
if (!segment_opt.has_value()) {
print("DEBUG: segment not found for ", username);
return; // Сегмент не найден
}
auto segment_it = segments.find(segment_opt->id);
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
print("DEBUG refresh_contributor_segment: user=", username);
print(" shares=", segment_opt->capital_contributor_shares);
print(" project_crps=", project.crps.contributor_cumulative_reward_per_share);
print(" last_crps=", segment_opt->last_contributor_reward_per_share);
segments.modify(segment_it, _capital, [&](auto &s) {
// Обновляем награды вкладчика
// Обновляем награды вкладчика через CRPS алгоритм
if (s.capital_contributor_shares.amount > 0) {
int64_t pending_contributor_reward = s.capital_contributor_shares.amount *
(project.crps.contributor_cumulative_reward_per_share - s.last_contributor_reward_per_share);
// Разность наград на долю
double reward_per_share_delta = project.crps.contributor_cumulative_reward_per_share - s.last_contributor_reward_per_share;
print(" delta=", reward_per_share_delta);
if (pending_contributor_reward > 0) {
s.contributor_bonus += eosio::asset(pending_contributor_reward, _root_govern_symbol);
s.last_contributor_reward_per_share = project.crps.contributor_cumulative_reward_per_share;
if (reward_per_share_delta > 0.0) {
// Простой расчет награды участника
double pending_reward_double = static_cast<double>(s.capital_contributor_shares.amount) * reward_per_share_delta;
int64_t pending_contributor_reward = static_cast<int64_t>(pending_reward_double);
print(" pending_reward=", pending_contributor_reward);
if (pending_contributor_reward > 0) {
s.contributor_bonus += eosio::asset(pending_contributor_reward, _root_govern_symbol);
s.last_contributor_reward_per_share = project.crps.contributor_cumulative_reward_per_share;
print(" new_bonus=", s.contributor_bonus);
}
}
}
// Обновление capital_contributor_shares на основе текущего баланса
eosio::asset capital_balance = Capital::Core::get_capital_program_user_share_balance(coopname, username);
if (s.capital_contributor_shares != capital_balance) {
// Корректируем общие доли в проекте
Capital::project_index projects(_capital, coopname.value);
auto project_it = projects.find(project.id);
projects.modify(project_it, _capital, [&](auto &p) {
p.crps.total_capital_contributors_shares = p.crps.total_capital_contributors_shares - s.capital_contributor_shares + capital_balance;
});
// Если становится новым вкладчиком, инициализируем CRPS и устанавливаем флаг
if (!s.is_contributor && capital_balance.amount > 0) {
s.is_contributor = true;
s.last_contributor_reward_per_share = project.crps.contributor_cumulative_reward_per_share;
// Увеличиваем счетчик зарегистрированных вкладчиков
Capital::Projects::increment_total_contributors(coopname, project_hash);
}
s.capital_contributor_shares = capital_balance;
} else {
print(" no shares!");
}
});
}
@@ -24,7 +24,7 @@ namespace Capital::Core {
if (is_segment_updated) {
return; // Сегмент обновлен
}
print("before refresh_segment");
// Обновляем награды для каждой роли отдельно
refresh_author_segment(coopname, project_hash, username);
refresh_coordinator_segment(coopname, project_hash, username);
@@ -148,12 +148,16 @@ namespace Capital::Core {
}
project_wallets.modify(wallet_it, _capital, [&](auto &w) {
// Рассчитываем накопленные награды от членских взносов на основе долей в кошельке проекта
int64_t pending_membership_reward = w.shares.amount *
(project.membership.cumulative_reward_per_share - w.last_membership_reward_per_share);
// Рассчитываем накопленные награды от членских взносов
double membership_delta = project.membership.cumulative_reward_per_share - w.last_membership_reward_per_share;
if (pending_membership_reward > 0) {
w.membership_available += eosio::asset(pending_membership_reward, _root_govern_symbol);
if (membership_delta > 0.0) {
double pending_reward_double = static_cast<double>(w.shares.amount) * membership_delta;
int64_t pending_membership_reward = static_cast<int64_t>(pending_reward_double);
if (pending_membership_reward > 0) {
w.membership_available += eosio::asset(pending_membership_reward, _root_govern_symbol);
}
}
w.last_membership_reward_per_share = project.membership.cumulative_reward_per_share;
@@ -14,15 +14,10 @@ namespace Capital::Core {
void increment_authors_crps_in_project(eosio::name coopname, const checksum256 &project_hash,
const eosio::asset &base_reward, const eosio::asset &bonus_reward);
/**
* @brief Обновляет CRPS поля в проекте для координаторов при добавлении наград
*/
void update_coordinator_crps(eosio::name coopname, const checksum256 &project_hash, const eosio::asset &reward_amount);
/**
* @brief Обновляет CRPS поля в проекте для вкладчиков при добавлении наград
*/
void update_contributor_crps(eosio::name coopname, const checksum256 &project_hash, const eosio::asset &reward_amount);
void increment_contributors_crps_in_project(eosio::name coopname, const checksum256 &project_hash, const eosio::asset &reward_amount);
/**
* @brief Обновляет сегмент участника - пересчитывает накопленные награды на основе CRPS
@@ -17,8 +17,8 @@ namespace Capital::Core {
state.program_membership_available += amount;
if (total_shares.amount > 0) {
// Рассчитываем награду на долю в базовых единицах
int64_t delta = amount.amount / total_shares.amount;
// Рассчитываем награду на долю
double delta = static_cast<double>(amount.amount) / static_cast<double>(total_shares.amount);
state.program_membership_cumulative_reward_per_share += delta;
}
@@ -40,14 +40,19 @@ namespace Capital::Core {
auto state = Capital::get_global_state(coopname);
// Считаем дельту CRPS
int64_t current_crps = state.program_membership_cumulative_reward_per_share;
int64_t last_crps = capital_wallet_opt.has_value() ? capital_wallet_opt->last_program_crps : 0;
int64_t delta = current_crps - last_crps;
double current_crps = state.program_membership_cumulative_reward_per_share;
double last_crps = capital_wallet_opt.has_value() ? capital_wallet_opt->last_program_crps : 0.0;
double delta = current_crps - last_crps;
if (delta > 0) {
// Начисляем вознаграждение
eosio::asset reward_amount = eosio::asset(share_balance.amount * delta, _root_govern_symbol);
Capital::Wallets::upsert_capital_wallet(coopname, username, current_crps, reward_amount);
if (delta > 0.0) {
// Рассчитываем вознаграждение
double pending_reward_double = static_cast<double>(share_balance.amount) * delta;
int64_t pending_reward = static_cast<int64_t>(pending_reward_double);
if (pending_reward > 0) {
eosio::asset reward_amount = eosio::asset(pending_reward, _root_govern_symbol);
Capital::Wallets::upsert_capital_wallet(coopname, username, current_crps, reward_amount);
}
}
}
@@ -234,7 +234,8 @@ namespace Capital::Core::Generation {
// Используем total_shares для расчета распределения на основе долей
if (project -> membership.total_shares.amount > 0) {
int64_t delta = membership_current_fund.amount / project->membership.total_shares.amount;
double delta = static_cast<double>(membership_current_fund.amount) /
static_cast<double>(project->membership.total_shares.amount);
p.membership.cumulative_reward_per_share += delta;
};
});
@@ -8,16 +8,22 @@ namespace Capital {
/**
* @brief Структура для данных CRPS (Cumulative Reward Per Share) распределения наград
*
* CRPS алгоритм с использованием double для точности:
* 1. При добавлении награды: crps += награда / количество_долей
* 2. При расчете награды: pending = доли_участника * (current_crps - last_crps)
*
* Все CRPS поля используют double для высокой точности без проблем с overflow
*/
struct crps_data {
// Общее количество долей вкладчиков
eosio::asset total_capital_contributors_shares = asset(0, _root_govern_symbol); ///< Общее количество долей вкладчиков в проекте
// Общее количество долей вкладчиков (изменяется только при регистрации/обновлении долей участников)
eosio::asset total_capital_contributors_shares = asset(0, _root_govern_symbol); ///< Сумма всех capital_contributor_shares зарегистрированных вкладчиков
// Накопительные награды на долю
int64_t author_base_cumulative_reward_per_share = 0; ///< Накопительная базовая награда на авторскую долю
int64_t author_bonus_cumulative_reward_per_share = 0; ///< Накопительная бонусная награда на авторскую долю
int64_t coordinator_cumulative_reward_per_share = 0; ///< Накопительная награда на координаторскую долю
int64_t contributor_cumulative_reward_per_share = 0; ///< Накопительная награда на вкладчическую долю
// Накопительные награды на долю (double для точности)
double author_base_cumulative_reward_per_share = 0.0; ///< Накопительная базовая награда на авторскую долю
double author_bonus_cumulative_reward_per_share = 0.0; ///< Накопительная бонусная награда на авторскую долю
double coordinator_cumulative_reward_per_share = 0.0; ///< Накопительная награда на координаторскую долю (НЕ ИСПОЛЬЗУЕТСЯ)
double contributor_cumulative_reward_per_share = 0.0; ///< Накопительная награда на вкладчическую долю
};
} // namespace Capital
@@ -209,4 +209,6 @@ inline void create_debt_agenda(
}
} // namespace Capital::Debts
@@ -31,7 +31,7 @@ namespace Capital {
asset program_membership_funded = asset(0, _root_govern_symbol); ///< Общая сумма членских взносов по программе
asset program_membership_available = asset(0, _root_govern_symbol); ///< Доступная сумма членских взносов по программе
asset program_membership_distributed = asset(0, _root_govern_symbol); ///< Распределенная сумма членских взносов по программе
int64_t program_membership_cumulative_reward_per_share; ///< Накопительное вознаграждение на долю в членских взносах
double program_membership_cumulative_reward_per_share; ///< Накопительное вознаграждение на долю в членских взносах
config config; ///< Управляемая конфигурация контракта
@@ -7,7 +7,7 @@ using namespace eosio;
struct membership_crps {
int64_t cumulative_reward_per_share = 0; ///< Сумма накопленных членских взносов на одну долю вкладчика проекта
double cumulative_reward_per_share = 0.0; ///< Сумма накопленных членских взносов на одну долю
eosio::asset total_shares = asset(0, _root_govern_symbol); ///< Общая сумма долей всех участников в проекте (только сконвертированные в кошельки проекта)
eosio::asset funded = asset(0, _root_govern_symbol); ///< Общее количество поступивших членских взносов
@@ -1,4 +1,7 @@
namespace Capital::Memo {
inline std::string get_debt_memo(eosio::name username) {
return "Выдача ссуды для " + username.to_string();
}
inline std::string get_invest_memo(uint64_t contributor_id) {
return "Зачёт части целевого паевого взноса по программе 'Цифровой Кошелёк' в качестве паевого взноса по договору УХД с ID: " + std::to_string(contributor_id);
@@ -668,5 +668,19 @@ namespace Capital::Projects {
}
/**
* @brief Удаляет проект
* @param coopname Имя кооператива
* @param project_hash Хэш проекта
*/
inline void delete_project(eosio::name coopname, const checksum256 &project_hash) {
auto exist_project = get_project_or_fail(coopname, project_hash);
project_index projects(_capital, coopname.value);
auto project_itr = projects.find(exist_project.id);
projects.erase(project_itr);
}
}// namespace Project
@@ -135,9 +135,9 @@ inline void create_result_for_participant(eosio::name coopname, const checksum25
/**
* @brief Удаляет объект результата и возвращает статус сегмента в ready
* @brief Удаляет объект результата
*/
inline void delete_result_and_reset_segment(eosio::name coopname, const checksum256 &project_hash,
inline void delete_result(eosio::name coopname, const checksum256 &project_hash,
eosio::name username) {
// Удаляем объект результата
auto result_opt = get_result_by_project_and_username(coopname, project_hash, username);
@@ -149,19 +149,6 @@ inline void delete_result_and_reset_segment(eosio::name coopname, const checksum
}
/**
* @brief Удаляет объект результата после успешного принятия
*/
inline void delete_result(eosio::name coopname, const checksum256 &project_hash,
eosio::name username) {
auto result_opt = get_result_by_project_and_username(coopname, project_hash, username);
if (result_opt.has_value()) {
result_index results(_capital, coopname.value);
auto result_itr = results.find(result_opt->id);
results.erase(result_itr);
}
}
/**
* @brief Обновляет статус результата
*/
@@ -195,14 +182,14 @@ inline void set_result_authorization(eosio::name coopname, const checksum256 &re
/**
* @brief Устанавливает одобренное заявление результата
*/
inline void set_result_approved_statement(eosio::name coopname, const checksum256 &result_hash, const document2 &approved_statement, eosio::name payer) {
inline void set_result_approved_statement(eosio::name coopname, const checksum256 &result_hash, const document2 &approved_statement) {
auto exist_result = get_result(coopname, result_hash);
eosio::check(exist_result.has_value(), "Объект результата не найден");
result_index results(_capital, coopname.value);
auto result = results.find(exist_result->id);
results.modify(result, payer, [&](auto &r){
results.modify(result, _capital, [&](auto &r){
r.statement = approved_statement;
});
}
@@ -217,7 +204,7 @@ inline void set_result_act1(eosio::name coopname, const checksum256 &result_hash
result_index results(_capital, coopname.value);
auto result = results.find(exist_result->id);
results.modify(result, _capital, [&](auto &r){
results.modify(result, coopname, [&](auto &r){
r.act = act;
});
}
@@ -232,7 +219,7 @@ inline void set_result_act2(eosio::name coopname, const checksum256 &result_hash
result_index results(_capital, coopname.value);
auto result = results.find(exist_result->id);
results.modify(result, _capital, [&](auto &r){
results.modify(result, coopname, [&](auto &r){
r.act = act;
});
}
@@ -241,18 +228,36 @@ inline void set_result_act2(eosio::name coopname, const checksum256 &result_hash
* @brief Отправляет результат на рассмотрение в совет
*/
inline void send_result_to_soviet(eosio::name coopname, eosio::name username, const checksum256 &result_hash, const document2 &approved_statement) {
::Soviet::create_agenda(
_capital,
coopname,
username,
Names::SovietActions::CREATE_RESULT,
result_hash,
_capital,
Names::Capital::AUTHORIZE_RESULT,
Names::Capital::DECLINE_RESULT,
approved_statement,
std::string("")
);
::Soviet::create_agenda(
_capital,
coopname,
username,
Names::SovietActions::CREATE_RESULT,
result_hash,
_capital,
Names::Capital::AUTHORIZE_RESULT,
Names::Capital::DECLINE_RESULT,
approved_statement,
std::string("")
);
}
/**
* @brief Отправляет результат на одобрение председателем
*/
inline void send_result_for_approval(eosio::name coopname, eosio::name username, const checksum256 &result_hash, const document2 &statement) {
::Soviet::create_approval(
_capital,
coopname,
username,
statement,
Names::Capital::CREATE_RESULT,
result_hash,
_capital,
Names::Capital::APPROVE_RESULT,
Names::Capital::DECLINE_RESULT,
std::string("")
);
}
} // namespace Results
@@ -69,15 +69,13 @@ namespace Capital::Segments {
eosio::asset property_base = asset(0, _root_govern_symbol); ///< Стоимость внесенного имущества участника
// CRPS поля для масштабируемого распределения наград
int64_t last_author_base_reward_per_share = 0; ///< Последняя зафиксированная базовая награда на долю для авторов
int64_t last_author_bonus_reward_per_share = 0; ///< Последняя зафиксированная бонусная награда на долю для авторов
int64_t last_coordinator_reward_per_share = 0; ///< Последняя зафиксированная награда на долю для координаторов
int64_t last_contributor_reward_per_share = 0; ///< Последняя зафиксированная награда на долю для вкладчиков
double last_author_base_reward_per_share = 0.0; ///< Последняя зафиксированная базовая награда на долю для авторов
double last_author_bonus_reward_per_share = 0.0; ///< Последняя зафиксированная бонусная награда на долю для авторов
double last_contributor_reward_per_share = 0.0; ///< Последняя зафиксированная награда на долю для вкладчиков
// Доли в программе и проекте
eosio::asset capital_contributor_shares = asset(0, _root_govern_symbol); ///< Количество долей вкладчика в программе капитализации
eosio::asset project_contributor_shares = asset(0, _root_govern_symbol); ///< Текущая доля участника в проекте (базовые_вклады - конвертации - ссуды)
// Последняя известная сумма инвестиций в проекте для расчета provisional_amount
eosio::asset last_known_invest_pool = asset(0, _root_govern_symbol); ///< Последняя известная сумма инвестиций в проекте
@@ -271,31 +269,28 @@ inline bool is_voting_participant(eosio::name coopname, const checksum256 &proje
* @return true если сегмент обновлен
*/
inline bool is_segment_updated(eosio::name coopname, const checksum256 &project_hash, eosio::name username) {
auto segment_opt = get_segment(coopname, project_hash, username);
if (!segment_opt.has_value()) {
return false; // Сегмент не найден
}
auto segment = get_segment_or_fail(coopname, project_hash, username, "Сегмент пайщика не найден");
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
// Проверяем актуальность CRPS для каждой роли
bool author_updated = (!segment_opt->is_author) ||
(segment_opt->last_author_base_reward_per_share == project.crps.author_base_cumulative_reward_per_share &&
segment_opt->last_author_bonus_reward_per_share == project.crps.author_bonus_cumulative_reward_per_share);
bool author_updated = (!segment.is_author) ||
(segment.last_author_base_reward_per_share == project.crps.author_base_cumulative_reward_per_share &&
segment.last_author_bonus_reward_per_share == project.crps.author_bonus_cumulative_reward_per_share);
// Координаторы используют пропорциональное распределение на основе coordinators_investment_pool
bool coordinator_updated = (!segment_opt->is_coordinator) ||
(segment_opt->last_known_coordinators_investment_pool == project.fact.coordinators_investment_pool);
bool coordinator_updated = (!segment.is_coordinator) ||
(segment.last_known_coordinators_investment_pool == project.fact.coordinators_investment_pool);
bool contributor_updated = (!segment_opt->is_contributor) ||
(segment_opt->last_contributor_reward_per_share == project.crps.contributor_cumulative_reward_per_share);
bool contributor_updated = (!segment.is_contributor) ||
(segment.last_contributor_reward_per_share == project.crps.contributor_cumulative_reward_per_share);
// Проверяем актуальность инвестиционного пула для расчета provisional_amount (нужно всем ролям)
bool invest_pool_updated = (segment_opt->last_known_invest_pool == project.fact.invest_pool);
bool invest_pool_updated = (segment.last_known_invest_pool == project.fact.invest_pool);
// Проверяем актуальность базового пула создателей для корректного расчета использования инвестиций (нужно только инвесторам)
bool creators_base_pool_updated = (!segment_opt->is_investor) ||
(segment_opt->last_known_creators_base_pool == project.fact.creators_base_pool);
bool creators_base_pool_updated = (!segment.is_investor) ||
(segment.last_known_creators_base_pool == project.fact.creators_base_pool);
return author_updated && coordinator_updated && contributor_updated && invest_pool_updated && creators_base_pool_updated;
}
@@ -427,7 +422,7 @@ inline void update_segment_conversion(eosio::name coopname, const checksum256 &p
* @brief Объединенная функция: обновляет сегмент после принятия результата и пересчитывает доли участника
* Оптимизированная версия для избежания двойного обновления одной записи
*/
inline void update_segment_after_result_contribution_with_shares(eosio::name coopname, const checksum256 &project_hash,
inline void update_segment_after_result_contribution(eosio::name coopname, const checksum256 &project_hash,
eosio::name username,
eosio::asset debt_settled_amount = asset(0, _root_govern_symbol)) {
segments_index segments(_capital, coopname.value);
@@ -445,18 +440,6 @@ inline void update_segment_after_result_contribution_with_shares(eosio::name coo
if (debt_settled_amount.amount > 0) {
s.debt_settled += debt_settled_amount;
}
// Пересчитываем доли участника: базовые_вклады - конвертации - ссуды
eosio::asset base_contributions = s.creator_base + s.author_base + s.coordinator_base + s.property_base;
//TODO: тут кажется это не нужно т.к. суммы равны нулю - проверить
eosio::asset conversions = s.converted_to_wallet + s.converted_to_capital;
s.project_contributor_shares = base_contributions - conversions - s.debt_amount;
// Доли не могут быть отрицательными
if (s.project_contributor_shares.amount < 0) {
s.project_contributor_shares = asset(0, _root_govern_symbol);
}
});
}
@@ -515,7 +498,38 @@ inline void decrease_debt_amount(eosio::name coopname, uint64_t segment_id, eosi
s.debt_amount -= amount;
});
}
/**
* @brief Удаляет сегмент участника
* @param coopname Имя кооператива
* @param project_hash Хэш проекта
* @param username Имя пользователя
*/
inline void remove_segment(eosio::name coopname, const checksum256 &project_hash, eosio::name username) {
segments_index segments(_capital, coopname.value);
auto idx = segments.get_index<"byprojuser"_n>();
auto key = combine_checksum_ids(project_hash, username);
auto segment_itr = idx.find(key);
eosio::check(segment_itr != idx.end(), "Сегмент участника не найден");
idx.erase(segment_itr);
}
/**
* @brief Проверяет наличие сегментов в проекте
* @param coopname Имя кооператива
* @param project_hash Хэш проекта
* @return true если есть сегменты, false если нет
*/
inline bool has_project_segments(eosio::name coopname, const checksum256 &project_hash) {
segments_index segments(_capital, coopname.value);
auto idx = segments.get_index<"byproject"_n>();
auto it = idx.lower_bound(project_hash);
return it != idx.end() && it->project_hash == project_hash;
}
} // namespace Capital::Segments
@@ -12,7 +12,7 @@ namespace Capital {
uint64_t id; ///< ID кошелька (внутренний ключ)
eosio::name coopname; ///< Имя кооператива
eosio::name username; ///< Имя пользователя
int64_t last_program_crps = 0; ///< Последнее значение программной CRPS для членских взносов
double last_program_crps = 0.0; ///< Последнее значение программной CRPS для членских взносов
eosio::asset capital_available = asset(0, _root_govern_symbol); ///< Доступные средства от членских взносов для вывода
uint64_t primary_key() const { return id; } ///< Первичный ключ (1)
@@ -38,7 +38,7 @@ namespace Capital {
checksum256 project_hash; ///< Хэш проекта
eosio::name username; ///< Имя пользователя
eosio::asset shares = asset(0, _root_govern_symbol); ///< Доли участника в проекте для получения членских взносов
int64_t last_membership_reward_per_share = 0; ///< Последнее значение CRPS для членских взносов
double last_membership_reward_per_share = 0.0; ///< Последнее значение CRPS для членских взносов
eosio::asset membership_available = asset(0, _root_govern_symbol); ///< Доступные средства от членских взносов для вывода
uint64_t primary_key() const { return id; } ///< Первичный ключ (1)
@@ -63,6 +63,7 @@ namespace Names {
// Коллбэки для результатов
constexpr eosio::name AUTHORIZE_RESULT = "authrslt"_n;
constexpr eosio::name DECLINE_RESULT = "declrslt"_n;
constexpr eosio::name APPROVE_RESULT = "approverslt"_n;
// Коллбэки для платежей
constexpr eosio::name CONFIRM_EXPENSE_PAYMENT = "exppaycnfrm"_n;
@@ -110,5 +111,6 @@ namespace Names {
constexpr eosio::name CREATE_WITHDRAW_1 = "createwthd1"_n; // акцепт возврата из задания
constexpr eosio::name CREATE_WITHDRAW_2 = "createwthd2"_n; // акцепт возврата из проекта
constexpr eosio::name CREATE_WITHDRAW_3 = "createwthd3"_n; // акцепт возврата из программы
constexpr eosio::name CREATE_RESULT = "createresult"_n; // акцепт результата
}
}
@@ -86,9 +86,9 @@ public:
}
static void validate_asset(const eosio::asset& amount) {
check(amount.symbol == _root_govern_symbol, "Invalid token symbol");
check(amount.is_valid(), "Invalid asset");
check(amount.amount > 0, "Amount must be positive");
check(amount.symbol == _root_govern_symbol, "Неверный символ токена");
check(amount.is_valid(), "Неверный актив");
check(amount.amount >= 0, "Сумма должна быть неотрицательной");
}
/**
@@ -58,7 +58,6 @@ export interface IApprovecmmt {
coopname: IName
master: IName
commit_hash: IChecksum256
empty_document: IDocument2
}
export interface IApprovedebt {
@@ -395,12 +394,6 @@ export interface IDebtpaydcln {
reason: string
}
export interface IDeclineapprv {
coopname: IName
result_hash: IChecksum256
decline_reason: IDocument2
}
export interface IDeclinecmmt {
coopname: IName
master: IName
@@ -447,7 +440,7 @@ export interface IDeclpinv {
export interface IDeclrslt {
coopname: IName
result_hash: IChecksum256
decision: IDocument2
reason: string
}
export interface IDelproject {
@@ -704,6 +697,7 @@ export interface IPushrslt {
contribution_amount: IAsset
debt_amount: IAsset
statement: IDocument2
debt_hashes: IChecksum256[]
}
export interface IRefreshprog {
@@ -843,7 +837,7 @@ export interface ISignact1 {
export interface ISignact2 {
coopname: IName
username: IName
chairman: IName
result_hash: IChecksum256
act: IDocument2
}
@@ -394,12 +394,6 @@ export interface IDebtpaydcln {
reason: string
}
export interface IDeclineapprv {
coopname: IName
result_hash: IChecksum256
decline_reason: IDocument2
}
export interface IDeclinecmmt {
coopname: IName
master: IName
@@ -446,7 +440,7 @@ export interface IDeclpinv {
export interface IDeclrslt {
coopname: IName
result_hash: IChecksum256
decision: IDocument2
reason: string
}
export interface IDelproject {
@@ -843,7 +837,7 @@ export interface ISignact1 {
export interface ISignact2 {
coopname: IName
username: IName
chairman: IName
result_hash: IChecksum256
act: IDocument2
}