before coordinators distribution change
This commit is contained in:
@@ -9,7 +9,7 @@ export default antfu(
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'no-unused-vars': 'warn',
|
||||
'no-unused-vars': 'off',
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
'no-console': 'off',
|
||||
'unicorn/prefer-number-properties': 'off',
|
||||
|
||||
@@ -330,7 +330,7 @@ export async function startInfra() {
|
||||
await blockchain.transfer({
|
||||
from: 'eosio',
|
||||
to: config.provider,
|
||||
quantity: `100.0000 ${config.token.symbol}`,
|
||||
quantity: `1000.0000 ${config.token.symbol}`,
|
||||
memo: '',
|
||||
})
|
||||
|
||||
|
||||
@@ -6,20 +6,8 @@ import type { Account, Contract, Keys } from '../types'
|
||||
import config, { GOVERN_SYMBOL, SYMBOL } from '../configs'
|
||||
import Blockchain from '../blockchain'
|
||||
import { sendPostToCoopbackWithSecret, sleep } from '../utils'
|
||||
import { fakeDocument } from '../tests/shared/fakeDocument'
|
||||
|
||||
// const test_hash
|
||||
// = '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777'
|
||||
// const test_sign
|
||||
// = 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8'
|
||||
// const test_pkey = 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J'
|
||||
// const test_meta = JSON.stringify({})
|
||||
|
||||
// const document = {
|
||||
// hash: test_hash,
|
||||
// signature: test_sign,
|
||||
// public_key: test_pkey,
|
||||
// meta: test_meta,
|
||||
// }
|
||||
|
||||
export class CooperativeClass {
|
||||
public blockchain: Blockchain
|
||||
@@ -28,7 +16,7 @@ export class CooperativeClass {
|
||||
this.blockchain = blockchain
|
||||
}
|
||||
|
||||
async addUser(username: string, keys?: Keys) {
|
||||
async addUser(username: string, keys?: Keys, referer?: string) {
|
||||
const account = await this.blockchain.generateKeypair(
|
||||
username,
|
||||
keys,
|
||||
@@ -38,7 +26,7 @@ export class CooperativeClass {
|
||||
|
||||
const data: RegistratorContract.Actions.AddUser.IAddUser = {
|
||||
coopname: config.provider,
|
||||
referer: '',
|
||||
referer: referer || '',
|
||||
username: account.username,
|
||||
type: 'individual',
|
||||
created_at: '2025-02-05T09:34:27',
|
||||
@@ -92,12 +80,7 @@ export class CooperativeClass {
|
||||
administrator: config.provider,
|
||||
username: username!,
|
||||
agreement_type: 'wallet',
|
||||
document: { // отправляем произвольный документ с валидной подписью
|
||||
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
meta: '{}',
|
||||
},
|
||||
document: fakeDocument,
|
||||
})
|
||||
|
||||
console.log('создаём кошелёк')
|
||||
@@ -114,7 +97,7 @@ export class CooperativeClass {
|
||||
}
|
||||
}
|
||||
|
||||
export async function addUser(username: string) {
|
||||
export async function addUser(username: string, referer?: string) {
|
||||
// инициализируем инстанс с ключами
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
const cooperative = new CooperativeClass(blockchain)
|
||||
@@ -124,5 +107,5 @@ export async function addUser(username: string) {
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
})
|
||||
}, referer)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
// eslint-disable-next-line ts/consistent-type-imports
|
||||
import { CapitalContract, SovietContract } from 'cooptypes'
|
||||
import Blockchain from '../blockchain'
|
||||
import config from '../configs'
|
||||
import { getTotalRamUsage, globalRamStats } from '../utils/getTotalRamUsage'
|
||||
import { addUser } from '../init/participant'
|
||||
import { generateRandomUsername } from '../utils/randomUsername'
|
||||
import { generateRandomSHA256 } from '../utils/randomHash'
|
||||
import { sleep } from '../utils'
|
||||
import { getCoopProgramWallet } from './wallet/walletUtils'
|
||||
import { registerContributor } from './capital/registerContributor'
|
||||
import { signAppendix } from './capital/signAppendix'
|
||||
import { capitalProgramId, sourceProgramId } from './capital/consts'
|
||||
import { makeCombinedChecksum256NameIndexKey } from './shared/combinedKeys'
|
||||
import { commitToResult } from './capital/commitToResult'
|
||||
import { processApprove } from './capital/processApprove'
|
||||
import { refreshSegment } from './capital/refreshSegment'
|
||||
import { signWalletAgreement } from './wallet/signWalletAgreement'
|
||||
import { fakeDocument } from './shared/fakeDocument'
|
||||
import { depositToWallet } from './wallet/depositToWallet'
|
||||
import { signCapitalAgreement } from './capital/signCapitalAgreement'
|
||||
import { addAuthor } from './capital/addAuthor'
|
||||
import { getSegment } from './capital/getSegment'
|
||||
import { investInProject } from './capital/investInProject'
|
||||
|
||||
// const CLI_PATH = 'src/index.ts'
|
||||
|
||||
@@ -19,25 +33,23 @@ let _project3: CapitalContract.Actions.CreateProject.ICreateProject
|
||||
let tester1: string
|
||||
let tester2: string
|
||||
let tester3: string
|
||||
let tester4: string
|
||||
let tester5: string
|
||||
|
||||
let investor1: string
|
||||
|
||||
let investor2: string
|
||||
let investor3: string
|
||||
// let result1: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
// let result2: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
// let result3: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
|
||||
const commits: string[] = []
|
||||
const tester1CommitHours = 10
|
||||
const tester2CommitHours = 10
|
||||
const investAmount = '100000.0000 RUB'
|
||||
|
||||
const fakeDocument = {
|
||||
hash: '157192B276DA23CC84AB078FC8755C051C5F0430BF4802E55718221E6B76C777',
|
||||
public_key: 'PUB_K1_5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzzEtUA4',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
meta: '{}',
|
||||
}
|
||||
|
||||
const tester2CommitHours = 20
|
||||
const tester3CommitHours = 100
|
||||
const investAmount1 = 100000
|
||||
const investAmount2 = 25000
|
||||
const investAmount3 = 25000
|
||||
beforeAll(async () => {
|
||||
await blockchain.update_pass_instance()
|
||||
|
||||
@@ -57,6 +69,22 @@ beforeAll(async () => {
|
||||
console.log('tester3: ', tester3)
|
||||
await addUser(tester3)
|
||||
|
||||
tester4 = generateRandomUsername()
|
||||
console.log('tester4: ', tester4)
|
||||
await addUser(tester4)
|
||||
|
||||
tester5 = generateRandomUsername()
|
||||
console.log('tester5: ', tester5)
|
||||
await addUser(tester5)
|
||||
|
||||
investor2 = generateRandomUsername()
|
||||
console.log('investor2: ', investor2)
|
||||
await addUser(investor2, tester4)
|
||||
|
||||
investor3 = generateRandomUsername()
|
||||
console.log('investor3: ', investor3)
|
||||
await addUser(investor3, tester5)
|
||||
|
||||
// const { stdout } = await execa('esno', [CLI_PATH, 'boot'], { stdio: 'inherit' })
|
||||
// expect(stdout).toContain('Boot process completed')
|
||||
}, 500_000)
|
||||
@@ -72,6 +100,8 @@ afterAll(() => {
|
||||
}
|
||||
|
||||
console.log(`\n💾 **TOTAL RAM USED IN TESTS**: ${(totalGlobalRam / 1024).toFixed(2)} kb\n`)
|
||||
|
||||
console.log(`tester1: ${tester1}\ntester2: ${tester2}\ntester3: ${tester3}\ntester4: ${tester4}\ntester5: ${tester5}\ninvestor1: ${investor1}\ninvestor2: ${investor2}\ninvestor3: ${investor3}`)
|
||||
})
|
||||
|
||||
describe('тест контракта CAPITAL', () => {
|
||||
@@ -166,294 +196,643 @@ describe('тест контракта CAPITAL', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// it('инициализируем контракт CAPITAL', async () => {
|
||||
// const data: CapitalContract.Actions.Init.IInit = {
|
||||
// coopname: 'voskhod',
|
||||
// initiator: 'voskhod',
|
||||
// }
|
||||
it('инициализируем контракт CAPITAL и сверяем конфиг', async () => {
|
||||
const data: CapitalContract.Actions.SetConfig.ISetConfig = {
|
||||
coopname: 'voskhod',
|
||||
config: {
|
||||
coordinator_bonus_percent: 4,
|
||||
expense_pool_percent: 100,
|
||||
coordinator_invite_validity_days: 30,
|
||||
voting_period_in_days: 7,
|
||||
authors_voting_percent: 38.2,
|
||||
creators_voting_percent: 38.2,
|
||||
},
|
||||
}
|
||||
|
||||
// const state = (await blockchain.getTableRows(
|
||||
// CapitalContract.contractName.production,
|
||||
// CapitalContract.contractName.production,
|
||||
// 'state',
|
||||
// 1,
|
||||
// 'voskhod',
|
||||
// 'voskhod',
|
||||
// ))[0]
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SetConfig.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: 'voskhod',
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// if (state) {
|
||||
// expect(state.coopname).toBe('voskhod')
|
||||
// }
|
||||
// else {
|
||||
// const result = await blockchain.api.transact(
|
||||
// {
|
||||
// actions: [
|
||||
// {
|
||||
// account: CapitalContract.contractName.production,
|
||||
// name: CapitalContract.Actions.Init.actionName,
|
||||
// authorization: [
|
||||
// {
|
||||
// actor: 'voskhod',
|
||||
// permission: 'active',
|
||||
// },
|
||||
// ],
|
||||
// data,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// blocksBehind: 3,
|
||||
// expireSeconds: 30,
|
||||
// },
|
||||
// )
|
||||
// getTotalRamUsage(result)
|
||||
const state = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
CapitalContract.contractName.production,
|
||||
'state',
|
||||
1,
|
||||
'voskhod',
|
||||
'voskhod',
|
||||
))[0]
|
||||
console.log('state: ', state)
|
||||
expect(state).toBeDefined()
|
||||
expect(state.coopname).toBe('voskhod')
|
||||
expect(parseFloat(state.config.coordinator_bonus_percent)).toBe(data.config.coordinator_bonus_percent)
|
||||
expect(parseFloat(state.config.expense_pool_percent)).toBe(data.config.expense_pool_percent)
|
||||
expect(parseFloat(state.config.coordinator_invite_validity_days)).toBe(data.config.coordinator_invite_validity_days)
|
||||
expect(parseFloat(state.config.voting_period_in_days)).toBe(data.config.voting_period_in_days)
|
||||
expect(parseFloat(state.config.authors_voting_percent)).toBe(data.config.authors_voting_percent)
|
||||
expect(parseFloat(state.config.creators_voting_percent)).toBe(data.config.creators_voting_percent)
|
||||
})
|
||||
|
||||
// expect(result.transaction_id).toBeDefined()
|
||||
// }
|
||||
// })
|
||||
it('создаём проект', async () => {
|
||||
const hash = generateRandomSHA256()
|
||||
const parentHash = '0000000000000000000000000000000000000000000000000000000000000000'
|
||||
|
||||
// it('создаём проект', async () => {
|
||||
// const hash = generateRandomSHA256()
|
||||
// const parentHash = '0000000000000000000000000000000000000000000000000000000000000000'
|
||||
const data: CapitalContract.Actions.CreateProject.ICreateProject = {
|
||||
coopname: 'voskhod',
|
||||
project_hash: hash,
|
||||
parent_hash: parentHash,
|
||||
title: `Идея ${hash.slice(0, 10)}`,
|
||||
description: `Описание ${hash.slice(0, 10)}`,
|
||||
meta: '',
|
||||
}
|
||||
|
||||
// const data: CapitalContract.Actions.CreateProject.ICreateProject = {
|
||||
// coopname: 'voskhod',
|
||||
// application: 'voskhod',
|
||||
// title: `Идея ${hash.slice(0, 10)}`,
|
||||
// description: `Описание ${hash.slice(0, 10)}`,
|
||||
// project_hash: hash,
|
||||
// parent_project_hash: parentHash,
|
||||
// parent_distribution_ratio: 0,
|
||||
// terms: '',
|
||||
// subject: '',
|
||||
// }
|
||||
project1 = data
|
||||
|
||||
// project1 = data
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CreateProject.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: 'voskhod',
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
// const result = await blockchain.api.transact(
|
||||
// {
|
||||
// actions: [
|
||||
// {
|
||||
// account: CapitalContract.contractName.production,
|
||||
// name: CapitalContract.Actions.CreateProject.actionName,
|
||||
// authorization: [
|
||||
// {
|
||||
// actor: 'voskhod',
|
||||
// permission: 'active',
|
||||
// },
|
||||
// ],
|
||||
// data,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// blocksBehind: 3,
|
||||
// expireSeconds: 30,
|
||||
// },
|
||||
// )
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// expect(result.transaction_id).toBeDefined()
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
'voskhod',
|
||||
'projects',
|
||||
1,
|
||||
project1.project_hash,
|
||||
project1.project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// const project = (await blockchain.getTableRows(
|
||||
// CapitalContract.contractName.production,
|
||||
// 'voskhod',
|
||||
// 'projects',
|
||||
// 1,
|
||||
// project1.project_hash,
|
||||
// project1.project_hash,
|
||||
// 3,
|
||||
// 'sha256',
|
||||
// ))[0]
|
||||
expect(project).toBeDefined()
|
||||
console.log('project: ', project)
|
||||
expect(project.coopname).toBe('voskhod')
|
||||
expect(project.status).toBe('pending')
|
||||
// План/факт и счетчики по умолчанию
|
||||
expect(project.plan.target_expense_pool).toBe('0.0000 RUB')
|
||||
expect(project.fact.target_expense_pool).toBe('0.0000 RUB')
|
||||
expect(project.fact.accumulated_expense_pool).toBe('0.0000 RUB')
|
||||
expect(project.fact.used_expense_pool).toBe('0.0000 RUB')
|
||||
expect(project.fact.invest_pool).toBe('0.0000 RUB')
|
||||
expect(project.counts.total_authors).toBe(0)
|
||||
expect(project.counts.total_creators).toBe(0)
|
||||
expect(project.counts.total_investors).toBe(0)
|
||||
expect(project.counts.total_contributors).toBe(0)
|
||||
expect(project.counts.total_commits).toBe(0)
|
||||
expect(project.project_hash).toBe(data.project_hash)
|
||||
expect(project.title).toBe(data.title)
|
||||
expect(project.description).toBe(data.description)
|
||||
getTotalRamUsage(result)
|
||||
})
|
||||
|
||||
// expect(project).toBeDefined()
|
||||
it('заключаем договор УХД и приложения к договору со множеством участников', async () => {
|
||||
const testerNames = [tester1, tester2, tester3, tester4, tester5, investor1, investor2, investor3]
|
||||
for (const tester of testerNames) {
|
||||
const appendixHash = generateRandomSHA256()
|
||||
const contributorHash = generateRandomSHA256()
|
||||
await registerContributor(blockchain, 'voskhod', tester, contributorHash, '1000.0000 RUB')
|
||||
await signAppendix(blockchain, 'voskhod', tester, project1.project_hash, appendixHash)
|
||||
const contributor = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
'voskhod',
|
||||
'contributors',
|
||||
1,
|
||||
tester,
|
||||
tester,
|
||||
2,
|
||||
'i64',
|
||||
))[0]
|
||||
expect(contributor).toBeDefined()
|
||||
expect(contributor.appendixes).toContain(project1.project_hash)
|
||||
expect(contributor.status).toBe('active')
|
||||
}
|
||||
})
|
||||
|
||||
// expect(project.status).toBe('created')
|
||||
// expect(project.target).toBe('0.0000 RUB')
|
||||
// expect(project.invested).toBe('0.0000 RUB')
|
||||
// expect(project.authors_count).toBe(0)
|
||||
// expect(project.authors_shares).toBe(0)
|
||||
// expect(project.project_hash).toBe(data.project_hash)
|
||||
// expect(project.title).toBe(data.title)
|
||||
// expect(project.description).toBe(data.description)
|
||||
// getTotalRamUsage(result)
|
||||
// })
|
||||
it(`подписываем соглашения о ЦПП "Цифровой Кошелек" и "Капитализация"`, async () => {
|
||||
const testerNames = [tester1, tester2, tester3, tester4, tester5, investor1, investor2, investor3] // Можно передавать любое количество пользователей
|
||||
|
||||
// it('заключаем договор УХД с автором по проекту', async () => {
|
||||
// const testerNames = [tester1, tester2, investor1] // Можно передавать любое количество пользователей
|
||||
for (const tester of testerNames) {
|
||||
await signWalletAgreement(blockchain, 'voskhod', tester, fakeDocument)
|
||||
await signCapitalAgreement(blockchain, 'voskhod', tester, fakeDocument)
|
||||
}
|
||||
})
|
||||
|
||||
// for (const tester of testerNames) {
|
||||
// await registerAndApproveUHDContract(blockchain, tester, project1.project_hash)
|
||||
it(`пополняем баланс кошелька investor1`, async () => {
|
||||
const { depositId, program, userWallet } = await depositToWallet(blockchain, 'voskhod', investor1, investAmount1)
|
||||
})
|
||||
|
||||
// // Получаем все решения и утверждаем последнее
|
||||
// const decisions = await blockchain.getTableRows(
|
||||
// SovietContract.contractName.production,
|
||||
// 'voskhod',
|
||||
// 'decisions',
|
||||
// 1000,
|
||||
// )
|
||||
// const lastDecision = decisions[decisions.length - 1]
|
||||
it(`пополняем баланс кошелька investor2`, async () => {
|
||||
const { depositId, program, userWallet } = await depositToWallet(blockchain, 'voskhod', investor2, investAmount2 * 2)
|
||||
})
|
||||
|
||||
// console.log(`Последнее решение: `, lastDecision)
|
||||
// await processDecision(blockchain, lastDecision.id)
|
||||
it(`пополняем баланс кошелька investor3`, async () => {
|
||||
const { depositId, program, userWallet } = await depositToWallet(blockchain, 'voskhod', investor3, investAmount3)
|
||||
})
|
||||
|
||||
// // Проверка финального статуса контрибьютора
|
||||
// const contributor = (await blockchain.getTableRows(
|
||||
// CapitalContract.contractName.production,
|
||||
// 'voskhod',
|
||||
// 'contributors',
|
||||
// 1,
|
||||
// tester,
|
||||
// tester,
|
||||
// 2,
|
||||
// 'i64',
|
||||
// ))[0]
|
||||
it('добавляем мастера к проекту', async () => {
|
||||
const data: CapitalContract.Actions.SetMaster.ISetMaster = {
|
||||
coopname: 'voskhod',
|
||||
project_hash: project1.project_hash,
|
||||
master: tester1,
|
||||
}
|
||||
|
||||
// console.log(`Контрибьютор ${tester} после авторизации: `, contributor)
|
||||
// expect(contributor).toBeDefined()
|
||||
// expect(contributor.status).toBe('authorized')
|
||||
// }
|
||||
// })
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SetMaster.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: 'voskhod',
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// it('добавляем автора к идее', async () => {
|
||||
// const data: CapitalContract.Actions.AddAuthor.IAddAuthor = {
|
||||
// coopname: 'voskhod',
|
||||
// application: 'voskhod',
|
||||
// project_hash: project1.project_hash,
|
||||
// author: tester1,
|
||||
// shares: '100',
|
||||
// }
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
'voskhod',
|
||||
'projects',
|
||||
1,
|
||||
project1.project_hash,
|
||||
project1.project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// const result = await blockchain.api.transact(
|
||||
// {
|
||||
// actions: [
|
||||
// {
|
||||
// account: CapitalContract.contractName.production,
|
||||
// name: CapitalContract.Actions.AddAuthor.actionName,
|
||||
// authorization: [
|
||||
// {
|
||||
// actor: 'voskhod',
|
||||
// permission: 'active',
|
||||
// },
|
||||
// ],
|
||||
// data,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// blocksBehind: 3,
|
||||
// expireSeconds: 30,
|
||||
// },
|
||||
// )
|
||||
// getTotalRamUsage(result)
|
||||
// expect(result.transaction_id).toBeDefined()
|
||||
expect(project).toBeDefined()
|
||||
console.log('project: ', project)
|
||||
expect(project.master).toBe(tester1)
|
||||
})
|
||||
|
||||
// const author = (await blockchain.getTableRows(
|
||||
// CapitalContract.contractName.production,
|
||||
// 'voskhod',
|
||||
// 'authors',
|
||||
// 1,
|
||||
// project1.project_hash,
|
||||
// project1.project_hash,
|
||||
// 3,
|
||||
// 'sha256',
|
||||
// ))[0]
|
||||
// console.log('author: ', author)
|
||||
// expect(author).toBeDefined()
|
||||
// expect(author.username).toBe(tester1)
|
||||
// expect(author.shares).toBe(100)
|
||||
// expect(author.project_hash).toBe(project1.project_hash)
|
||||
it('добавляем автора к идее', async () => {
|
||||
const { project, segment } = await addAuthor(blockchain, 'voskhod', project1.project_hash, tester1)
|
||||
|
||||
// const project = (await blockchain.getTableRows(
|
||||
// CapitalContract.contractName.production,
|
||||
// 'voskhod',
|
||||
// 'projects',
|
||||
// 1,
|
||||
// project1.project_hash,
|
||||
// project1.project_hash,
|
||||
// 3,
|
||||
// 'sha256',
|
||||
// ))[0]
|
||||
console.log('segment: ', segment)
|
||||
expect(segment).toBeDefined()
|
||||
expect(segment.username).toBe(tester1)
|
||||
expect(segment.project_hash).toBe(project1.project_hash)
|
||||
expect(segment.is_author).toBe(1)
|
||||
expect(segment.is_creator).toBe(0)
|
||||
expect(segment.is_coordinator).toBe(0)
|
||||
expect(segment.is_investor).toBe(0)
|
||||
expect(segment.is_contributor).toBe(0)
|
||||
expect(segment.is_coordinator).toBe(0)
|
||||
expect(segment.is_investor).toBe(0)
|
||||
expect(segment.is_contributor).toBe(0)
|
||||
expect(project).toBeDefined()
|
||||
expect(project.counts.total_authors).toBe(1)
|
||||
})
|
||||
|
||||
// expect(project).toBeDefined()
|
||||
// expect(project.authors_count).toBe(1)
|
||||
// expect(project.authors_shares).toBe(100)
|
||||
// })
|
||||
it('устанавливаем план проекта', async () => {
|
||||
const data: CapitalContract.Actions.SetPlan.ISetPlan = {
|
||||
coopname: 'voskhod',
|
||||
master: tester1,
|
||||
project_hash: project1.project_hash,
|
||||
plan_creators_hours: 100,
|
||||
plan_expenses: '10000.0000 RUB',
|
||||
plan_hour_cost: '1000.0000 RUB',
|
||||
}
|
||||
|
||||
// it('создать результат', async () => {
|
||||
// const data: CapitalContract.Actions.CreateResult.ICreateResult = {
|
||||
// coopname: 'voskhod',
|
||||
// application: 'voskhod',
|
||||
// project_hash: project1.project_hash,
|
||||
// result_hash: generateRandomSHA256(),
|
||||
// assignee: 'ant',
|
||||
// assignment: 'Задание #1: тут описание',
|
||||
// }
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SetPlan.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: 'voskhod',
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// result1 = data
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
'voskhod',
|
||||
'projects',
|
||||
1,
|
||||
project1.project_hash,
|
||||
project1.project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// const result = await blockchain.api.transact(
|
||||
// {
|
||||
// actions: [
|
||||
// {
|
||||
// account: CapitalContract.contractName.production,
|
||||
// name: CapitalContract.Actions.CreateResult.actionName,
|
||||
// authorization: [
|
||||
// {
|
||||
// actor: 'voskhod',
|
||||
// permission: 'active',
|
||||
// },
|
||||
// ],
|
||||
// data,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// blocksBehind: 3,
|
||||
// expireSeconds: 30,
|
||||
// },
|
||||
// )
|
||||
expect(project).toBeDefined()
|
||||
console.log('plan: ', project.plan)
|
||||
// Проверяем плановые показатели согласно формулам из generation.cpp
|
||||
expect(project.plan.hour_cost).toBe('1000.0000 RUB')
|
||||
expect(parseInt(project.plan.creators_hours)).toBe(100)
|
||||
expect(project.plan.target_expense_pool).toBe('10000.0000 RUB')
|
||||
|
||||
// getTotalRamUsage(result)
|
||||
// expect(result.transaction_id).toBeDefined()
|
||||
// Себестоимости и премии
|
||||
expect(project.plan.creators_base_pool).toBe('100000.0000 RUB')
|
||||
expect(project.plan.authors_base_pool).toBe('61800.0000 RUB')
|
||||
expect(project.plan.coordinators_base_pool).toBe('6872.0000 RUB')
|
||||
expect(project.plan.creators_bonus_pool).toBe('100000.0000 RUB')
|
||||
expect(project.plan.authors_bonus_pool).toBe('61800.0000 RUB')
|
||||
|
||||
// const blockchainResult = (await blockchain.getTableRows(
|
||||
// CapitalContract.contractName.production,
|
||||
// 'voskhod',
|
||||
// 'results',
|
||||
// 1,
|
||||
// result1.result_hash,
|
||||
// result1.result_hash,
|
||||
// 2,
|
||||
// 'sha256',
|
||||
// ))[0]
|
||||
// Инвестиции и коэффициент возврата
|
||||
expect(project.plan.invest_pool).toBe('168672.0000 RUB')
|
||||
expect(project.plan.total_received_investments).toBe('178672.0000 RUB')
|
||||
expect(project.plan.coordinators_investment_pool).toBe('178672.0000 RUB')
|
||||
expect(parseFloat(project.plan.return_base_percent)).toBeCloseTo(100, 10)
|
||||
expect(parseFloat(project.plan.use_invest_percent)).toBeCloseTo(100, 10)
|
||||
|
||||
// expect(blockchainResult).toBeDefined()
|
||||
// expect(blockchainResult.result_hash).toBe(result1.result_hash)
|
||||
// expect(blockchainResult.project_hash).toBe(project1.project_hash)
|
||||
// expect(blockchainResult.coopname).toBe('voskhod')
|
||||
// })
|
||||
// Итого генерация, бонусы вкладчиков и общая сумма с расходами
|
||||
expect(project.plan.total_generation_pool).toBe('330472.0000 RUB')
|
||||
expect(project.plan.contributors_bonus_pool).toBe('534703.6960 RUB')
|
||||
expect(project.plan.total).toBe('875175.6960 RUB')
|
||||
})
|
||||
|
||||
// it(`подписать соглашение о ЦПП "Цифровой Кошелек" и пополнить баланс кошелька investor1`, async () => {
|
||||
// {
|
||||
// const { wallet, program } = await signWalletAgreement(blockchain, 'voskhod', investor1, fakeDocument)
|
||||
// console.log('wallet investor1: ', wallet)
|
||||
// }
|
||||
// {
|
||||
// const { depositId, program, userWallet } = await depositToWallet(blockchain, 'voskhod', investor1, 100000)
|
||||
// }
|
||||
// })
|
||||
it('стартовать проект на приём коммитов', async () => {
|
||||
const data: CapitalContract.Actions.StartProject.IStartProject = {
|
||||
coopname: 'voskhod',
|
||||
project_hash: project1.project_hash,
|
||||
}
|
||||
const tx = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.StartProject.actionName,
|
||||
authorization: [{ actor: 'voskhod', permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
getTotalRamUsage(tx)
|
||||
expect(tx.transaction_id).toBeDefined()
|
||||
|
||||
// it('подписать соглашение о ЦПП "Капитализация"', async () => {
|
||||
// const testerNames = [tester1, tester2, investor1] // Можно передавать любое количество пользователей
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
'voskhod',
|
||||
'projects',
|
||||
1,
|
||||
project1.project_hash,
|
||||
project1.project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
expect(project).toBeDefined()
|
||||
expect(project.status).toBe('active')
|
||||
})
|
||||
|
||||
// for (const tester of testerNames) {
|
||||
// await signCapitalAgreement(blockchain, 'voskhod', tester, fakeDocument)
|
||||
// }
|
||||
// })
|
||||
it('добавить коммит создателя tester1 на 10 часов по 1000 RUB', async () => {
|
||||
const { finalProject, commitHash, commit } = await commitToResult(blockchain, 'voskhod', project1.project_hash, tester1, tester1CommitHours)
|
||||
commits.push(commitHash)
|
||||
|
||||
// it('инвестировать в проект аккаунтом investor1', async () => {
|
||||
// await investInProject(blockchain, 'voskhod', investor1, project1.project_hash, investAmount, fakeDocument)
|
||||
// })
|
||||
console.log('commit: ', commit)
|
||||
console.log('project fact: ', finalProject.fact)
|
||||
expect(finalProject.fact.creators_hours).toBe(10)
|
||||
expect(finalProject.fact.creators_base_pool).toBe('10000.0000 RUB')
|
||||
expect(finalProject.fact.creators_bonus_pool).toBe('10000.0000 RUB')
|
||||
expect(finalProject.fact.authors_bonus_pool).toBe('6180.0000 RUB')
|
||||
expect(finalProject.fact.total_generation_pool).toBe('32360.0000 RUB')
|
||||
expect(finalProject.fact.contributors_bonus_pool).toBe('52358.4800 RUB')
|
||||
expect(finalProject.fact.total).toBe('84718.4800 RUB')
|
||||
})
|
||||
|
||||
it('обновить CRPS и проверить распределение авторских наград после первого коммита', async () => {
|
||||
// Обновляем сегмент tester1 через CRPS
|
||||
const { prevSegment, updatedSegment } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester1)
|
||||
|
||||
console.log('Сегмент до обновления:', prevSegment)
|
||||
console.log('Сегмент после обновления:', updatedSegment)
|
||||
|
||||
// Проверяем, что весь пул авторских вознаграждений распределился одному автору
|
||||
// В проекте authors_bonus_pool = '6180.0000 RUB' должен полностью перейти в author_bonus
|
||||
expect(updatedSegment.author_bonus).toBe('6180.0000 RUB')
|
||||
|
||||
// Базовая награда автора также должна быть распределена
|
||||
// Из формулы генерации: base_bonus_coefficient * authors_bonus_pool / total_authors = 1.0 * 6180 / 1 = 6180
|
||||
expect(updatedSegment.author_base).toBe('6180.0000 RUB')
|
||||
|
||||
// Проверяем, что is_author = true
|
||||
expect(updatedSegment.is_author).toBe(1)
|
||||
|
||||
console.log(`✅ Авторские награды успешно распределены: base=${updatedSegment.author_base}, bonus=${updatedSegment.author_bonus}`)
|
||||
})
|
||||
|
||||
it('добавляем второго автора tester2 к идее', async () => {
|
||||
const { project, segment } = await addAuthor(blockchain, 'voskhod', project1.project_hash, tester2)
|
||||
|
||||
console.log('segment tester2: ', segment)
|
||||
expect(segment).toBeDefined()
|
||||
expect(segment.username).toBe(tester2)
|
||||
expect(segment.project_hash).toBe(project1.project_hash)
|
||||
expect(segment.is_author).toBe(1)
|
||||
expect(segment.is_creator).toBe(0)
|
||||
expect(segment.is_coordinator).toBe(0)
|
||||
expect(segment.is_investor).toBe(0)
|
||||
expect(segment.is_contributor).toBe(0)
|
||||
expect(project).toBeDefined()
|
||||
expect(project.counts.total_authors).toBe(2) // Теперь у нас 2 автора
|
||||
})
|
||||
|
||||
it('добавить коммит создателя tester2 на 20 часов по 1000 RUB', async () => {
|
||||
const { finalProject, commitHash, commit } = await commitToResult(blockchain, 'voskhod', project1.project_hash, tester2, tester2CommitHours)
|
||||
commits.push(commitHash)
|
||||
|
||||
console.log('commit tester2: ', commit)
|
||||
console.log('project fact after tester2 commit: ', finalProject.fact)
|
||||
|
||||
expect(commit.amounts.creators_base_pool).toBe('20000.0000 RUB')
|
||||
expect(commit.amounts.creators_bonus_pool).toBe('20000.0000 RUB')
|
||||
expect(commit.amounts.authors_bonus_pool).toBe('12360.0000 RUB')
|
||||
expect(commit.amounts.authors_base_pool).toBe('12360.0000 RUB')
|
||||
expect(commit.amounts.total_generation_pool).toBe('64720.0000 RUB')
|
||||
expect(commit.amounts.contributors_bonus_pool).toBe('104716.9600 RUB')
|
||||
expect(commit.amounts.total_contribution).toBe('169436.9600 RUB')
|
||||
|
||||
// Проверяем накопительные значения после двух коммитов (10 + 20 = 30 часов)
|
||||
expect(finalProject.fact.creators_hours).toBe(30)
|
||||
expect(finalProject.fact.creators_base_pool).toBe('30000.0000 RUB')
|
||||
expect(finalProject.fact.creators_bonus_pool).toBe('30000.0000 RUB')
|
||||
expect(finalProject.fact.authors_bonus_pool).toBe('18540.0000 RUB')
|
||||
expect(finalProject.fact.total_generation_pool).toBe('97080.0000 RUB')
|
||||
expect(finalProject.fact.contributors_bonus_pool).toBe('157075.4400 RUB')
|
||||
expect(finalProject.fact.total).toBe('254155.4400 RUB')
|
||||
})
|
||||
|
||||
it('обновить CRPS для обоих авторов и проверить распределение авторских наград', async () => {
|
||||
// Обновляем сегмент tester1 через CRPS
|
||||
await sleep(1000) // т.к. ранее мы делали обновление и будет дублирование - надо подождать
|
||||
const { prevSegment: prevTester1, updatedSegment: updatedTester1 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester1)
|
||||
// Обновляем сегмент tester2 через CRPS
|
||||
const { prevSegment: prevTester2, updatedSegment: updatedTester2 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester2)
|
||||
|
||||
// console.log('Tester1 - сегмент до обновления:', prevTester1)
|
||||
// console.log('Tester1 - сегмент после обновления:', updatedTester1)
|
||||
// console.log('Tester2 - сегмент до обновления:', prevTester2)
|
||||
// console.log('Tester2 - сегмент после обновления:', updatedTester2)
|
||||
|
||||
// Получаем проект для проверки текущего authors_bonus_pool
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
'voskhod',
|
||||
'projects',
|
||||
1,
|
||||
project1.project_hash,
|
||||
project1.project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('Current project authors_bonus_pool:', project.fact.authors_bonus_pool)
|
||||
|
||||
// После второго коммита авторский пул составляет 18540 RUB, прирост составил 12360 RUB
|
||||
// Он должен распределиться поровну между двумя авторами: 12360 / 2 = 6180 RUB каждому
|
||||
|
||||
// Проверяем tester1 (у него была награда от первого коммита + доля от второго)
|
||||
// tester1 должен получить: 6180 (от первого коммита) + 6180 (доля от второго) = 12360
|
||||
// Но поскольку распределение происходит от текущего пула, каждый получает половину от 12360 = 6180
|
||||
expect(updatedTester1.author_bonus).toBe('12360.0000 RUB')
|
||||
expect(updatedTester1.author_base).toBe('12360.0000 RUB')
|
||||
// Проверяем что сумма к ссуде равна 0 т.к. инвестиций нет
|
||||
expect(updatedTester1.provisional_amount).toBe('0.0000 RUB')
|
||||
|
||||
// Проверяем tester2 (получает свою долю от текущего пула)
|
||||
expect(updatedTester2.author_bonus).toBe('6180.0000 RUB')
|
||||
expect(updatedTester2.author_base).toBe('6180.0000 RUB')
|
||||
// Проверяем что сумма к ссуде равна 0 т.к. инвестиций нет
|
||||
expect(updatedTester2.provisional_amount).toBe('0.0000 RUB')
|
||||
|
||||
// Проверяем, что оба являются авторами
|
||||
expect(updatedTester1.is_author).toBe(1)
|
||||
expect(updatedTester2.is_author).toBe(1)
|
||||
|
||||
console.log(`✅ Авторские награды успешно распределены между двумя авторами:`)
|
||||
console.log(` tester1: base=${updatedTester1.author_base}, bonus=${updatedTester1.author_bonus}`)
|
||||
console.log(` tester2: base=${updatedTester2.author_base}, bonus=${updatedTester2.author_bonus}`)
|
||||
})
|
||||
|
||||
it('проверяем, что оба пайщика являются авторами и создателями', async () => {
|
||||
const segment1 = await getSegment(blockchain, 'voskhod', project1.project_hash, tester1)
|
||||
const segment2 = await getSegment(blockchain, 'voskhod', project1.project_hash, tester2)
|
||||
expect(segment1.is_author).toBe(1)
|
||||
expect(segment2.is_author).toBe(1)
|
||||
expect(segment1.is_creator).toBe(1)
|
||||
expect(segment2.is_creator).toBe(1)
|
||||
})
|
||||
|
||||
it('открыть приём инвестиций', async () => {
|
||||
const data: CapitalContract.Actions.OpenProject.IOpenProject = {
|
||||
coopname: 'voskhod',
|
||||
project_hash: project1.project_hash,
|
||||
}
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.OpenProject.actionName,
|
||||
authorization: [{ actor: 'voskhod', permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ blocksBehind: 3, expireSeconds: 30 },
|
||||
)
|
||||
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
'voskhod',
|
||||
'projects',
|
||||
1,
|
||||
project1.project_hash,
|
||||
project1.project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
console.log('project: ', project)
|
||||
expect(project).toBeDefined()
|
||||
expect(project.is_opened).toBe(1)
|
||||
expect(project.fact.target_expense_pool).toBe('10000.0000 RUB')
|
||||
expect(project.fact.accumulated_expense_pool).toBe('0.0000 RUB')
|
||||
expect(project.fact.used_expense_pool).toBe('0.0000 RUB')
|
||||
})
|
||||
|
||||
it('инвестировать в проект аккаунтом investor1 на 100000 RUB', async () => {
|
||||
fakeDocument.signatures[0].signer = investor1
|
||||
const { project, transactionId } = await investInProject(blockchain, 'voskhod', investor1, project1.project_hash, `${investAmount1}.0000 RUB`, fakeDocument)
|
||||
expect(project).toBeDefined()
|
||||
expect(transactionId).toBeDefined()
|
||||
expect(project.fact.total_received_investments).toBe('100000.0000 RUB')
|
||||
expect(project.fact.invest_pool).toBe('90000.0000 RUB')
|
||||
expect(project.fact.accumulated_expense_pool).toBe('10000.0000 RUB')
|
||||
expect(parseFloat(project.fact.use_invest_percent)).toBeCloseTo(58.54, 1)
|
||||
expect(parseFloat(project.fact.return_base_percent)).toBeCloseTo(100, 1)
|
||||
})
|
||||
|
||||
it('обновляем сегменты tester1, tester2 и investor1 и проверяем обеспечение инвестициями для ссуд', async () => {
|
||||
await sleep(1000)
|
||||
|
||||
const { prevSegment: prevTester1, updatedSegment: updatedTester1 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester1)
|
||||
const { prevSegment: prevTester2, updatedSegment: updatedTester2 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester2)
|
||||
const { prevSegment: prevInvestor1, updatedSegment: updatedInvestor1 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, investor1)
|
||||
|
||||
console.log('updatedTester1: ', updatedTester1)
|
||||
console.log('updatedTester2: ', updatedTester2)
|
||||
console.log('updatedInvestor1: ', updatedInvestor1)
|
||||
|
||||
expect(updatedTester1.provisional_amount).toBe('22360.0000 RUB')
|
||||
expect(updatedTester2.provisional_amount).toBe('26180.0000 RUB')
|
||||
expect(updatedInvestor1.provisional_amount).toBe('0.0000 RUB')
|
||||
// Проверяем что сумма инвестиций равна 100000 RUB
|
||||
expect(updatedInvestor1.investor_amount).toBe('100000.0000 RUB')
|
||||
// Проверяем что используемая сумма инвестиций равна 58540 RUB
|
||||
expect(updatedInvestor1.investor_base).toBe('58540.0000 RUB')
|
||||
})
|
||||
|
||||
it('tester3 вносит 100000 RUB коммитом и делает сумму инвестиций недостаточной для полного обеспечения', async () => {
|
||||
const { finalProject, commitHash, commit } = await commitToResult(blockchain, 'voskhod', project1.project_hash, tester3, tester3CommitHours)
|
||||
commits.push(commitHash)
|
||||
|
||||
console.log('commit tester3: ', commit)
|
||||
console.log('project fact after tester3 commit: ', finalProject.fact)
|
||||
|
||||
expect(finalProject.fact.creators_hours).toBe(130)
|
||||
expect(finalProject.fact.creators_base_pool).toBe('130000.0000 RUB')
|
||||
expect(finalProject.fact.creators_bonus_pool).toBe('130000.0000 RUB')
|
||||
expect(finalProject.fact.authors_base_pool).toBe('80340.0000 RUB')
|
||||
expect(finalProject.fact.authors_bonus_pool).toBe('80340.0000 RUB')
|
||||
expect(finalProject.fact.total_generation_pool).toBe('420680.0000 RUB')
|
||||
expect(finalProject.fact.contributors_bonus_pool).toBe('680660.2400 RUB')
|
||||
expect(finalProject.fact.total).toBe('1101340.2400 RUB')
|
||||
|
||||
expect(parseFloat(finalProject.fact.use_invest_percent)).toBeCloseTo(100, 1)
|
||||
expect(parseFloat(finalProject.fact.return_base_percent)).toBeCloseTo(42.78, 1)
|
||||
|
||||
await sleep(1000)
|
||||
// Проверяем сегменты
|
||||
const { prevSegment: prevTester1, updatedSegment: updatedTester1 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester1)
|
||||
const { prevSegment: prevTester2, updatedSegment: updatedTester2 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester2)
|
||||
const { prevSegment: prevInvestor1, updatedSegment: updatedInvestor1 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, investor1)
|
||||
|
||||
// console.log('prevTester2: ', prevTester2)
|
||||
// console.log('updatedTester2: ', updatedTester2)
|
||||
// console.log('updatedInvestor1: ', updatedInvestor1)
|
||||
|
||||
expect(updatedTester1.provisional_amount).toBe('22788.8181 RUB')
|
||||
expect(updatedTester2.provisional_amount).toBe('24423.3146 RUB')
|
||||
expect(updatedInvestor1.provisional_amount).toBe('0.0000 RUB')
|
||||
expect(updatedInvestor1.investor_base).toBe('100000.0000 RUB')
|
||||
expect(updatedInvestor1.investor_amount).toBe('100000.0000 RUB')
|
||||
})
|
||||
|
||||
it('инвестировать в проект аккаунтом investor2 на 25000 RUB с координатором tester4', async () => {
|
||||
fakeDocument.signatures[0].signer = investor2
|
||||
const { project, prevProject, transactionId } = await investInProject(blockchain, 'voskhod', investor2, project1.project_hash, `${investAmount2}.0000 RUB`, fakeDocument)
|
||||
expect(project).toBeDefined()
|
||||
expect(transactionId).toBeDefined()
|
||||
// console.log('prevProject: ', prevProject)
|
||||
// console.log('project: ', project)
|
||||
expect(project.counts.total_coordinators).toBe(1)
|
||||
expect(project.fact.coordinators_investment_pool).toBe('25000.0000 RUB')
|
||||
expect(project.fact.coordinators_base_pool).toBe('961.5384 RUB')
|
||||
expect(project.fact.total_received_investments).toBe('125000.0000 RUB')
|
||||
expect(parseFloat(project.fact.return_base_percent)).toBeCloseTo(54.42, 1)
|
||||
expect(parseFloat(project.fact.use_invest_percent)).toBeCloseTo(100, 1)
|
||||
|
||||
// Проверяем сегменты
|
||||
const { prevSegment: prevTester4, updatedSegment: updatedTester4 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester4)
|
||||
expect(updatedTester4.provisional_amount).toBe('523.3133 RUB')
|
||||
expect(updatedTester4.coordinator_base).toBe('961.5384 RUB')
|
||||
expect(updatedTester4.is_coordinator).toBe(1)
|
||||
expect(updatedTester4.coordinator_investments).toBe('25000.0000 RUB')
|
||||
|
||||
console.log('updatedTester4: ', updatedTester4)
|
||||
|
||||
const { prevSegment: prevInvestor2, updatedSegment: updatedInvestor2 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, investor2)
|
||||
console.log('updatedInvestor2: ', updatedInvestor2)
|
||||
expect(updatedInvestor2.provisional_amount).toBe('0.0000 RUB')
|
||||
expect(updatedInvestor2.investor_base).toBe('25000.0000 RUB')
|
||||
expect(updatedInvestor2.investor_amount).toBe('25000.0000 RUB')
|
||||
})
|
||||
|
||||
it('инвестировать в проект аккаунтом investor3 еще на 25000 RUB с координатором tester5', async () => {
|
||||
fakeDocument.signatures[0].signer = investor3
|
||||
const { project, prevProject, transactionId } = await investInProject(blockchain, 'voskhod', investor3, project1.project_hash, `${investAmount3}.0000 RUB`, fakeDocument)
|
||||
expect(project).toBeDefined()
|
||||
expect(transactionId).toBeDefined()
|
||||
console.log('prevProject: ', prevProject)
|
||||
console.log('project: ', project)
|
||||
expect(project.counts.total_coordinators).toBe(2)
|
||||
expect(project.fact.coordinators_investment_pool).toBe('50000.0000 RUB')
|
||||
expect(project.fact.coordinators_base_pool).toBe('1923.0768 RUB')
|
||||
expect(project.fact.total_received_investments).toBe('150000.0000 RUB')
|
||||
expect(parseFloat(project.fact.return_base_percent)).toBeCloseTo(65.9558, 1)
|
||||
expect(parseFloat(project.fact.use_invest_percent)).toBeCloseTo(100, 1)
|
||||
|
||||
const { prevSegment: prevTester5, updatedSegment: updatedTester5 } = await refreshSegment(blockchain, 'voskhod', project1.project_hash, tester5)
|
||||
console.log('updatedTester5: ', updatedTester5)
|
||||
// expect(updatedTester5.provisional_amount).toBe('0.0000 RUB')
|
||||
// expect(updatedTester5.investor_base).toBe('25000.0000 RUB')
|
||||
// expect(updatedTester5.investor_amount).toBe('25000.0000 RUB')
|
||||
})
|
||||
|
||||
// it('финансировать результат проекта на 10000 RUB', async () => {
|
||||
// await allocateFundsToResult(blockchain, 'voskhod', project1.project_hash, result1.result_hash, '10000.0000 RUB')
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
|
||||
|
||||
export async function addAuthor(blockchain: Blockchain, coopname: string, project_hash: string, author: string) {
|
||||
const data: CapitalContract.Actions.AddAuthor.IAddAuthor = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
project_hash,
|
||||
author,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.AddAuthor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
const key = makeCombinedChecksum256NameIndexKey(project_hash, author)
|
||||
const rows = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
key,
|
||||
key,
|
||||
3,
|
||||
'i128',
|
||||
))
|
||||
|
||||
const segment = rows[0]
|
||||
|
||||
console.log('segment: ', segment)
|
||||
expect(segment).toBeDefined()
|
||||
expect(segment.username).toBe(author)
|
||||
expect(segment.project_hash).toBe(project_hash)
|
||||
expect(segment.is_author).toBe(1)
|
||||
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Projects.tableName,
|
||||
1000,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
expect(project).toBeDefined()
|
||||
|
||||
return {
|
||||
project,
|
||||
segment,
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,20 @@ import { expect } from 'vitest'
|
||||
import { CapitalContract, SovietContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { fakeDocument } from '../soviet/fakeDocument'
|
||||
import { processDecision } from '../soviet/processDecision'
|
||||
import { ratePerHour } from './consts'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import { processApprove } from './processApprove'
|
||||
|
||||
export async function commitToResult(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
resultHash: string,
|
||||
projectHash: string,
|
||||
creator: string,
|
||||
spendHours: number,
|
||||
) {
|
||||
const commitHash = generateRandomSHA256()
|
||||
const totalSpended = `${(spendHours * parseFloat(ratePerHour)).toFixed(4)} RUB`
|
||||
console.log(`\n🔹 Начало коммита: ${commitHash}, часы: ${spendHours}, сумма: ${totalSpended}`)
|
||||
|
||||
// Получение текущих данных перед коммитом
|
||||
const prevResult = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'results',
|
||||
1,
|
||||
resultHash,
|
||||
resultHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0] || { spended: '0.0000 RUB', commits_count: 0 }
|
||||
console.log(`\n🔹 Начало коммита: ${commitHash}, проект: ${projectHash}, часы: ${spendHours}`)
|
||||
|
||||
// Получение текущих данных проекта перед коммитом
|
||||
const prevProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
@@ -53,21 +39,33 @@ export async function commitToResult(
|
||||
))[0] || { contributed_hours: 0, available: '0.0000 RUB' }
|
||||
|
||||
console.log('📊 Балансы до коммита:')
|
||||
console.log('▶ Результат:', prevResult)
|
||||
console.log('▶ Проект:', prevProject)
|
||||
console.log('▶ Контрибьютор:', prevContributor)
|
||||
|
||||
// Обновим состояние проекта после возможной смены статуса
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
const prevTotalCommits = projectBefore?.counts?.total_commits ?? 0
|
||||
const prevCreatorsHours = projectBefore?.fact?.creators_hours ?? 0
|
||||
|
||||
// Создание коммита
|
||||
const commitData: CapitalContract.Actions.CreateCommit.ICommit = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
result_hash: resultHash,
|
||||
commit_hash: commitHash,
|
||||
contributed_hours: spendHours,
|
||||
username: creator,
|
||||
project_hash: projectHash,
|
||||
commit_hash: commitHash,
|
||||
creator_hours: spendHours,
|
||||
}
|
||||
|
||||
console.log(`\n🚀 Отправка транзакции CreateCommit для результата ${resultHash}`)
|
||||
const createCommitResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
@@ -89,7 +87,7 @@ export async function commitToResult(
|
||||
expect(createCommitResult.transaction_id).toBeDefined()
|
||||
|
||||
// Проверка созданного коммита
|
||||
let blockchainCommit = (await blockchain.getTableRows(
|
||||
const blockchainCommit = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'commits',
|
||||
@@ -103,39 +101,15 @@ export async function commitToResult(
|
||||
console.log('🔍 Коммит после создания:', blockchainCommit)
|
||||
expect(blockchainCommit).toBeDefined()
|
||||
expect(blockchainCommit.commit_hash).toBe(commitHash)
|
||||
expect(blockchainCommit.spended).toBe(totalSpended)
|
||||
expect(blockchainCommit.project_hash).toBe(projectHash)
|
||||
expect(blockchainCommit.status).toBe('created')
|
||||
|
||||
// Утверждение коммита
|
||||
const approveCommitData: SovietContract.Actions.Approves.ConfirmApprove.IConfirmApprove = {
|
||||
coopname,
|
||||
approval_hash: commitHash,
|
||||
approved_document: fakeDocument,
|
||||
}
|
||||
|
||||
console.log(`\n✅ Подтверждение коммита ${commitHash}`)
|
||||
const approveCommitResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Approves.ConfirmApprove.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: approveCommitData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(approveCommitResult)
|
||||
expect(approveCommitResult.transaction_id).toBeDefined()
|
||||
const approveCommitResult = await processApprove(blockchain, coopname, commitHash)
|
||||
|
||||
// Проверка утвержденного коммита
|
||||
blockchainCommit = (await blockchain.getTableRows(
|
||||
const blockchainEmptyCommit = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'commits',
|
||||
@@ -147,96 +121,9 @@ export async function commitToResult(
|
||||
))[0]
|
||||
|
||||
// Коммит удаляем после утверждения
|
||||
expect(blockchainCommit).toBeUndefined()
|
||||
|
||||
// // Получение всех решений и выполнение последнего
|
||||
// const decisions = await blockchain.getTableRows(
|
||||
// SovietContract.contractName.production,
|
||||
// coopname,
|
||||
// 'decisions',
|
||||
// 1000,
|
||||
// )
|
||||
// const lastDecision = decisions[decisions.length - 1]
|
||||
|
||||
// console.log(`\n📜 Выполнение последнего решения: ${lastDecision.id}`)
|
||||
// await processDecision(blockchain, lastDecision.id)
|
||||
|
||||
// {
|
||||
// // Утверждение коммита
|
||||
// const act1Data: CapitalContract.Actions.SetAct1.ISetAct1 = {
|
||||
// coopname,
|
||||
// application: coopname,
|
||||
// username: creator,
|
||||
// commit_hash: commitHash,
|
||||
// act: fakeDocument,
|
||||
// }
|
||||
|
||||
// console.log(`\n✅ Установка акта1 по коммиту ${commitHash}`)
|
||||
// const act1Result = await blockchain.api.transact(
|
||||
// {
|
||||
// actions: [
|
||||
// {
|
||||
// account: CapitalContract.contractName.production,
|
||||
// name: CapitalContract.Actions.SetAct1.actionName,
|
||||
// authorization: [{ actor: coopname, permission: 'active' }],
|
||||
// data: act1Data,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// blocksBehind: 3,
|
||||
// expireSeconds: 30,
|
||||
// },
|
||||
// )
|
||||
|
||||
// getTotalRamUsage(act1Result)
|
||||
// expect(act1Result.transaction_id).toBeDefined()
|
||||
// }
|
||||
|
||||
// {
|
||||
// // Утверждение коммита
|
||||
// const act2Data: CapitalContract.Actions.SetAct2.ISetAct2 = {
|
||||
// coopname,
|
||||
// application: coopname,
|
||||
// username: creator,
|
||||
// commit_hash: commitHash,
|
||||
// act: fakeDocument,
|
||||
// }
|
||||
|
||||
// console.log(`\n✅ Установка акта2 по коммиту ${commitHash}`)
|
||||
// const act2Result = await blockchain.api.transact(
|
||||
// {
|
||||
// actions: [
|
||||
// {
|
||||
// account: CapitalContract.contractName.production,
|
||||
// name: CapitalContract.Actions.SetAct2.actionName,
|
||||
// authorization: [{ actor: coopname, permission: 'active' }],
|
||||
// data: act2Data,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// blocksBehind: 3,
|
||||
// expireSeconds: 30,
|
||||
// },
|
||||
// )
|
||||
|
||||
// getTotalRamUsage(act2Result)
|
||||
// expect(act2Result.transaction_id).toBeDefined()
|
||||
// }
|
||||
|
||||
// Проверка результата после утверждения коммита
|
||||
const finalResult = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'results',
|
||||
1,
|
||||
resultHash,
|
||||
resultHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0]
|
||||
expect(blockchainEmptyCommit).toBeUndefined()
|
||||
|
||||
// Проверка проекта после утверждения коммита
|
||||
const finalProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
@@ -260,23 +147,18 @@ export async function commitToResult(
|
||||
))[0]
|
||||
|
||||
console.log('\n📊 Балансы после утверждения коммита:')
|
||||
console.log('▶ Результат:', finalResult)
|
||||
console.log('▶ Проект:', finalProject)
|
||||
console.log('▶ Контрибьютор:', finalContributor)
|
||||
|
||||
// expect(parseFloat(finalResult.spended)).toBe(parseFloat(prevResult.spended) + parseFloat(totalSpended))
|
||||
// expect(parseFloat(finalProject.spended)).toBe(parseFloat(prevProject.spended))
|
||||
|
||||
// Проверка, что у контрибьютора увеличились contributed_hours и available
|
||||
// expect(parseFloat(finalContributor.contributed_hours)).toBe(parseFloat(prevContributor.contributed_hours) + spendHours)
|
||||
// expect(parseFloat(finalContributor.available)).toBe(parseFloat(prevContributor.available) + parseFloat(totalSpended))
|
||||
// Проверки по проекту
|
||||
expect(finalProject.counts.total_commits).toBe(prevTotalCommits + 1)
|
||||
expect(parseInt(finalProject.fact.creators_hours)).toBe(prevCreatorsHours + spendHours)
|
||||
|
||||
console.log(`\n✅ Коммит ${commitHash} завершен успешно!`)
|
||||
|
||||
return {
|
||||
commitHash,
|
||||
commit: blockchainCommit,
|
||||
transactionId: approveCommitResult.transaction_id,
|
||||
finalResult,
|
||||
finalProject,
|
||||
finalContributor,
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ export const walletProgramId = 1
|
||||
export const ratePerHour = '1000.0000 RUB'
|
||||
export const sourceProgramName = 'source'
|
||||
export const capitalProgramName = 'capital'
|
||||
export const circulationAccountId = 80
|
||||
@@ -0,0 +1,19 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
|
||||
|
||||
export async function getSegment(blockchain: Blockchain, coopname: string, project_hash: string, author: string): Promise<CapitalContract.Tables.Segments.ISegment> {
|
||||
const key = makeCombinedChecksum256NameIndexKey(project_hash, author)
|
||||
const rows = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
key,
|
||||
key,
|
||||
3,
|
||||
'i128',
|
||||
))
|
||||
|
||||
return rows[0]
|
||||
}
|
||||
@@ -4,7 +4,9 @@ import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { getCoopProgramWallet, getUserProgramWallet } from '../wallet/walletUtils'
|
||||
import { processDecision } from '../soviet/processDecision'
|
||||
import { capitalProgramId, sourceProgramId } from './consts'
|
||||
import { processApprove } from './processApprove'
|
||||
import { getSegment } from './getSegment'
|
||||
import { sourceProgramId } from './consts'
|
||||
|
||||
export async function investInProject(
|
||||
blockchain: any,
|
||||
@@ -17,18 +19,6 @@ export async function investInProject(
|
||||
const investHash = generateRandomSHA256()
|
||||
console.log(`\n🔹 Начало инвестиции: ${investHash}`)
|
||||
|
||||
// Получение текущих данных перед инвестицией
|
||||
const prevContributor = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
investor,
|
||||
investor,
|
||||
2,
|
||||
'i64',
|
||||
))[0] || { invested: '0.0000 RUB' }
|
||||
|
||||
const prevProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
@@ -40,11 +30,10 @@ export async function investInProject(
|
||||
'sha256',
|
||||
))[0] || { invested: '0.0000 RUB', available: '0.0000 RUB' }
|
||||
|
||||
const prevUserWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId) || { blocked: '0.0000 RUB' }
|
||||
const prevProgramWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId) || { blocked: '0.0000 RUB', share_contributions: '0.0000 RUB' }
|
||||
const prevUserWallet = await getUserProgramWallet(blockchain, coopname, investor, sourceProgramId) || { blocked: '0.0000 RUB' }
|
||||
const prevProgramWallet = await getCoopProgramWallet(blockchain, coopname, sourceProgramId) || { blocked: '0.0000 RUB', share_contributions: '0.0000 RUB' }
|
||||
|
||||
console.log('📊 Балансы до инвестиции:')
|
||||
console.log('▶ Контрибьютор:', prevContributor)
|
||||
console.log('▶ Проект:', prevProject)
|
||||
console.log('▶ Кошелек пользователя:', prevUserWallet)
|
||||
console.log('▶ Кошелек программы:', prevProgramWallet)
|
||||
@@ -81,7 +70,7 @@ export async function investInProject(
|
||||
getTotalRamUsage(createInvestResult)
|
||||
expect(createInvestResult.transaction_id).toBeDefined()
|
||||
|
||||
let blockchainInvest = (await blockchain.getTableRows(
|
||||
const blockchainInvest = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'invests',
|
||||
@@ -97,36 +86,11 @@ export async function investInProject(
|
||||
expect(blockchainInvest.status).toBe('created')
|
||||
|
||||
// Утверждение инвестиции
|
||||
const approveInvestData: CapitalContract.Actions.ApproveInvest.IApproveInvest = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
approver: 'ant',
|
||||
invest_hash: investHash,
|
||||
approved_statement: fakeDocument,
|
||||
}
|
||||
|
||||
console.log(`\n✅ Подтверждение инвестиции ${investHash}`)
|
||||
const approveInvestResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ApproveInvest.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: approveInvestData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
const approveInvestResult = await processApprove(blockchain, coopname, investHash)
|
||||
|
||||
getTotalRamUsage(approveInvestResult)
|
||||
expect(approveInvestResult.transaction_id).toBeDefined()
|
||||
|
||||
blockchainInvest = (await blockchain.getTableRows(
|
||||
// Проверка утвержденной инвестиции
|
||||
const blockchainEmptyInvest = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'invests',
|
||||
@@ -137,49 +101,10 @@ export async function investInProject(
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('🔍 Инвестиция после утверждения:', blockchainInvest)
|
||||
expect(blockchainInvest).toBeDefined()
|
||||
expect(blockchainInvest.status).toBe('approved')
|
||||
// Инвестиция удаляется после утверждения
|
||||
expect(blockchainEmptyInvest).toBeUndefined()
|
||||
|
||||
// Получение всех решений и выполнение последнего
|
||||
const decisions = await blockchain.getTableRows(
|
||||
SovietContract.contractName.production,
|
||||
coopname,
|
||||
'decisions',
|
||||
1000,
|
||||
)
|
||||
const lastDecision = decisions[decisions.length - 1]
|
||||
|
||||
console.log(`\n📜 Выполнение последнего решения: ${lastDecision.id}`)
|
||||
await processDecision(blockchain, lastDecision.id)
|
||||
|
||||
blockchainInvest = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'invests',
|
||||
1,
|
||||
investHash,
|
||||
investHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('📌 Инвестиция после завершения:', blockchainInvest)
|
||||
expect(blockchainInvest).toBeUndefined()
|
||||
|
||||
// Получение новых данных после инвестиции
|
||||
const contributor = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
investor,
|
||||
investor,
|
||||
2,
|
||||
'i64',
|
||||
))[0]
|
||||
|
||||
const project = (await blockchain.getTableRows(
|
||||
const finalProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
@@ -190,24 +115,30 @@ export async function investInProject(
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
const userWallet = await getUserProgramWallet(blockchain, coopname, investor, capitalProgramId)
|
||||
const programWallet = await getCoopProgramWallet(blockchain, coopname, capitalProgramId)
|
||||
const finalUserWallet = await getUserProgramWallet(blockchain, coopname, investor, sourceProgramId)
|
||||
const finalProgramWallet = await getCoopProgramWallet(blockchain, coopname, sourceProgramId)
|
||||
|
||||
// Получение сегмента инвестора для данного проекта
|
||||
const segment = await getSegment(blockchain, coopname, projectHash, investor)
|
||||
|
||||
console.log('\n📊 Балансы после инвестиции:')
|
||||
console.log('▶ Контрибьютор:', contributor)
|
||||
console.log('▶ Проект:', project)
|
||||
console.log('▶ Кошелек пользователя:', userWallet)
|
||||
console.log('▶ Кошелек программы:', programWallet)
|
||||
console.log('▶ Проект:', finalProject)
|
||||
console.log('▶ Сегмент:', segment)
|
||||
console.log('▶ Кошелек пользователя:', finalUserWallet)
|
||||
console.log('▶ Кошелек программы:', finalProgramWallet)
|
||||
|
||||
// Проверка изменения балансов
|
||||
expect(parseFloat(contributor.invested)).toBe(parseFloat(prevContributor.invested) + parseFloat(investAmount))
|
||||
expect(parseFloat(project.invested)).toBe(parseFloat(prevProject.invested) + parseFloat(investAmount))
|
||||
expect(parseFloat(project.available)).toBe(parseFloat(prevProject.available) + parseFloat(investAmount))
|
||||
expect(parseFloat(userWallet.blocked)).toBe(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount))
|
||||
expect(parseFloat(programWallet.blocked)).toBe(parseFloat(prevProgramWallet.blocked) + parseFloat(investAmount))
|
||||
expect(parseFloat(programWallet.share_contributions)).toBe(parseFloat(prevProgramWallet.share_contributions) + parseFloat(investAmount))
|
||||
expect(parseFloat(finalUserWallet.blocked)).toBe(parseFloat(prevUserWallet.blocked) + parseFloat(investAmount))
|
||||
expect(parseFloat(finalProgramWallet.blocked)).toBe(parseFloat(prevProgramWallet.blocked) + parseFloat(investAmount))
|
||||
|
||||
console.log(`\n✅ Инвестирование на ${investAmount} завершено успешно!`)
|
||||
|
||||
return { investHash, transactionId: approveInvestResult.transaction_id }
|
||||
return {
|
||||
investHash,
|
||||
invest: blockchainInvest,
|
||||
transactionId: approveInvestResult.transaction_id,
|
||||
prevProject,
|
||||
project: finalProject,
|
||||
segment,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { expect } from 'vitest'
|
||||
import { SovietContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
|
||||
export async function processApprove(blockchain: any, coopname: string, approvalHash: string) {
|
||||
const data: SovietContract.Actions.Approves.ConfirmApprove.IConfirmApprove = {
|
||||
coopname,
|
||||
approval_hash: approvalHash,
|
||||
approved_document: fakeDocument,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Approves.ConfirmApprove.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import type Blockchain from '../../blockchain'
|
||||
|
||||
export async function refreshSegment(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
projectHash: string,
|
||||
username: string,
|
||||
) {
|
||||
console.log(`\n🔄 Обновляем сегмент CRPS для ${username} в проекте ${projectHash}`)
|
||||
|
||||
// Получение данных сегмента перед обновлением
|
||||
const prevSegment = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
)).filter((s: any) => s.project_hash === projectHash && s.username === username)[0]
|
||||
|
||||
// console.log('📊 Сегмент до обновления:', prevSegment)
|
||||
|
||||
// Обновление сегмента через rfrshsegment
|
||||
const refreshSegmentData: CapitalContract.Actions.RefreshSegment.IRefreshSegment = {
|
||||
coopname,
|
||||
project_hash: projectHash,
|
||||
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,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(refreshResult)
|
||||
expect(refreshResult.transaction_id).toBeDefined()
|
||||
|
||||
// Получение обновленного сегмента
|
||||
const updatedSegment = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
)).filter((s: any) => s.project_hash === projectHash && s.username === username)[0]
|
||||
|
||||
// console.log('📊 Сегмент после обновления:', updatedSegment)
|
||||
console.log(`✅ Сегмент для ${username} обновлен успешно!`)
|
||||
|
||||
return {
|
||||
transactionId: refreshResult.transaction_id,
|
||||
prevSegment,
|
||||
updatedSegment,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,64 @@
|
||||
export function registerContributor() {
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { processApprove } from './processApprove'
|
||||
|
||||
export async function registerContributor(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
username: string,
|
||||
contributorHash: string,
|
||||
ratePerHour: string,
|
||||
) {
|
||||
const contract = fakeDocument
|
||||
contract.signatures[0].signer = username
|
||||
const data: CapitalContract.Actions.RegisterContributor.IRegisterContributor = {
|
||||
coopname,
|
||||
username,
|
||||
contributor_hash: contributorHash,
|
||||
rate_per_hour: ratePerHour,
|
||||
is_external_contract: false,
|
||||
contract,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.RegisterContributor.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// Аппрув председателя (через совет) по хэшу договора УХД
|
||||
await processApprove(blockchain, coopname, contributorHash)
|
||||
|
||||
// Проверяем, что контрибьютор активирован и договор сохранён
|
||||
const contributor = (
|
||||
await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
username,
|
||||
username,
|
||||
2,
|
||||
'i64',
|
||||
)
|
||||
)[0]
|
||||
|
||||
expect(contributor).toBeDefined()
|
||||
expect(contributor.status).toBe('active')
|
||||
expect(contributor.contract).toBeDefined()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { processApprove } from './processApprove'
|
||||
|
||||
export async function signAppendix(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
username: string,
|
||||
projectHash: string,
|
||||
appendixHash: string,
|
||||
) {
|
||||
// Подписание приложения пайщиком (через кооператив, как и в контракте)
|
||||
const data: CapitalContract.Actions.SignAppendix.ISignAppendix = {
|
||||
coopname,
|
||||
username,
|
||||
project_hash: projectHash,
|
||||
appendix_hash: appendixHash,
|
||||
document: fakeDocument,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SignAppendix.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// Аппрув председателя (через совет) по хэшу приложения
|
||||
await processApprove(blockchain, coopname, appendixHash)
|
||||
|
||||
// Проверяем, что приложение применилось к контрибьютору, а запись удалена
|
||||
const contributor = (
|
||||
await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
username,
|
||||
username,
|
||||
2,
|
||||
'i64',
|
||||
)
|
||||
)[0]
|
||||
|
||||
expect(contributor).toBeDefined()
|
||||
expect(contributor.appendixes).toContain(projectHash)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Комбинирование индекса (project_hash + username) как в C++ combine_checksum_ids
|
||||
// Возвращает десятичную строку uint128 для использования с key_type 'i128'
|
||||
|
||||
function charToSymbol(c: string): number {
|
||||
if (c === '.') return 0
|
||||
if (c >= '1' && c <= '5') return c.charCodeAt(0) - '1'.charCodeAt(0) + 1
|
||||
if (c >= 'a' && c <= 'z') return c.charCodeAt(0) - 'a'.charCodeAt(0) + 6
|
||||
return 0
|
||||
}
|
||||
|
||||
function nameToUint64(name: string): bigint {
|
||||
let value = 0n
|
||||
let i = 0
|
||||
for (; i < 12 && i < name.length; i++) {
|
||||
value <<= 5n
|
||||
value |= BigInt(charToSymbol(name[i]))
|
||||
}
|
||||
value <<= 4n + 5n * BigInt(12 - i)
|
||||
if (i < name.length) {
|
||||
value |= BigInt(charToSymbol(name[i])) & 0x0fn
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function hexToBytes(hex: string): Uint8Array {
|
||||
const clean = hex.startsWith('0x') ? hex.slice(2) : hex
|
||||
if (clean.length !== 64) throw new Error('Invalid sha256 hex')
|
||||
const out = new Uint8Array(32)
|
||||
for (let i = 0; i < 32; i++) {
|
||||
out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function readBigUInt64LE(bytes: Uint8Array, offset = 0): bigint {
|
||||
let result = 0n
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result |= BigInt(bytes[offset + i]) << (8n * BigInt(i))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function makeCombinedChecksum256NameIndexKey(projectHashHex: string, username: string): string {
|
||||
const bytes = hexToBytes(projectHashHex)
|
||||
const truncatedHashLE = readBigUInt64LE(bytes, 0)
|
||||
const nameValue = nameToUint64(username)
|
||||
const key128 = (truncatedHashLE << 64n) | nameValue
|
||||
return key128.toString()
|
||||
}
|
||||
|
||||
export function makeCombinedChecksum256Uint64IndexKey(projectHashHex: string, id: number): string {
|
||||
const bytes = hexToBytes(projectHashHex)
|
||||
const truncatedHashLE = readBigUInt64LE(bytes, 0)
|
||||
const idValue = BigInt(id)
|
||||
const key128 = (truncatedHashLE << 64n) | idValue
|
||||
return key128.toString()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export const fakeDocument = {
|
||||
version: '1.0.0',
|
||||
hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
doc_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
meta_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
meta: '{}',
|
||||
signatures: [{
|
||||
id: 1,
|
||||
signed_hash: '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777',
|
||||
signer: 'cooperative1',
|
||||
public_key: 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J',
|
||||
signature: 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8',
|
||||
signed_at: '2025-05-14T12:22:26',
|
||||
meta: '{}',
|
||||
}],
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { GatewayContract, WalletContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { compareTokenAmounts, getCoopProgramWallet, getCoopWallet, getDeposit, getUserProgramWallet } from './walletUtils'
|
||||
import { compareTokenAmounts, getCirculationAccount, getCoopProgramWallet, getDeposit, getUserProgramWallet } from './walletUtils'
|
||||
|
||||
export async function depositToWallet(blockchain: any, coopname: string, username: string, amount: number) {
|
||||
const depositId = randomInt(100000)
|
||||
@@ -48,8 +48,7 @@ export async function depositToWallet(blockchain: any, coopname: string, usernam
|
||||
const prevUserWallet = await getUserProgramWallet(blockchain, coopname, username, 1)
|
||||
const prevUserWalletAvailable = prevUserWallet?.available || '0.0000 RUB'
|
||||
|
||||
const prevCoopWallet = await getCoopWallet(blockchain, coopname)
|
||||
const prevCoopWalletAvailable = prevCoopWallet?.circulating_account?.available || '0.0000 RUB'
|
||||
const prevCoopWalletAvailable = (await getCirculationAccount(blockchain, coopname)).available
|
||||
|
||||
const data2: GatewayContract.Actions.CompleteIncome.ICompleteIncome = {
|
||||
coopname,
|
||||
@@ -85,7 +84,7 @@ export async function depositToWallet(blockchain: any, coopname: string, usernam
|
||||
|
||||
// Проверяем изменение балансов
|
||||
compareTokenAmounts(prevUserWalletAvailable, userWallet.available, amount)
|
||||
compareTokenAmounts(prevCoopWalletAvailable, (await getCoopWallet(blockchain, coopname)).circulating_account.available, amount)
|
||||
compareTokenAmounts(prevCoopWalletAvailable, (await getCirculationAccount(blockchain, coopname)).available, amount)
|
||||
|
||||
return { depositId, program, userWallet }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FundContract, SovietContract, WalletContract } from 'cooptypes'
|
||||
import { FundContract, LedgerContract, SovietContract, WalletContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { circulationAccountId } from '../capital/consts'
|
||||
|
||||
export async function getUserProgramWallet(blockchain: any, coopname: string, username: string, program_id: number) {
|
||||
const wallets = await blockchain.getTableRows(
|
||||
@@ -29,6 +30,20 @@ export async function getCoopProgramWallet(blockchain: any, coopname: string, pr
|
||||
return program[0]
|
||||
}
|
||||
|
||||
export async function getLedgerAccounts(blockchain: Blockchain, coopname: string) {
|
||||
return (await blockchain.getTableRows(
|
||||
LedgerContract.contractName.production,
|
||||
coopname,
|
||||
LedgerContract.Tables.Laccount.tableName,
|
||||
100,
|
||||
))
|
||||
}
|
||||
|
||||
export async function getCirculationAccount(blockchain: Blockchain, coopname: string) {
|
||||
const accounts = await getLedgerAccounts(blockchain, coopname)
|
||||
return accounts.find((account: any) => account.id === circulationAccountId) || { available: '0.0000 RUB' }
|
||||
}
|
||||
|
||||
export async function getCoopWallet(blockchain: any, coopname: string) {
|
||||
return (await blockchain.getTableRows(
|
||||
FundContract.contractName.production,
|
||||
|
||||
@@ -7,4 +7,12 @@ alwaysApply: true
|
||||
- entity/ - слой сущностей домена. Содержит выделенные сущности, структуры, индексы их таблиц, и inline реализации простейших бизнес-функций, которые по своим названиям помогают явно определить что происходит с сущностью.
|
||||
- core/ - слой корневой бизнес-логики, которая объединяет несколько сущностей. Если бизнес-функция сущности требует для своего исполнения нескольких операций с сущностями или она явно относится к доменному уровню приложения за счет своего применения, то такая функция должна быть вынесена в ядро.
|
||||
|
||||
Мы допускаем последовательное-повторное обновление сущностей по ходу исполнения действия контракта, если это необходимо в пользу ясности. Длинна типа name не должна превышать 12 символов и не должна содержать специальных знаков.
|
||||
Мы допускаем последовательное-повторное обновление сущностей по ходу исполнения действия контракта, если это необходимо в пользу ясности. Длинна типа name не должна превышать 12 символов и не должна содержать специальных знаков.
|
||||
|
||||
При документировании контрактов используй следующие группы для doxygen:
|
||||
\ingroup public_contracts - для контрактов
|
||||
\ingroup public_actions - для действий
|
||||
\ingroup public_consts - для действий
|
||||
\ingroup public_tables - для действий
|
||||
И используй следующий тег для указания требуемой аудентификации:
|
||||
* @note Авторизация требуется от аккаунта: @p coopname
|
||||
@@ -874,7 +874,7 @@ WARN_LOGFILE =
|
||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = README.md cpp/common/ cpp/registrator/ cpp/marketplace/ cpp/soviet/ cpp/system/ cpp/gateway/ cpp/fund/ cpp/draft cpp/contributor
|
||||
INPUT = README.md cpp/
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||
|
||||
@@ -17,7 +17,8 @@ void capital::createcmmt(eosio::name coopname, eosio::name application, eosio::n
|
||||
"Пайщик не подписывал приложение к договору УХД для данного проекта");
|
||||
|
||||
// Проверяем, что действие с указанным хэшем не существует
|
||||
Capital::Commits::get_commit_or_fail(coopname, commit_hash);
|
||||
auto commit = Capital::Commits::get_commit(coopname, commit_hash);
|
||||
eosio::check(!commit.has_value(), "Коммит с указанным хэшем уже существует");
|
||||
|
||||
// Проверяем, что сумма часов создателя больше 0
|
||||
eosio::check(creator_hours > 0, "Только положительная сумма часов создателя");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
void capital::rfrshsegment(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);
|
||||
|
||||
@@ -11,6 +11,8 @@ void capital::createinvest(name coopname, name application, name username, check
|
||||
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
|
||||
|
||||
eosio::check(project.is_opened == true, "Проект закрыт для инвестиций");
|
||||
|
||||
std::string memo = Capital::Memo::get_invest_memo(contributor -> id);
|
||||
|
||||
// блокируем средства в программе кошелька
|
||||
|
||||
@@ -10,6 +10,7 @@ void capital::returnunused(name coopname, name application, checksum256 project_
|
||||
eosio::check(segment.is_investor, "Пользователь не является инвестором проекта");
|
||||
|
||||
// Рассчитываем неиспользованную сумму
|
||||
eosio::check(segment.investor_base <= segment.investor_amount, "Ошибка: использованная сумма больше инвестированной");
|
||||
eosio::asset unused_amount = segment.investor_amount - segment.investor_base;
|
||||
eosio::check(unused_amount.amount > 0, "Нет неиспользованных средств для возврата");
|
||||
|
||||
@@ -22,6 +23,14 @@ void capital::returnunused(name coopname, name application, checksum256 project_
|
||||
Wallet::sub_blocked_funds(_capital, coopname, username, unused_amount, _source_program, memo);
|
||||
Wallet::add_available_funds(_capital, coopname, username, unused_amount, _wallet_program, memo);
|
||||
|
||||
// Обновляем проект - увеличиваем сумму возвращенных инвестиций
|
||||
Capital::project_index projects(_capital, coopname.value);
|
||||
auto project_itr = projects.find(project.id);
|
||||
|
||||
projects.modify(project_itr, application, [&](auto &p) {
|
||||
p.fact.total_returned_investments += unused_amount;
|
||||
});
|
||||
|
||||
// Обновляем сегмент - отмечаем что неиспользованные средства возвращены
|
||||
Capital::Segments::segments_index segments(_capital, coopname.value);
|
||||
auto segment_itr = segments.find(segment.id);
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# Приложения к договорам УХД
|
||||
|
||||
## Бизнес-процесс
|
||||
|
||||
### Общая схема
|
||||
1. **Основной договор УХД** - пайщик подписывает общий договор участия в хозяйственной деятельности
|
||||
2. **Приложения к проектам** - для участия в конкретном проекте пайщик должен подписать отдельное приложение
|
||||
|
||||
### Процесс создания приложения
|
||||
|
||||
1. **Создание заявки** (`createappndx`)
|
||||
- Приложение создает заявку на подписание приложения к договору УХД
|
||||
- Проверяется наличие основного договора УХД у пайщика
|
||||
- Проверяется отсутствие уже подписанного приложения для данного проекта
|
||||
- Создается временная запись в таблице `appendixes`
|
||||
- Заявка отправляется на рассмотрение председателю
|
||||
|
||||
2. **Рассмотрение председателем**
|
||||
- Председатель рассматривает заявку через контракт совета
|
||||
- Принимает решение: одобрить или отклонить
|
||||
|
||||
3. **Обработка решения**
|
||||
|
||||
**При одобрении** (`apprvappndx`):
|
||||
- `project_hash` добавляется в вектор `contributor.appendixes`
|
||||
- Временная запись удаляется из таблицы `appendixes`
|
||||
- Пайщик получает право участвовать в проекте
|
||||
|
||||
**При отклонении** (`dclineappndx`):
|
||||
- Временная запись удаляется из таблицы `appendixes`
|
||||
- Пайщик не получает право участвовать в проекте
|
||||
|
||||
### Проверка прав участия
|
||||
|
||||
При выполнении операций (создание коммитов, инвестиций, выводов) система проверяет:
|
||||
1. Наличие основного договора УХД (`contributor.status == "authorized"`)
|
||||
2. Наличие подписанного приложения для проекта (`project_hash` в `contributor.appendixes`)
|
||||
|
||||
### Структуры данных
|
||||
|
||||
- **`contributor.appendixes`** - вектор хэшей проектов с одобренными приложениями
|
||||
- **`appendixes`** - временная таблица для хранения заявок на рассмотрении
|
||||
@@ -2,25 +2,17 @@ void capital::apprvappndx(eosio::name coopname, checksum256 appendix_hash, docum
|
||||
require_auth(_soviet);
|
||||
|
||||
// Находим приложение
|
||||
auto appendix = Capital::get_appendix(coopname, appendix_hash);
|
||||
auto appendix = Capital::Appendix::get_appendix(coopname, appendix_hash);
|
||||
eosio::check(appendix.has_value(), "Приложение не найдено");
|
||||
eosio::check(appendix -> status == Capital::Appendix::Status::CREATED, "Приложение уже обработано");
|
||||
|
||||
Capital::appendix_index appendixes(_capital, coopname.value);
|
||||
auto itr = appendixes.find(appendix -> id);
|
||||
|
||||
// Находим контрибьютора с основным договором УХД
|
||||
auto contributor = Capital::Contributors::get_contributor(coopname, appendix -> username);
|
||||
eosio::check(contributor.has_value(), "Контрибьютор с основным договором УХД не найден");
|
||||
|
||||
// Добавляем project_hash в вектор appendixes у контрибьютора
|
||||
Capital::contributor_index contributors(_capital, coopname.value);
|
||||
auto contributor_itr = contributors.find(contributor -> id);
|
||||
// Добавляем проект в вектор appendixes у контрибьютора
|
||||
Capital::Contributors::push_appendix_to_contributor(coopname, appendix -> username, appendix -> project_hash);
|
||||
|
||||
contributors.modify(contributor_itr, _capital, [&](auto &c) {
|
||||
c.appendixes.push_back(appendix -> project_hash);
|
||||
});
|
||||
|
||||
// Удаляем запись из таблицы appendixes
|
||||
appendixes.erase(itr);
|
||||
// Удаляем приложение
|
||||
Capital::Appendix::delete_appendix(coopname, appendix -> id);
|
||||
}
|
||||
@@ -2,13 +2,10 @@ void capital::dclineappndx(eosio::name coopname, checksum256 appendix_hash, std:
|
||||
require_auth(_soviet);
|
||||
|
||||
// Находим приложение
|
||||
auto appendix = Capital::get_appendix(coopname, appendix_hash);
|
||||
eosio::check(appendix.has_value(), "Приложение не найдено");
|
||||
eosio::check(appendix -> status == Capital::Appendix::Status::CREATED, "Приложение уже обработано");
|
||||
auto appendix = Capital::Appendix::get_appendix(coopname, appendix_hash);
|
||||
|
||||
Capital::appendix_index appendixes(_capital, coopname.value);
|
||||
auto itr = appendixes.find(appendix -> id);
|
||||
|
||||
// Удаляем запись из таблицы appendixes - приложение отклонено
|
||||
appendixes.erase(itr);
|
||||
// Удаляем приложение
|
||||
if (appendix.has_value()) {
|
||||
Capital::Appendix::delete_appendix(coopname, appendix -> id);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,21 @@
|
||||
void capital::signappndx(eosio::name coopname, eosio::name application, eosio::name username, checksum256 project_hash, checksum256 appendix_hash, document2 document) {
|
||||
void capital::signappndx(eosio::name coopname, eosio::name username, checksum256 project_hash, checksum256 appendix_hash, document2 document) {
|
||||
require_auth(coopname);
|
||||
|
||||
// Проверяем что пользователь подписал общий договор УХД
|
||||
auto contributor = Capital::Contributors::get_contributor(coopname, username);
|
||||
eosio::check(contributor.has_value(), "Пайщик не подписывал основной договор УХД");
|
||||
eosio::check(contributor -> status == Capital::Contributors::Status::PENDING, "Основной договор УХД не активен");
|
||||
eosio::check(contributor -> status == Capital::Contributors::Status::ACTIVE, "Основной договор УХД не активен");
|
||||
|
||||
// Проверяем что приложение с таким хэшем не существует
|
||||
auto exist_appendix = Capital::get_appendix(coopname, appendix_hash);
|
||||
auto exist_appendix = Capital::Appendix::get_appendix(coopname, appendix_hash);
|
||||
eosio::check(!exist_appendix.has_value(), "Приложение с указанным хэшем уже существует");
|
||||
|
||||
// Проверяем что у пользователя нет уже приложения для этого проекта
|
||||
eosio::check(!Capital::Contributors::is_contributor_has_appendix_in_project(coopname, project_hash, username),
|
||||
"У пайщика уже есть приложение для данного проекта");
|
||||
|
||||
Capital::appendix_index appendixes(_capital, coopname.value);
|
||||
auto appendix_id = get_global_id_in_scope(_capital, coopname, "appendixes"_n);
|
||||
|
||||
appendixes.emplace(coopname, [&](auto &a) {
|
||||
a.id = appendix_id;
|
||||
a.coopname = coopname;
|
||||
a.username = username;
|
||||
a.project_hash = project_hash;
|
||||
a.appendix_hash = appendix_hash;
|
||||
a.status = Capital::Appendix::Status::CREATED;
|
||||
a.created_at = current_time_point();
|
||||
a.appendix = document;
|
||||
});
|
||||
// Создаём приложение к договору УХД
|
||||
Capital::Appendix::create_appendix(coopname, username, project_hash, appendix_hash, document);
|
||||
|
||||
// Отправляем на аппрув председателю
|
||||
::Soviet::create_approval(
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/**
|
||||
* Создаёт проект.
|
||||
*
|
||||
* Выполняет проверки отсутствия проекта с указанным хэшем и валидирует
|
||||
* родительский хэш согласно правилам: допускается либо пустой `parent_hash`,
|
||||
* либо ссылка на проект первого уровня без собственного родителя.
|
||||
* После проверок создаёт запись в таблице `projects` со статусом `created`.
|
||||
*
|
||||
* \ingroup public_actions
|
||||
*
|
||||
* @param coopname Имя кооператива (scope таблицы).
|
||||
* @param project_hash Уникальный хэш проекта.
|
||||
* @param parent_hash Хэш родителя или пустой хэш, если проект корневой.
|
||||
* @param title Название проекта.
|
||||
* @param description Описание проекта.
|
||||
* @param meta Дополнительные метаданные проекта.
|
||||
*
|
||||
* @note Авторизация требуется от аккаунта: @p coopname
|
||||
*/
|
||||
void capital::createproj (
|
||||
eosio::name coopname,
|
||||
checksum256 project_hash,
|
||||
|
||||
@@ -5,7 +5,7 @@ void capital::openproject(name coopname, checksum256 project_hash) {
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
|
||||
|
||||
// Проверяем, что проект в статусе "created"
|
||||
eosio::check(project.status == Capital::Projects::Status::CREATED, "Проект должен быть в статусе 'created'");
|
||||
eosio::check(project.is_opened == false, "Проект уже открыт");
|
||||
|
||||
// Открываем проект через доменную логику
|
||||
Capital::Projects::open_project(coopname, project_hash);
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
void capital::setconfig(eosio::name coopname, eosio::name initiator, Capital::config cfg) {
|
||||
check_auth_or_fail(_capital, coopname, initiator, "setconfig"_n);
|
||||
|
||||
/**
|
||||
* Устанавливает конфигурацию кооператива.
|
||||
*
|
||||
* Обновляет глобальное состояние контракта для указанного кооператива, создавая запись,
|
||||
* если она отсутствует, либо модифицируя существующую.
|
||||
*
|
||||
* \ingroup public_actions
|
||||
*
|
||||
* @param coopname Имя кооператива.
|
||||
* @param config Новая конфигурация кооператива.
|
||||
*
|
||||
* @note Авторизация требуется от аккаунта: @p coopname
|
||||
*/
|
||||
void capital::setconfig(eosio::name coopname, Capital::config config) {
|
||||
require_auth(coopname);
|
||||
|
||||
eosio::check(config.coordinator_bonus_percent >= 0.0 && config.coordinator_bonus_percent <= 10, "Координатор не может получить больше 0.1 (10%) от инвестиций");
|
||||
eosio::check(config.expense_pool_percent >= 0.0 && config.expense_pool_percent <= 100, "Процент инвестиций в пул расходов должен быть между 0 и 1 (100%)");
|
||||
eosio::check(config.coordinator_invite_validity_days >= 0, "Срок действия приглашения координатора должен быть не отрицательным числом дней");
|
||||
eosio::check(config.voting_period_in_days >= 1 && config.voting_period_in_days <= 30, "Период голосования должен быть между 1 и 30 днями");
|
||||
eosio::check(config.authors_voting_percent >= 0.0 && config.authors_voting_percent <= 100, "Процент премий авторов на голосовании должен быть между 0 и 100");
|
||||
eosio::check(config.creators_voting_percent >= 0.0 && config.creators_voting_percent <= 100, "Процент премий создателей на голосовании должен быть между 0 и 100");
|
||||
|
||||
Capital::global_state_table global_state_inst(_self, _self.value);
|
||||
auto itr = global_state_inst.find(coopname.value);
|
||||
|
||||
if (itr == global_state_inst.end()) {
|
||||
global_state_inst.emplace(initiator, [&](auto& s) {
|
||||
global_state_inst.emplace(coopname, [&](auto& s) {
|
||||
s.coopname = coopname;
|
||||
s.config = cfg;
|
||||
s.config = config;
|
||||
});
|
||||
} else {
|
||||
global_state_inst.modify(itr, initiator, [&](auto& s) {
|
||||
s.config = cfg;
|
||||
global_state_inst.modify(itr, coopname, [&](auto& s) {
|
||||
s.config = config;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ void capital::startproject(name coopname, checksum256 project_hash) {
|
||||
// Проверяем существование проекта и получаем его
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
|
||||
|
||||
// Проверяем, что проект в статусе "opened"
|
||||
eosio::check(project.status == Capital::Projects::Status::OPENED, "Проект должен быть в статусе 'opened'");
|
||||
// Проверяем, что проект в статусе "pending"
|
||||
eosio::check(project.status == Capital::Projects::Status::PENDING, "Проект должен быть в статусе 'pending'");
|
||||
|
||||
// Обновляем статус проекта на "active"
|
||||
Capital::Projects::update_status(coopname, project_hash, Capital::Projects::Status::ACTIVE);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
void capital::cmpltproject(name coopname, checksum256 project_hash) {
|
||||
void capital::startvoting(name coopname, checksum256 project_hash) {
|
||||
require_auth(coopname);
|
||||
|
||||
// Проверяем существование проекта и получаем его
|
||||
@@ -15,10 +15,5 @@ void capital::setmaster(name coopname, checksum256 project_hash, name master) {
|
||||
"Мастер должен быть участником проекта");
|
||||
|
||||
// Назначаем мастера проекта
|
||||
Capital::project_index projects(_capital, coopname.value);
|
||||
auto project_itr = projects.find(project->id);
|
||||
|
||||
projects.modify(project_itr, coopname, [&](auto &p) {
|
||||
p.master = master;
|
||||
});
|
||||
Capital::Projects::set_master(coopname, project -> id, master);
|
||||
}
|
||||
@@ -1,17 +1,32 @@
|
||||
void capital::setplan(name coopname, checksum256 project_hash, uint64_t plan_creators_hours, asset plan_expenses, asset plan_hour_cost){
|
||||
/**
|
||||
* @brief Устанавливает плановые показатели проекта.
|
||||
*
|
||||
* Действие рассчитывает плановые пулы (себестоимости, премии, инвестиции, расходы)
|
||||
* на основании входных параметров и текущего конфига кооператива, и сохраняет их в поле `plan` проекта.
|
||||
*
|
||||
* @ingroup public_actions
|
||||
* @param coopname Имя кооператива (scope таблицы).
|
||||
* @param project_hash Хэш проекта, для которого устанавливается план.
|
||||
* @param plan_creators_hours Планируемое время создателей в часах (> 0).
|
||||
* @param plan_expenses Планируемый целевой размер пула расходов (`asset`).
|
||||
* @param plan_hour_cost Планируемая стоимость часа создателя (`asset`).
|
||||
* @note Авторизация требуется от аккаунта: @p coopname
|
||||
*/
|
||||
void capital::setplan(name coopname, name master, checksum256 project_hash, uint64_t plan_creators_hours, asset plan_expenses, asset plan_hour_cost){
|
||||
require_auth(coopname);
|
||||
|
||||
// Получаем проект
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
|
||||
|
||||
// Проверяем, что проект в статусе "created"
|
||||
eosio::check(project.status == Capital::Projects::Status::CREATED, "Проект должен быть в статусе 'created'");
|
||||
eosio::check(project.master == master, "Мастер проекта не совпадает с мастером, который устанавливает план");
|
||||
|
||||
// Проверяем, что проект в статусе "pending"
|
||||
eosio::check(project.status == Capital::Projects::Status::PENDING, "Проект должен быть в статусе 'pending'");
|
||||
|
||||
Wallet::validate_asset(plan_expenses);
|
||||
Wallet::validate_asset(plan_hour_cost);
|
||||
|
||||
eosio::check(plan_creators_hours > 0, "План времени должен быть положительным");
|
||||
eosio::check(plan_hour_cost.amount > 0, "Стоимость нормо-часа должна быть положительной");
|
||||
|
||||
// Вычисляем плановые показатели через ядро
|
||||
auto calculated_plan = Capital::Core::Generation::calculate_plan_generation_amounts(coopname, plan_hour_cost, plan_creators_hours, plan_expenses);
|
||||
|
||||
@@ -9,14 +9,14 @@ void capital::diallocate(eosio::name coopname, eosio::name application, checksum
|
||||
eosio::check(project.fact.program_invest_pool.amount > 0, "Нет программных средств для возврата");
|
||||
|
||||
// Неиспользованная сумма программных средств
|
||||
// При коэффициенте > 1 часть программных средств не используется
|
||||
// При коэффициенте > 100% часть программных средств не используется
|
||||
eosio::asset unused_program_amount = asset(0, _root_govern_symbol);
|
||||
|
||||
if (project.fact.return_cost_coefficient > 1.0) {
|
||||
if (project.fact.use_invest_percent > 100.0) {
|
||||
// Рассчитываем фактически использованную сумму программных средств
|
||||
eosio::asset used_program_amount = Capital::Core::Generation::calculate_investor_used_amount(
|
||||
project.fact.program_invest_pool,
|
||||
project.fact.return_cost_coefficient
|
||||
project.fact.use_invest_percent
|
||||
);
|
||||
|
||||
unused_program_amount = project.fact.program_invest_pool - used_program_amount;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* @brief Регистрация пайщика в контракте и получение договора УХД от него.
|
||||
*/
|
||||
void capital::regcontrib(eosio::name coopname, eosio::name application, eosio::name username, checksum256 contributor_hash, eosio::asset rate_per_hour, bool is_external_contract, document2 contract) {
|
||||
check_auth_or_fail(_capital, coopname, application, "regcontrib"_n);
|
||||
void capital::regcontrib(eosio::name coopname, eosio::name username, checksum256 contributor_hash, eosio::asset rate_per_hour, bool is_external_contract, document2 contract) {
|
||||
require_auth(coopname);
|
||||
|
||||
// если договор не внешний, то проверяем его на корректность
|
||||
if (!is_external_contract) {
|
||||
@@ -10,25 +10,15 @@ void capital::regcontrib(eosio::name coopname, eosio::name application, eosio::n
|
||||
verify_document_or_fail(contract, {username});
|
||||
}
|
||||
|
||||
Wallet::validate_asset(rate_per_hour);
|
||||
|
||||
auto exist_by_username = Capital::Contributors::get_contributor(coopname, username);
|
||||
eosio::check(!exist_by_username.has_value(), "Пайщик уже обладает подписанным договором УХД");
|
||||
|
||||
auto exist_by_hash = Capital::Contributors::get_contributor_by_hash(coopname, contributor_hash);
|
||||
eosio::check(!exist_by_hash.has_value(), "Контрибьютор с данным хэшем уже зарегистрирован");
|
||||
|
||||
Capital::contributor_index contributors(_capital, coopname.value);
|
||||
|
||||
contributors.emplace(coopname, [&](auto &c) {
|
||||
c.id = get_global_id_in_scope(_capital, coopname, "contributors"_n);
|
||||
c.coopname = coopname;
|
||||
c.username = username;
|
||||
c.contributor_hash = contributor_hash;
|
||||
c.status = Capital::Contributors::Status::PENDING;
|
||||
c.is_external_contract = is_external_contract;
|
||||
c.contract = contract;
|
||||
c.created_at = eosio::current_time_point();
|
||||
c.rate_per_hour = rate_per_hour;
|
||||
});
|
||||
Capital::Contributors::create_contributor(coopname, username, contributor_hash, is_external_contract, contract, rate_per_hour);
|
||||
|
||||
std::string memo = "";
|
||||
|
||||
|
||||
@@ -56,4 +56,7 @@ void capital::pushrslt(name coopname, name application, checksum256 project_hash
|
||||
// для избежания двойного обновления одной записи
|
||||
Capital::Segments::update_segment_after_result_contribution_with_shares(coopname, project_hash, application, application,
|
||||
available_base_after_pay_debt, debt_amount);
|
||||
|
||||
// Обновляем накопительные показатели контрибьютора на основе его ролей в сегменте
|
||||
Capital::Contributors::update_contributor_ratings_from_segment(coopname, segment);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
// Запустить проект на приём коммитов
|
||||
#include "app/managment/startproject.cpp"
|
||||
// Завершение проекта и начало голосования
|
||||
#include "app/managment/cmpltproject.cpp"
|
||||
#include "app/managment/startvoting.cpp"
|
||||
// Завершение голосования
|
||||
#include "app/managment/cmpltvoting.cpp"
|
||||
// Закрытие проекта
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
* \ingroup public_actions
|
||||
*/
|
||||
[[eosio::action]]
|
||||
void setconfig(name coopname, name initiator, Capital::config cfg);
|
||||
void setconfig(name coopname, Capital::config config);
|
||||
|
||||
// Создать проект
|
||||
[[eosio::action]]
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
|
||||
// Завершить проект и начать голосование
|
||||
[[eosio::action]]
|
||||
void cmpltproject(name coopname, checksum256 project_hash);
|
||||
void startvoting(name coopname, checksum256 project_hash);
|
||||
|
||||
// Завершить голосование
|
||||
[[eosio::action]]
|
||||
@@ -172,7 +172,7 @@ public:
|
||||
|
||||
// Регистрация
|
||||
[[eosio::action]]
|
||||
void regcontrib(eosio::name coopname, eosio::name application, eosio::name username, checksum256 contributor_hash, eosio::asset rate_per_hour, bool is_external_contract, document2 contract);
|
||||
void regcontrib(eosio::name coopname, eosio::name username, checksum256 contributor_hash, eosio::asset rate_per_hour, bool is_external_contract, document2 contract);
|
||||
[[eosio::action]]
|
||||
void approvereg(eosio::name coopname, checksum256 contributor_hash, document2 contract);
|
||||
[[eosio::action]]
|
||||
@@ -180,7 +180,7 @@ public:
|
||||
|
||||
// Приложения к договору УХД
|
||||
[[eosio::action]]
|
||||
void signappndx(eosio::name coopname, eosio::name application, eosio::name username, checksum256 project_hash, checksum256 appendix_hash, document2 document);
|
||||
void signappndx(eosio::name coopname, eosio::name username, checksum256 project_hash, checksum256 appendix_hash, document2 document);
|
||||
[[eosio::action]]
|
||||
void apprvappndx(eosio::name coopname, checksum256 appendix_hash, document2 approved_document);
|
||||
[[eosio::action]]
|
||||
@@ -241,7 +241,7 @@ public:
|
||||
|
||||
// Планирование
|
||||
[[eosio::action]] void setmaster(name coopname, checksum256 project_hash, name master);
|
||||
[[eosio::action]] void setplan(name coopname, checksum256 project_hash, uint64_t plan_creators_hours, asset plan_expenses, asset plan_hour_cost);
|
||||
[[eosio::action]] void setplan(name coopname, name master, checksum256 project_hash, uint64_t plan_creators_hours, asset plan_expenses, asset plan_hour_cost);
|
||||
[[eosio::action]] void expandexpnss(name coopname, checksum256 project_hash, asset additional_expenses);
|
||||
|
||||
// CRPS
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Capital::Core {
|
||||
if (!exist_segment.has_value()) {
|
||||
segments.emplace(_capital, [&](auto &g){
|
||||
g.id = segments.available_primary_key();
|
||||
g.coopname = coopname;
|
||||
g.project_hash = project_hash;
|
||||
g.username = username;
|
||||
g.is_author = true;
|
||||
|
||||
@@ -41,6 +41,7 @@ void upsert_contributor_segment(eosio::name coopname, const checksum256 &project
|
||||
if (!exist_segment.has_value()) {
|
||||
segments.emplace(_capital, [&](auto &g){
|
||||
g.id = segments.available_primary_key();
|
||||
g.coopname = coopname;
|
||||
g.project_hash = project_hash;
|
||||
g.username = username;
|
||||
g.is_contributor = true; // Устанавливаем флаг вкладчика
|
||||
|
||||
@@ -3,19 +3,58 @@
|
||||
namespace Capital::Core {
|
||||
|
||||
/**
|
||||
* @brief Обновляет CRPS поля в проекте для координаторов при добавлении наград
|
||||
* @brief Рассчитывает пропорциональную долю координатора в общем пуле премий
|
||||
* @param coordinator_investments Сумма инвестиций, привлеченная координатором
|
||||
* @param total_coordinator_investments Общая сумма инвестиций, привлеченная всеми координаторами
|
||||
* @param plan_total_investments Плановая общая сумма инвестиций проекта
|
||||
* @param coordinator_base_pool Общий пул премий координаторов
|
||||
* @param all_coordinators_scores Сумма всех промежуточных баллов R всех координаторов
|
||||
* @return Доля координатора в пуле премий
|
||||
*/
|
||||
inline void update_coordinator_crps(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);
|
||||
|
||||
projects.modify(project, _capital, [&](auto &p) {
|
||||
if (p.counts.total_coordinators > 0) {
|
||||
// Рассчитываем дельту reward per share
|
||||
int64_t delta = reward_amount.amount / p.counts.total_coordinators;
|
||||
p.crps.coordinator_cumulative_reward_per_share += delta;
|
||||
}
|
||||
});
|
||||
inline eosio::asset calculate_coordinator_proportional_share(
|
||||
const eosio::asset &coordinator_investments,
|
||||
const eosio::asset &total_coordinator_investments,
|
||||
const eosio::asset &plan_total_investments,
|
||||
const eosio::asset &coordinator_base_pool,
|
||||
double all_coordinators_scores
|
||||
) {
|
||||
if (coordinator_investments.amount == 0 ||
|
||||
total_coordinator_investments.amount == 0 ||
|
||||
plan_total_investments.amount == 0 ||
|
||||
all_coordinators_scores <= 0.0) {
|
||||
return eosio::asset(0, _root_govern_symbol);
|
||||
}
|
||||
|
||||
// Rn = (Yn / invest_plan) * (Yn / YN_fact_invest)
|
||||
double coordinator_score =
|
||||
(static_cast<double>(coordinator_investments.amount) / static_cast<double>(plan_total_investments.amount)) *
|
||||
(static_cast<double>(coordinator_investments.amount) / static_cast<double>(total_coordinator_investments.amount));
|
||||
|
||||
// Zn = Rn / RN * coordinator_base_fact
|
||||
double share_ratio = coordinator_score / all_coordinators_scores;
|
||||
int64_t coordinator_share = static_cast<int64_t>(
|
||||
static_cast<double>(coordinator_base_pool.amount) * share_ratio
|
||||
);
|
||||
|
||||
return eosio::asset(coordinator_share, _root_govern_symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Рассчитывает промежуточный балл R для координатора
|
||||
*/
|
||||
inline double calculate_coordinator_score(
|
||||
const eosio::asset &coordinator_investments,
|
||||
const eosio::asset &total_coordinator_investments,
|
||||
const eosio::asset &plan_total_investments
|
||||
) {
|
||||
if (coordinator_investments.amount == 0 ||
|
||||
total_coordinator_investments.amount == 0 ||
|
||||
plan_total_investments.amount == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return (static_cast<double>(coordinator_investments.amount) / static_cast<double>(plan_total_investments.amount)) *
|
||||
(static_cast<double>(coordinator_investments.amount) / static_cast<double>(total_coordinator_investments.amount));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,16 +69,16 @@ void upsert_coordinator_segment(eosio::name coopname, const checksum256 &project
|
||||
eosio::name coordinator_username, const eosio::asset &rised_amount) {
|
||||
Segments::segments_index segments(_capital, coopname.value);
|
||||
auto exist_segment = Segments::get_segment(coopname, project_hash, coordinator_username);
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
|
||||
|
||||
if (!exist_segment.has_value()) {
|
||||
segments.emplace(_capital, [&](auto &g){
|
||||
g.id = segments.available_primary_key();
|
||||
g.coopname = coopname;
|
||||
g.project_hash = project_hash;
|
||||
g.username = coordinator_username;
|
||||
g.coordinator_investments = rised_amount;
|
||||
g.is_coordinator = true;
|
||||
g.last_coordinator_reward_per_share = project.crps.coordinator_cumulative_reward_per_share;
|
||||
// Не устанавливаем CRPS поля, т.к. используем пропорциональное распределение
|
||||
});
|
||||
|
||||
Capital::Projects::increment_total_coordinators(coopname, project_hash);
|
||||
@@ -57,32 +96,55 @@ void upsert_coordinator_segment(eosio::name coopname, const checksum256 &project
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обновляет награды координатора в сегменте
|
||||
* @brief Обновляет награды координатора в сегменте на основе пропорционального распределения
|
||||
*/
|
||||
void refresh_coordinator_segment(eosio::name coopname, const checksum256 &project_hash, eosio::name username) {
|
||||
Segments::segments_index segments(_capital, coopname.value);
|
||||
auto segment_opt = Segments::get_segment(coopname, project_hash, username);
|
||||
|
||||
if (!segment_opt.has_value()) {
|
||||
return; // Сегмент не найден
|
||||
}
|
||||
|
||||
auto segment_it = segments.find(segment_opt->id);
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
|
||||
|
||||
segments.modify(segment_it, _capital, [&](auto &s) {
|
||||
if (s.is_coordinator) {
|
||||
int64_t pending_coordinator_reward = 1 *
|
||||
(project.crps.coordinator_cumulative_reward_per_share - s.last_coordinator_reward_per_share);
|
||||
|
||||
if (pending_coordinator_reward > 0) {
|
||||
eosio::asset coordinator_reward_asset = eosio::asset(pending_coordinator_reward, _root_govern_symbol);
|
||||
s.coordinator_base += coordinator_reward_asset;
|
||||
s.last_coordinator_reward_per_share = project.crps.coordinator_cumulative_reward_per_share;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Если нет координаторов или премий к распределению, ничего не делаем
|
||||
if (project.counts.total_coordinators == 0 || project.fact.coordinators_base_pool.amount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем всех координаторов проекта
|
||||
auto coordinators = Capital::Segments::get_project_coordinators(coopname, project_hash);
|
||||
|
||||
if (coordinators.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Рассчитываем общую сумму промежуточных баллов R всех координаторов
|
||||
double total_coordinators_score = 0.0;
|
||||
for (const auto& coord : coordinators) {
|
||||
total_coordinators_score += calculate_coordinator_score(
|
||||
coord.coordinator_investments,
|
||||
project.fact.coordinators_investment_pool,
|
||||
project.plan.total_received_investments
|
||||
);
|
||||
}
|
||||
|
||||
if (total_coordinators_score <= 0.0) {
|
||||
return; // Нет баллов для распределения
|
||||
}
|
||||
|
||||
// Обновляем координаторские доли для всех координаторов проекта
|
||||
Segments::segments_index segments(_capital, coopname.value);
|
||||
|
||||
for (const auto& coord : coordinators) {
|
||||
auto segment_it = segments.find(coord.id);
|
||||
|
||||
eosio::asset new_coordinator_base = calculate_coordinator_proportional_share(
|
||||
coord.coordinator_investments,
|
||||
project.fact.coordinators_investment_pool,
|
||||
project.plan.total_received_investments,
|
||||
project.fact.coordinators_base_pool,
|
||||
total_coordinators_score
|
||||
);
|
||||
|
||||
segments.modify(segment_it, _capital, [&](auto &s) {
|
||||
s.coordinator_base = new_coordinator_base;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace Capital::Core
|
||||
@@ -0,0 +1,150 @@
|
||||
#include "crps.hpp"
|
||||
|
||||
namespace Capital::Core {
|
||||
|
||||
/**
|
||||
* @brief Рассчитывает пропорциональную долю координатора в общем пуле премий
|
||||
* @param coordinator_investments Сумма инвестиций, привлеченная координатором
|
||||
* @param total_coordinator_investments Общая сумма инвестиций, привлеченная всеми координаторами
|
||||
* @param plan_total_investments Плановая общая сумма инвестиций проекта
|
||||
* @param coordinator_base_pool Общий пул премий координаторов
|
||||
* @param all_coordinators_scores Сумма всех промежуточных баллов R всех координаторов
|
||||
* @return Доля координатора в пуле премий
|
||||
*/
|
||||
inline eosio::asset calculate_coordinator_proportional_share(
|
||||
const eosio::asset &coordinator_investments,
|
||||
const eosio::asset &total_coordinator_investments,
|
||||
const eosio::asset &plan_total_investments,
|
||||
const eosio::asset &coordinator_base_pool,
|
||||
double all_coordinators_scores
|
||||
) {
|
||||
if (coordinator_investments.amount == 0 ||
|
||||
total_coordinator_investments.amount == 0 ||
|
||||
plan_total_investments.amount == 0 ||
|
||||
all_coordinators_scores <= 0.0) {
|
||||
return eosio::asset(0, _root_govern_symbol);
|
||||
}
|
||||
|
||||
// Rn = (Yn / invest_plan) * (Yn / YN_fact_invest)
|
||||
double coordinator_score =
|
||||
(static_cast<double>(coordinator_investments.amount) / static_cast<double>(plan_total_investments.amount)) *
|
||||
(static_cast<double>(coordinator_investments.amount) / static_cast<double>(total_coordinator_investments.amount));
|
||||
|
||||
// Zn = Rn / RN * coordinator_base_fact
|
||||
double share_ratio = coordinator_score / all_coordinators_scores;
|
||||
int64_t coordinator_share = static_cast<int64_t>(
|
||||
static_cast<double>(coordinator_base_pool.amount) * share_ratio
|
||||
);
|
||||
|
||||
return eosio::asset(coordinator_share, _root_govern_symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Рассчитывает промежуточный балл R для координатора
|
||||
*/
|
||||
inline double calculate_coordinator_score(
|
||||
const eosio::asset &coordinator_investments,
|
||||
const eosio::asset &total_coordinator_investments,
|
||||
const eosio::asset &plan_total_investments
|
||||
) {
|
||||
if (coordinator_investments.amount == 0 ||
|
||||
total_coordinator_investments.amount == 0 ||
|
||||
plan_total_investments.amount == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return (static_cast<double>(coordinator_investments.amount) / static_cast<double>(plan_total_investments.amount)) *
|
||||
(static_cast<double>(coordinator_investments.amount) / static_cast<double>(total_coordinator_investments.amount));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Создает или обновляет запись координатора в таблице segments.
|
||||
* @param coopname Имя кооператива (scope таблицы).
|
||||
* @param project_hash Хэш проекта.
|
||||
* @param coordinator_username Имя пользователя координатора.
|
||||
* @param rised_amount Сумма привлеченных средств.
|
||||
*/
|
||||
void upsert_coordinator_segment(eosio::name coopname, const checksum256 &project_hash,
|
||||
eosio::name coordinator_username, const eosio::asset &rised_amount) {
|
||||
Segments::segments_index segments(_capital, coopname.value);
|
||||
auto exist_segment = Segments::get_segment(coopname, project_hash, coordinator_username);
|
||||
|
||||
if (!exist_segment.has_value()) {
|
||||
segments.emplace(_capital, [&](auto &g){
|
||||
g.id = segments.available_primary_key();
|
||||
g.coopname = coopname;
|
||||
g.project_hash = project_hash;
|
||||
g.username = coordinator_username;
|
||||
g.coordinator_investments = rised_amount;
|
||||
g.is_coordinator = true;
|
||||
// Не устанавливаем CRPS поля, т.к. используем пропорциональное распределение
|
||||
});
|
||||
|
||||
Capital::Projects::increment_total_coordinators(coopname, project_hash);
|
||||
} else {
|
||||
auto segment = segments.find(exist_segment->id);
|
||||
segments.modify(segment, _capital, [&](auto &g) {
|
||||
if (!g.is_coordinator) {
|
||||
g.is_coordinator = true;
|
||||
Capital::Projects::increment_total_coordinators(coopname, project_hash);
|
||||
}
|
||||
g.coordinator_investments += rised_amount;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обновляет награды координатора в сегменте на основе пропорционального распределения
|
||||
*/
|
||||
void refresh_coordinator_segment(eosio::name coopname, const checksum256 &project_hash, eosio::name username) {
|
||||
auto project = Capital::Projects::get_project_or_fail(coopname, project_hash);
|
||||
|
||||
// Если нет координаторов или премий к распределению, ничего не делаем
|
||||
if (project.counts.total_coordinators == 0 || project.fact.coordinators_base_pool.amount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем всех координаторов проекта
|
||||
auto coordinators = Capital::Segments::get_project_coordinators(coopname, project_hash);
|
||||
|
||||
if (coordinators.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Рассчитываем общую сумму промежуточных баллов R всех координаторов
|
||||
double total_coordinators_score = 0.0;
|
||||
for (const auto& coord : coordinators) {
|
||||
total_coordinators_score += calculate_coordinator_score(
|
||||
coord.coordinator_investments,
|
||||
project.fact.coordinators_investment_pool,
|
||||
project.plan.total_received_investments
|
||||
);
|
||||
}
|
||||
|
||||
if (total_coordinators_score <= 0.0) {
|
||||
return; // Нет баллов для распределения
|
||||
}
|
||||
|
||||
// Обновляем координаторские доли для всех координаторов проекта
|
||||
Segments::segments_index segments(_capital, coopname.value);
|
||||
|
||||
for (const auto& coord : coordinators) {
|
||||
auto segment_it = segments.find(coord.id);
|
||||
|
||||
eosio::asset new_coordinator_base = calculate_coordinator_proportional_share(
|
||||
coord.coordinator_investments,
|
||||
project.fact.coordinators_investment_pool,
|
||||
project.plan.total_received_investments,
|
||||
project.fact.coordinators_base_pool,
|
||||
total_coordinators_score
|
||||
);
|
||||
|
||||
segments.modify(segment_it, _capital, [&](auto &s) {
|
||||
s.coordinator_base = new_coordinator_base;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace Capital::Core
|
||||
@@ -17,6 +17,7 @@ namespace Capital::Core {
|
||||
if (!exist_segment.has_value()) {
|
||||
segments.emplace(_capital, [&](auto &g){
|
||||
g.id = segments.available_primary_key();
|
||||
g.coopname = coopname;
|
||||
g.project_hash = project_hash;
|
||||
g.username = username;
|
||||
g.creator_base = delta_amounts.creators_base_pool;
|
||||
|
||||
@@ -6,7 +6,7 @@ using std::string;
|
||||
// Подключение всех CRPS реализаций
|
||||
#include "author_crps.cpp"
|
||||
#include "contributor_crps.cpp"
|
||||
#include "coordinator_crps.cpp"
|
||||
#include "coordinator_share.cpp" // Пропорциональное распределение для координаторов
|
||||
#include "creator_crps.cpp"
|
||||
#include "program_crps.cpp"
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Capital::Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Пересчитывает доступную сумму к компенсации на основе инвестиций с учетом return_cost_coefficient
|
||||
* @brief Пересчитывает доступную сумму к компенсации на основе инвестиций с учетом return_base_percent
|
||||
*/
|
||||
void refresh_provisional_amount(eosio::name coopname, const checksum256 &project_hash, eosio::name username) {
|
||||
auto segment_opt = Segments::get_segment(coopname, project_hash, username);
|
||||
@@ -62,47 +62,28 @@ namespace Capital::Core {
|
||||
segments.modify(segment_it, _capital, [&](auto &s) {
|
||||
s.provisional_amount = eosio::asset(0, _root_govern_symbol);
|
||||
s.last_known_invest_pool = project.fact.invest_pool; // Все равно синхронизируем инвестиции
|
||||
s.last_known_creators_base_pool = project.fact.creators_base_pool; // Синхронизируем с актуальной суммой базового пула создателей
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Рассчитываем provisional_amount с учетом return_cost_coefficient
|
||||
double return_coefficient = project.fact.return_cost_coefficient;
|
||||
// Рассчитываем provisional_amount с учетом return_base_percent (в процентах)
|
||||
double return_coefficient_percent = project.fact.return_base_percent;
|
||||
|
||||
// Ограничиваем коэффициент максимум 1.0 (100% компенсации)
|
||||
if (return_coefficient > 1.0) {
|
||||
return_coefficient = 1.0;
|
||||
}
|
||||
|
||||
// provisional_amount = себестоимость_труда * return_cost_coefficient
|
||||
// provisional_amount = себестоимость_труда * (return_base_percent / 100)
|
||||
int64_t provisional_amount = static_cast<int64_t>(
|
||||
static_cast<double>(user_base_amount) * return_coefficient
|
||||
static_cast<double>(user_base_amount) * (return_coefficient_percent / 100.0)
|
||||
);
|
||||
|
||||
// Общая сумма базовых полей всех ролей в проекте
|
||||
int64_t total_base_amount = project.fact.creators_base_pool.amount +
|
||||
project.fact.authors_base_pool.amount +
|
||||
project.fact.coordinators_base_pool.amount;
|
||||
eosio::check(provisional_amount <= project.fact.invest_pool.amount, "Cумма доступной ссуды не может превышать сумму инвестиций проекта");
|
||||
|
||||
// Если есть инвестиции, проверяем что provisional_amount не превышает долю в инвестициях
|
||||
if (project.fact.invest_pool.amount > 0 && total_base_amount > 0) {
|
||||
int64_t user_investment_share = static_cast<int64_t>(
|
||||
(static_cast<double>(user_base_amount) * static_cast<double>(project.fact.invest_pool.amount)) /
|
||||
static_cast<double>(total_base_amount)
|
||||
);
|
||||
|
||||
// provisional_amount не может превышать долю в инвестициях
|
||||
if (provisional_amount > user_investment_share) {
|
||||
provisional_amount = user_investment_share;
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем provisional_amount и last_known_invest_pool в сегменте
|
||||
// Обновляем provisional_amount и синхронизируем известные пулы в сегменте
|
||||
Segments::segments_index segments(_capital, coopname.value);
|
||||
auto segment_it = segments.find(segment_opt->id);
|
||||
segments.modify(segment_it, _capital, [&](auto &s) {
|
||||
s.provisional_amount = eosio::asset(provisional_amount, _root_govern_symbol);
|
||||
s.last_known_invest_pool = project.fact.invest_pool; // Синхронизируем с актуальной суммой инвестиций
|
||||
s.last_known_creators_base_pool = project.fact.creators_base_pool; // Синхронизируем с актуальной суммой базового пула создателей
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,6 +100,7 @@ namespace Capital::Core {
|
||||
if (!exist_segment.has_value()) {
|
||||
segments.emplace(_capital, [&](auto &g){
|
||||
g.id = segments.available_primary_key();
|
||||
g.coopname = coopname;
|
||||
g.project_hash = project_hash;
|
||||
g.username = username;
|
||||
g.investor_amount = investor_amount;
|
||||
@@ -196,11 +178,14 @@ namespace Capital::Core {
|
||||
auto segment = segments.find(segment_opt->id);
|
||||
|
||||
segments.modify(segment, _capital, [&](auto &s) {
|
||||
// Рассчитываем фактически используемую сумму инвестора
|
||||
// Рассчитываем фактически используемую сумму инвестора (используем коэффициент возврата инвестиций)
|
||||
s.investor_base = Capital::Core::Generation::calculate_investor_used_amount(
|
||||
s.investor_amount,
|
||||
project.fact.return_cost_coefficient
|
||||
project.fact.use_invest_percent
|
||||
);
|
||||
// Синхронизируем известные пулы для корректного определения необходимости обновления сегмента
|
||||
s.last_known_invest_pool = project.fact.invest_pool;
|
||||
s.last_known_creators_base_pool = project.fact.creators_base_pool;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,48 +20,81 @@ namespace Capital::Core::Generation {
|
||||
/**
|
||||
* @brief Функция расчета коэффициента возврата себестоимости (для фактических показателей)
|
||||
*/
|
||||
double calculate_return_cost_coefficient(const fact_pool& current_pools) {
|
||||
// invest_pool теперь содержит все инвестиции (включая программные)
|
||||
if (current_pools.invest_pool.amount == 0) {
|
||||
double calculate_return_base_percent(eosio::asset creators_base_pool, eosio::asset authors_base_pool, eosio::asset coordinators_base_pool, eosio::asset invest_pool) {
|
||||
// Только трудозатраты (без расходов)
|
||||
double work_costs = static_cast<double>(
|
||||
creators_base_pool.amount +
|
||||
authors_base_pool.amount +
|
||||
coordinators_base_pool.amount
|
||||
);
|
||||
|
||||
// Если нет трудозатрат, коэффициент = 0%
|
||||
if (work_costs == 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double total_costs = static_cast<double>(
|
||||
current_pools.creators_base_pool.amount +
|
||||
current_pools.authors_base_pool.amount +
|
||||
current_pools.coordinators_base_pool.amount +
|
||||
current_pools.target_expense_pool.amount
|
||||
);
|
||||
double available_investments = static_cast<double>(invest_pool.amount);
|
||||
|
||||
double total_investments = static_cast<double>(current_pools.invest_pool.amount);
|
||||
|
||||
return total_costs / total_investments;
|
||||
// Коэффициент в процентах показывает какую долю себестоимости можно компенсировать
|
||||
// Не может быть больше 100.0% компенсации
|
||||
double coefficient_percent = (available_investments / work_costs) * 100.0;
|
||||
return coefficient_percent > 100.0 ? 100.0 : coefficient_percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция расчета коэффициента возврата себестоимости (для плановых показателей)
|
||||
*/
|
||||
double calculate_return_cost_coefficient(const plan_pool& current_pools) {
|
||||
// invest_pool теперь содержит все инвестиции (включая программные)
|
||||
if (current_pools.invest_pool.amount == 0) {
|
||||
* @brief Функция расчета коэффициента возврата инвестиций для плановых показателей
|
||||
*/
|
||||
double calculate_use_invest_percent_planned(eosio::asset creators_base_pool, eosio::asset authors_base_pool, eosio::asset coordinators_base_pool, eosio::asset target_expense_pool, eosio::asset total_received_investments) {
|
||||
// Используем общую сумму инвестиций
|
||||
if (total_received_investments.amount == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Все затраты проекта (трудозатраты + расходы)
|
||||
double total_costs = static_cast<double>(
|
||||
current_pools.creators_base_pool.amount +
|
||||
current_pools.authors_base_pool.amount +
|
||||
current_pools.coordinators_base_pool.amount +
|
||||
current_pools.target_expense_pool.amount
|
||||
creators_base_pool.amount +
|
||||
authors_base_pool.amount +
|
||||
coordinators_base_pool.amount +
|
||||
target_expense_pool.amount
|
||||
);
|
||||
|
||||
double total_investments = static_cast<double>(current_pools.invest_pool.amount);
|
||||
double total_investments = static_cast<double>(total_received_investments.amount);
|
||||
|
||||
return total_costs / total_investments;
|
||||
// Коэффициент в процентах показывает долю инвестиций, которая используется
|
||||
// Физически не может быть больше 100.0% использования
|
||||
double coefficient_percent = (total_costs / total_investments) * 100.0;
|
||||
return coefficient_percent > 100.0 ? 100.0 : coefficient_percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция расчета коэффициента возврата инвестиций для фактических показателей
|
||||
*/
|
||||
double calculate_use_invest_percent(eosio::asset creators_base_pool, eosio::asset authors_base_pool, eosio::asset coordinators_base_pool, eosio::asset accumulated_expense_pool, eosio::asset used_expense_pool, eosio::asset total_received_investments) {
|
||||
// Используем общую сумму инвестиций
|
||||
if (total_received_investments.amount == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Все затраты проекта (трудозатраты + фактические расходы)
|
||||
double total_costs = static_cast<double>(
|
||||
creators_base_pool.amount +
|
||||
authors_base_pool.amount +
|
||||
coordinators_base_pool.amount +
|
||||
accumulated_expense_pool.amount +
|
||||
used_expense_pool.amount
|
||||
);
|
||||
|
||||
double total_investments = static_cast<double>(total_received_investments.amount);
|
||||
|
||||
// Коэффициент в процентах показывает долю инвестиций, которая используется
|
||||
// Физически не может быть больше 100.0% использования
|
||||
double coefficient_percent = (total_costs / total_investments) * 100.0;
|
||||
return coefficient_percent > 100.0 ? 100.0 : coefficient_percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Функция расчета плановых показателей проекта
|
||||
*/
|
||||
* @brief Функция расчета плановых показателей проекта
|
||||
*/
|
||||
plan_pool calculate_plan_generation_amounts(
|
||||
eosio::name coopname,
|
||||
const eosio::asset& plan_hour_cost,
|
||||
@@ -94,7 +127,7 @@ namespace Capital::Core::Generation {
|
||||
|
||||
// расчет планируемых премий координаторов
|
||||
|
||||
plan.coordinators_base_pool = (plan.creators_base_pool + plan.authors_base_pool + plan.target_expense_pool) * st.config.coordinator_bonus_percent;
|
||||
plan.coordinators_base_pool = (plan.creators_base_pool + plan.authors_base_pool + plan.target_expense_pool) * st.config.coordinator_bonus_percent / 100;
|
||||
|
||||
// расчет премий создателя
|
||||
double creators_bonus_double = static_cast<double>(plan.creators_base_pool.amount * CREATORS_BONUS_COEFFICIENT);
|
||||
@@ -104,15 +137,19 @@ namespace Capital::Core::Generation {
|
||||
double authors_bonus_double = static_cast<double>(plan.authors_base_pool.amount * AUTHOR_BONUS_COEFFICIENT);
|
||||
plan.authors_bonus_pool = eosio::asset(int64_t(authors_bonus_double), _root_govern_symbol);
|
||||
|
||||
// расчет планируемой суммы инвестиций
|
||||
plan.invest_pool = plan.creators_base_pool + plan.authors_base_pool + plan.coordinators_base_pool + plan.target_expense_pool;
|
||||
// расчет общей планируемой суммы инвестиций (все затраты)
|
||||
plan.total_received_investments = plan.creators_base_pool + plan.authors_base_pool + plan.coordinators_base_pool + plan.target_expense_pool;
|
||||
|
||||
// расчет планируемой суммы инвестиций (только для трудозатрат, после вычета расходов)
|
||||
plan.invest_pool = plan.creators_base_pool + plan.authors_base_pool + plan.coordinators_base_pool;
|
||||
|
||||
// расчет суммы, которую координаторы привлекут в проект
|
||||
// считаем, что координаторы будут привлекать всю сумму инвестиций
|
||||
plan.coordinators_investment_pool = plan.invest_pool;
|
||||
// считаем, что координаторы будут привлекать всю общую сумму инвестиций
|
||||
plan.coordinators_investment_pool = plan.total_received_investments;
|
||||
|
||||
// коэффициент возврата себестоимости для плана будет равен 1, т.к. мы планируем привлечение инвестиций в полном размере себестоимости
|
||||
plan.return_cost_coefficient = calculate_return_cost_coefficient(plan);
|
||||
// коэффициенты возврата
|
||||
plan.return_base_percent = calculate_return_base_percent(plan.creators_base_pool, plan.authors_base_pool, plan.coordinators_base_pool, plan.invest_pool);
|
||||
plan.use_invest_percent = calculate_use_invest_percent_planned(plan.creators_base_pool, plan.authors_base_pool, plan.coordinators_base_pool, plan.target_expense_pool, plan.total_received_investments);
|
||||
|
||||
plan.total_generation_pool = plan.creators_base_pool + plan.authors_base_pool + plan.coordinators_base_pool + plan.creators_bonus_pool + plan.authors_bonus_pool;
|
||||
|
||||
@@ -173,7 +210,7 @@ namespace Capital::Core::Generation {
|
||||
eosio::symbol sym = investment_amount.symbol;
|
||||
|
||||
// Рассчитываем премию координатора от инвестиций
|
||||
double k = st.config.coordinator_bonus_percent;
|
||||
double k = st.config.coordinator_bonus_percent / 100;
|
||||
return eosio::asset(int64_t(amount * k / (1 + k)), sym);
|
||||
}
|
||||
|
||||
@@ -204,22 +241,16 @@ namespace Capital::Core::Generation {
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Рассчитывает фактически используемую сумму инвестора с учетом коэффициента возврата
|
||||
* @param investor_base Базовая сумма инвестора
|
||||
* @param return_cost_coefficient Коэффициент возврата себестоимости проекта
|
||||
* @brief Рассчитывает фактически используемую сумму инвестора с учетом коэффициента использования
|
||||
* @param investor_amount Общая сумма инвестора
|
||||
* @param use_invest_percent_percent Коэффициент используемых инвестиций в процентах (от 0.0 до 100.0)
|
||||
* @return Фактически используемая сумма инвестора
|
||||
*/
|
||||
eosio::asset calculate_investor_used_amount(const eosio::asset& investor_base, double return_cost_coefficient) {
|
||||
if (return_cost_coefficient <= 1.0) {
|
||||
// Если коэффициент <= 1, то все средства инвестора используются
|
||||
return investor_base;
|
||||
}
|
||||
eosio::asset calculate_investor_used_amount(const eosio::asset& investor_amount, double use_invest_percent_percent) {
|
||||
// Используемая сумма = общая_сумма * (коэффициент_процентов / 100)
|
||||
double used_amount = static_cast<double>(investor_amount.amount) * (use_invest_percent_percent / 100.0);
|
||||
|
||||
// Если коэффициент > 1, то используется только часть средств инвестора
|
||||
// Используемая сумма = базовая_сумма / коэффициент
|
||||
double used_amount = static_cast<double>(investor_base.amount) / return_cost_coefficient;
|
||||
|
||||
return eosio::asset(static_cast<int64_t>(used_amount), investor_base.symbol);
|
||||
return eosio::asset(static_cast<int64_t>(used_amount), investor_amount.symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +276,8 @@ namespace Capital::Core::Generation {
|
||||
p.fact.coordinators_base_pool += coordinator_base;
|
||||
|
||||
// Пересчитываем коэффициент возврата себестоимости
|
||||
p.fact.return_cost_coefficient = calculate_return_cost_coefficient(p.fact);
|
||||
p.fact.return_base_percent = calculate_return_base_percent(p.fact.creators_base_pool, p.fact.authors_base_pool, p.fact.coordinators_base_pool, p.fact.invest_pool);
|
||||
p.fact.use_invest_percent = calculate_use_invest_percent(p.fact.creators_base_pool, p.fact.authors_base_pool, p.fact.coordinators_base_pool, p.fact.accumulated_expense_pool, p.fact.used_expense_pool, p.fact.total_received_investments);
|
||||
|
||||
// Пересчитываем премии вкладчиков, т.к. премии координаторов влияют на премии вкладчиков
|
||||
p.fact.contributors_bonus_pool = calculate_contributors_bonus_pool(p.fact.total_generation_pool);
|
||||
@@ -257,8 +289,8 @@ namespace Capital::Core::Generation {
|
||||
p.fact.total = p.fact.total_contribution + p.fact.used_expense_pool;
|
||||
});
|
||||
|
||||
// Распределяем награды координаторов через CRPS систему
|
||||
Capital::Core::update_coordinator_crps(coopname, project_hash, coordinator_base);
|
||||
// Примечание: Координаторские награды будут распределены пропорционально при обновлении сегментов
|
||||
// через refresh_coordinator_segment на основе привлеченных каждым координатором средств
|
||||
}
|
||||
|
||||
} // namespace Capital::Core
|
||||
@@ -43,12 +43,17 @@ namespace Capital::Core::Generation {
|
||||
/**
|
||||
* @brief Функция расчета коэффициента возврата себестоимости (для фактических показателей)
|
||||
*/
|
||||
double calculate_return_cost_coefficient(const fact_pool& current_pools);
|
||||
double calculate_return_base_percent(eosio::asset creators_base_pool, eosio::asset authors_base_pool, eosio::asset coordinators_base_pool, eosio::asset invest_pool);
|
||||
|
||||
/**
|
||||
* @brief Функция расчета коэффициента возврата себестоимости (для плановых показателей)
|
||||
* @brief Функция расчета коэффициента используемых инвестиций для плановых показателей
|
||||
*/
|
||||
double calculate_return_cost_coefficient(const plan_pool& current_pools);
|
||||
double calculate_use_invest_percent_planned(eosio::asset creators_base_pool, eosio::asset authors_base_pool, eosio::asset coordinators_base_pool, eosio::asset target_expense_pool, eosio::asset total_received_investments);
|
||||
|
||||
/**
|
||||
* @brief Функция расчета коэффициента используемых инвестиций для фактических показателей
|
||||
*/
|
||||
double calculate_use_invest_percent(eosio::asset creators_base_pool, eosio::asset authors_base_pool, eosio::asset coordinators_base_pool, eosio::asset accumulated_expense_pool, eosio::asset used_expense_pool, eosio::asset total_received_investments);
|
||||
|
||||
/**
|
||||
* @brief Распределение паевых средств проекта
|
||||
@@ -56,9 +61,9 @@ namespace Capital::Core::Generation {
|
||||
void distribute_project_membership_funds(eosio::name coopname, uint64_t project_id, asset amount, uint8_t level);
|
||||
|
||||
/**
|
||||
* @brief Рассчитывает фактически используемую сумму инвестора с учетом коэффициента возврата
|
||||
* @brief Рассчитывает фактически используемую сумму инвестора с учетом коэффициента использования
|
||||
*/
|
||||
eosio::asset calculate_investor_used_amount(const eosio::asset& investor_base, double return_cost_coefficient);
|
||||
eosio::asset calculate_investor_used_amount(const eosio::asset& investor_amount, double use_invest_percent);
|
||||
|
||||
/**
|
||||
* @brief Добавляет координаторские средства к проекту
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Capital::Core {
|
||||
if (expense_gap.amount > 0) {
|
||||
// Рассчитываем процент от инвестиций для пула расходов
|
||||
auto st = Capital::get_global_state(coopname);
|
||||
eosio::asset potential_to_expense = amount * st.config.expense_pool_percent;
|
||||
eosio::asset potential_to_expense = amount * st.config.expense_pool_percent / 100;
|
||||
|
||||
// Но не больше, чем нужно для достижения цели
|
||||
to_expense_pool = (potential_to_expense.amount <= expense_gap.amount) ? potential_to_expense : expense_gap;
|
||||
@@ -58,9 +58,11 @@ namespace Capital::Core {
|
||||
// Также фиксируем в program_invest_pool для отслеживания программных средств
|
||||
p.fact.program_invest_pool += to_invest_pool;
|
||||
p.fact.accumulated_expense_pool += to_expense_pool;
|
||||
p.fact.total_received_investments += amount; // Увеличиваем общую сумму полученных инвестиций
|
||||
|
||||
// Пересчитываем коэффициент возврата себестоимости
|
||||
p.fact.return_cost_coefficient = Capital::Core::Generation::calculate_return_cost_coefficient(p.fact);
|
||||
// Пересчитываем коэффициенты возврата
|
||||
p.fact.return_base_percent = Capital::Core::Generation::calculate_return_base_percent(p.fact.creators_base_pool, p.fact.authors_base_pool, p.fact.coordinators_base_pool, p.fact.invest_pool);
|
||||
p.fact.use_invest_percent = Capital::Core::Generation::calculate_use_invest_percent(p.fact.creators_base_pool, p.fact.authors_base_pool, p.fact.coordinators_base_pool, p.fact.accumulated_expense_pool, p.fact.used_expense_pool, p.fact.total_received_investments);
|
||||
});
|
||||
|
||||
// Списываем средства из глобального пула
|
||||
|
||||
@@ -37,20 +37,49 @@ typedef eosio::multi_index<
|
||||
indexed_by<"byprojuser"_n, const_mem_fun<appendix, uint128_t, &appendix::by_project_user>>
|
||||
> appendix_index;
|
||||
|
||||
}// namespace Capital
|
||||
|
||||
/**
|
||||
* @brief Получает приложение по хэшу
|
||||
*/
|
||||
inline std::optional<appendix> get_appendix(eosio::name coopname, const checksum256 &appendix_hash) {
|
||||
appendix_index appendixes(_capital, coopname.value);
|
||||
auto by_hash = appendixes.get_index<"byhash"_n>();
|
||||
auto itr = by_hash.find(appendix_hash);
|
||||
|
||||
namespace Capital::Appendix {
|
||||
inline void create_appendix(eosio::name coopname, eosio::name username, checksum256 project_hash, checksum256 appendix_hash, document2 document){
|
||||
Capital::appendix_index appendixes(_capital, coopname.value);
|
||||
auto appendix_id = get_global_id_in_scope(_capital, coopname, "appendixes"_n);
|
||||
|
||||
if (itr == by_hash.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *itr;
|
||||
appendixes.emplace(coopname, [&](auto &a) {
|
||||
a.id = appendix_id;
|
||||
a.coopname = coopname;
|
||||
a.username = username;
|
||||
a.project_hash = project_hash;
|
||||
a.appendix_hash = appendix_hash;
|
||||
a.status = Capital::Appendix::Status::CREATED;
|
||||
a.created_at = current_time_point();
|
||||
a.appendix = document;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Получает приложение по хэшу
|
||||
*/
|
||||
inline std::optional<appendix> get_appendix(eosio::name coopname, const checksum256 &appendix_hash) {
|
||||
appendix_index appendixes(_capital, coopname.value);
|
||||
auto by_hash = appendixes.get_index<"byhash"_n>();
|
||||
auto itr = by_hash.find(appendix_hash);
|
||||
|
||||
if (itr == by_hash.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *itr;
|
||||
}
|
||||
|
||||
}// namespace Capital
|
||||
/**
|
||||
* @brief Удаляет приложение из таблицы appendixes
|
||||
*/
|
||||
inline void delete_appendix(eosio::name coopname, uint64_t appendix_id){
|
||||
// Удаляем запись из таблицы appendixes
|
||||
Capital::appendix_index appendixes(_capital, coopname.value);
|
||||
auto itr = appendixes.find(appendix_id);
|
||||
appendixes.erase(itr);
|
||||
}
|
||||
|
||||
}// namespace Capital::Appendix
|
||||
|
||||
@@ -26,17 +26,34 @@ namespace Capital {
|
||||
|
||||
eosio::asset capital_available = asset(0, _root_govern_symbol); ///< Накопленные членские взносы по программе капитализации
|
||||
int64_t reward_per_share_last = 0; ///< Последний зафиксированный cumulative_reward_per_share по программе капитализации
|
||||
|
||||
|
||||
eosio::asset contributed_as_investor = asset(0, _root_govern_symbol); ///< Сумма, вложенная в проект как инвестор
|
||||
eosio::asset contributed_as_creator = asset(0, _root_govern_symbol); ///< Сумма, вложенная в проект как создатель
|
||||
eosio::asset contributed_as_author = asset(0, _root_govern_symbol); ///< Сумма, вложенная в проект как автор
|
||||
eosio::asset contributed_as_coordinator = asset(0, _root_govern_symbol); ///< Сумма, вложенная в проект как координатор
|
||||
eosio::asset contributed_as_contributor = asset(0, _root_govern_symbol); ///< Сумма, вложенная в проект как контрибьютор
|
||||
|
||||
uint64_t primary_key() const { return id; }
|
||||
uint64_t by_username() const { return username.value; }
|
||||
checksum256 by_hash() const { return contributor_hash; }
|
||||
|
||||
uint64_t by_investor_rating() const { return contributed_as_investor.amount; }
|
||||
uint64_t by_creator_rating() const { return contributed_as_creator.amount; }
|
||||
uint64_t by_author_rating() const { return contributed_as_author.amount; }
|
||||
uint64_t by_coordinator_rating() const { return contributed_as_coordinator.amount; }
|
||||
uint64_t by_contributor_rating() const { return contributed_as_contributor.amount; }
|
||||
uint64_t by_rating() const { return contributed_as_investor.amount + contributed_as_creator.amount + contributed_as_author.amount + contributed_as_coordinator.amount + contributed_as_contributor.amount; }
|
||||
};
|
||||
|
||||
typedef eosio::multi_index<
|
||||
"contributors"_n, contributor,
|
||||
indexed_by<"byusername"_n, const_mem_fun<contributor, uint64_t, &contributor::by_username>>,
|
||||
indexed_by<"byhash"_n, const_mem_fun<contributor, checksum256, &contributor::by_hash>>
|
||||
indexed_by<"byhash"_n, const_mem_fun<contributor, checksum256, &contributor::by_hash>>,
|
||||
indexed_by<"byinvrate"_n, const_mem_fun<contributor, uint64_t, &contributor::by_investor_rating>>,
|
||||
indexed_by<"bycreatorate"_n, const_mem_fun<contributor, uint64_t, &contributor::by_creator_rating>>,
|
||||
indexed_by<"byauthorate"_n, const_mem_fun<contributor, uint64_t, &contributor::by_author_rating>>,
|
||||
indexed_by<"bycoordrate"_n, const_mem_fun<contributor, uint64_t, &contributor::by_coordinator_rating>>,
|
||||
indexed_by<"bycontrate"_n, const_mem_fun<contributor, uint64_t, &contributor::by_contributor_rating>>,
|
||||
indexed_by<"byrating"_n, const_mem_fun<contributor, uint64_t, &contributor::by_rating>>
|
||||
> contributor_index;
|
||||
}// namespace Capital
|
||||
|
||||
@@ -49,6 +66,36 @@ namespace Capital::Contributors {
|
||||
const eosio::name PENDING = "pending"_n; ///< Ожидает подтверждения
|
||||
const eosio::name ACTIVE = "active"_n; ///< Авторизован/активен
|
||||
}
|
||||
|
||||
inline void create_contributor(eosio::name coopname, eosio::name username, checksum256 contributor_hash, bool is_external_contract, document2 contract, eosio::asset rate_per_hour){
|
||||
Capital::contributor_index contributors(_capital, coopname.value);
|
||||
|
||||
contributors.emplace(coopname, [&](auto &c) {
|
||||
c.id = get_global_id_in_scope(_capital, coopname, "contributors"_n);
|
||||
c.coopname = coopname;
|
||||
c.username = username;
|
||||
c.contributor_hash = contributor_hash;
|
||||
c.status = Capital::Contributors::Status::PENDING;
|
||||
c.is_external_contract = is_external_contract;
|
||||
c.contract = contract;
|
||||
c.created_at = eosio::current_time_point();
|
||||
c.rate_per_hour = rate_per_hour;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Добавляет project_hash в вектор appendixes у контрибьютора
|
||||
*/
|
||||
inline void push_appendix_to_contributor(eosio::name coopname, eosio::name username, checksum256 project_hash){
|
||||
contributor_index contributors(_capital, coopname.value);
|
||||
auto username_index = contributors.get_index<"byusername"_n>();
|
||||
|
||||
auto itr = username_index.find(username.value);
|
||||
|
||||
username_index.modify(*itr, _capital, [&](auto &c) {
|
||||
c.appendixes.push_back(project_hash);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Получает участника по имени аккаунта.
|
||||
@@ -125,4 +172,36 @@ inline std::optional<contributor> get_active_contributor_with_appendix_or_fail(e
|
||||
return contributor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обновляет накопительные показатели контрибьютора на основе его ролей и вкладов в сегменте
|
||||
*/
|
||||
inline void update_contributor_ratings_from_segment(eosio::name coopname, const Capital::Segments::segment& segment) {
|
||||
Capital::contributor_index contributors(_capital, coopname.value);
|
||||
auto contributor_itr = contributors.find(segment.username.value);
|
||||
|
||||
eosio::check(contributor_itr != contributors.end(), "Контрибьютор не найден");
|
||||
|
||||
contributors.modify(contributor_itr, _capital, [&](auto &c) {
|
||||
if (segment.is_investor) {
|
||||
c.contributed_as_investor += segment.investor_base;
|
||||
}
|
||||
|
||||
if (segment.is_author) {
|
||||
c.contributed_as_author += (segment.author_base + segment.author_bonus);
|
||||
}
|
||||
|
||||
if (segment.is_creator) {
|
||||
c.contributed_as_creator += (segment.creator_base + segment.creator_bonus);
|
||||
}
|
||||
|
||||
if (segment.is_coordinator) {
|
||||
c.contributed_as_coordinator += segment.coordinator_base;
|
||||
}
|
||||
|
||||
if (segment.is_contributor) {
|
||||
c.contributed_as_contributor += segment.contributor_bonus;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}// namespace Capital::Contributors
|
||||
@@ -15,8 +15,9 @@ struct fact_pool {
|
||||
eosio::asset hour_cost = asset(0, _root_govern_symbol); ///< Фактическая стоимость часа
|
||||
uint64_t creators_hours = 0; ///< Фактическое время в часах
|
||||
|
||||
// Коэффициент возврата себестоимости
|
||||
double return_cost_coefficient = 0.0;
|
||||
// Коэффициенты возврата (в процентах)
|
||||
double return_base_percent = 0.0; ///< Коэффициент возврата себестоимости для создателей/авторов/координаторов (0-100%)
|
||||
double use_invest_percent = 0.0; ///< Коэффициент используемых инвестиций для инвесторов (0-100%)
|
||||
|
||||
// Пулы себестоимостей
|
||||
eosio::asset creators_base_pool = asset(0, _root_govern_symbol); ///< Фактическая себестоимость создателей
|
||||
@@ -34,9 +35,11 @@ struct fact_pool {
|
||||
eosio::asset used_expense_pool = asset(0, _root_govern_symbol); ///< Фактически израсходованные средства
|
||||
|
||||
// Пулы инвестиций
|
||||
eosio::asset invest_pool = asset(0, _root_govern_symbol); ///< Фактические инвестиции от инвесторов
|
||||
eosio::asset invest_pool = asset(0, _root_govern_symbol); ///< Фактические инвестиции от инвесторов (после вычета расходов)
|
||||
eosio::asset coordinators_investment_pool = asset(0, _root_govern_symbol); ///< Фактические инвестиции, привлеченные координаторами
|
||||
eosio::asset program_invest_pool = asset(0, _root_govern_symbol); ///< Фактические программные инвестиции, аллоцированные в проект
|
||||
eosio::asset total_received_investments = asset(0, _root_govern_symbol); ///< Общая сумма всех полученных инвестиций (до распределения)
|
||||
eosio::asset total_returned_investments = asset(0, _root_govern_symbol); ///< Общая сумма возвращенных неиспользованных инвестиций
|
||||
|
||||
// Общая сумма генерации (без вкладчиков)
|
||||
eosio::asset total_generation_pool = asset(0, _root_govern_symbol); ///< Общая фактическая сумма генерации
|
||||
|
||||
@@ -35,9 +35,6 @@ struct generation_amounts {
|
||||
// Общая сумма вкладов всех пайщиков
|
||||
eosio::asset total_contribution = asset(0, _root_govern_symbol); ///< Общая сумма вкладов всех пайщиков
|
||||
|
||||
// Общая сумма вкладов с расходами
|
||||
eosio::asset total = asset(0, _root_govern_symbol); ///< Общая сумма вкладов с расходами
|
||||
|
||||
};
|
||||
|
||||
} // namespace Capital
|
||||
@@ -9,8 +9,8 @@ namespace Capital {
|
||||
* @brief Конфигурация контракта, управляемая пользователем.
|
||||
*/
|
||||
struct config {
|
||||
double coordinator_bonus_percent = 0.04; ///< Процент премий координатора от инвестиций (по умолчанию 4%)
|
||||
double expense_pool_percent = 1.0; ///< Процент инвестиций в пул расходов (по умолчанию 1.0)
|
||||
double coordinator_bonus_percent = 4; ///< Процент премий координатора от инвестиций (по умолчанию 4%)
|
||||
double expense_pool_percent = 100; ///< Процент инвестиций в пул расходов (по умолчанию 1.0)
|
||||
uint32_t coordinator_invite_validity_days = 30; ///< Срок действия приглашения координатора (по умолчанию 30 дней)
|
||||
uint32_t voting_period_in_days = 7; ///< Период голосования в днях (по умолчанию 7)
|
||||
double authors_voting_percent = 38.2; ///< Процент премий авторов для голосования (по умолчанию)
|
||||
|
||||
@@ -15,8 +15,9 @@ struct plan_pool {
|
||||
eosio::asset hour_cost = asset(0, _root_govern_symbol); ///< Планируемая стоимость часа
|
||||
uint64_t creators_hours = 0; ///< Планируемое время в часах
|
||||
|
||||
// Коэффициент возврата себестоимости
|
||||
double return_cost_coefficient = 0.0;
|
||||
// Коэффициенты возврата (в процентах)
|
||||
double return_base_percent = 0.0; ///< Коэффициент возврата себестоимости для создателей/авторов/координаторов (0-100%)
|
||||
double use_invest_percent = 0.0; ///< Коэффициент используемых инвестиций для инвесторов (0-100%)
|
||||
|
||||
// Пулы себестоимостей
|
||||
eosio::asset creators_base_pool = asset(0, _root_govern_symbol); ///< Планируемая себестоимость создателей
|
||||
@@ -32,9 +33,10 @@ struct plan_pool {
|
||||
eosio::asset target_expense_pool = asset(0, _root_govern_symbol); ///< Планируемый размер расходов
|
||||
|
||||
// Пулы инвестиций
|
||||
eosio::asset invest_pool = asset(0, _root_govern_symbol); ///< Планируемые инвестиции
|
||||
eosio::asset invest_pool = asset(0, _root_govern_symbol); ///< Планируемые инвестиции (после вычета расходов)
|
||||
eosio::asset coordinators_investment_pool = asset(0, _root_govern_symbol); ///< Планируемые инвестиции от координаторов
|
||||
eosio::asset program_invest_pool = asset(0, _root_govern_symbol); ///< Планируемые программные инвестиции
|
||||
eosio::asset total_received_investments = asset(0, _root_govern_symbol); ///< Планируемая общая сумма инвестиций (до распределения)
|
||||
|
||||
// Общая сумма генерации (без вкладчиков)
|
||||
eosio::asset total_generation_pool = asset(0, _root_govern_symbol); ///< Общая планируемая сумма генерации
|
||||
|
||||
@@ -17,8 +17,7 @@ namespace Capital::Projects::Status {
|
||||
/**
|
||||
* @brief Константы статусов проекта
|
||||
*/
|
||||
const eosio::name CREATED = "created"_n; ///< Проект создан
|
||||
const eosio::name OPENED = "opened"_n; ///< Проект открыт для инвестиций
|
||||
const eosio::name PENDING = "pending"_n; ///< Проект создан
|
||||
const eosio::name ACTIVE = "active"_n; ///< Проект активен для коммитов
|
||||
const eosio::name VOTING = "voting"_n; ///< Проект на голосовании
|
||||
const eosio::name COMPLETED = "completed"_n; ///< Проект завершен
|
||||
@@ -38,7 +37,9 @@ struct [[eosio::table, eosio::contract(CAPITAL)]] project {
|
||||
checksum256 parent_hash; // Хэш родительского проекта (если есть)
|
||||
|
||||
eosio::name status; // Статус проекта
|
||||
|
||||
|
||||
bool is_opened; // Открыт ли проект для инвестиций
|
||||
|
||||
// Мастер проекта
|
||||
name master; // Мастер проекта
|
||||
|
||||
@@ -140,7 +141,7 @@ namespace Capital::Projects {
|
||||
|
||||
projects.emplace(coopname, [&](auto& row) {
|
||||
row.id = get_global_id_in_scope(_capital, coopname, "projects"_n);
|
||||
row.status = Capital::Projects::Status::CREATED;
|
||||
row.status = Capital::Projects::Status::PENDING;
|
||||
row.project_hash = project_hash;
|
||||
row.parent_hash = parent_hash;
|
||||
row.coopname = coopname;
|
||||
@@ -184,9 +185,30 @@ namespace Capital::Projects {
|
||||
p.fact.total_generation_pool += delta.total_generation_pool;
|
||||
p.fact.contributors_bonus_pool += delta.contributors_bonus_pool;
|
||||
p.fact.total_contribution += delta.total_contribution;
|
||||
p.fact.total += p.fact.total_contribution + p.fact.used_expense_pool;
|
||||
p.fact.total = p.fact.total_contribution + p.fact.used_expense_pool;
|
||||
|
||||
// Пересчитываем коэффициенты
|
||||
p.fact.return_base_percent = Capital::Core::Generation::calculate_return_base_percent(p.fact.creators_base_pool, p.fact.authors_base_pool, p.fact.coordinators_base_pool, p.fact.invest_pool);
|
||||
p.fact.use_invest_percent = Capital::Core::Generation::calculate_use_invest_percent(p.fact.creators_base_pool, p.fact.authors_base_pool, p.fact.coordinators_base_pool, p.fact.accumulated_expense_pool, p.fact.used_expense_pool, p.fact.total_received_investments);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Назначает мастера проекта
|
||||
*
|
||||
* @param coopname Имя кооператива
|
||||
* @param project_id ID проекта
|
||||
* @param master Имя мастера
|
||||
*/
|
||||
inline void set_master(eosio::name coopname, uint64_t project_id, eosio::name master) {
|
||||
Capital::project_index projects(_capital, coopname.value);
|
||||
auto project_itr = projects.find(project_id);
|
||||
|
||||
projects.modify(project_itr, coopname, [&](auto &p) {
|
||||
p.master = master;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Обновляет статус проекта.
|
||||
@@ -244,7 +266,7 @@ namespace Capital::Projects {
|
||||
if (expense_gap.amount > 0) {
|
||||
// Рассчитываем процент от инвестиций для пула расходов
|
||||
auto st = Capital::get_global_state(coopname);
|
||||
eosio::asset potential_to_expense = amount * st.config.expense_pool_percent;
|
||||
eosio::asset potential_to_expense = amount * st.config.expense_pool_percent / 100;
|
||||
|
||||
// Но не больше, чем нужно для достижения цели
|
||||
to_expense_pool = (potential_to_expense.amount <= expense_gap.amount) ? potential_to_expense : expense_gap;
|
||||
@@ -256,9 +278,11 @@ namespace Capital::Projects {
|
||||
// Обновляем пулы
|
||||
p.fact.invest_pool += to_invest_pool;
|
||||
p.fact.accumulated_expense_pool += to_expense_pool;
|
||||
p.fact.total_received_investments += amount; // Увеличиваем общую сумму полученных инвестиций
|
||||
|
||||
// Пересчитываем коэффициент возврата себестоимости
|
||||
p.fact.return_cost_coefficient = Capital::Core::Generation::calculate_return_cost_coefficient(p.fact);
|
||||
// Пересчитываем коэффициенты возврата
|
||||
p.fact.return_base_percent = Capital::Core::Generation::calculate_return_base_percent(p.fact.creators_base_pool, p.fact.authors_base_pool, p.fact.coordinators_base_pool, p.fact.invest_pool);
|
||||
p.fact.use_invest_percent = Capital::Core::Generation::calculate_use_invest_percent(p.fact.creators_base_pool, p.fact.authors_base_pool, p.fact.coordinators_base_pool, p.fact.accumulated_expense_pool, p.fact.used_expense_pool, p.fact.total_received_investments);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -300,7 +324,7 @@ namespace Capital::Projects {
|
||||
// так как фактических поступлений и трат еще не было
|
||||
|
||||
// Меняем статус на "opened"
|
||||
p.status = Capital::Projects::Status::OPENED;
|
||||
p.is_opened = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Capital::Segments {
|
||||
struct [[eosio::table, eosio::contract(CAPITAL)]] segment {
|
||||
uint64_t id; // Идентификатор сегмента
|
||||
checksum256 project_hash; // С каким проектом связан сегмент
|
||||
eosio::name coopname; // Имя кооператива
|
||||
eosio::name username; // Имя участника
|
||||
|
||||
// Статус результата сегмента
|
||||
@@ -69,6 +70,9 @@ namespace Capital::Segments {
|
||||
// Последняя известная сумма инвестиций в проекте для расчета provisional_amount
|
||||
eosio::asset last_known_invest_pool = asset(0, _root_govern_symbol);
|
||||
|
||||
// Последняя известная сумма базового пула создателей для расчета использования инвестиций
|
||||
eosio::asset last_known_creators_base_pool = asset(0, _root_govern_symbol);
|
||||
|
||||
// Финансовые данные для ссуд
|
||||
eosio::asset provisional_amount = asset(0, _root_govern_symbol); //доступная сумма для залога при получении ссуды
|
||||
eosio::asset debt_amount = asset(0, _root_govern_symbol); //сумма, которая уже выдана в ссуду
|
||||
@@ -299,16 +303,22 @@ inline bool is_segment_updated(eosio::name coopname, const checksum256 &project_
|
||||
(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 coordinator_updated = (!segment_opt->is_coordinator) ||
|
||||
(segment_opt->last_coordinator_reward_per_share == project.crps.coordinator_cumulative_reward_per_share);
|
||||
(project.fact.coordinators_base_pool.amount == 0 ||
|
||||
project.fact.coordinators_investment_pool.amount == 0);
|
||||
|
||||
bool contributor_updated = (!segment_opt->is_contributor) ||
|
||||
(segment_opt->last_contributor_reward_per_share == project.crps.contributor_cumulative_reward_per_share);
|
||||
|
||||
// Проверяем актуальность инвестиционного пула для расчета provisional_amount
|
||||
// Проверяем актуальность инвестиционного пула для расчета provisional_amount (нужно всем ролям)
|
||||
bool invest_pool_updated = (segment_opt->last_known_invest_pool == project.fact.invest_pool);
|
||||
|
||||
return author_updated && coordinator_updated && contributor_updated && invest_pool_updated;
|
||||
// Проверяем актуальность базового пула создателей для корректного расчета использования инвестиций (нужно только инвесторам)
|
||||
bool creators_base_pool_updated = (!segment_opt->is_investor) ||
|
||||
(segment_opt->last_known_creators_base_pool == project.fact.creators_base_pool);
|
||||
|
||||
return author_updated && coordinator_updated && contributor_updated && invest_pool_updated && creators_base_pool_updated;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,16 +25,15 @@ namespace Capital {
|
||||
*
|
||||
*/
|
||||
struct voting_data {
|
||||
uint32_t total_voters = 0; ///< Общее количество участников
|
||||
uint32_t votes_received = 0; ///< Количество полученных голосов
|
||||
uint32_t total_voters; ///< Общее количество участников
|
||||
uint32_t votes_received; ///< Количество полученных голосов
|
||||
|
||||
double authors_voting_percent = 38.2; ///< Процент премий авторов для голосования (по умолчанию; уточняется из config при инициализации голосования)
|
||||
double creators_voting_percent = 38.2; ///< Процент премий создателей для голосования (по умолчанию; уточняется из config при инициализации голосования)
|
||||
double authors_voting_percent; ///< Процент премий авторов для голосования (по умолчанию; уточняется из config при инициализации голосования)
|
||||
double creators_voting_percent; ///< Процент премий создателей для голосования (по умолчанию; уточняется из config при инициализации голосования)
|
||||
|
||||
voting_amounts amounts;
|
||||
voting_amounts amounts; ///< Суммы на голосовании
|
||||
|
||||
time_point_sec created_at = current_time_point();
|
||||
time_point_sec voting_deadline = time_point_sec(current_time_point().sec_since_epoch() + 7 * 86400); ///< Дедлайн голосования (по умолчанию 7 дней; уточняется из config при старте голосования)
|
||||
time_point_sec voting_deadline; ///< Дедлайн голосования
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export * as SetConfig from './setConfig'
|
||||
export * as CreateProject from './createProject'
|
||||
export * as OpenProject from './openProject'
|
||||
export * as StartProject from './startProject'
|
||||
export * as CompleteProject from './completeProject'
|
||||
export * as StartVoting from './startVoting'
|
||||
export * as CompleteVoting from './completeVoting'
|
||||
export * as FinalVoting from './finalVoting'
|
||||
export * as SubmitVote from './submitVote'
|
||||
|
||||
+2
-2
@@ -7,9 +7,9 @@ export const authorizations = [{ permissions: [Permissions.active], actor: Actor
|
||||
/**
|
||||
* Имя действия
|
||||
*/
|
||||
export const actionName = 'cmpltproject'
|
||||
export const actionName = 'startvoting'
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
export type ICompleteProject = Capital.ICmpltproject
|
||||
export type IStartVoting = Capital.IStartvoting
|
||||
@@ -158,7 +158,7 @@ export interface ICloseproject {
|
||||
project_hash: IChecksum256
|
||||
}
|
||||
|
||||
export interface ICmpltproject {
|
||||
export interface IStartvoting {
|
||||
coopname: IName
|
||||
project_hash: IChecksum256
|
||||
}
|
||||
@@ -642,7 +642,6 @@ export interface IRefreshproj {
|
||||
|
||||
export interface IRegcontrib {
|
||||
coopname: IName
|
||||
application: IName
|
||||
username: IName
|
||||
contributor_hash: IChecksum256
|
||||
rate_per_hour: IAsset
|
||||
@@ -729,8 +728,7 @@ export interface ISegment {
|
||||
|
||||
export interface ISetconfig {
|
||||
coopname: IName
|
||||
initiator: IName
|
||||
cfg: IConfig
|
||||
config: IConfig
|
||||
}
|
||||
|
||||
export interface ISetmaster {
|
||||
@@ -741,6 +739,7 @@ export interface ISetmaster {
|
||||
|
||||
export interface ISetplan {
|
||||
coopname: IName
|
||||
master: IName
|
||||
project_hash: IChecksum256
|
||||
plan_creators_hours: IUint64
|
||||
plan_expenses: IAsset
|
||||
@@ -767,7 +766,6 @@ export interface ISignact2 {
|
||||
|
||||
export interface ISignappndx {
|
||||
coopname: IName
|
||||
application: IName
|
||||
username: IName
|
||||
project_hash: IChecksum256
|
||||
appendix_hash: IChecksum256
|
||||
|
||||
Reference in New Issue
Block a user