From 4b1525506d4faf5ecc9d3a28a0a51fe63deaca21 Mon Sep 17 00:00:00 2001 From: KavataK Date: Fri, 26 Sep 2025 16:16:45 +0300 Subject: [PATCH 1/3] QBond proposal --- SmartContracts/2025-09-26-QBond.md | 1403 ++++++++++++++++++++++++++++ 1 file changed, 1403 insertions(+) create mode 100644 SmartContracts/2025-09-26-QBond.md diff --git a/SmartContracts/2025-09-26-QBond.md b/SmartContracts/2025-09-26-QBond.md new file mode 100644 index 0000000..fe886e8 --- /dev/null +++ b/SmartContracts/2025-09-26-QBond.md @@ -0,0 +1,1403 @@ +# Proposal to include QBond Smart Contract + +## Proposal +Allow QBond Smart Contract to be deployed on Qubic. + +## Available Options +> Option 0: no, don't allow + +> Option 1: yes, allow + +## What is QBond? +QBond is a liquid version of Qearn that not only allows users to lock funds and earn staking income, but also provides an additional tool for investors in the form of trading staked positions. QBond transforms illiquid Qearn locks into tradable, liquid assets. + +## Problem with Qearn +When you lock QUBIC in Qearn, your tokens are frozen for 1 full year in order to receive the full reward. This creates an illiquidity problem: once locked, you can't use or trade your funds until the lock period expires. + +## QBond Solution +QBond solves this by tokenizing Qearn locks into tradable assets, providing liquidity while maintaining yield generation. + +### How it works step by step: +1. **Deposit** → You send QUBIC to QBond instead of directly into Qearn +2. **Auto-Lock** → QBond automatically locks your QUBIC into Qearn on your behalf +3. **Minting** → You receive MBND tokens (1 MBND = 1,000,000 QU). Example: MBND182 if locked at epoch 182. This token represents your claim on Qearn's rewards after 52 weeks +4. **Liquidity** → Unlike Qearn, you can sell or trade your QBond tokens immediately on the secondary market through the built-in order book +5. **Maturity** → At the end of 52 weeks, each MBND can be redeemed for its underlying principal + earned rewards + +**In short: QBond = Liquid Qearn positions. You earn yield but keep flexibility.** + +## QBond Key Features + +| Feature | Description | +|---------|-------------| +| **QU Staking** | Stake QU and receive MBNDXXX tokens (XXX = current epoch number) for every million QU staked | +| **Token Transfer** | Transfer MBond tokens to another ID | +| **Token Trading** | Create bid/ask orders and cancel existing orders through integrated order book | +| **QU Burning** | Function to replenish reserves to cover smart contract execution costs | +| **Fee-Free ID List** | Admin address can add/remove IDs from the fee-free list | +| **Statistics** | Access comprehensive statistics for specific epochs or specific addresses | + +## Fee Structure +- **Fund Locking**: 0.5% +- **Trading**: 0.03% +- **Token Transfer**: 100 QU + +## Fee Income Distribution +- **99%** to SC QVault +- **1%** for dev team + +99% of the revenue generated by QBond will be sent directly to the QVault smart contract, where it will be distributed to $QVAULT holders and $QCAP holders. + +Through QVault, these rewards are automatically redirected to QCAP holders, of which around 50% is typically reinvested into new IPOs and smart contracts. This reinvestment strategy creates a powerful compounding effect: QCAP keeps expanding its exposure to revenue-generating assets, while also driving more $QUBIC burns in the future. + +All costs of developing and maintaining QBond are borne by QCAP holders, as they are the primary beneficiaries of this system. + +## Risk Disclosure +It is important to highlight the following risks: + +- Since QBond is fully dependent on Qearn, if Qearn is ever discontinued, QBond would also be forced to cease operations +- As Qearn rewards are halved over time, QBond's fees and revenue will also be halved accordingly + +Because of these risks, the QBond IPO is not designed to be an attractive IPO. Its sole purpose is to strengthen QCAP by channeling all revenue back to $QCAP holders. + +## Technical Implementation +From: [qubic/core#559](https://github.com/qubic/core/pull/559) + +Please find the current implementation details are below: + +``` +using namespace QPI; + +constexpr uint64 QBOND_MAX_EPOCH_COUNT = 1024ULL; +constexpr uint64 QBOND_MBOND_PRICE = 1000000ULL; +constexpr uint64 QBOND_MAX_QUEUE_SIZE = 10ULL; +constexpr uint64 QBOND_MIN_MBONDS_TO_STAKE = 10ULL; +constexpr sint64 QBOND_MBONDS_EMISSION = 1000000000LL; +constexpr uint16 QBOND_START_EPOCH = 182; + +constexpr uint64 QBOND_STAKE_FEE_PERCENT = 50; // 0.5% +constexpr uint64 QBOND_TRADE_FEE_PERCENT = 3; // 0.03% +constexpr uint64 QBOND_MBOND_TRANSFER_FEE = 100; + +constexpr uint64 QBOND_QVAULT_DISTRIBUTION_PERCENT = 9900; // 99% + +struct QBOND2 +{ +}; + +struct QBOND : public ContractBase +{ +public: + struct StakeEntry + { + id staker; + sint64 amount; + }; + + struct MBondInfo + { + uint64 name; + sint64 stakersAmount; + sint64 totalStaked; + }; + + struct Stake_input + { + sint64 quMillions; + }; + struct Stake_output + { + }; + + struct TransferMBondOwnershipAndPossession_input + { + id newOwnerAndPossessor; + sint64 epoch; + sint64 numberOfMBonds; + }; + struct TransferMBondOwnershipAndPossession_output + { + sint64 transferredMBonds; + }; + + struct AddAskOrder_input + { + sint64 epoch; + sint64 price; + sint64 numberOfMBonds; + }; + struct AddAskOrder_output + { + sint64 addedMBondsAmount; + }; + + struct RemoveAskOrder_input + { + sint64 epoch; + sint64 price; + sint64 numberOfMBonds; + }; + struct RemoveAskOrder_output + { + sint64 removedMBondsAmount; + }; + + struct AddBidOrder_input + { + sint64 epoch; + sint64 price; + sint64 numberOfMBonds; + }; + struct AddBidOrder_output + { + sint64 addedMBondsAmount; + }; + + struct RemoveBidOrder_input + { + sint64 epoch; + sint64 price; + sint64 numberOfMBonds; + }; + struct RemoveBidOrder_output + { + sint64 removedMBondsAmount; + }; + + struct BurnQU_input + { + sint64 amount; + }; + struct BurnQU_output + { + sint64 amount; + }; + + struct UpdateCFA_input + { + id user; + bit operation; // 0 to remove, 1 to add + }; + struct UpdateCFA_output + { + bit result; + }; + + struct GetFees_input + { + }; + struct GetFees_output + { + uint64 stakeFeePercent; + uint64 tradeFeePercent; + uint64 transferFee; + }; + + struct GetEarnedFees_input + { + }; + struct GetEarnedFees_output + { + uint64 stakeFees; + uint64 tradeFees; + }; + + struct GetInfoPerEpoch_input + { + sint64 epoch; + }; + struct GetInfoPerEpoch_output + { + uint64 stakersAmount; + sint64 totalStaked; + sint64 apy; + }; + + struct GetOrders_input + { + sint64 epoch; + sint64 askOrdersOffset; + sint64 bidOrdersOffset; + }; + struct GetOrders_output + { + struct Order + { + id owner; + sint64 epoch; + sint64 numberOfMBonds; + sint64 price; + }; + + Array askOrders; + Array bidOrders; + }; + + struct GetUserOrders_input + { + id owner; + sint64 askOrdersOffset; + sint64 bidOrdersOffset; + }; + struct GetUserOrders_output + { + struct Order + { + id owner; + sint64 epoch; + sint64 numberOfMBonds; + sint64 price; + }; + + Array askOrders; + Array bidOrders; + }; + + struct GetMBondsTable_input + { + }; + struct GetMBondsTable_output + { + struct TableEntry + { + sint64 epoch; + uint64 apy; + }; + Array info; + }; + + struct GetUserMBonds_input + { + id owner; + }; + struct GetUserMBonds_output + { + sint64 totalMBondsAmount; + struct MBondEntity + { + sint64 epoch; + sint64 amount; + uint64 apy; + }; + Array mbonds; + }; + + struct GetCFA_input + { + }; + struct GetCFA_output + { + Array commissionFreeAddresses; + }; + +private: + Array _stakeQueue; + HashMap _epochMbondInfoMap; + HashMap _userTotalStakedMap; + HashSet _commissionFreeAddresses; + uint64 _qearnIncomeAmount; + uint64 _totalEarnedAmount; + uint64 _earnedAmountFromTrade; + uint64 _distributedAmount; + id _adminAddress; + id _devAddress; + + struct _Order + { + id owner; + sint64 epoch; + sint64 numberOfMBonds; + }; + Collection<_Order, 1048576> _askOrders; + Collection<_Order, 1048576> _bidOrders; + + struct _NumberOfReservedMBonds_input + { + id owner; + sint64 epoch; + } _numberOfReservedMBonds_input; + + struct _NumberOfReservedMBonds_output + { + sint64 amount; + } _numberOfReservedMBonds_output; + + struct _NumberOfReservedMBonds_locals + { + sint64 elementIndex; + id mbondIdentity; + _Order order; + MBondInfo tempMbondInfo; + }; + + PRIVATE_FUNCTION_WITH_LOCALS(_NumberOfReservedMBonds) + { + output.amount = 0; + if (!state._epochMbondInfoMap.get((uint16)input.epoch, locals.tempMbondInfo)) + { + return; + } + + locals.mbondIdentity = SELF; + locals.mbondIdentity.u64._3 = locals.tempMbondInfo.name; + + locals.elementIndex = state._askOrders.headIndex(locals.mbondIdentity, 0); + while (locals.elementIndex != NULL_INDEX) + { + locals.order = state._askOrders.element(locals.elementIndex); + if (locals.order.epoch == input.epoch && locals.order.owner == input.owner) + { + output.amount += locals.order.numberOfMBonds; + } + + locals.elementIndex = state._askOrders.nextElementIndex(locals.elementIndex); + } + } + + struct Stake_locals + { + sint64 amountInQueue; + sint64 userMBondsAmount; + sint64 tempAmount; + uint64 counter; + sint64 amountToStake; + uint64 amountAndFee; + StakeEntry tempStakeEntry; + MBondInfo tempMbondInfo; + QEARN::lock_input lock_input; + QEARN::lock_output lock_output; + }; + + PUBLIC_PROCEDURE_WITH_LOCALS(Stake) + { + locals.amountAndFee = sadd(smul((uint64) input.quMillions, QBOND_MBOND_PRICE), div(smul((uint64) input.quMillions, QBOND_MBOND_PRICE) * QBOND_STAKE_FEE_PERCENT, 10000ULL)); + + if (input.quMillions <= 0 + || !state._epochMbondInfoMap.get(qpi.epoch(), locals.tempMbondInfo) + || qpi.invocationReward() < 0 + || (uint64) qpi.invocationReward() < locals.amountAndFee) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + return; + } + + if ((uint64) qpi.invocationReward() > locals.amountAndFee ) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - locals.amountAndFee); + } + + if (state._commissionFreeAddresses.getElementIndex(qpi.invocator()) != NULL_INDEX) + { + qpi.transfer(qpi.invocator(), div(smul((uint64) input.quMillions, QBOND_MBOND_PRICE) * QBOND_STAKE_FEE_PERCENT, 10000ULL)); + } + else + { + state._totalEarnedAmount += div(smul((uint64) input.quMillions, QBOND_MBOND_PRICE) * QBOND_STAKE_FEE_PERCENT, 10000ULL); + } + + locals.amountInQueue = input.quMillions; + for (locals.counter = 0; locals.counter < QBOND_MAX_QUEUE_SIZE; locals.counter++) + { + if (state._stakeQueue.get(locals.counter).staker != NULL_ID) + { + locals.amountInQueue += state._stakeQueue.get(locals.counter).amount; + } + else + { + locals.tempStakeEntry.staker = qpi.invocator(); + locals.tempStakeEntry.amount = input.quMillions; + state._stakeQueue.set(locals.counter, locals.tempStakeEntry); + break; + } + } + + if (locals.amountInQueue < QBOND_MIN_MBONDS_TO_STAKE) + { + return; + } + + locals.tempStakeEntry.staker = NULL_ID; + locals.tempStakeEntry.amount = 0; + locals.amountToStake = 0; + for (locals.counter = 0; locals.counter < QBOND_MAX_QUEUE_SIZE; locals.counter++) + { + if (state._stakeQueue.get(locals.counter).staker == NULL_ID) + { + break; + } + + if (state._userTotalStakedMap.get(qpi.invocator(), locals.userMBondsAmount)) + { + state._userTotalStakedMap.replace(qpi.invocator(), locals.userMBondsAmount + state._stakeQueue.get(locals.counter).amount); + } + else + { + state._userTotalStakedMap.set(qpi.invocator(), state._stakeQueue.get(locals.counter).amount); + } + + if (qpi.numberOfPossessedShares(locals.tempMbondInfo.name, SELF, state._stakeQueue.get(locals.counter).staker, state._stakeQueue.get(locals.counter).staker, SELF_INDEX, SELF_INDEX) <= 0) + { + locals.tempMbondInfo.stakersAmount++; + } + qpi.transferShareOwnershipAndPossession(locals.tempMbondInfo.name, SELF, SELF, SELF, state._stakeQueue.get(locals.counter).amount, state._stakeQueue.get(locals.counter).staker); + locals.amountToStake += state._stakeQueue.get(locals.counter).amount; + state._stakeQueue.set(locals.counter, locals.tempStakeEntry); + } + + locals.tempMbondInfo.totalStaked += locals.amountToStake; + state._epochMbondInfoMap.replace(qpi.epoch(), locals.tempMbondInfo); + + INVOKE_OTHER_CONTRACT_PROCEDURE(QEARN, lock, locals.lock_input, locals.lock_output, locals.amountToStake * QBOND_MBOND_PRICE); + } + + struct TransferMBondOwnershipAndPossession_locals + { + MBondInfo tempMbondInfo; + }; + + PUBLIC_PROCEDURE_WITH_LOCALS(TransferMBondOwnershipAndPossession) + { + if (input.numberOfMBonds >= MAX_AMOUNT) + { + return; + } + + if (qpi.invocationReward() < QBOND_MBOND_TRANSFER_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + return; + } + + if (qpi.invocationReward() > QBOND_MBOND_TRANSFER_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - QBOND_MBOND_TRANSFER_FEE); + } + + state._numberOfReservedMBonds_input.epoch = input.epoch; + state._numberOfReservedMBonds_input.owner = qpi.invocator(); + CALL(_NumberOfReservedMBonds, state._numberOfReservedMBonds_input, state._numberOfReservedMBonds_output); + + if (state._epochMbondInfoMap.get((uint16)input.epoch, locals.tempMbondInfo) + && qpi.numberOfPossessedShares(locals.tempMbondInfo.name, SELF, qpi.invocator(), qpi.invocator(), SELF_INDEX, SELF_INDEX) - state._numberOfReservedMBonds_output.amount < input.numberOfMBonds) + { + output.transferredMBonds = 0; + qpi.transfer(qpi.invocator(), QBOND_MBOND_TRANSFER_FEE); + } + else + { + if (qpi.numberOfPossessedShares(locals.tempMbondInfo.name, SELF, input.newOwnerAndPossessor, input.newOwnerAndPossessor, SELF_INDEX, SELF_INDEX) <= 0) + { + locals.tempMbondInfo.stakersAmount++; + } + output.transferredMBonds = qpi.transferShareOwnershipAndPossession(locals.tempMbondInfo.name, SELF, qpi.invocator(), qpi.invocator(), input.numberOfMBonds, input.newOwnerAndPossessor) < 0 ? 0 : input.numberOfMBonds; + if (qpi.numberOfPossessedShares(locals.tempMbondInfo.name, SELF, qpi.invocator(), qpi.invocator(), SELF_INDEX, SELF_INDEX) <= 0) + { + locals.tempMbondInfo.stakersAmount--; + } + state._epochMbondInfoMap.replace((uint16)input.epoch, locals.tempMbondInfo); + if (state._commissionFreeAddresses.getElementIndex(qpi.invocator()) != NULL_INDEX) + { + qpi.transfer(qpi.invocator(), QBOND_MBOND_TRANSFER_FEE); + } + else + { + state._totalEarnedAmount += QBOND_MBOND_TRANSFER_FEE; + } + } + } + + struct AddAskOrder_locals + { + MBondInfo tempMbondInfo; + id mbondIdentity; + sint64 elementIndex; + sint64 nextElementIndex; + sint64 fee; + _Order tempAskOrder; + _Order tempBidOrder; + }; + + PUBLIC_PROCEDURE_WITH_LOCALS(AddAskOrder) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + + if (input.price <= 0 || input.price >= MAX_AMOUNT || input.numberOfMBonds <= 0 || input.numberOfMBonds >= MAX_AMOUNT || !state._epochMbondInfoMap.get((uint16)input.epoch, locals.tempMbondInfo)) + { + output.addedMBondsAmount = 0; + return; + } + + state._numberOfReservedMBonds_input.epoch = input.epoch; + state._numberOfReservedMBonds_input.owner = qpi.invocator(); + CALL(_NumberOfReservedMBonds, state._numberOfReservedMBonds_input, state._numberOfReservedMBonds_output); + if (qpi.numberOfPossessedShares(locals.tempMbondInfo.name, SELF, qpi.invocator(), qpi.invocator(), SELF_INDEX, SELF_INDEX) - state._numberOfReservedMBonds_output.amount < input.numberOfMBonds) + { + output.addedMBondsAmount = 0; + return; + } + + output.addedMBondsAmount = input.numberOfMBonds; + + locals.mbondIdentity = SELF; + locals.mbondIdentity.u64._3 = locals.tempMbondInfo.name; + + locals.elementIndex = state._bidOrders.headIndex(locals.mbondIdentity); + while (locals.elementIndex != NULL_INDEX) + { + if (input.price > state._bidOrders.priority(locals.elementIndex)) + { + break; + } + + locals.nextElementIndex = state._bidOrders.nextElementIndex(locals.elementIndex); + + locals.tempBidOrder = state._bidOrders.element(locals.elementIndex); + if (input.numberOfMBonds <= locals.tempBidOrder.numberOfMBonds) + { + qpi.transferShareOwnershipAndPossession( + locals.tempMbondInfo.name, + SELF, + qpi.invocator(), + qpi.invocator(), + input.numberOfMBonds, + locals.tempBidOrder.owner); + + locals.fee = div(input.numberOfMBonds * state._bidOrders.priority(locals.elementIndex) * QBOND_TRADE_FEE_PERCENT, 10000ULL); + qpi.transfer(qpi.invocator(), input.numberOfMBonds * state._bidOrders.priority(locals.elementIndex) - locals.fee); + if (state._commissionFreeAddresses.getElementIndex(qpi.invocator()) != NULL_INDEX) + { + qpi.transfer(qpi.invocator(), locals.fee); + } + else + { + state._totalEarnedAmount += locals.fee; + state._earnedAmountFromTrade += locals.fee; + } + + if (input.numberOfMBonds < locals.tempBidOrder.numberOfMBonds) + { + locals.tempBidOrder.numberOfMBonds -= input.numberOfMBonds; + state._bidOrders.replace(locals.elementIndex, locals.tempBidOrder); + } + else if (input.numberOfMBonds == locals.tempBidOrder.numberOfMBonds) + { + state._bidOrders.remove(locals.elementIndex); + } + return; + } + else if (input.numberOfMBonds > locals.tempBidOrder.numberOfMBonds) + { + qpi.transferShareOwnershipAndPossession( + locals.tempMbondInfo.name, + SELF, + qpi.invocator(), + qpi.invocator(), + locals.tempBidOrder.numberOfMBonds, + locals.tempBidOrder.owner); + + locals.fee = div(locals.tempBidOrder.numberOfMBonds * state._bidOrders.priority(locals.elementIndex) * QBOND_TRADE_FEE_PERCENT, 10000ULL); + qpi.transfer(qpi.invocator(), locals.tempBidOrder.numberOfMBonds * state._bidOrders.priority(locals.elementIndex) - locals.fee); + if (state._commissionFreeAddresses.getElementIndex(qpi.invocator()) != NULL_INDEX) + { + qpi.transfer(qpi.invocator(), locals.fee); + } + else + { + state._totalEarnedAmount += locals.fee; + state._earnedAmountFromTrade += locals.fee; + } + state._bidOrders.remove(locals.elementIndex); + input.numberOfMBonds -= locals.tempBidOrder.numberOfMBonds; + } + + locals.elementIndex = locals.nextElementIndex; + } + + if (state._askOrders.population(locals.mbondIdentity) == 0) + { + locals.tempAskOrder.epoch = input.epoch; + locals.tempAskOrder.numberOfMBonds = input.numberOfMBonds; + locals.tempAskOrder.owner = qpi.invocator(); + state._askOrders.add(locals.mbondIdentity, locals.tempAskOrder, -input.price); + return; + } + + locals.elementIndex = state._askOrders.headIndex(locals.mbondIdentity, 0); + while (locals.elementIndex != NULL_INDEX) + { + if (input.price < -state._askOrders.priority(locals.elementIndex)) + { + locals.tempAskOrder.epoch = input.epoch; + locals.tempAskOrder.numberOfMBonds = input.numberOfMBonds; + locals.tempAskOrder.owner = qpi.invocator(); + state._askOrders.add(locals.mbondIdentity, locals.tempAskOrder, -input.price); + break; + } + else if (input.price == -state._askOrders.priority(locals.elementIndex)) + { + if (state._askOrders.element(locals.elementIndex).owner == qpi.invocator()) + { + locals.tempAskOrder = state._askOrders.element(locals.elementIndex); + locals.tempAskOrder.numberOfMBonds += input.numberOfMBonds; + state._askOrders.replace(locals.elementIndex, locals.tempAskOrder); + break; + } + } + + if (state._askOrders.nextElementIndex(locals.elementIndex) == NULL_INDEX) + { + locals.tempAskOrder.epoch = input.epoch; + locals.tempAskOrder.numberOfMBonds = input.numberOfMBonds; + locals.tempAskOrder.owner = qpi.invocator(); + state._askOrders.add(locals.mbondIdentity, locals.tempAskOrder, -input.price); + break; + } + + locals.elementIndex = state._askOrders.nextElementIndex(locals.elementIndex); + } + } + + struct RemoveAskOrder_locals + { + MBondInfo tempMbondInfo; + id mbondIdentity; + sint64 elementIndex; + _Order order; + }; + + PUBLIC_PROCEDURE_WITH_LOCALS(RemoveAskOrder) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + + output.removedMBondsAmount = 0; + + if (input.price <= 0 || input.price >= MAX_AMOUNT || input.numberOfMBonds <= 0 || input.numberOfMBonds >= MAX_AMOUNT || !state._epochMbondInfoMap.get((uint16) input.epoch, locals.tempMbondInfo)) + { + return; + } + + locals.mbondIdentity = SELF; + locals.mbondIdentity.u64._3 = locals.tempMbondInfo.name; + + locals.elementIndex = state._askOrders.headIndex(locals.mbondIdentity, 0); + while (locals.elementIndex != NULL_INDEX) + { + if (input.price == -state._askOrders.priority(locals.elementIndex) && state._askOrders.element(locals.elementIndex).owner == qpi.invocator()) + { + if (state._askOrders.element(locals.elementIndex).numberOfMBonds <= input.numberOfMBonds) + { + state._askOrders.remove(locals.elementIndex); + output.removedMBondsAmount = state._askOrders.element(locals.elementIndex).numberOfMBonds; + } + else + { + locals.order = state._askOrders.element(locals.elementIndex); + locals.order.numberOfMBonds -= input.numberOfMBonds; + state._askOrders.replace(locals.elementIndex, locals.order); + output.removedMBondsAmount = input.numberOfMBonds; + } + break; + } + + locals.elementIndex = state._askOrders.nextElementIndex(locals.elementIndex); + } + } + + struct AddBidOrder_locals + { + MBondInfo tempMbondInfo; + id mbondIdentity; + sint64 elementIndex; + sint64 nextElementIndex; + sint64 fee; + _Order tempAskOrder; + _Order tempBidOrder; + }; + + PUBLIC_PROCEDURE_WITH_LOCALS(AddBidOrder) + { + if (qpi.invocationReward() < smul(input.numberOfMBonds, input.price) + || input.price <= 0 + || input.price >= MAX_AMOUNT + || input.numberOfMBonds <= 0 + || input.numberOfMBonds >= MAX_AMOUNT + || !state._epochMbondInfoMap.get((uint16)input.epoch, locals.tempMbondInfo)) + { + output.addedMBondsAmount = 0; + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + return; + } + + if (qpi.invocationReward() > smul(input.numberOfMBonds, input.price)) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - smul(input.numberOfMBonds, input.price)); + } + + output.addedMBondsAmount = input.numberOfMBonds; + + locals.mbondIdentity = SELF; + locals.mbondIdentity.u64._3 = locals.tempMbondInfo.name; + + locals.elementIndex = state._askOrders.headIndex(locals.mbondIdentity); + while (locals.elementIndex != NULL_INDEX) + { + if (input.price < -state._askOrders.priority(locals.elementIndex)) + { + break; + } + + locals.nextElementIndex = state._askOrders.nextElementIndex(locals.elementIndex); + + locals.tempAskOrder = state._askOrders.element(locals.elementIndex); + if (input.numberOfMBonds <= locals.tempAskOrder.numberOfMBonds) + { + qpi.transferShareOwnershipAndPossession( + locals.tempMbondInfo.name, + SELF, + locals.tempAskOrder.owner, + locals.tempAskOrder.owner, + input.numberOfMBonds, + qpi.invocator()); + + if (state._commissionFreeAddresses.getElementIndex(locals.tempAskOrder.owner) != NULL_INDEX) + { + qpi.transfer(locals.tempAskOrder.owner, -(input.numberOfMBonds * state._askOrders.priority(locals.elementIndex))); + } + else + { + locals.fee = div(input.numberOfMBonds * -state._askOrders.priority(locals.elementIndex) * QBOND_TRADE_FEE_PERCENT, 10000ULL); + qpi.transfer(locals.tempAskOrder.owner, -(input.numberOfMBonds * state._askOrders.priority(locals.elementIndex)) - locals.fee); + state._totalEarnedAmount += locals.fee; + state._earnedAmountFromTrade += locals.fee; + } + + if (input.price > -state._askOrders.priority(locals.elementIndex)) + { + qpi.transfer(qpi.invocator(), input.numberOfMBonds * (input.price + state._askOrders.priority(locals.elementIndex))); // ask orders priotiry is always negative + } + + if (input.numberOfMBonds < locals.tempAskOrder.numberOfMBonds) + { + locals.tempAskOrder.numberOfMBonds -= input.numberOfMBonds; + state._askOrders.replace(locals.elementIndex, locals.tempAskOrder); + } + else if (input.numberOfMBonds == locals.tempAskOrder.numberOfMBonds) + { + state._askOrders.remove(locals.elementIndex); + } + return; + } + else if (input.numberOfMBonds > locals.tempAskOrder.numberOfMBonds) + { + qpi.transferShareOwnershipAndPossession( + locals.tempMbondInfo.name, + SELF, + locals.tempAskOrder.owner, + locals.tempAskOrder.owner, + locals.tempAskOrder.numberOfMBonds, + qpi.invocator()); + + if (state._commissionFreeAddresses.getElementIndex(locals.tempAskOrder.owner) != NULL_INDEX) + { + qpi.transfer(locals.tempAskOrder.owner, -(locals.tempAskOrder.numberOfMBonds * state._askOrders.priority(locals.elementIndex))); + } + else + { + locals.fee = div(locals.tempAskOrder.numberOfMBonds * -state._askOrders.priority(locals.elementIndex) * QBOND_TRADE_FEE_PERCENT, 10000ULL); + qpi.transfer(locals.tempAskOrder.owner, -(locals.tempAskOrder.numberOfMBonds * state._askOrders.priority(locals.elementIndex)) - locals.fee); + state._totalEarnedAmount += locals.fee; + state._earnedAmountFromTrade += locals.fee; + } + + if (input.price > -state._askOrders.priority(locals.elementIndex)) + { + qpi.transfer(qpi.invocator(), locals.tempAskOrder.numberOfMBonds * (input.price + state._askOrders.priority(locals.elementIndex))); // ask orders priotiry is always negative + } + + state._askOrders.remove(locals.elementIndex); + input.numberOfMBonds -= locals.tempAskOrder.numberOfMBonds; + } + + locals.elementIndex = locals.nextElementIndex; + } + + if (state._bidOrders.population(locals.mbondIdentity) == 0) + { + locals.tempBidOrder.epoch = input.epoch; + locals.tempBidOrder.numberOfMBonds = input.numberOfMBonds; + locals.tempBidOrder.owner = qpi.invocator(); + state._bidOrders.add(locals.mbondIdentity, locals.tempBidOrder, input.price); + return; + } + + locals.elementIndex = state._bidOrders.headIndex(locals.mbondIdentity); + while (locals.elementIndex != NULL_INDEX) + { + if (input.price > state._bidOrders.priority(locals.elementIndex)) + { + locals.tempBidOrder.epoch = input.epoch; + locals.tempBidOrder.numberOfMBonds = input.numberOfMBonds; + locals.tempBidOrder.owner = qpi.invocator(); + state._bidOrders.add(locals.mbondIdentity, locals.tempBidOrder, input.price); + break; + } + else if (input.price == state._bidOrders.priority(locals.elementIndex)) + { + if (state._bidOrders.element(locals.elementIndex).owner == qpi.invocator()) + { + locals.tempBidOrder = state._bidOrders.element(locals.elementIndex); + locals.tempBidOrder.numberOfMBonds += input.numberOfMBonds; + state._bidOrders.replace(locals.elementIndex, locals.tempBidOrder); + break; + } + } + + if (state._bidOrders.nextElementIndex(locals.elementIndex) == NULL_INDEX) + { + locals.tempBidOrder.epoch = input.epoch; + locals.tempBidOrder.numberOfMBonds = input.numberOfMBonds; + locals.tempBidOrder.owner = qpi.invocator(); + state._bidOrders.add(locals.mbondIdentity, locals.tempBidOrder, input.price); + break; + } + + locals.elementIndex = state._bidOrders.nextElementIndex(locals.elementIndex); + } + } + + struct RemoveBidOrder_locals + { + MBondInfo tempMbondInfo; + id mbondIdentity; + sint64 elementIndex; + _Order order; + }; + + PUBLIC_PROCEDURE_WITH_LOCALS(RemoveBidOrder) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + + output.removedMBondsAmount = 0; + + if (input.price <= 0 || input.price >= MAX_AMOUNT || input.numberOfMBonds <= 0 || input.numberOfMBonds >= MAX_AMOUNT || !state._epochMbondInfoMap.get((uint16)input.epoch, locals.tempMbondInfo)) + { + return; + } + + locals.mbondIdentity = SELF; + locals.mbondIdentity.u64._3 = locals.tempMbondInfo.name; + + locals.elementIndex = state._bidOrders.headIndex(locals.mbondIdentity); + while (locals.elementIndex != NULL_INDEX) + { + if (input.price == state._bidOrders.priority(locals.elementIndex) && state._bidOrders.element(locals.elementIndex).owner == qpi.invocator()) + { + if (state._bidOrders.element(locals.elementIndex).numberOfMBonds <= input.numberOfMBonds) + { + output.removedMBondsAmount = state._bidOrders.element(locals.elementIndex).numberOfMBonds; + state._bidOrders.remove(locals.elementIndex); + } + else + { + locals.order = state._bidOrders.element(locals.elementIndex); + locals.order.numberOfMBonds -= input.numberOfMBonds; + state._bidOrders.replace(locals.elementIndex, locals.order); + output.removedMBondsAmount = input.numberOfMBonds; + } + qpi.transfer(qpi.invocator(), output.removedMBondsAmount * input.price); + break; + } + + locals.elementIndex = state._bidOrders.nextElementIndex(locals.elementIndex); + } + } + + PUBLIC_PROCEDURE(BurnQU) + { + if (input.amount <= 0 || input.amount >= MAX_AMOUNT || qpi.invocationReward() < input.amount) + { + output.amount = -1; + if (input.amount == 0) + { + output.amount = 0; + } + + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + return; + } + + if (qpi.invocationReward() > input.amount) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - input.amount); + } + + qpi.burn(input.amount); + output.amount = input.amount; + } + + PUBLIC_PROCEDURE(UpdateCFA) + { + if (qpi.invocationReward() > 0 && qpi.invocationReward() <= MAX_AMOUNT) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + + if (qpi.invocator() != state._adminAddress) + { + return; + } + + if (input.operation == 0) + { + state._commissionFreeAddresses.remove(input.user); + } + else + { + state._commissionFreeAddresses.add(input.user); + } + } + + struct GetInfoPerEpoch_locals + { + sint64 index; + QEARN::getLockInfoPerEpoch_input tempInput; + QEARN::getLockInfoPerEpoch_output tempOutput; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(GetInfoPerEpoch) + { + output.totalStaked = 0; + output.stakersAmount = 0; + output.apy = 0; + + locals.index = state._epochMbondInfoMap.getElementIndex((uint16)input.epoch); + + if (locals.index == NULL_INDEX) + { + return; + } + + locals.tempInput.Epoch = (uint32) input.epoch; + CALL_OTHER_CONTRACT_FUNCTION(QEARN, getLockInfoPerEpoch, locals.tempInput, locals.tempOutput); + + output.totalStaked = state._epochMbondInfoMap.value(locals.index).totalStaked; + output.stakersAmount = state._epochMbondInfoMap.value(locals.index).stakersAmount; + output.apy = locals.tempOutput.yield; + } + + PUBLIC_FUNCTION(GetFees) + { + output.stakeFeePercent = QBOND_STAKE_FEE_PERCENT; + output.tradeFeePercent = QBOND_TRADE_FEE_PERCENT; + output.transferFee = QBOND_MBOND_TRANSFER_FEE; + } + + PUBLIC_FUNCTION(GetEarnedFees) + { + output.stakeFees = state._totalEarnedAmount - state._earnedAmountFromTrade; + output.tradeFees = state._earnedAmountFromTrade; + } + + struct GetOrders_locals + { + MBondInfo tempMbondInfo; + id mbondIdentity; + sint64 elementIndex; + sint64 arrayElementIndex; + GetOrders_output::Order tempOrder; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(GetOrders) + { + if (!state._epochMbondInfoMap.get((uint16)input.epoch, locals.tempMbondInfo)) + { + return; + } + + locals.arrayElementIndex = 0; + locals.mbondIdentity = SELF; + locals.mbondIdentity.u64._3 = locals.tempMbondInfo.name; + + locals.elementIndex = state._askOrders.headIndex(locals.mbondIdentity, 0); + while (locals.elementIndex != NULL_INDEX && locals.arrayElementIndex < 256) + { + if (input.askOrdersOffset > 0) + { + input.askOrdersOffset--; + locals.elementIndex = state._askOrders.nextElementIndex(locals.elementIndex); + continue; + } + + locals.tempOrder.owner = state._askOrders.element(locals.elementIndex).owner; + locals.tempOrder.epoch = state._askOrders.element(locals.elementIndex).epoch; + locals.tempOrder.numberOfMBonds = state._askOrders.element(locals.elementIndex).numberOfMBonds; + locals.tempOrder.price = -state._askOrders.priority(locals.elementIndex); + output.askOrders.set(locals.arrayElementIndex, locals.tempOrder); + locals.arrayElementIndex++; + locals.elementIndex = state._askOrders.nextElementIndex(locals.elementIndex); + } + + locals.arrayElementIndex = 0; + locals.elementIndex = state._bidOrders.headIndex(locals.mbondIdentity); + while (locals.elementIndex != NULL_INDEX && locals.arrayElementIndex < 256) + { + if (input.bidOrdersOffset > 0) + { + input.bidOrdersOffset--; + locals.elementIndex = state._bidOrders.nextElementIndex(locals.elementIndex); + continue; + } + + locals.tempOrder.owner = state._bidOrders.element(locals.elementIndex).owner; + locals.tempOrder.epoch = state._bidOrders.element(locals.elementIndex).epoch; + locals.tempOrder.numberOfMBonds = state._bidOrders.element(locals.elementIndex).numberOfMBonds; + locals.tempOrder.price = state._bidOrders.priority(locals.elementIndex); + output.bidOrders.set(locals.arrayElementIndex, locals.tempOrder); + locals.arrayElementIndex++; + locals.elementIndex = state._bidOrders.nextElementIndex(locals.elementIndex); + } + } + + struct GetUserOrders_locals + { + sint64 epoch; + MBondInfo tempMbondInfo; + id mbondIdentity; + sint64 elementIndex1; + sint64 arrayElementIndex1; + sint64 elementIndex2; + sint64 arrayElementIndex2; + GetUserOrders_output::Order tempOrder; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(GetUserOrders) + { + for (locals.epoch = QBOND_START_EPOCH; locals.epoch <= qpi.epoch(); locals.epoch++) + { + if (!state._epochMbondInfoMap.get((uint16)locals.epoch, locals.tempMbondInfo)) + { + continue; + } + + locals.mbondIdentity = SELF; + locals.mbondIdentity.u64._3 = locals.tempMbondInfo.name; + + locals.elementIndex1 = state._askOrders.headIndex(locals.mbondIdentity, 0); + while (locals.elementIndex1 != NULL_INDEX && locals.arrayElementIndex1 < 256) + { + if (state._askOrders.element(locals.elementIndex1).owner != input.owner) + { + locals.elementIndex1 = state._askOrders.nextElementIndex(locals.elementIndex1); + continue; + } + + if (input.askOrdersOffset > 0) + { + input.askOrdersOffset--; + locals.elementIndex1 = state._askOrders.nextElementIndex(locals.elementIndex1); + continue; + } + + locals.tempOrder.owner = input.owner; + locals.tempOrder.epoch = state._askOrders.element(locals.elementIndex1).epoch; + locals.tempOrder.numberOfMBonds = state._askOrders.element(locals.elementIndex1).numberOfMBonds; + locals.tempOrder.price = -state._askOrders.priority(locals.elementIndex1); + output.askOrders.set(locals.arrayElementIndex1, locals.tempOrder); + locals.arrayElementIndex1++; + locals.elementIndex1 = state._askOrders.nextElementIndex(locals.elementIndex1); + } + + locals.elementIndex2 = state._bidOrders.headIndex(locals.mbondIdentity); + while (locals.elementIndex2 != NULL_INDEX && locals.arrayElementIndex2 < 256) + { + if (state._bidOrders.element(locals.elementIndex2).owner != input.owner) + { + locals.elementIndex2 = state._bidOrders.nextElementIndex(locals.elementIndex2); + continue; + } + + if (input.bidOrdersOffset > 0) + { + input.bidOrdersOffset--; + locals.elementIndex2 = state._bidOrders.nextElementIndex(locals.elementIndex2); + continue; + } + + locals.tempOrder.owner = input.owner; + locals.tempOrder.epoch = state._bidOrders.element(locals.elementIndex2).epoch; + locals.tempOrder.numberOfMBonds = state._bidOrders.element(locals.elementIndex2).numberOfMBonds; + locals.tempOrder.price = state._bidOrders.priority(locals.elementIndex2); + output.bidOrders.set(locals.arrayElementIndex2, locals.tempOrder); + locals.arrayElementIndex2++; + locals.elementIndex2 = state._bidOrders.nextElementIndex(locals.elementIndex2); + } + } + } + + struct GetMBondsTable_locals + { + sint64 epoch; + sint64 index; + MBondInfo tempMBondInfo; + GetMBondsTable_output::TableEntry tempTableEntry; + QEARN::getLockInfoPerEpoch_input tempInput; + QEARN::getLockInfoPerEpoch_output tempOutput; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(GetMBondsTable) + { + for (locals.epoch = QBOND_START_EPOCH; locals.epoch <= qpi.epoch(); locals.epoch++) + { + if (state._epochMbondInfoMap.get((uint16)locals.epoch, locals.tempMBondInfo)) + { + locals.tempInput.Epoch = (uint32) locals.epoch; + CALL_OTHER_CONTRACT_FUNCTION(QEARN, getLockInfoPerEpoch, locals.tempInput, locals.tempOutput); + locals.tempTableEntry.epoch = locals.epoch; + locals.tempTableEntry.apy = locals.tempOutput.yield; + output.info.set(locals.index, locals.tempTableEntry); + locals.index++; + } + } + } + + struct GetUserMBonds_locals + { + GetUserMBonds_output::MBondEntity tempMbondEntity; + sint64 epoch; + sint64 index; + sint64 mbondsAmount; + MBondInfo tempMBondInfo; + QEARN::getLockInfoPerEpoch_input tempInput; + QEARN::getLockInfoPerEpoch_output tempOutput; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(GetUserMBonds) + { + output.totalMBondsAmount = 0; + if (state._userTotalStakedMap.get(input.owner, locals.mbondsAmount)) + { + output.totalMBondsAmount = locals.mbondsAmount; + } + + for (locals.epoch = QBOND_START_EPOCH; locals.epoch <= qpi.epoch(); locals.epoch++) + { + if (!state._epochMbondInfoMap.get((uint16)locals.epoch, locals.tempMBondInfo)) + { + continue; + } + + locals.mbondsAmount = qpi.numberOfPossessedShares(locals.tempMBondInfo.name, SELF, input.owner, input.owner, SELF_INDEX, SELF_INDEX); + if (locals.mbondsAmount <= 0) + { + continue; + } + + locals.tempInput.Epoch = (uint32) locals.epoch; + CALL_OTHER_CONTRACT_FUNCTION(QEARN, getLockInfoPerEpoch, locals.tempInput, locals.tempOutput); + + locals.tempMbondEntity.epoch = locals.epoch; + locals.tempMbondEntity.amount = locals.mbondsAmount; + locals.tempMbondEntity.apy = locals.tempOutput.yield; + output.mbonds.set(locals.index, locals.tempMbondEntity); + locals.index++; + } + } + + struct GetCFA_locals + { + sint64 index; + sint64 counter; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(GetCFA) + { + locals.index = state._commissionFreeAddresses.nextElementIndex(NULL_INDEX); + while (locals.index != NULL_INDEX) + { + output.commissionFreeAddresses.set(locals.counter, state._commissionFreeAddresses.key(locals.index)); + locals.counter++; + locals.index = state._commissionFreeAddresses.nextElementIndex(locals.index); + } + } + + REGISTER_USER_FUNCTIONS_AND_PROCEDURES() + { + REGISTER_USER_PROCEDURE(Stake, 1); + REGISTER_USER_PROCEDURE(TransferMBondOwnershipAndPossession, 2); + REGISTER_USER_PROCEDURE(AddAskOrder, 3); + REGISTER_USER_PROCEDURE(RemoveAskOrder, 4); + REGISTER_USER_PROCEDURE(AddBidOrder, 5); + REGISTER_USER_PROCEDURE(RemoveBidOrder, 6); + REGISTER_USER_PROCEDURE(BurnQU, 7); + REGISTER_USER_PROCEDURE(UpdateCFA, 8); + + REGISTER_USER_FUNCTION(GetFees, 1); + REGISTER_USER_FUNCTION(GetEarnedFees, 2); + REGISTER_USER_FUNCTION(GetInfoPerEpoch, 3); + REGISTER_USER_FUNCTION(GetOrders, 4); + REGISTER_USER_FUNCTION(GetUserOrders, 5); + REGISTER_USER_FUNCTION(GetMBondsTable, 6); + REGISTER_USER_FUNCTION(GetUserMBonds, 7); + REGISTER_USER_FUNCTION(GetCFA, 8); + } + + INITIALIZE() + { + state._devAddress = ID(_B, _O, _N, _D, _D, _J, _N, _U, _H, _O, _G, _Y, _L, _A, _A, _A, _C, _V, _X, _C, _X, _F, _G, _F, _R, _C, _S, _D, _C, _U, _W, _C, _Y, _U, _N, _K, _M, _P, _G, _O, _I, _F, _E, _P, _O, _E, _M, _Y, _T, _L, _Q, _L, _F, _C, _S, _B); + state._adminAddress = ID(_B, _O, _N, _D, _A, _A, _F, _B, _U, _G, _H, _E, _L, _A, _N, _X, _G, _H, _N, _L, _M, _S, _U, _I, _V, _B, _K, _B, _H, _A, _Y, _E, _Q, _S, _Q, _B, _V, _P, _V, _N, _B, _H, _L, _F, _J, _I, _A, _Z, _F, _Q, _C, _W, _W, _B, _V, _E); + state._commissionFreeAddresses.add(state._adminAddress); + } + + PRE_ACQUIRE_SHARES() + { + output.allowTransfer = true; + } + + struct BEGIN_EPOCH_locals + { + sint8 chunk; + uint64 currentName; + StakeEntry emptyEntry; + sint64 totalReward; + sint64 rewardPerMBond; + Asset tempAsset; + MBondInfo tempMbondInfo; + AssetOwnershipIterator assetIt; + id mbondIdentity; + sint64 elementIndex; + sint64 nextElementIndex; + }; + + BEGIN_EPOCH_WITH_LOCALS() + { + if (state._qearnIncomeAmount > 0 && state._epochMbondInfoMap.get((uint16) (qpi.epoch() - 53), locals.tempMbondInfo)) + { + locals.totalReward = state._qearnIncomeAmount - locals.tempMbondInfo.totalStaked * QBOND_MBOND_PRICE; + locals.rewardPerMBond = QPI::div(locals.totalReward, locals.tempMbondInfo.totalStaked); + + locals.tempAsset.assetName = locals.tempMbondInfo.name; + locals.tempAsset.issuer = SELF; + locals.assetIt.begin(locals.tempAsset); + while (!locals.assetIt.reachedEnd()) + { + qpi.transfer(locals.assetIt.owner(), (QBOND_MBOND_PRICE + locals.rewardPerMBond) * locals.assetIt.numberOfOwnedShares()); + qpi.transferShareOwnershipAndPossession( + locals.tempMbondInfo.name, + SELF, + locals.assetIt.owner(), + locals.assetIt.owner(), + locals.assetIt.numberOfOwnedShares(), + NULL_ID); + locals.assetIt.next(); + } + state._qearnIncomeAmount = 0; + + locals.mbondIdentity = SELF; + locals.mbondIdentity.u64._3 = locals.tempMbondInfo.name; + + locals.elementIndex = state._askOrders.headIndex(locals.mbondIdentity); + while (locals.elementIndex != NULL_INDEX) + { + locals.nextElementIndex = state._askOrders.nextElementIndex(locals.elementIndex); + state._askOrders.remove(locals.elementIndex); + locals.elementIndex = locals.nextElementIndex; + } + + locals.elementIndex = state._bidOrders.headIndex(locals.mbondIdentity); + while (locals.elementIndex != NULL_INDEX) + { + locals.nextElementIndex = state._bidOrders.nextElementIndex(locals.elementIndex); + state._bidOrders.remove(locals.elementIndex); + locals.elementIndex = locals.nextElementIndex; + } + } + + locals.currentName = 1145979469ULL; // MBND + + locals.chunk = (sint8) (48 + mod(div((uint64)qpi.epoch(), 100ULL), 10ULL)); + locals.currentName |= (uint64)locals.chunk << (4 * 8); + + locals.chunk = (sint8) (48 + mod(div((uint64)qpi.epoch(), 10ULL), 10ULL)); + locals.currentName |= (uint64)locals.chunk << (5 * 8); + + locals.chunk = (sint8) (48 + mod((uint64)qpi.epoch(), 10ULL)); + locals.currentName |= (uint64)locals.chunk << (6 * 8); + + qpi.issueAsset(locals.currentName, SELF, 0, QBOND_MBONDS_EMISSION, 0); + + locals.emptyEntry.staker = NULL_ID; + locals.emptyEntry.amount = 0; + locals.tempMbondInfo.name = locals.currentName; + locals.tempMbondInfo.totalStaked = 0; + locals.tempMbondInfo.stakersAmount = 0; + state._epochMbondInfoMap.set(qpi.epoch(), locals.tempMbondInfo); + state._stakeQueue.setAll(locals.emptyEntry); + } + + struct POST_INCOMING_TRANSFER_locals + { + MBondInfo tempMbondInfo; + }; + + POST_INCOMING_TRANSFER_WITH_LOCALS() + { + if (input.sourceId == id(QEARN_CONTRACT_INDEX, 0, 0, 0) && state._epochMbondInfoMap.get(qpi.epoch() - 52, locals.tempMbondInfo)) + { + state._qearnIncomeAmount = input.amount; + } + } + + struct END_EPOCH_locals + { + sint64 availableMbonds; + MBondInfo tempMbondInfo; + sint64 counter; + StakeEntry tempStakeEntry; + sint64 amountToQvault; + sint64 amountToDev; + }; + + END_EPOCH_WITH_LOCALS() + { + locals.amountToQvault = div((state._totalEarnedAmount - state._distributedAmount) * QBOND_QVAULT_DISTRIBUTION_PERCENT, 10000ULL); + locals.amountToDev = state._totalEarnedAmount - state._distributedAmount - locals.amountToQvault; + qpi.transfer(id(QVAULT_CONTRACT_INDEX, 0, 0, 0), locals.amountToQvault); + qpi.transfer(state._devAddress, locals.amountToDev); + state._distributedAmount += locals.amountToQvault; + state._distributedAmount += locals.amountToDev; + + locals.tempStakeEntry.staker = NULL_ID; + locals.tempStakeEntry.amount = 0; + for (locals.counter = 0; locals.counter < QBOND_MAX_QUEUE_SIZE; locals.counter++) + { + if (state._stakeQueue.get(locals.counter).staker == NULL_ID) + { + break; + } + + qpi.transfer(state._stakeQueue.get(locals.counter).staker, state._stakeQueue.get(locals.counter).amount * QBOND_MBOND_PRICE); + state._stakeQueue.set(locals.counter, locals.tempStakeEntry); + } + + if (state._epochMbondInfoMap.get(qpi.epoch(), locals.tempMbondInfo)) + { + locals.availableMbonds = qpi.numberOfPossessedShares(locals.tempMbondInfo.name, SELF, SELF, SELF, SELF_INDEX, SELF_INDEX); + qpi.transferShareOwnershipAndPossession(locals.tempMbondInfo.name, SELF, SELF, SELF, locals.availableMbonds, NULL_ID); + } + + state._commissionFreeAddresses.cleanupIfNeeded(); + state._askOrders.cleanupIfNeeded(); + state._bidOrders.cleanupIfNeeded(); + } +}; + +``` From 5ca31eb57e059e6f6c736ae634f963b6c77c8853 Mon Sep 17 00:00:00 2001 From: KavataK Date: Sat, 7 Mar 2026 13:01:00 +0300 Subject: [PATCH 2/3] Upgrade QVault SC proposal --- SmartContracts/2026-03-07-Upgrade-QVault.md | 3917 +++++++++++++++++++ 1 file changed, 3917 insertions(+) create mode 100644 SmartContracts/2026-03-07-Upgrade-QVault.md diff --git a/SmartContracts/2026-03-07-Upgrade-QVault.md b/SmartContracts/2026-03-07-Upgrade-QVault.md new file mode 100644 index 0000000..6319cd7 --- /dev/null +++ b/SmartContracts/2026-03-07-Upgrade-QVault.md @@ -0,0 +1,3917 @@ +# Proposal: Upgrade SC QVault to Version 2.0 + +## Proposal + +Upgrade the existing QVault Smart Contract. + +This upgrade introduces the new governance and operational framework for Qubic Capital, with the main objective of automating all operational processes currently performed manually by the team. + +Through QVAULT v2, investment decisions, fund allocations, and governance actions will be executed through structured on-chain proposals, eliminating the need for manual intervention and bringing true operational decentralization to the project. + +## Available Options + +**Option 0:** No — do not upgrade the contract. + +**Option 1:** Yes — approve the upgrade to enable multi-asset functionality. + +--- + +## Governance Overview + +Qubic Capital operates as a DAO (Decentralized Autonomous Organization) through **QVAULT v2**. + + +Decisions are made through proposals that are voted on by **QCAP holders** with voting power. +Depending on the proposal type, execution may be either: + +- **Automatic (on-chain)** +- **Operational (off-chain)** — only applicable to **GP (General Proposal)** + +--- + +## General Rules for Proposals + +All proposals: + +- Must include a **GitHub link** explaining the rationale and details +- Require a **10,000,000 Qubic proposal fee** +- Finish automatically at the **epoch change (Wednesday 12:00 UTC)** + +The proposal fee helps prevent spam. It is added to the **epoch distribution pool** and distributed to **QCAP holders** as part of the regular distribution cycle. + +--- + +## Types of Proposals + +The following proposal types are available: + +- QCAP General Proposal +- Quorum Requirement +- IPO Participation +- Qearn Participation +- Fundraising Proposal +- Marketplace Proposal +- Allocation Percentage + +--- + +## Proposal Submission Rules + +Each proposal costs **10M $QUBIC**. + +To create a proposal, a user must have either: + +- **10,000 $QCAP staked**, or +- **1 $QVAULT token in possession** + +Only IDs that have **$QCAP staked from the previous epoch** are allowed to participate in voting. + +A vote is considered **valid only if the quorum requirement is reached**. + +--- + +## Proposal Types Explained + +### General Proposal + +General proposals are simple and **do not impact QCAP investments**. + +Examples include: + +- Election of an admin or team member +- Community sentiment checks +- Non-binding governance discussions + +--- + +### Quorum Requirement + +This proposal type adjusts the **minimum quorum required** for a proposal to be considered valid. + +- Default value: **67%** +- Allowed range: **33.33% (1/3) to 67% (2/3)** + +The quorum is calculated based on the **amount of $QCAP staked**. + +--- + +### IPO Participation + +A proposal may be submitted to participate in a specific **IPO (Initial Project Offering)**. + +Participants vote on **how much to invest (numerical value)**. + +At the end of the vote, **QVAULT calculates the weighted average investment**, meaning: + +- A holder with **10,000 $QCAP** has **10× more voting power** than a holder with **1,000 $QCAP**. + +--- + +### Qearn Participation + +A proposal may suggest allocating funds to Qearn. + +Example proposal: + +> Invest **XXX QUS per epoch for the next Y epochs**. + +Users vote **Yes** or **No**. + +If approved: + +- QVAULT will allocate **XXX QUS each epoch** for **Y consecutive epochs** +- The funds will remain **locked for 52 epochs** + +The total locked amount will therefore be: + +**XXX × Y QUS** + +After the lock period ends, Qearn returns: + +- **XXX QUS (principal)** +- **Interest** + +Only the **interest is distributed as revenue**, while the **principal remains inside QVAULT**. + +--- + +### Fundraising Proposal + +Example proposal: + +> Sell **100,000 $QCAP for 1,000,000 Qubic** + +If approved: + +- QVAULT allows the sale of the specified tokens. + +Funds raised from this proposal **are not considered revenue** and **must not be distributed**. + +Token sale limits: + +- **2026:** 15,571,429 $QCAP +- **2027:** 18,000,000 $QCAP +- **After 2027:** the remaining **3,000,000 $QCAP** can be sold at any time. + +--- + +### Marketplace Proposal + +A user may submit a proposal to sell assets to QVAULT. + +Example: + +> Sell **10 Quottery and 1 QX** for **10B Qubic and 10,000 $QCAP** + +Process: + +1. The seller sends the assets to **QVAULT**. +2. The proposal goes live for voting. + +If approved: + +- The seller receives **10B Qubic and 10,000 $QCAP**. + +If rejected: + +- QVAULT returns the assets to the seller. + +If QVAULT does not have sufficient funds (either **Qubic** or **QCAP availability for the current year**), the seller will also receive their assets back. + +--- + +### Allocation Percentage + +This proposal allows modification of the **revenue allocation percentages**. + +Possible categories: + +- **Reinvested** +- **Distributed** +- **Burned** +- **Shareholders** + +Important rule: + +- **Shareholders revenue is permanently fixed at 3%.** + +Therefore, the remaining categories must **always sum to 97%**. + +--- + +## Updated Smart Contract Code + +
+Show / hide updated code + +``` + +using namespace QPI; + +// QVAULT Contract Constants +// Asset identifiers for QCAP and QVAULT tokens +constexpr uint64 QVAULT_QCAP_ASSETNAME = 1346454353; // QCAP token asset name +constexpr uint64 QVAULT_QVAULT_ASSETNAME = 92686824592977; // QVAULT token asset name + +// Financial and operational constants +constexpr uint64 QVAULT_PROPOSAL_FEE = 10000000; // Fee required to submit proposals (in qubic) +constexpr uint64 QVAULT_IPO_PARTICIPATION_MIN_FUND = 1000000000; // Minimum fund required for IPO participation +constexpr uint32 QVAULT_QCAP_MAX_SUPPLY = 21000000; // Maximum total supply of QCAP tokens +constexpr uint32 QVAULT_MAX_NUMBER_OF_PROPOSAL = 65536; // Maximum number of proposals allowed +constexpr uint32 QVAULT_X_MULTIPLIER = 1048576; // X multiplier for QVAULT +constexpr uint32 QVAULT_MIN_QUORUM_REQ = 330; // Minimum quorum requirement (330) +constexpr uint32 QVAULT_MAX_QUORUM_REQ = 670; // Maximum quorum requirement (670) +constexpr uint32 QVAULT_MAX_URLS_COUNT = 256; // Maximum number of URLs allowed +constexpr uint32 QVAULT_MIN_VOTING_POWER = 10000; // Minimum voting power required +constexpr uint32 QVAULT_SUM_OF_ALLOCATION_PERCENTAGES = 970; // Sum of allocation percentages +constexpr uint32 QVAULT_MAX_USER_VOTES = 16; // Maximum number of votes per user + +// Yearly QCAP sale limits +constexpr uint32 QVAULT_2025MAX_QCAP_SALE_AMOUNT = 10714286; // Maximum QCAP sales for 2025 +constexpr uint32 QVAULT_2026MAX_QCAP_SALE_AMOUNT = 15571429; // Maximum QCAP sales for 2026 +constexpr uint32 QVAULT_2027MAX_QCAP_SALE_AMOUNT = 18000000; // Maximum QCAP sales for 2027 + +// Return code constants +constexpr sint32 QVAULT_SUCCESS = 0; // Operation completed successfully +constexpr sint32 QVAULT_INSUFFICIENT_QCAP = 1; // User doesn't have enough QCAP tokens +constexpr sint32 QVAULT_NOT_ENOUGH_STAKE = 2; // User doesn't have enough staked tokens +constexpr sint32 QVAULT_NOT_STAKER = 3; // User is not a staker +constexpr sint32 QVAULT_INSUFFICIENT_FUND = 4; // Insufficient funds for operation +constexpr sint32 QVAULT_NOT_TRANSFERRED_SHARE = 5; // Share transfer failed +constexpr sint32 QVAULT_NOT_IN_TIME = 6; // Operation attempted outside allowed timeframe +constexpr sint32 QVAULT_NOT_FAIR = 7; // Allocation proposal percentages don't sum to 1000 +constexpr sint32 QVAULT_OVERFLOW_SALE_AMOUNT = 8; // QCAP sale amount exceeds yearly limit +constexpr sint32 QVAULT_OVERFLOW_PROPOSAL = 9; // Proposal ID exceeds maximum allowed +constexpr sint32 QVAULT_OVERFLOW_VOTES = 10; // Maximum number of votes per user exceeded +constexpr sint32 QVAULT_SAME_DECISION = 11; // User is voting with same decision as before +constexpr sint32 QVAULT_ENDED_PROPOSAL = 12; // Proposal voting period has ended +constexpr sint32 QVAULT_NO_VOTING_POWER = 13; // User has no voting power +constexpr sint32 QVAULT_FAILED = 14; // Operation failed +constexpr sint32 QVAULT_INSUFFICIENT_SHARE = 15; // Insufficient shares for operation +constexpr sint32 QVAULT_ERROR_TRANSFER_ASSET = 16; // Asset transfer error occurred +constexpr sint32 QVAULT_INSUFFICIENT_VOTING_POWER = 17; // User doesn't have minimum voting power (10000) +constexpr sint32 QVAULT_INPUT_ERROR = 18; // Invalid input parameters +constexpr sint32 QVAULT_OVERFLOW_STAKER = 19; // Maximum number of stakers exceeded +constexpr sint32 QVAULT_INSUFFICIENT_SHARE_OR_VOTING_POWER = 20; // User doesn't have minimum voting power (10000) or shares +constexpr sint32 QVAULT_NOT_FOUND_STAKER_ADDRESS = 21; // User not found in staker list + +// Return result of proposal constants +constexpr uint8 QVAULT_PROPOSAL_PASSED = 0; +constexpr uint8 QVAULT_PROPOSAL_REJECTED = 1; +constexpr uint8 QVAULT_PROPOSAL_INSUFFICIENT_QUORUM = 2; +constexpr uint8 QVAULT_PROPOSAL_INSUFFICIENT_VOTING_POWER = 3; +constexpr uint8 QVAULT_PROPOSAL_INSUFFICIENT_QCAP = 4; +constexpr uint8 QVAULT_PROPOSAL_NOT_STARTED = 5; + +/** + * QVAULT2 - Placeholder struct for future use + */ +struct QVAULT2 +{ +}; + +/** + * QVAULT Contract + * Main contract for managing QCAP token staking, voting, and governance + * Inherits from ContractBase to provide basic contract functionality + */ +struct QVAULT : public ContractBase +{ + +public: + + /** + * Input structure for getData function + * No input parameters required - returns all contract state data + */ + struct getData_input + { + }; + + /** + * Output structure for getData function + * Contains comprehensive contract state information including: + * - Administrative data (fees) + * - Financial data (funds, revenue, market cap) + * - Staking data (staked amounts, voting power) + * - Proposal counts for each type + * - Configuration parameters (percentages, limits) + */ + struct getData_output + { + sint32 returnCode; // Status code indicating success or failure + uint64 totalVotingPower; // Total voting power across all stakers + uint64 proposalCreateFund; // Fund accumulated from proposal fees + uint64 reinvestingFund; // Fund available for reinvestment + uint64 totalEpochRevenue; // Total revenue for current epoch + uint64 fundForBurn; // Fund allocated for token burning + uint64 totalStakedQcapAmount; // Total amount of QCAP tokens staked + uint64 qcapMarketCap; // Current QCAP market capitalization + uint64 raisedFundByQcap; // Total funds raised from QCAP sales + uint64 lastRoundPriceOfQcap; // QCAP price from last fundraising round + uint64 revenueByQearn; // Revenue generated from QEarn operations + uint32 qcapSoldAmount; // Total QCAP tokens sold to date + uint32 shareholderDividend; // Dividend percentage for shareholders (per mille) + uint32 QCAPHolderPermille; // Revenue allocation for QCAP holders (per mille) + uint32 reinvestingPermille; // Revenue allocation for reinvestment (per mille) + uint32 burnPermille; // Revenue allocation for burning (per mille) + uint32 qcapBurnPermille; // Revenue allocation for QCAP burning (per mille) + uint32 numberOfStaker; // Number of active stakers + uint32 numberOfVotingPower; // Number of users with voting power + uint32 numberOfGP; // Number of General Proposals + uint32 numberOfQCP; // Number of Quorum Change Proposals + uint32 numberOfIPOP; // Number of IPO Participation Proposals + uint32 numberOfQEarnP; // Number of QEarn Participation Proposals + uint32 numberOfFundP; // Number of Fundraising Proposals + uint32 numberOfMKTP; // Number of Marketplace Proposals + uint32 numberOfAlloP; // Number of Allocation Proposals + uint32 transferRightsFee; // Fee for transferring share management rights + uint32 minQuorumRq; // Minimum quorum requirement (330) + uint32 maxQuorumRq; // Maximum quorum requirement (670) + uint32 totalQcapBurntAmount; // Total QCAP tokens burned to date + uint32 circulatingSupply; // Current circulating supply of QCAP + uint32 quorumPercent; // Current quorum percentage for proposals + }; + + /** + * Input structure for stake function + * @param amount Number of QCAP tokens to stake + */ + struct stake_input + { + uint32 amount; + }; + + /** + * Output structure for stake function + * @param returnCode Status code indicating success or failure + */ + struct stake_output + { + sint32 returnCode; + }; + + /** + * Input structure for unStake function + * @param amount Number of QCAP tokens to unstake + */ + struct unStake_input + { + uint32 amount; + }; + + /** + * Output structure for unStake function + * @param returnCode Status code indicating success or failure + */ + struct unStake_output + { + sint32 returnCode; + }; + + /** + * Input structure for submitGP (General Proposal) function + * @param url URL containing proposal details (max 256 bytes) + */ + struct submitGP_input + { + Array url; + }; + + /** + * Output structure for submitGP function + * @param returnCode Status code indicating success or failure + */ + struct submitGP_output + { + sint32 returnCode; + }; + + /** + * Input structure for submitQCP (Quorum Change Proposal) function + * @param newQuorumPercent New quorum percentage to set (330-670) + * @param url URL containing proposal details (max 256 bytes) + */ + struct submitQCP_input + { + uint32 newQuorumPercent; + Array url; + }; + + /** + * Output structure for submitQCP function + * @param returnCode Status code indicating success or failure + */ + struct submitQCP_output + { + sint32 returnCode; + }; + + /** + * Input structure for submitIPOP (IPO Participation Proposal) function + * @param ipoContractIndex Index of the IPO contract to participate in + * @param url URL containing proposal details (max 256 bytes) + */ + struct submitIPOP_input + { + uint32 ipoContractIndex; + Array url; + }; + + /** + * Output structure for submitIPOP function + * @param returnCode Status code indicating success or failure + */ + struct submitIPOP_output + { + sint32 returnCode; + }; + + /** + * Input structure for submitQEarnP (QEarn Participation Proposal) function + * @param amountPerEpoch Amount to invest per epoch + * @param numberOfEpoch Number of epochs to participate (max 52) + * @param url URL containing proposal details (max 256 bytes) + */ + struct submitQEarnP_input + { + uint64 amountPerEpoch; + uint32 numberOfEpoch; + Array url; + }; + + /** + * Output structure for submitQEarnP function + * @param returnCode Status code indicating success or failure + */ + struct submitQEarnP_output + { + sint32 returnCode; + }; + + /** + * Input structure for submitFundP (Fundraising Proposal) function + * @param priceOfOneQcap Price per QCAP token in qubic + * @param amountOfQcap Amount of QCAP tokens to sell + * @param url URL containing proposal details (max 256 bytes) + */ + struct submitFundP_input + { + uint64 priceOfOneQcap; + uint32 amountOfQcap; + Array url; + }; + + /** + * Output structure for submitFundP function + * @param returnCode Status code indicating success or failure + */ + struct submitFundP_output + { + sint32 returnCode; + }; + + /** + * Input structure for submitMKTP (Marketplace Proposal) function + * @param amountOfQubic Amount of qubic to spend + * @param shareName Name/identifier of the share to purchase + * @param amountOfQcap Amount of QCAP tokens to offer + * @param indexOfShare Index of the share in the marketplace + * @param amountOfShare Amount of shares to purchase + * @param url URL containing proposal details (max 256 bytes) + */ + struct submitMKTP_input + { + uint64 amountOfQubic; + uint64 shareName; + uint32 amountOfQcap; + uint32 indexOfShare; + uint32 amountOfShare; + Array url; + }; + + /** + * Output structure for submitMKTP function + * @param returnCode Status code indicating success or failure + */ + struct submitMKTP_output + { + sint32 returnCode; + }; + + /** + * Input structure for submitAlloP (Allocation Proposal) function + * @param reinvested Percentage for reinvestment (per mille) + * @param burn Percentage for burning (per mille) + * @param distribute Percentage for distribution (per mille) + * @param url URL containing proposal details (max 256 bytes) + * Note: All percentages must sum to 970 (per mille) + */ + struct submitAlloP_input + { + uint32 reinvested; + uint32 burn; + uint32 distribute; + Array url; + }; + + /** + * Output structure for submitAlloP function + * @param returnCode Status code indicating success or failure + */ + struct submitAlloP_output + { + sint32 returnCode; + }; + + /** + * Input structure for voteInProposal function + * @param priceOfIPO IPO price for IPO participation proposals + * @param proposalType Type of proposal (1=GP, 2=QCP, 3=IPOP, 4=QEarnP, 5=FundP, 6=MKTP, 7=AlloP) + * @param proposalId ID of the proposal to vote on + * @param yes Voting decision (1=yes, 0=no) + */ + struct voteInProposal_input + { + uint64 priceOfIPO; + uint32 proposalType; + uint32 proposalId; + bit yes; + }; + + /** + * Output structure for voteInProposal function + * @param returnCode Status code indicating success or failure + */ + struct voteInProposal_output + { + sint32 returnCode; + }; + + /** + * Input structure for buyQcap function + * @param amount Number of QCAP tokens to purchase + */ + struct buyQcap_input + { + uint32 amount; + }; + + /** + * Output structure for buyQcap function + * @param returnCode Status code indicating success or failure + */ + struct buyQcap_output + { + sint32 returnCode; + }; + + /** + * Input structure for TransferShareManagementRights function + * @param asset Asset information (name and issuer) + * @param numberOfShares Number of shares to transfer management rights for + * @param newManagingContractIndex Index of the new managing contract + */ + struct TransferShareManagementRights_input + { + Asset asset; + sint64 numberOfShares; + uint32 newManagingContractIndex; + }; + + /** + * Output structure for TransferShareManagementRights function + * @param transferredNumberOfShares Number of shares successfully transferred + * @param returnCode Status code indicating success or failure + */ + struct TransferShareManagementRights_output + { + sint64 transferredNumberOfShares; + sint32 returnCode; + }; + +protected: + + /** + * Staking information structure + * Tracks individual staker addresses and their staked amounts + */ + struct stakingInfo + { + id stakerAddress; // Address of the staker + uint32 amount; // Amount of QCAP tokens staked + }; + + // Storage arrays for staking and voting power data + Array staker; // Array of all stakers (max 1M stakers) + Array votingPower; // Array of users with voting power + + /** + * General Proposal (GP) information structure + * Stores details about general governance proposals + */ + struct GPInfo + { + id proposer; // Address of the proposal creator + uint32 currentTotalVotingPower; // Total voting power when proposal was created + uint32 numberOfYes; // Number of yes votes received + uint32 numberOfNo; // Number of no votes received + uint32 proposedEpoch; // Epoch when proposal was created + uint32 currentQuorumPercent; // Quorum percentage when proposal was created + Array url; // URL containing proposal details + uint8 result; // Proposal result: 0=passed, 1=rejected, 2=insufficient quorum + }; + + // Storage array for general proposals + Array GP; + + /** + * Quorum Change Proposal (QCP) information structure + * Stores details about proposals to change the voting quorum percentage + */ + struct QCPInfo + { + id proposer; // Address of the proposal creator + uint32 currentTotalVotingPower; // Total voting power when proposal was created + uint32 numberOfYes; // Number of yes votes received + uint32 numberOfNo; // Number of no votes received + uint32 proposedEpoch; // Epoch when proposal was created + uint32 currentQuorumPercent; // Current quorum percentage + uint32 newQuorumPercent; // Proposed new quorum percentage + Array url; // URL containing proposal details + uint8 result; // Proposal result: 0=passed, 1=rejected, 2=insufficient quorum + }; + + // Storage array for quorum change proposals + Array QCP; + + /** + * IPO Participation Proposal (IPOP) information structure + * Stores details about proposals to participate in IPO contracts + */ + struct IPOPInfo + { + id proposer; // Address of the proposal creator + uint64 totalWeight; // Total weighted voting power for IPO participation + uint64 assignedFund; // Amount of funds assigned for IPO participation + uint32 currentTotalVotingPower; // Total voting power when proposal was created + uint32 numberOfYes; // Number of yes votes received + uint32 numberOfNo; // Number of no votes received + uint32 proposedEpoch; // Epoch when proposal was created + uint32 ipoContractIndex; // Index of the IPO contract to participate in + uint32 currentQuorumPercent; // Current quorum percentage + Array url; // URL containing proposal details + uint8 result; // Proposal result: 0=passed, 1=rejected, 2=insufficient quorum, 3=insufficient funds + }; + + // Storage array for IPO participation proposals + Array IPOP; + + /** + * QEarn Participation Proposal (QEarnP) information structure + * Stores details about proposals to participate in QEarn contracts + */ + struct QEarnPInfo + { + id proposer; // Address of the proposal creator + uint64 amountOfInvestPerEpoch; // Amount to invest per epoch + uint64 assignedFundPerEpoch; // Amount of funds assigned per epoch + uint32 currentTotalVotingPower; // Total voting power when proposal was created + uint32 numberOfYes; // Number of yes votes received + uint32 numberOfNo; // Number of no votes received + uint32 proposedEpoch; // Epoch when proposal was created + uint32 currentQuorumPercent; // Current quorum percentage + Array url; // URL containing proposal details + uint8 numberOfEpoch; // Number of epochs to participate + uint8 result; // Proposal result: 0=passed, 1=rejected, 2=insufficient quorum, 3=insufficient funds + }; + + // Storage array for QEarn participation proposals + Array QEarnP; + + /** + * Fundraising Proposal (FundP) information structure + * Stores details about proposals to sell QCAP tokens for fundraising + */ + struct FundPInfo + { + id proposer; // Address of the proposal creator + uint64 pricePerOneQcap; // Price per QCAP token in qubic + uint32 currentTotalVotingPower; // Total voting power when proposal was created + uint32 numberOfYes; // Number of yes votes received + uint32 numberOfNo; // Number of no votes received + uint32 amountOfQcap; // Total amount of QCAP tokens to sell + uint32 restSaleAmount; // Remaining amount of QCAP tokens available for sale + uint32 proposedEpoch; // Epoch when proposal was created + uint32 currentQuorumPercent; // Current quorum percentage + Array url; // URL containing proposal details + uint8 result; // Proposal result: 0=passed, 1=rejected, 2=insufficient quorum + }; + + // Storage array for fundraising proposals + Array FundP; + + /** + * Marketplace Proposal (MKTP) information structure + * Stores details about proposals to purchase shares from the marketplace + */ + struct MKTPInfo + { + id proposer; // Address of the proposal creator + uint64 amountOfQubic; // Amount of qubic to spend on shares + uint64 shareName; // Name/identifier of the share to purchase + uint32 currentTotalVotingPower; // Total voting power when proposal was created + uint32 numberOfYes; // Number of yes votes received + uint32 numberOfNo; // Number of no votes received + uint32 amountOfQcap; // Amount of QCAP tokens to offer + uint32 currentQuorumPercent; // Current quorum percentage + uint32 proposedEpoch; // Epoch when proposal was created + uint32 shareIndex; // Index of the share in the marketplace + uint32 amountOfShare; // Amount of shares to purchase + Array url; // URL containing proposal details + uint8 result; // Proposal result: 0=passed, 1=rejected, 2=insufficient quorum, 3=insufficient funds, 4=insufficient QCAP + }; + + // Storage array for marketplace proposals + Array MKTP; + + /** + * Allocation Proposal (AlloP) information structure + * Stores details about proposals to change revenue allocation percentages + * All percentages are in per mille (parts per thousand) + */ + struct AlloPInfo + { + id proposer; // Address of the proposal creator + uint32 currentTotalVotingPower; // Total voting power when proposal was created + uint32 numberOfYes; // Number of yes votes received + uint32 numberOfNo; // Number of no votes received + uint32 proposedEpoch; // Epoch when proposal was created + uint32 currentQuorumPercent; // Current quorum percentage + uint32 reinvested; // Percentage for reinvestment (per mille) + uint32 distributed; // Percentage for distribution (per mille) + uint32 burnQcap; // Percentage for QCAP burning (per mille) + Array url; // URL containing proposal details + uint8 result; // Proposal result: 0=passed, 1=rejected, 2=insufficient quorum + }; + + // Storage array for allocation proposals + Array AlloP; + + // Contract configuration and administration + id QCAP_ISSUER; // Address that can issue QCAP tokens + + /** + * Vote status information structure + * Tracks individual user votes on proposals + */ + struct voteStatusInfo + { + uint64 priceOfIPO; // IPO price for IPO participation proposals + uint32 proposalId; // ID of the proposal voted on + uint8 proposalType; // Type of proposal (1-7) + bit decision; // Voting decision (1=yes, 0=no) + }; + + // Storage for user voting history and vote counts + HashMap, QVAULT_X_MULTIPLIER> vote; // User voting history (max 16 votes per user) + HashMap countOfVote; // Count of votes per user + + // Financial state variables + uint64 proposalCreateFund; // Fund accumulated from proposal fees + uint64 reinvestingFund; // Fund available for reinvestment + uint64 totalEpochRevenue; // Total revenue for current epoch + uint64 fundForBurn; // Fund allocated for token burning + uint64 totalHistoryRevenue; // Total historical revenue + uint64 rasiedFundByQcap; // Total funds raised from QCAP sales + uint64 lastRoundPriceOfQcap; // QCAP price from last fundraising round + uint64 revenueByQearn; // Revenue generated from QEarn operations + + // Per-epoch revenue tracking arrays + Array revenueInQcapPerEpoch; // Revenue in QCAP per epoch + Array revenueForOneQcapPerEpoch; // Revenue per QCAP token per epoch + Array revenueForOneQvaultPerEpoch; // Revenue per QVAULT share per epoch + Array revenueForReinvestPerEpoch; // Revenue for reinvestment per epoch + Array revenuePerShare; // Revenue per share per epoch + Array burntQcapAmPerEpoch; // QCAP amount burned per epoch + + // Staking and voting state + uint32 totalVotingPower; // Total voting power across all stakers + uint32 totalStakedQcapAmount; // Total amount of QCAP tokens staked + uint32 qcapSoldAmount; // Total QCAP tokens sold to date + + // Revenue allocation percentages (per mille) + uint32 shareholderDividend; // Dividend percentage for shareholders + uint32 QCAPHolderPermille; // Revenue allocation for QCAP holders + uint32 reinvestingPermille; // Revenue allocation for reinvestment + uint32 burnPermille; // Revenue allocation for burning + uint32 qcapBurnPermille; // Revenue allocation for QCAP burning + uint32 totalQcapBurntAmount; // Total QCAP tokens burned to date + + // Counters for stakers and voting power + uint32 numberOfStaker; // Number of active stakers + uint32 numberOfVotingPower; // Number of users with voting power + + // Proposal counters for each type + uint32 numberOfGP; // Number of General Proposals + uint32 numberOfQCP; // Number of Quorum Change Proposals + uint32 numberOfIPOP; // Number of IPO Participation Proposals + uint32 numberOfQEarnP; // Number of QEarn Participation Proposals + uint32 numberOfFundP; // Number of Fundraising Proposals + uint32 numberOfMKTP; // Number of Marketplace Proposals + uint32 numberOfAlloP; // Number of Allocation Proposals + + // Configuration parameters + uint32 transferRightsFee; // Fee for transferring share management rights + uint32 quorumPercent; // Current quorum percentage for proposals + + /** + * @return pack qvault datetime data from year, month, day, hour, minute, second to a uint32 + * year is counted from 24 (2024) + */ + inline static void packQvaultDate(uint32 _year, uint32 _month, uint32 _day, uint32 _hour, uint32 _minute, uint32 _second, uint32& res) + { + res = ((_year - 24) << 26) | (_month << 22) | (_day << 17) | (_hour << 12) | (_minute << 6) | (_second); + } + + inline static uint32 qvaultGetYear(uint32 data) + { + return ((data >> 26) + 24); + } + inline static uint32 qvaultGetMonth(uint32 data) + { + return ((data >> 22) & 0b1111); + } + inline static uint32 qvaultGetDay(uint32 data) + { + return ((data >> 17) & 0b11111); + } + inline static uint32 qvaultGetHour(uint32 data) + { + return ((data >> 12) & 0b11111); + } + inline static uint32 qvaultGetMinute(uint32 data) + { + return ((data >> 6) & 0b111111); + } + inline static uint32 qvaultGetSecond(uint32 data) + { + return (data & 0b111111); + } + /* + * @return unpack qvault datetime from uin32 to year, month, day, hour, minute, secon + */ + inline static void unpackQvaultDate(uint8& _year, uint8& _month, uint8& _day, uint8& _hour, uint8& _minute, uint8& _second, uint32 data) + { + _year = qvaultGetYear(data); // 6 bits + _month = qvaultGetMonth(data); //4bits + _day = qvaultGetDay(data); //5bits + _hour = qvaultGetHour(data); //5bits + _minute = qvaultGetMinute(data); //6bits + _second = qvaultGetSecond(data); //6bits + } + + /** + * Local variables for getData function + */ + struct getData_locals + { + Asset qcapAsset; // QCAP asset information + sint32 _t; // Loop counter variable + }; + + /** + * Retrieves comprehensive contract state data + * Returns all important contract information including financial data, + * staking statistics, proposal counts, and configuration parameters + * + * @param input No input parameters required + * @param output Comprehensive contract state data + */ + PUBLIC_FUNCTION_WITH_LOCALS(getData) + { + output.quorumPercent = state.quorumPercent; + output.totalVotingPower = state.totalVotingPower; + output.proposalCreateFund = state.proposalCreateFund; + output.reinvestingFund = state.reinvestingFund; + output.totalEpochRevenue = state.totalEpochRevenue; + output.shareholderDividend = state.shareholderDividend; + output.QCAPHolderPermille = state.QCAPHolderPermille; + output.reinvestingPermille = state.reinvestingPermille; + output.burnPermille = state.burnPermille; + output.qcapBurnPermille = state.qcapBurnPermille; + output.numberOfStaker = state.numberOfStaker; + output.numberOfVotingPower = state.numberOfVotingPower; + output.qcapSoldAmount = state.qcapSoldAmount; + output.numberOfGP = state.numberOfGP; + output.numberOfQCP = state.numberOfQCP; + output.numberOfIPOP = state.numberOfIPOP; + output.numberOfQEarnP = state.numberOfQEarnP; + output.numberOfFundP = state.numberOfFundP; + output.numberOfMKTP = state.numberOfMKTP; + output.numberOfAlloP = state.numberOfAlloP; + output.transferRightsFee = state.transferRightsFee; + output.fundForBurn = state.fundForBurn; + output.totalStakedQcapAmount = state.totalStakedQcapAmount; + output.minQuorumRq = QVAULT_MIN_QUORUM_REQ; + output.maxQuorumRq = QVAULT_MAX_QUORUM_REQ; + output.totalQcapBurntAmount = state.totalQcapBurntAmount; + output.raisedFundByQcap = state.rasiedFundByQcap; + + locals.qcapAsset.assetName = QVAULT_QCAP_ASSETNAME; + locals.qcapAsset.issuer = state.QCAP_ISSUER; + + output.circulatingSupply = (uint32)(qpi.numberOfShares(locals.qcapAsset) - qpi.numberOfShares(locals.qcapAsset, AssetOwnershipSelect::byOwner(SELF), AssetPossessionSelect::byPossessor(SELF)) + state.totalStakedQcapAmount); + for (locals._t = state.numberOfFundP - 1; locals._t >= 0; locals._t--) + { + if (state.FundP.get(locals._t).result == 0 && state.FundP.get(locals._t).proposedEpoch + 1 < qpi.epoch()) + { + output.qcapMarketCap = output.circulatingSupply * state.FundP.get(locals._t).pricePerOneQcap; + break; + } + } + output.lastRoundPriceOfQcap = state.lastRoundPriceOfQcap; + output.revenueByQearn = state.revenueByQearn; + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for stake function + */ + struct stake_locals + { + stakingInfo user; // User staking information + sint32 _t; // Loop counter variable + bool isNewStaker; // Flag to check if the user is a new staker + }; + + /** + * Stakes QCAP tokens to earn voting power and revenue + * Transfers QCAP tokens from user to contract and updates staking records + * + * @param input Amount of QCAP tokens to stake + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(stake) + { + if (input.amount > (uint32)qpi.numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, state.QCAP_ISSUER, qpi.invocator(), qpi.invocator(), SELF_INDEX, SELF_INDEX)) + { + output.returnCode = QVAULT_INSUFFICIENT_QCAP; + return ; + } + + for (locals._t = 0 ; locals._t < (sint32)state.numberOfStaker; locals._t++) + { + if (state.staker.get(locals._t).stakerAddress == qpi.invocator()) + { + break; + } + } + + if (locals._t == state.numberOfStaker) + { + if (state.numberOfStaker >= QVAULT_X_MULTIPLIER) + { + output.returnCode = QVAULT_OVERFLOW_STAKER; + return ; + } + locals.user.amount = input.amount; + locals.user.stakerAddress = qpi.invocator(); + state.numberOfStaker++; + locals.isNewStaker = 1; + } + else + { + locals.user.amount = state.staker.get(locals._t).amount + input.amount; + locals.user.stakerAddress = state.staker.get(locals._t).stakerAddress; + } + + if (qpi.transferShareOwnershipAndPossession(QVAULT_QCAP_ASSETNAME, state.QCAP_ISSUER, qpi.invocator(), qpi.invocator(), input.amount, SELF) < 0) + { + if (locals.isNewStaker == 1) + { + state.numberOfStaker--; + } + output.returnCode = QVAULT_ERROR_TRANSFER_ASSET; + return ; + } + + state.totalStakedQcapAmount += input.amount; + state.staker.set(locals._t, locals.user); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for unStake function + */ + struct unStake_locals + { + stakingInfo user; // User staking information + sint32 _t; // Loop counter variable + }; + + /** + * Unstakes QCAP tokens, reducing voting power + * Transfers QCAP tokens back to user and updates staking records + * + * @param input Amount of QCAP tokens to unstake + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(unStake) + { + if (input.amount == 0) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + + for (locals._t = 0 ; locals._t < (sint32)state.numberOfStaker; locals._t++) + { + if (state.staker.get(locals._t).stakerAddress == qpi.invocator()) + { + if (state.staker.get(locals._t).amount < input.amount) + { + output.returnCode = QVAULT_NOT_ENOUGH_STAKE; + } + else if (state.staker.get(locals._t).amount >= input.amount) + { + if (qpi.transferShareOwnershipAndPossession(QVAULT_QCAP_ASSETNAME, state.QCAP_ISSUER, SELF, SELF, input.amount, qpi.invocator()) < 0) + { + output.returnCode = QVAULT_INSUFFICIENT_QCAP; + return ; + } + + locals.user.stakerAddress = state.staker.get(locals._t).stakerAddress; + locals.user.amount = state.staker.get(locals._t).amount - input.amount; + state.staker.set(locals._t, locals.user); + + state.totalStakedQcapAmount -= input.amount; + output.returnCode = QVAULT_SUCCESS; + if (locals.user.amount == 0) + { + state.numberOfStaker--; + state.staker.set(locals._t, state.staker.get(state.numberOfStaker)); + } + } + return ; + } + } + + output.returnCode = QVAULT_NOT_STAKER; + } + + /** + * Local variables for submitGP function + */ + struct submitGP_locals + { + Asset qvaultShare; // QVAULT share asset information + GPInfo newProposal; // New general proposal to create + sint32 _t; // Loop counter variable + }; + + /** + * Submits a General Proposal (GP) for governance voting + * Requires minimum voting power (10000) or QVAULT shares + * Charges proposal fee and creates new proposal record + * + * @param input URL containing proposal details + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(submitGP) + { + locals.qvaultShare.assetName = QVAULT_QVAULT_ASSETNAME; + locals.qvaultShare.issuer = NULL_ID; + + for (locals._t = 0 ; locals._t < (sint64)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == qpi.invocator()) + { + if (state.votingPower.get(locals._t).amount >= QVAULT_MIN_VOTING_POWER) + { + break; + } + else + { + if (qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + output.returnCode = QVAULT_INSUFFICIENT_VOTING_POWER; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + break; + } + } + } + + if(locals._t == state.numberOfVotingPower && qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_NO_VOTING_POWER; + return ; + } + + if (qpi.invocationReward() < QVAULT_PROPOSAL_FEE) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + if (state.numberOfGP >= QVAULT_MAX_NUMBER_OF_PROPOSAL) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + if (qpi.invocationReward() > QVAULT_PROPOSAL_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - QVAULT_PROPOSAL_FEE); + } + state.proposalCreateFund += QVAULT_PROPOSAL_FEE; + + locals.newProposal.currentQuorumPercent = state.quorumPercent; + locals.newProposal.currentTotalVotingPower = state.totalVotingPower; + for (locals._t = 0; locals._t < QVAULT_MAX_URLS_COUNT; locals._t++) + { + locals.newProposal.url.set(locals._t, input.url.get(locals._t)); + } + locals.newProposal.proposedEpoch = qpi.epoch(); + locals.newProposal.numberOfYes = 0; + locals.newProposal.numberOfNo = 0; + locals.newProposal.proposer = qpi.invocator(); + locals.newProposal.result = QVAULT_PROPOSAL_NOT_STARTED; + + state.GP.set(state.numberOfGP++, locals.newProposal); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for submitQCP function + */ + struct submitQCP_locals + { + Asset qvaultShare; // QVAULT share asset information + QCPInfo newProposal; // New quorum change proposal to create + sint32 _t; // Loop counter variable + }; + + /** + * Submits a Quorum Change Proposal (QCP) to modify voting quorum percentage + * Requires minimum voting power (10000) or QVAULT shares + * Charges proposal fee and creates new proposal record + * + * @param input New quorum percentage and URL containing proposal details + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(submitQCP) + { + if (input.newQuorumPercent > QVAULT_MAX_QUORUM_REQ || input.newQuorumPercent < QVAULT_MIN_QUORUM_REQ) + { + output.returnCode = QVAULT_INPUT_ERROR; + return; + } + + locals.qvaultShare.assetName = QVAULT_QVAULT_ASSETNAME; + locals.qvaultShare.issuer = NULL_ID; + + for (locals._t = 0 ; locals._t < (sint64)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == qpi.invocator()) + { + if (state.votingPower.get(locals._t).amount >= QVAULT_MIN_VOTING_POWER) + { + break; + } + else + { + if (qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + output.returnCode = QVAULT_INSUFFICIENT_VOTING_POWER; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + break; + } + } + } + + if(locals._t == state.numberOfVotingPower && qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_NO_VOTING_POWER; + return ; + } + + if (qpi.invocationReward() < QVAULT_PROPOSAL_FEE) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + if (state.numberOfQCP >= QVAULT_MAX_NUMBER_OF_PROPOSAL) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + if (qpi.invocationReward() > QVAULT_PROPOSAL_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - QVAULT_PROPOSAL_FEE); + } + state.proposalCreateFund += QVAULT_PROPOSAL_FEE; + + locals.newProposal.currentQuorumPercent = state.quorumPercent; + locals.newProposal.currentTotalVotingPower = state.totalVotingPower; + for (locals._t = 0; locals._t < QVAULT_MAX_URLS_COUNT; locals._t++) + { + locals.newProposal.url.set(locals._t, input.url.get(locals._t)); + } + locals.newProposal.proposedEpoch = qpi.epoch(); + locals.newProposal.numberOfYes = 0; + locals.newProposal.numberOfNo = 0; + locals.newProposal.proposer = qpi.invocator(); + locals.newProposal.result = QVAULT_PROPOSAL_NOT_STARTED; + + locals.newProposal.newQuorumPercent = input.newQuorumPercent; + + state.QCP.set(state.numberOfQCP++, locals.newProposal); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for submitIPOP function + */ + struct submitIPOP_locals + { + Asset qvaultShare; // QVAULT share asset information + IPOPInfo newProposal; // New IPO participation proposal to create + sint32 _t; // Loop counter variable + }; + + /** + * Submits an IPO Participation Proposal (IPOP) to participate in IPO contracts + * Requires minimum voting power (10000) or QVAULT shares + * Requires sufficient reinvesting fund (minimum 1B qubic) + * Charges proposal fee and creates new proposal record + * + * @param input IPO contract index and URL containing proposal details + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(submitIPOP) + { + if (state.reinvestingFund < QVAULT_IPO_PARTICIPATION_MIN_FUND) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + locals.qvaultShare.assetName = QVAULT_QVAULT_ASSETNAME; + locals.qvaultShare.issuer = NULL_ID; + + for (locals._t = 0 ; locals._t < (sint64)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == qpi.invocator()) + { + if (state.votingPower.get(locals._t).amount >= QVAULT_MIN_VOTING_POWER) + { + break; + } + else + { + if (qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + output.returnCode = QVAULT_INSUFFICIENT_VOTING_POWER; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + break; + } + } + } + + if(locals._t == state.numberOfVotingPower && qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_NO_VOTING_POWER; + return ; + } + + if (qpi.invocationReward() < QVAULT_PROPOSAL_FEE) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + if (state.numberOfIPOP >= QVAULT_MAX_NUMBER_OF_PROPOSAL) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + if (qpi.invocationReward() > QVAULT_PROPOSAL_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - QVAULT_PROPOSAL_FEE); + } + state.proposalCreateFund += QVAULT_PROPOSAL_FEE; + + locals.newProposal.currentQuorumPercent = state.quorumPercent; + locals.newProposal.currentTotalVotingPower = state.totalVotingPower; + for (locals._t = 0; locals._t < QVAULT_MAX_URLS_COUNT; locals._t++) + { + locals.newProposal.url.set(locals._t, input.url.get(locals._t)); + } + locals.newProposal.proposedEpoch = qpi.epoch(); + locals.newProposal.numberOfYes = 0; + locals.newProposal.numberOfNo = 0; + locals.newProposal.proposer = qpi.invocator(); + locals.newProposal.result = QVAULT_PROPOSAL_NOT_STARTED; + locals.newProposal.assignedFund = 0; + + locals.newProposal.ipoContractIndex = input.ipoContractIndex; + locals.newProposal.totalWeight = 0; + + state.IPOP.set(state.numberOfIPOP++, locals.newProposal); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for submitQEarnP function + */ + struct submitQEarnP_locals + { + Asset qvaultShare; // QVAULT share asset information + QEarnPInfo newProposal; // New QEarn participation proposal to create + sint32 _t; // Loop counter variable + }; + + /** + * Submits a QEarn Participation Proposal (QEarnP) to invest in QEarn contracts + * Requires minimum voting power (10000) or QVAULT shares + * Maximum participation period is 52 epochs + * Requires sufficient reinvesting fund for the investment amount + * Charges proposal fee and creates new proposal record + * + * @param input Investment amount per epoch, number of epochs, and URL containing proposal details + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(submitQEarnP) + { + if (input.numberOfEpoch > 52) + { + output.returnCode = QVAULT_INPUT_ERROR; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + if (input.amountPerEpoch * input.numberOfEpoch > state.reinvestingFund) + { + output.returnCode = QVAULT_INSUFFICIENT_FUND; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + locals.qvaultShare.assetName = QVAULT_QVAULT_ASSETNAME; + locals.qvaultShare.issuer = NULL_ID; + + for (locals._t = 0 ; locals._t < (sint64)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == qpi.invocator()) + { + if (state.votingPower.get(locals._t).amount >= QVAULT_MIN_VOTING_POWER) + { + break; + } + else + { + if (qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + output.returnCode = QVAULT_INSUFFICIENT_VOTING_POWER; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + break; + } + } + } + + + if(locals._t == state.numberOfVotingPower && qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_NO_VOTING_POWER; + return ; + } + + if (qpi.invocationReward() < QVAULT_PROPOSAL_FEE) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + if (state.numberOfQEarnP >= QVAULT_MAX_NUMBER_OF_PROPOSAL) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + if (qpi.invocationReward() > QVAULT_PROPOSAL_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - QVAULT_PROPOSAL_FEE); + } + state.proposalCreateFund += QVAULT_PROPOSAL_FEE; + + locals.newProposal.currentQuorumPercent = state.quorumPercent; + locals.newProposal.currentTotalVotingPower = state.totalVotingPower; + locals.newProposal.proposedEpoch = qpi.epoch(); + locals.newProposal.numberOfYes = 0; + locals.newProposal.numberOfNo = 0; + locals.newProposal.proposer = qpi.invocator(); + locals.newProposal.result = QVAULT_PROPOSAL_NOT_STARTED; + + locals.newProposal.assignedFundPerEpoch = input.amountPerEpoch; + locals.newProposal.amountOfInvestPerEpoch = input.amountPerEpoch; + locals.newProposal.numberOfEpoch = input.numberOfEpoch; + for (locals._t = 0; locals._t < QVAULT_MAX_URLS_COUNT; locals._t++) + { + locals.newProposal.url.set(locals._t, input.url.get(locals._t)); + } + + state.QEarnP.set(state.numberOfQEarnP++, locals.newProposal); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for submitFundP function + */ + struct submitFundP_locals + { + Asset qvaultShare; // QVAULT share asset information + FundPInfo newProposal; // New fundraising proposal to create + sint32 _t; // Loop counter variable + uint32 curDate; // Current date (packed) + uint8 year; // Current year + uint8 month; // Current month + uint8 day; // Current day + uint8 hour; // Current hour + uint8 minute; // Current minute + uint8 second; // Current second + }; + + /** + * Submits a Fundraising Proposal (FundP) to sell QCAP tokens + * Requires minimum voting power (10000) or QVAULT shares + * Validates yearly QCAP sale limits (2025-2027) + * Charges proposal fee and creates new proposal record + * + * @param input Price per QCAP, amount to sell, and URL containing proposal details + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(submitFundP) + { + locals.qvaultShare.assetName = QVAULT_QVAULT_ASSETNAME; + locals.qvaultShare.issuer = NULL_ID; + + for (locals._t = 0 ; locals._t < (sint64)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == qpi.invocator()) + { + if (state.votingPower.get(locals._t).amount >= QVAULT_MIN_VOTING_POWER) + { + break; + } + else + { + if (qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + output.returnCode = QVAULT_INSUFFICIENT_VOTING_POWER; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + break; + } + } + } + + if(locals._t == state.numberOfVotingPower && qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_NO_VOTING_POWER; + return ; + } + + packQvaultDate(qpi.year(), qpi.month(), qpi.day(), qpi.hour(), qpi.minute(), qpi.second(), locals.curDate); + unpackQvaultDate(locals.year, locals.month, locals.day, locals.hour, locals.minute, locals.second, locals.curDate); + if (locals.year == 25 && state.qcapSoldAmount + input.amountOfQcap > QVAULT_2025MAX_QCAP_SALE_AMOUNT) + { + output.returnCode = QVAULT_OVERFLOW_SALE_AMOUNT; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + else if (locals.year == 26 && state.qcapSoldAmount + input.amountOfQcap > QVAULT_2026MAX_QCAP_SALE_AMOUNT) + { + output.returnCode = QVAULT_OVERFLOW_SALE_AMOUNT; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + else if (locals.year == 27 && state.qcapSoldAmount + input.amountOfQcap > QVAULT_2027MAX_QCAP_SALE_AMOUNT) + { + output.returnCode = QVAULT_OVERFLOW_SALE_AMOUNT; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + else if (state.qcapSoldAmount + input.amountOfQcap > QVAULT_QCAP_MAX_SUPPLY) + { + output.returnCode = QVAULT_OVERFLOW_SALE_AMOUNT; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + + if (qpi.invocationReward() < QVAULT_PROPOSAL_FEE) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + if (state.numberOfFundP >= QVAULT_MAX_NUMBER_OF_PROPOSAL) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + if (qpi.invocationReward() > QVAULT_PROPOSAL_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - QVAULT_PROPOSAL_FEE); + } + state.proposalCreateFund += QVAULT_PROPOSAL_FEE; + + locals.newProposal.currentQuorumPercent = state.quorumPercent; + locals.newProposal.currentTotalVotingPower = state.totalVotingPower; + for (locals._t = 0; locals._t < QVAULT_MAX_URLS_COUNT; locals._t++) + { + locals.newProposal.url.set(locals._t, input.url.get(locals._t)); + } + locals.newProposal.proposedEpoch = qpi.epoch(); + locals.newProposal.numberOfYes = 0; + locals.newProposal.numberOfNo = 0; + locals.newProposal.proposer = qpi.invocator(); + locals.newProposal.result = QVAULT_PROPOSAL_NOT_STARTED; + + locals.newProposal.restSaleAmount = input.amountOfQcap; + locals.newProposal.amountOfQcap = input.amountOfQcap; + locals.newProposal.pricePerOneQcap = input.priceOfOneQcap; + + state.FundP.set(state.numberOfFundP++, locals.newProposal); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for submitMKTP function + */ + struct submitMKTP_locals + { + Asset qvaultShare; // QVAULT share asset information + MKTPInfo newProposal; // New marketplace proposal to create + sint32 _t; // Loop counter variable + }; + + /** + * Submits a Marketplace Proposal (MKTP) to purchase shares from marketplace + * Requires proposal fee payment + * Transfers shares from user to contract as collateral + * Creates new proposal record for voting + * + * @param input Qubic amount, share details, QCAP amount, and URL containing proposal details + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(submitMKTP) + { + if (qpi.invocationReward() < QVAULT_PROPOSAL_FEE) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + if (qpi.transferShareOwnershipAndPossession(input.shareName, NULL_ID, qpi.invocator(), qpi.invocator(), input.amountOfShare, SELF) < 0) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_NOT_TRANSFERRED_SHARE; + return ; + } + if (state.numberOfMKTP >= QVAULT_MAX_NUMBER_OF_PROPOSAL) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + if (qpi.invocationReward() > QVAULT_PROPOSAL_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - QVAULT_PROPOSAL_FEE); + } + state.proposalCreateFund += QVAULT_PROPOSAL_FEE; + + locals.newProposal.currentQuorumPercent = state.quorumPercent; + locals.newProposal.currentTotalVotingPower = state.totalVotingPower; + for (locals._t = 0; locals._t < QVAULT_MAX_URLS_COUNT; locals._t++) + { + locals.newProposal.url.set(locals._t, input.url.get(locals._t)); + } + locals.newProposal.proposedEpoch = qpi.epoch(); + locals.newProposal.numberOfYes = 0; + locals.newProposal.numberOfNo = 0; + locals.newProposal.proposer = qpi.invocator(); + locals.newProposal.result = QVAULT_PROPOSAL_NOT_STARTED; + + locals.newProposal.shareIndex = input.indexOfShare; + locals.newProposal.amountOfShare = input.amountOfShare; + locals.newProposal.amountOfQubic = input.amountOfQubic; + locals.newProposal.amountOfQcap = input.amountOfQcap; + locals.newProposal.shareName = input.shareName; + + state.MKTP.set(state.numberOfMKTP++, locals.newProposal); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for submitAlloP function + */ + struct submitAlloP_locals + { + Asset qvaultShare; // QVAULT share asset information + AlloPInfo newProposal; // New allocation proposal to create + sint32 _t; // Loop counter variable + uint32 curDate; // Current date (packed) + uint8 year; // Current year + uint8 month; // Current month + uint8 day; // Current day + uint8 hour; // Current hour + uint8 minute; // Current minute + uint8 second; // Current second + }; + + /** + * Submits an Allocation Proposal (AlloP) to change revenue allocation percentages + * Requires minimum voting power (10000) or QVAULT shares + * Validates allocation percentages sum to 970 (per mille) + * Charges proposal fee and creates new proposal record + * + * @param input Allocation percentages and URL containing proposal details + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(submitAlloP) + { + locals.qvaultShare.assetName = QVAULT_QVAULT_ASSETNAME; + locals.qvaultShare.issuer = NULL_ID; + + for (locals._t = 0 ; locals._t < (sint64)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == qpi.invocator()) + { + if (state.votingPower.get(locals._t).amount >= QVAULT_MIN_VOTING_POWER) + { + break; + } + else + { + if (qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + output.returnCode = QVAULT_INSUFFICIENT_VOTING_POWER; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + break; + } + } + } + + if(locals._t == state.numberOfVotingPower && qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(qpi.invocator()), AssetPossessionSelect::byPossessor(qpi.invocator())) <= 0) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_NO_VOTING_POWER; + return ; + } + + packQvaultDate(qpi.year(), qpi.month(), qpi.day(), qpi.hour(), qpi.minute(), qpi.second(), locals.curDate); + unpackQvaultDate(locals.year, locals.month, locals.day, locals.hour, locals.minute, locals.second, locals.curDate); + if (locals.year < 29 && input.burn != 0) + { + output.returnCode = QVAULT_NOT_IN_TIME; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + + if (input.burn + input.distribute + input.reinvested != QVAULT_SUM_OF_ALLOCATION_PERCENTAGES) + { + output.returnCode = QVAULT_NOT_FAIR; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + + if (qpi.invocationReward() < QVAULT_PROPOSAL_FEE) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + if (state.numberOfAlloP >= QVAULT_MAX_NUMBER_OF_PROPOSAL) + { + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + if (qpi.invocationReward() > QVAULT_PROPOSAL_FEE) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - QVAULT_PROPOSAL_FEE); + } + state.proposalCreateFund += QVAULT_PROPOSAL_FEE; + + locals.newProposal.currentQuorumPercent = state.quorumPercent; + locals.newProposal.currentTotalVotingPower = state.totalVotingPower; + for (locals._t = 0; locals._t < QVAULT_MAX_URLS_COUNT; locals._t++) + { + locals.newProposal.url.set(locals._t, input.url.get(locals._t)); + } + locals.newProposal.proposedEpoch = qpi.epoch(); + locals.newProposal.numberOfYes = 0; + locals.newProposal.numberOfNo = 0; + locals.newProposal.proposer = qpi.invocator(); + locals.newProposal.result = QVAULT_PROPOSAL_NOT_STARTED; + + locals.newProposal.burnQcap = input.burn; + locals.newProposal.distributed = input.distribute; + locals.newProposal.reinvested = input.reinvested; + + state.AlloP.set(state.numberOfAlloP++, locals.newProposal); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Local variables for voteInProposal function + */ + struct voteInProposal_locals + { + GPInfo updatedGProposal; // Updated general proposal + QCPInfo updatedQCProposal; // Updated quorum change proposal + IPOPInfo updatedIPOProposal; // Updated IPO participation proposal + QEarnPInfo updatedQEarnProposal; // Updated QEarn participation proposal + FundPInfo updatedFundProposal; // Updated fundraising proposal + MKTPInfo updatedMKTProposal; // Updated marketplace proposal + AlloPInfo updatedAlloProposal; // Updated allocation proposal + Array newVoteList; // Updated vote list for user + voteStatusInfo newVote; // New vote to add + sint32 numberOfYes; // Number of yes votes to add + sint32 numberOfNo; // Number of no votes to add + sint32 _t, _r; // Loop counter variables + uint8 countOfVote; // Current vote count for user + bit statusOfProposal; // Whether proposal is still active + }; + + /** + * Votes on active proposals of various types + * Supports 7 different proposal types with different voting logic + * Updates proposal vote counts and user voting history + * Prevents duplicate votes and enforces voting rules + * + * @param input Proposal type, ID, voting decision, and IPO price (if applicable) + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(voteInProposal) + { + if (input.proposalType > 7 || input.proposalType < 1) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + switch (input.proposalType) + { + case 1: + if (input.proposalId >= state.numberOfGP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 2: + if (input.proposalId >= state.numberOfQCP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 3: + if (input.proposalId >= state.numberOfIPOP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + if (input.yes && (input.priceOfIPO < QVAULT_IPO_PARTICIPATION_MIN_FUND || input.priceOfIPO > state.reinvestingFund)) + { + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return; + } + break; + case 4: + if (input.proposalId >= state.numberOfQEarnP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 5: + if (input.proposalId >= state.numberOfFundP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 6: + if (input.proposalId >= state.numberOfMKTP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 7: + if (input.proposalId >= state.numberOfAlloP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + } + + if (state.countOfVote.get(qpi.invocator(), locals.countOfVote)) + { + state.vote.get(qpi.invocator(), locals.newVoteList); + for (locals._r = 0; locals._r < locals.countOfVote; locals._r++) + { + if (locals.newVoteList.get(locals._r).proposalId == input.proposalId && locals.newVoteList.get(locals._r).proposalType == input.proposalType) + { + break; + } + } + } + if (locals.countOfVote == QVAULT_MAX_USER_VOTES && locals._r == locals.countOfVote) + { + output.returnCode = QVAULT_OVERFLOW_VOTES; + return ; + } + if (locals._r < locals.countOfVote && locals.newVoteList.get(locals._r).decision == input.yes) + { + output.returnCode = QVAULT_SAME_DECISION; + return ; + } + + switch (input.proposalType) + { + case 1: + locals.updatedGProposal = state.GP.get(input.proposalId); + locals.statusOfProposal = state.GP.get(input.proposalId).proposedEpoch == qpi.epoch() ? 1 : 0; + break; + case 2: + locals.updatedQCProposal = state.QCP.get(input.proposalId); + locals.statusOfProposal = state.QCP.get(input.proposalId).proposedEpoch == qpi.epoch() ? 1 : 0; + break; + case 3: + locals.updatedIPOProposal = state.IPOP.get(input.proposalId); + locals.statusOfProposal = state.IPOP.get(input.proposalId).proposedEpoch == qpi.epoch() ? 1 : 0; + break; + case 4: + locals.updatedQEarnProposal = state.QEarnP.get(input.proposalId); + locals.statusOfProposal = state.QEarnP.get(input.proposalId).proposedEpoch == qpi.epoch() ? 1 : 0; + break; + case 5: + locals.updatedFundProposal = state.FundP.get(input.proposalId); + locals.statusOfProposal = state.FundP.get(input.proposalId).proposedEpoch == qpi.epoch() ? 1 : 0; + break; + case 6: + locals.updatedMKTProposal = state.MKTP.get(input.proposalId); + locals.statusOfProposal = state.MKTP.get(input.proposalId).proposedEpoch == qpi.epoch() ? 1 : 0; + break; + case 7: + locals.updatedAlloProposal = state.AlloP.get(input.proposalId); + locals.statusOfProposal = state.AlloP.get(input.proposalId).proposedEpoch == qpi.epoch() ? 1 : 0; + break; + default: + break; + } + + if (locals.statusOfProposal == 0) + { + output.returnCode = QVAULT_ENDED_PROPOSAL; + return ; + } + + if (locals.statusOfProposal == 1) + { + locals.numberOfYes = 0; + locals.numberOfNo = 0; + for (locals._t = 0 ; locals._t < (sint32)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == qpi.invocator()) + { + if (input.yes == 1) + { + locals.numberOfYes = state.votingPower.get(locals._t).amount; + if (locals._r < locals.countOfVote) + { + locals.numberOfNo -= state.votingPower.get(locals._t).amount; + } + } + else + { + locals.numberOfNo = state.votingPower.get(locals._t).amount; + if (locals._r < locals.countOfVote) + { + locals.numberOfYes -= state.votingPower.get(locals._t).amount; + } + } + switch (input.proposalType) + { + case 1: + locals.updatedGProposal.numberOfYes += locals.numberOfYes; + locals.updatedGProposal.numberOfNo += locals.numberOfNo; + state.GP.set(input.proposalId, locals.updatedGProposal); + break; + case 2: + locals.updatedQCProposal.numberOfYes += locals.numberOfYes; + locals.updatedQCProposal.numberOfNo += locals.numberOfNo; + state.QCP.set(input.proposalId, locals.updatedQCProposal); + break; + case 3: + if (input.yes) + { + locals.updatedIPOProposal.totalWeight += locals.numberOfYes * input.priceOfIPO; + } + else if (locals._r < locals.countOfVote) + { + locals.updatedIPOProposal.totalWeight -= locals.numberOfNo * locals.newVoteList.get(locals._r).priceOfIPO; + } + locals.updatedIPOProposal.numberOfYes += locals.numberOfYes; + locals.updatedIPOProposal.numberOfNo += locals.numberOfNo; + state.IPOP.set(input.proposalId, locals.updatedIPOProposal); + break; + case 4: + locals.updatedQEarnProposal.numberOfYes += locals.numberOfYes; + locals.updatedQEarnProposal.numberOfNo += locals.numberOfNo; + state.QEarnP.set(input.proposalId, locals.updatedQEarnProposal); + break; + case 5: + locals.updatedFundProposal.numberOfYes += locals.numberOfYes; + locals.updatedFundProposal.numberOfNo += locals.numberOfNo; + state.FundP.set(input.proposalId, locals.updatedFundProposal); + break; + case 6: + locals.updatedMKTProposal.numberOfYes += locals.numberOfYes; + locals.updatedMKTProposal.numberOfNo += locals.numberOfNo; + state.MKTP.set(input.proposalId, locals.updatedMKTProposal); + break; + case 7: + locals.updatedAlloProposal.numberOfYes += locals.numberOfYes; + locals.updatedAlloProposal.numberOfNo += locals.numberOfNo; + state.AlloP.set(input.proposalId, locals.updatedAlloProposal); + break; + default: + break; + } + if (state.countOfVote.get(qpi.invocator(), locals.countOfVote)) + { + locals.newVote.proposalId = input.proposalId; + locals.newVote.proposalType = input.proposalType; + locals.newVote.decision = input.yes; + locals.newVote.priceOfIPO = input.priceOfIPO; + if (locals._r < locals.countOfVote) + { + locals.newVoteList.set(locals._r, locals.newVote); + } + else + { + locals.newVoteList.set(locals.countOfVote, locals.newVote); + state.countOfVote.set(qpi.invocator(), locals.countOfVote + 1); + } + } + else + { + locals.newVote.proposalId = input.proposalId; + locals.newVote.proposalType = input.proposalType; + locals.newVote.decision = input.yes; + locals.newVote.priceOfIPO = input.priceOfIPO; + locals.newVoteList.set(0, locals.newVote); + state.countOfVote.set(qpi.invocator(), 1); + } + state.vote.set(qpi.invocator(), locals.newVoteList); + output.returnCode = QVAULT_SUCCESS; + return ; + } + } + } + + output.returnCode = QVAULT_NO_VOTING_POWER; + } + + /** + * Local variables for buyQcap function + */ + struct buyQcap_locals + { + QX::TransferShareManagementRights_input transferShareManagementRights_input; // Input for QX contract call + QX::TransferShareManagementRights_output transferShareManagementRights_output; // Output from QX contract call + FundPInfo updatedFundProposal; // Updated fundraising proposal + sint32 _t; // Loop counter variable + uint32 curDate; // Current date (packed) + uint8 year; // Current year + uint8 month; // Current month + uint8 day; // Current day + uint8 hour; // Current hour + uint8 minute; // Current minute + uint8 second; // Current second + }; + + /** + * Purchases QCAP tokens from active fundraising proposals + * Validates yearly QCAP sale limits (2025-2027) + * Finds best available price from passed fundraising proposals + * Transfers QCAP tokens to buyer and updates proposal sale amounts + * + * @param input Amount of QCAP tokens to purchase + * @param output Status code indicating success or failure + */ + PUBLIC_PROCEDURE_WITH_LOCALS(buyQcap) + { + packQvaultDate(qpi.year(), qpi.month(), qpi.day(), qpi.hour(), qpi.minute(), qpi.second(), locals.curDate); + unpackQvaultDate(locals.year, locals.month, locals.day, locals.hour, locals.minute, locals.second, locals.curDate); + if (locals.year == 25 && state.qcapSoldAmount + input.amount > QVAULT_2025MAX_QCAP_SALE_AMOUNT) + { + output.returnCode = QVAULT_OVERFLOW_SALE_AMOUNT; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + else if (locals.year == 26 && state.qcapSoldAmount + input.amount > QVAULT_2026MAX_QCAP_SALE_AMOUNT) + { + output.returnCode = QVAULT_OVERFLOW_SALE_AMOUNT; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + else if (locals.year == 27 && state.qcapSoldAmount + input.amount > QVAULT_2027MAX_QCAP_SALE_AMOUNT) + { + output.returnCode = QVAULT_OVERFLOW_SALE_AMOUNT; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + else if (state.qcapSoldAmount + input.amount > QVAULT_QCAP_MAX_SUPPLY) + { + output.returnCode = QVAULT_OVERFLOW_SALE_AMOUNT; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + return ; + } + + for (locals._t = state.numberOfFundP - 1; locals._t >= 0; locals._t--) + { + if (state.FundP.get(locals._t).result == 0 && state.FundP.get(locals._t).proposedEpoch + 1 < qpi.epoch() && state.FundP.get(locals._t).restSaleAmount >= input.amount) + { + if (qpi.invocationReward() >= (sint64)state.FundP.get(locals._t).pricePerOneQcap * input.amount) + { + if (qpi.invocationReward() > (sint64)state.FundP.get(locals._t).pricePerOneQcap * input.amount) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - (state.FundP.get(locals._t).pricePerOneQcap * input.amount)); + } + + state.rasiedFundByQcap += state.FundP.get(locals._t).pricePerOneQcap * input.amount; + locals.transferShareManagementRights_input.asset.assetName = QVAULT_QCAP_ASSETNAME; + locals.transferShareManagementRights_input.asset.issuer = state.QCAP_ISSUER; + locals.transferShareManagementRights_input.newManagingContractIndex = SELF_INDEX; + locals.transferShareManagementRights_input.numberOfShares = input.amount; + + INVOKE_OTHER_CONTRACT_PROCEDURE(QX, TransferShareManagementRights, locals.transferShareManagementRights_input, locals.transferShareManagementRights_output, 0); + + qpi.transferShareOwnershipAndPossession(QVAULT_QCAP_ASSETNAME, state.QCAP_ISSUER, SELF, SELF, input.amount, qpi.invocator()); + + state.qcapSoldAmount += input.amount; + locals.updatedFundProposal = state.FundP.get(locals._t); + locals.updatedFundProposal.restSaleAmount -= input.amount; + state.FundP.set(locals._t, locals.updatedFundProposal); + + state.reinvestingFund += state.FundP.get(locals._t).pricePerOneQcap * input.amount; + output.returnCode = QVAULT_SUCCESS; + return ; + } + } + } + + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + + output.returnCode = QVAULT_FAILED; + } + + /** + * Transfers share management rights to another contract + * Requires payment of transfer rights fee + * Releases shares from current contract to new managing contract + * Used for QCAP token transfers and other asset management + * + * @param input Asset information, number of shares, and new managing contract index + * @param output Number of shares transferred and status code + */ + PUBLIC_PROCEDURE(TransferShareManagementRights) + { + if (qpi.invocationReward() < state.transferRightsFee) + { + output.returnCode = QVAULT_INSUFFICIENT_FUND; + return ; + } + + if (qpi.numberOfPossessedShares(input.asset.assetName, input.asset.issuer,qpi.invocator(), qpi.invocator(), SELF_INDEX, SELF_INDEX) < input.numberOfShares) + { + // not enough shares available + output.transferredNumberOfShares = 0; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + + output.returnCode = QVAULT_INSUFFICIENT_SHARE; + } + else + { + if (qpi.releaseShares(input.asset, qpi.invocator(), qpi.invocator(), input.numberOfShares, + input.newManagingContractIndex, input.newManagingContractIndex, state.transferRightsFee) < 0) + { + // error + output.transferredNumberOfShares = 0; + if (qpi.invocationReward() > 0) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward()); + } + + output.returnCode = QVAULT_ERROR_TRANSFER_ASSET; + } + else + { + // success + output.transferredNumberOfShares = input.numberOfShares; + if (qpi.invocationReward() > state.transferRightsFee) + { + qpi.transfer(qpi.invocator(), qpi.invocationReward() - state.transferRightsFee); + } + + output.returnCode = QVAULT_SUCCESS; + } + } + } + +public: + /** + * Input structure for getStakedAmountAndVotingPower function + * @param address User address to query staking information for + */ + struct getStakedAmountAndVotingPower_input + { + id address; + }; + + /** + * Output structure for getStakedAmountAndVotingPower function + * @param stakedAmount Amount of QCAP tokens staked by the user + * @param votingPower Voting power of the user + */ + struct getStakedAmountAndVotingPower_output + { + sint32 returnCode; // Status code indicating success or failure + uint32 stakedAmount; + uint32 votingPower; + }; + + /** + * Local variables for getStakedAmountAndVotingPower function + */ + struct getStakedAmountAndVotingPower_locals + { + sint32 _t; // Loop counter variable + }; + + /** + * Retrieves staking information for a specific user address + * Returns both staked amount and voting power + * + * @param input User address to query + * @param output Staked amount and voting power for the user + */ + PUBLIC_FUNCTION_WITH_LOCALS(getStakedAmountAndVotingPower) + { + for (locals._t = 0; locals._t < (sint64)state.numberOfStaker; locals._t++) + { + if (state.staker.get(locals._t).stakerAddress == input.address) + { + output.stakedAmount = state.staker.get(locals._t).amount; + break; + } + } + for (locals._t = 0; locals._t < (sint64)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == input.address) + { + output.votingPower = state.votingPower.get(locals._t).amount; + break; + } + } + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getGP function + * @param proposalId ID of the general proposal to retrieve + */ + struct getGP_input + { + uint32 proposalId; + }; + + /** + * Output structure for getGP function + * @param proposal General proposal information + */ + struct getGP_output + { + sint32 returnCode; // Status code indicating success or failure + GPInfo proposal; + }; + + /** + * Retrieves information about a specific General Proposal (GP) + * + * @param input Proposal ID to query + * @param output General proposal information + */ + PUBLIC_FUNCTION(getGP) + { + if (input.proposalId >= state.numberOfGP) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + output.proposal = state.GP.get(input.proposalId); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getQCP function + * @param proposalId ID of the quorum change proposal to retrieve + */ + struct getQCP_input + { + uint32 proposalId; + }; + + /** + * Output structure for getQCP function + * @param proposal Quorum change proposal information + */ + struct getQCP_output + { + sint32 returnCode; // Status code indicating success or failure + QCPInfo proposal; + }; + + /** + * Retrieves information about a specific Quorum Change Proposal (QCP) + * + * @param input Proposal ID to query + * @param output Quorum change proposal information + */ + PUBLIC_FUNCTION(getQCP) + { + if (input.proposalId >= state.numberOfQCP) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + output.proposal = state.QCP.get(input.proposalId); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getIPOP function + * @param proposalId ID of the IPO participation proposal to retrieve + */ + struct getIPOP_input + { + uint32 proposalId; + }; + + /** + * Output structure for getIPOP function + * @param proposal IPO participation proposal information + */ + struct getIPOP_output + { + sint32 returnCode; // Status code indicating success or failure + IPOPInfo proposal; + }; + + /** + * Retrieves information about a specific IPO Participation Proposal (IPOP) + * + * @param input Proposal ID to query + * @param output IPO participation proposal information + */ + PUBLIC_FUNCTION(getIPOP) + { + if (input.proposalId >= state.numberOfIPOP) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + output.proposal = state.IPOP.get(input.proposalId); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getQEarnP function + * @param proposalId ID of the QEarn participation proposal to retrieve + */ + struct getQEarnP_input + { + uint32 proposalId; + }; + + /** + * Output structure for getQEarnP function + * @param proposal QEarn participation proposal information + */ + struct getQEarnP_output + { + sint32 returnCode; // Status code indicating success or failure + QEarnPInfo proposal; + }; + + /** + * Retrieves information about a specific QEarn Participation Proposal (QEarnP) + * + * @param input Proposal ID to query + * @param output QEarn participation proposal information + */ + PUBLIC_FUNCTION(getQEarnP) + { + if (input.proposalId >= state.numberOfQEarnP) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + output.proposal = state.QEarnP.get(input.proposalId); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getFundP function + * @param proposalId ID of the fundraising proposal to retrieve + */ + struct getFundP_input + { + uint32 proposalId; + }; + + /** + * Output structure for getFundP function + * @param proposal Fundraising proposal information + */ + struct getFundP_output + { + sint32 returnCode; // Status code indicating success or failure + FundPInfo proposal; + }; + + /** + * Retrieves information about a specific Fundraising Proposal (FundP) + * + * @param input Proposal ID to query + * @param output Fundraising proposal information + */ + PUBLIC_FUNCTION(getFundP) + { + if (input.proposalId >= state.numberOfFundP) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + output.proposal = state.FundP.get(input.proposalId); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getMKTP function + * @param proposalId ID of the marketplace proposal to retrieve + */ + struct getMKTP_input + { + uint32 proposalId; + }; + + /** + * Output structure for getMKTP function + * @param proposal Marketplace proposal information + */ + struct getMKTP_output + { + sint32 returnCode; // Status code indicating success or failure + MKTPInfo proposal; + }; + + /** + * Retrieves information about a specific Marketplace Proposal (MKTP) + * + * @param input Proposal ID to query + * @param output Marketplace proposal information + */ + PUBLIC_FUNCTION(getMKTP) + { + if (input.proposalId >= state.numberOfMKTP) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + output.proposal = state.MKTP.get(input.proposalId); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getAlloP function + * @param proposalId ID of the allocation proposal to retrieve + */ + struct getAlloP_input + { + uint32 proposalId; + }; + + /** + * Output structure for getAlloP function + * @param proposal Allocation proposal information + */ + struct getAlloP_output + { + sint32 returnCode; // Status code indicating success or failure + AlloPInfo proposal; + }; + + /** + * Retrieves information about a specific Allocation Proposal (AlloP) + * + * @param input Proposal ID to query + * @param output Allocation proposal information + */ + PUBLIC_FUNCTION(getAlloP) + { + if (input.proposalId >= state.numberOfAlloP) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + output.proposal = state.AlloP.get(input.proposalId); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getIdentitiesHvVtPw function + * @param offset Starting index for pagination + * @param count Number of identities to retrieve (max 256) + */ + struct getIdentitiesHvVtPw_input + { + uint32 offset; + uint32 count; + }; + + /** + * Output structure for getIdentitiesHvVtPw function + * @param idList Array of user addresses with voting power + * @param amountList Array of voting power amounts corresponding to each address + */ + struct getIdentitiesHvVtPw_output + { + sint32 returnCode; // Status code indicating success or failure + Array idList; + Array amountList; + }; + + /** + * Local variables for getIdentitiesHvVtPw function + */ + struct getIdentitiesHvVtPw_locals + { + sint32 _t, _r; // Loop counter variables + }; + + /** + * Retrieves a paginated list of identities that have voting power + * Returns both addresses and their corresponding voting power amounts + * Useful for governance and analytics purposes + * + * @param input Offset and count for pagination + * @param output Arrays of addresses and voting power amounts + */ + PUBLIC_FUNCTION_WITH_LOCALS(getIdentitiesHvVtPw) + { + if(input.count > QVAULT_MAX_URLS_COUNT || input.offset + input.count > QVAULT_X_MULTIPLIER) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + for (locals._t = (sint32)input.offset, locals._r = 0; locals._t < (sint32)(input.offset + input.count); locals._t++) + { + if (locals._t > (sint32)state.numberOfVotingPower) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + output.idList.set(locals._r, state.votingPower.get(locals._t).stakerAddress); + output.amountList.set(locals._r++, state.votingPower.get(locals._t).amount); + } + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for ppCreationPower function + * @param address User address to check proposal creation power for + */ + struct ppCreationPower_input + { + id address; + }; + + /** + * Output structure for ppCreationPower function + * @param status 0 = no proposal creation power, 1 = has proposal creation power + */ + struct ppCreationPower_output + { + sint32 returnCode; // Status code indicating success or failure + bit status; + }; + + /** + * Local variables for ppCreationPower function + */ + struct ppCreationPower_locals + { + Asset qvaultShare; // QVAULT share asset information + sint32 _t; // Loop counter variable + }; + + /** + * Checks if a user has the power to create proposals + * User must have either minimum voting power (10000) or QVAULT shares + * + * @param input User address to check + * @param output Status indicating whether user can create proposals + */ + PUBLIC_FUNCTION_WITH_LOCALS(ppCreationPower) + { + locals.qvaultShare.assetName = QVAULT_QVAULT_ASSETNAME; + locals.qvaultShare.issuer = NULL_ID; + + for (locals._t = 0; locals._t < (sint32)state.numberOfVotingPower; locals._t++) + { + if (state.votingPower.get(locals._t).stakerAddress == input.address) + { + if (state.votingPower.get(locals._t).amount >= QVAULT_MIN_VOTING_POWER) + { + output.returnCode = QVAULT_SUCCESS; + output.status = 1; + } + else + { + if (qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(input.address), AssetPossessionSelect::byPossessor(input.address)) > 0) + { + output.returnCode = QVAULT_SUCCESS; + output.status = 1; + } + else + { + output.returnCode = QVAULT_INSUFFICIENT_SHARE_OR_VOTING_POWER; + output.status = 0; + } + } + return ; + } + } + + if (qpi.numberOfShares(locals.qvaultShare, AssetOwnershipSelect::byOwner(input.address), AssetPossessionSelect::byPossessor(input.address)) > 0) + { + output.returnCode = QVAULT_SUCCESS; + output.status = 1; + } + else + { + output.returnCode = QVAULT_NOT_FOUND_STAKER_ADDRESS; + output.status = 0; + } + } + + /** + * Input structure for getQcapBurntAmountInLastEpoches function + * @param numberOfLastEpoches Number of recent epochs to check (max 100) + */ + struct getQcapBurntAmountInLastEpoches_input + { + uint32 numberOfLastEpoches; + }; + + /** + * Output structure for getQcapBurntAmountInLastEpoches function + * @param burntAmount Total amount of QCAP tokens burned in the specified epochs + */ + struct getQcapBurntAmountInLastEpoches_output + { + sint32 returnCode; // Status code indicating success or failure + uint32 burntAmount; + }; + + /** + * Local variables for getQcapBurntAmountInLastEpoches function + */ + struct getQcapBurntAmountInLastEpoches_locals + { + sint32 _t; // Loop counter variable + }; + + /** + * Calculates the total amount of QCAP tokens burned in recent epochs + * Useful for tracking token deflation and economic metrics + * Maximum lookback period is 100 epochs + * + * @param input Number of recent epochs to check + * @param output Total amount of QCAP tokens burned + */ + PUBLIC_FUNCTION_WITH_LOCALS(getQcapBurntAmountInLastEpoches) + { + if (input.numberOfLastEpoches > 100) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + for (locals._t = (sint32)qpi.epoch(); locals._t >= (sint32)(qpi.epoch() - input.numberOfLastEpoches); locals._t--) + { + output.burntAmount += state.burntQcapAmPerEpoch.get(locals._t); + } + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getAmountToBeSoldPerYear function + * @param year Year to check available QCAP sales for + */ + struct getAmountToBeSoldPerYear_input + { + uint32 year; + }; + + /** + * Output structure for getAmountToBeSoldPerYear function + * @param amount Amount of QCAP tokens available for sale in the specified year + */ + struct getAmountToBeSoldPerYear_output + { + uint32 amount; + }; + + /** + * Calculates the amount of QCAP tokens available for sale in a specific year + * Takes into account yearly limits and already sold amounts + * Returns 0 for years before 2025 + * + * @param input Year to check + * @param output Available QCAP amount for sale + */ + PUBLIC_FUNCTION(getAmountToBeSoldPerYear) + { + if (input.year < 2025) + { + output.amount = 0; + } + else if (input.year == 2025) + { + output.amount = QVAULT_2025MAX_QCAP_SALE_AMOUNT - state.qcapSoldAmount; + } + else if (input.year == 2026) + { + output.amount = QVAULT_2026MAX_QCAP_SALE_AMOUNT - state.qcapSoldAmount; + } + else if (input.year == 2027) + { + output.amount = QVAULT_2027MAX_QCAP_SALE_AMOUNT - state.qcapSoldAmount; + } + else if (input.year > 2027) + { + output.amount = QVAULT_QCAP_MAX_SUPPLY - state.qcapSoldAmount; + } + } + + /** + * Input structure for getTotalRevenueInQcap function + * No input parameters required + */ + struct getTotalRevenueInQcap_input + { + + }; + + /** + * Output structure for getTotalRevenueInQcap function + * @param revenue Total historical revenue in QCAP + */ + struct getTotalRevenueInQcap_output + { + uint64 revenue; + }; + + /** + * Retrieves the total historical revenue accumulated by the contract + * Represents the sum of all revenue across all epochs + * + * @param input No input parameters + * @param output Total historical revenue amount + */ + PUBLIC_FUNCTION(getTotalRevenueInQcap) + { + output.revenue = state.totalHistoryRevenue; + } + + /** + * Input structure for getRevenueInQcapPerEpoch function + * @param epoch Epoch number to retrieve revenue data for + */ + struct getRevenueInQcapPerEpoch_input + { + uint32 epoch; + }; + + /** + * Output structure for getRevenueInQcapPerEpoch function + * @param epochTotalRevenue Total revenue for the specified epoch + * @param epochOneQcapRevenue Revenue per QCAP token for the epoch + * @param epochOneQvaultRevenue Revenue per QVAULT share for the epoch + * @param epochReinvestAmount Amount allocated for reinvestment in the epoch + */ + struct getRevenueInQcapPerEpoch_output + { + uint64 epochTotalRevenue; + uint64 epochOneQcapRevenue; + uint64 epochOneQvaultRevenue; + uint64 epochReinvestAmount; + }; + + /** + * Retrieves detailed revenue information for a specific epoch + * Includes total revenue, per-token revenue, and reinvestment amounts + * Useful for historical analysis and user reward calculations + * + * @param input Epoch number to query + * @param output Detailed revenue breakdown for the epoch + */ + PUBLIC_FUNCTION(getRevenueInQcapPerEpoch) + { + output.epochTotalRevenue = state.revenueInQcapPerEpoch.get(input.epoch); + output.epochOneQcapRevenue = state.revenueForOneQcapPerEpoch.get(input.epoch); + output.epochOneQvaultRevenue = state.revenueForOneQvaultPerEpoch.get(input.epoch); + output.epochReinvestAmount = state.revenueForReinvestPerEpoch.get(input.epoch); + } + + /** + * Input structure for getRevenuePerShare function + * @param contractIndex Index of the contract to get revenue for + */ + struct getRevenuePerShare_input + { + uint32 contractIndex; + }; + + /** + * Output structure for getRevenuePerShare function + * @param revenue Revenue amount for the specified contract + */ + struct getRevenuePerShare_output + { + uint64 revenue; + }; + + /** + * Retrieves the revenue amount for a specific contract index + * Used for tracking revenue distribution across different contracts + * + * @param input Contract index to query + * @param output Revenue amount for the contract + */ + PUBLIC_FUNCTION(getRevenuePerShare) + { + output.revenue = state.revenuePerShare.get(input.contractIndex); + } + + /** + * Input structure for getAmountOfShareQvaultHold function + * @param assetInfo Asset information (name and issuer) to check + */ + struct getAmountOfShareQvaultHold_input + { + Asset assetInfo; + }; + + /** + * Output structure for getAmountOfShareQvaultHold function + * @param amount Amount of shares held by QVAULT contract + */ + struct getAmountOfShareQvaultHold_output + { + uint32 amount; + }; + + /** + * Retrieves the amount of a specific asset held by the QVAULT contract + * Useful for checking contract's asset holdings and portfolio composition + * + * @param input Asset information to query + * @param output Amount of shares held by the contract + */ + PUBLIC_FUNCTION(getAmountOfShareQvaultHold) + { + output.amount = (uint32)qpi.numberOfShares(input.assetInfo, AssetOwnershipSelect::byOwner(SELF), AssetPossessionSelect::byPossessor(SELF)); + } + + /** + * Input structure for getNumberOfHolderAndAvgAm function + * No input parameters required + */ + struct getNumberOfHolderAndAvgAm_input + { + + }; + + /** + * Output structure for getNumberOfHolderAndAvgAm function + * @param numberOfQcapHolder Number of unique QCAP token holders + * @param avgAmount Average QCAP amount per holder + */ + struct getNumberOfHolderAndAvgAm_output + { + sint32 returnCode; // Status code indicating success or failure + uint32 numberOfQcapHolder; + uint32 avgAmount; + }; + + /** + * Local variables for getNumberOfHolderAndAvgAm function + */ + struct getNumberOfHolderAndAvgAm_locals + { + AssetPossessionIterator iter; // Iterator for asset possession + Asset QCAPId; // QCAP asset identifier + uint32 count; // Counter for holders + uint32 numberOfDuplicatedPossesor; // Counter for duplicate possessors + }; + + /** + * Calculates the number of unique QCAP token holders and average amount per holder + * Accounts for duplicate possessors across different contracts + * Useful for token distribution analysis and economic metrics + * + * @param input No input parameters + * @param output Number of holders and average amount per holder + */ + PUBLIC_FUNCTION_WITH_LOCALS(getNumberOfHolderAndAvgAm) + { + locals.QCAPId.assetName = QVAULT_QCAP_ASSETNAME; + locals.QCAPId.issuer = state.QCAP_ISSUER; + + locals.iter.begin(locals.QCAPId); + while (!locals.iter.reachedEnd()) + { + if (locals.iter.numberOfPossessedShares() > 0 && locals.iter.possessor() != SELF) + { + locals.count++; + } + + if (qpi.numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, state.QCAP_ISSUER, locals.iter.possessor(), locals.iter.possessor(), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX) > 0 && qpi.numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, state.QCAP_ISSUER, locals.iter.possessor(), locals.iter.possessor(), QVAULT_CONTRACT_INDEX, QVAULT_CONTRACT_INDEX) > 0 && locals.iter.possessor() != SELF) + { + locals.numberOfDuplicatedPossesor++; + } + + locals.iter.next(); + } + + locals.numberOfDuplicatedPossesor = (uint32)div(locals.numberOfDuplicatedPossesor * 1ULL, 2ULL); + + output.numberOfQcapHolder = locals.count - locals.numberOfDuplicatedPossesor; + output.avgAmount = (uint32)div(qpi.numberOfShares(locals.QCAPId) - qpi.numberOfShares(locals.QCAPId, AssetOwnershipSelect::byOwner(SELF), AssetPossessionSelect::byPossessor(SELF)) + state.totalStakedQcapAmount * 1ULL, output.numberOfQcapHolder * 1ULL); + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getAmountForQearnInUpcomingEpoch function + * @param epoch Future epoch to check QEarn funding for + */ + struct getAmountForQearnInUpcomingEpoch_input + { + uint32 epoch; + }; + + /** + * Output structure for getAmountForQearnInUpcomingEpoch function + * @param amount Total amount allocated for QEarn in the specified epoch + */ + struct getAmountForQearnInUpcomingEpoch_output + { + sint32 returnCode; // Status code indicating success or failure + uint64 amount; + }; + + /** + * Local variables for getAmountForQearnInUpcomingEpoch function + */ + struct getAmountForQearnInUpcomingEpoch_locals + { + sint32 _t; // Loop counter variable + }; + + /** + * Calculates the total amount allocated for QEarn participation in a future epoch + * Only considers passed QEarn proposals that are still active + * Returns 0 for past or current epochs + * + * @param input Future epoch number to check + * @param output Total QEarn funding amount for the epoch + */ + PUBLIC_FUNCTION_WITH_LOCALS(getAmountForQearnInUpcomingEpoch) + { + if (input.epoch <= qpi.epoch()) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + for (locals._t = state.numberOfQEarnP - 1; locals._t >= 0; locals._t--) + { + if (state.QEarnP.get(locals._t).proposedEpoch + 1 < qpi.epoch() && state.QEarnP.get(locals._t).result == 0 && state.QEarnP.get(locals._t).numberOfEpoch + state.QEarnP.get(locals._t).proposedEpoch + 1 >= input.epoch) + { + output.amount += state.QEarnP.get(locals._t).assignedFundPerEpoch; + } + } + output.returnCode = QVAULT_SUCCESS; + } + + /** + * Input structure for getVoteInProposal function + * @param userID user identity + * @param proposalType Type of proposal (1=GP, 2=QCP, 3=IPOP, 4=QEarnP, 5=FundP, 6=MKTP, 7=AlloP) + * @param proposalId ID of the proposal + */ + struct getVoteInProposal_input + { + id userID; + uint64 proposalType; + uint64 proposalId; + }; + + /** + * Output structure for getVoteInProposal function + * @param isVoted Indicates whether the user has cast a vote in this proposal + * @param votingDecision Indicates how the user voted + */ + struct getVoteInProposal_output + { + sint64 returnCode; // Status code indicating success or failure + sint64 isVoted; + sint64 votingDecision; + }; + + /** + * Local variables for getVoteInProposal function + */ + struct getVoteInProposal_locals + { + uint8 countOfVote; + Array voteList; + uint64 _r; + }; + + /** + * Retrieves voting power in specific vote for a specific user address + * Returns voting power in specific vote + * + * @param input Type and Id of proposal and userID + * @param output Voting power for specific vote + */ + PUBLIC_FUNCTION_WITH_LOCALS(getVoteInProposal) + { + if (input.proposalType > 7 || input.proposalType < 1) + { + output.returnCode = QVAULT_INPUT_ERROR; + return ; + } + + switch (input.proposalType) + { + case 1: + if (input.proposalId >= state.numberOfGP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 2: + if (input.proposalId >= state.numberOfQCP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 3: + if (input.proposalId >= state.numberOfIPOP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 4: + if (input.proposalId >= state.numberOfQEarnP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 5: + if (input.proposalId >= state.numberOfFundP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 6: + if (input.proposalId >= state.numberOfMKTP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + case 7: + if (input.proposalId >= state.numberOfAlloP) + { + output.returnCode = QVAULT_OVERFLOW_PROPOSAL; + return ; + } + break; + } + + if (state.countOfVote.get(input.userID, locals.countOfVote)) + { + state.vote.get(input.userID, locals.voteList); + for (locals._r = 0; locals._r < locals.countOfVote; locals._r++) + { + if (locals.voteList.get(locals._r).proposalId == input.proposalId && locals.voteList.get(locals._r).proposalType == input.proposalType) + { + output.isVoted = 1; + output.votingDecision = locals.voteList.get(locals._r).decision; + } + } + } + } + + REGISTER_USER_FUNCTIONS_AND_PROCEDURES() + { + REGISTER_USER_FUNCTION(getData, 1); + REGISTER_USER_FUNCTION(getStakedAmountAndVotingPower, 2); + REGISTER_USER_FUNCTION(getGP, 3); + REGISTER_USER_FUNCTION(getQCP, 4); + REGISTER_USER_FUNCTION(getIPOP, 5); + REGISTER_USER_FUNCTION(getQEarnP, 6); + REGISTER_USER_FUNCTION(getFundP, 7); + REGISTER_USER_FUNCTION(getMKTP, 8); + REGISTER_USER_FUNCTION(getAlloP, 9); + REGISTER_USER_FUNCTION(getIdentitiesHvVtPw, 11); + REGISTER_USER_FUNCTION(ppCreationPower, 12); + REGISTER_USER_FUNCTION(getQcapBurntAmountInLastEpoches, 13); + REGISTER_USER_FUNCTION(getAmountToBeSoldPerYear, 14); + REGISTER_USER_FUNCTION(getTotalRevenueInQcap, 15); + REGISTER_USER_FUNCTION(getRevenueInQcapPerEpoch, 16); + REGISTER_USER_FUNCTION(getRevenuePerShare, 17); + REGISTER_USER_FUNCTION(getAmountOfShareQvaultHold, 18); + REGISTER_USER_FUNCTION(getNumberOfHolderAndAvgAm, 19); + REGISTER_USER_FUNCTION(getAmountForQearnInUpcomingEpoch, 20); + REGISTER_USER_FUNCTION(getVoteInProposal, 21); + + REGISTER_USER_PROCEDURE(stake, 1); + REGISTER_USER_PROCEDURE(unStake, 2); + REGISTER_USER_PROCEDURE(submitGP, 3); + REGISTER_USER_PROCEDURE(submitQCP, 4); + REGISTER_USER_PROCEDURE(submitIPOP, 5); + REGISTER_USER_PROCEDURE(submitQEarnP, 6); + REGISTER_USER_PROCEDURE(submitFundP, 7); + REGISTER_USER_PROCEDURE(submitMKTP, 8); + REGISTER_USER_PROCEDURE(submitAlloP, 9); + REGISTER_USER_PROCEDURE(voteInProposal, 11); + REGISTER_USER_PROCEDURE(buyQcap, 12); + REGISTER_USER_PROCEDURE(TransferShareManagementRights, 13); + } + + INITIALIZE() + { + state.QCAP_ISSUER = ID(_Q, _C, _A, _P, _W, _M, _Y, _R, _S, _H, _L, _B, _J, _H, _S, _T, _T, _Z, _Q, _V, _C, _I, _B, _A, _R, _V, _O, _A, _S, _K, _D, _E, _N, _A, _S, _A, _K, _N, _O, _B, _R, _G, _P, _F, _W, _W, _K, _R, _C, _U, _V, _U, _A, _X, _Y, _E); + state.qcapSoldAmount = 3130507; + state.transferRightsFee = 100; + state.quorumPercent = 670; + state.qcapBurnPermille = 0; + state.burnPermille = 0; + state.QCAPHolderPermille = 970; + state.reinvestingPermille = 0; + state.shareholderDividend = 30; + } + + /** + * Local variables for BEGIN_EPOCH function + */ + struct BEGIN_EPOCH_locals + { + QEARN::lock_input lock_input; // Input for QEARN lock function + QEARN::lock_output lock_output; // Output from QEARN lock function + QX::AssetAskOrders_input assetAskOrders_input; // Input for QX asset ask orders + QX::AssetAskOrders_output assetAskOrders_output; // Output from QX asset ask orders + QX::AddToBidOrder_input addToBidOrder_input; // Input for QX add to bid order + QX::AddToBidOrder_output addToBidOrder_output; // Output from QX add to bid order + QX::TransferShareManagementRights_input transferShareManagementRights_input; // Input for QX transfer rights + QX::TransferShareManagementRights_output transferShareManagementRights_output; // Output from QX transfer rights + uint32 purchasedQcap; // Amount of QCAP purchased for burning + sint32 _t; // Loop counter variable + }; + + /** + * Executes at the beginning of each epoch + * Handles QEarn fund locking, quorum changes, allocation updates, and QCAP burning + * Processes passed proposals and updates contract state accordingly + */ + BEGIN_EPOCH_WITH_LOCALS() + { + for (locals._t = 0 ; locals._t < (sint32)state.numberOfQEarnP; locals._t++) + { + if (state.QEarnP.get(locals._t).result == 0 && state.QEarnP.get(locals._t).proposedEpoch + state.QEarnP.get(locals._t).numberOfEpoch + 1 >= qpi.epoch()) + { + if (state.QEarnP.get(locals._t).proposedEpoch + 1 == qpi.epoch()) + { + continue; + } + INVOKE_OTHER_CONTRACT_PROCEDURE(QEARN, lock, locals.lock_input, locals.lock_output, state.QEarnP.get(locals._t).assignedFundPerEpoch); + } + } + + for (locals._t = state.numberOfQCP - 1 ; locals._t >= 0; locals._t--) + { + if (state.QCP.get(locals._t).result == 0 && state.QCP.get(locals._t).proposedEpoch + 2 == qpi.epoch()) + { + state.quorumPercent = state.QCP.get(locals._t).newQuorumPercent; + break; + } + if (state.QCP.get(locals._t).proposedEpoch + 2 < qpi.epoch()) + { + break; + } + } + + for (locals._t = state.numberOfAlloP - 1; locals._t >= 0; locals._t--) + { + if (state.AlloP.get(locals._t).result == 0 && state.AlloP.get(locals._t).proposedEpoch + 2 == qpi.epoch()) + { + state.QCAPHolderPermille = state.AlloP.get(locals._t).distributed; + state.reinvestingPermille = state.AlloP.get(locals._t).reinvested; + state.qcapBurnPermille = state.AlloP.get(locals._t).burnQcap; + break; + } + if (state.AlloP.get(locals._t).proposedEpoch + 2 < qpi.epoch()) + { + break; + } + } + + locals.purchasedQcap = 0; + while(state.fundForBurn > 0) + { + locals.assetAskOrders_input.assetName = QVAULT_QCAP_ASSETNAME; + locals.assetAskOrders_input.issuer = state.QCAP_ISSUER; + locals.assetAskOrders_input.offset = 0; + CALL_OTHER_CONTRACT_FUNCTION(QX, AssetAskOrders, locals.assetAskOrders_input, locals.assetAskOrders_output); + if (locals.assetAskOrders_output.orders.get(0).price <= (sint64)state.fundForBurn && locals.assetAskOrders_output.orders.get(0).price != 0) + { + locals.addToBidOrder_input.assetName = QVAULT_QCAP_ASSETNAME; + locals.addToBidOrder_input.issuer = state.QCAP_ISSUER; + locals.addToBidOrder_input.numberOfShares = (sint64)div(state.fundForBurn, locals.assetAskOrders_output.orders.get(0).price * 1ULL) > locals.assetAskOrders_output.orders.get(0).numberOfShares ? locals.assetAskOrders_output.orders.get(0).numberOfShares : div(state.fundForBurn, locals.assetAskOrders_output.orders.get(0).price * 1ULL); + locals.addToBidOrder_input.price = locals.assetAskOrders_output.orders.get(0).price; + + INVOKE_OTHER_CONTRACT_PROCEDURE(QX, AddToBidOrder, locals.addToBidOrder_input, locals.addToBidOrder_output, locals.addToBidOrder_input.price * locals.addToBidOrder_input.numberOfShares); + + state.fundForBurn -= locals.addToBidOrder_input.price * locals.addToBidOrder_input.numberOfShares; + state.burntQcapAmPerEpoch.set(qpi.epoch(), state.burntQcapAmPerEpoch.get(qpi.epoch()) + (uint32)locals.addToBidOrder_input.numberOfShares); + state.totalQcapBurntAmount += (uint32)locals.addToBidOrder_input.numberOfShares; + + locals.purchasedQcap += (uint32)locals.addToBidOrder_output.addedNumberOfShares; + } + else + { + break; + } + } + + locals.transferShareManagementRights_input.asset.assetName = QVAULT_QCAP_ASSETNAME; + locals.transferShareManagementRights_input.asset.issuer = state.QCAP_ISSUER; + locals.transferShareManagementRights_input.newManagingContractIndex = SELF_INDEX; + locals.transferShareManagementRights_input.numberOfShares = locals.purchasedQcap; + + INVOKE_OTHER_CONTRACT_PROCEDURE(QX, TransferShareManagementRights, locals.transferShareManagementRights_input, locals.transferShareManagementRights_output, 0); + + qpi.transferShareOwnershipAndPossession(QVAULT_QCAP_ASSETNAME, state.QCAP_ISSUER, SELF, SELF, locals.purchasedQcap, NULL_ID); + } + + /** + * Local variables for END_EPOCH function + */ + struct END_EPOCH_locals + { + QX::TransferShareManagementRights_input transferShareManagementRights_input; // Input for QX transfer rights + QX::TransferShareManagementRights_output transferShareManagementRights_output; // Output from QX transfer rights + GPInfo updatedGProposal; // Updated general proposal + QCPInfo updatedQCProposal; // Updated quorum change proposal + IPOPInfo updatedIPOProposal; // Updated IPO participation proposal + QEarnPInfo updatedQEarnProposal; // Updated QEarn participation proposal + FundPInfo updatedFundProposal; // Updated fundraising proposal + MKTPInfo updatedMKTProposal; // Updated marketplace proposal + AlloPInfo updatedAlloProposal; // Updated allocation proposal + Entity entity; // Entity information + AssetPossessionIterator iter; // Iterator for asset possession + Asset QCAPId; // QCAP asset identifier + id possessorPubkey; // Possessor public key + uint64 paymentForShareholders; // Payment amount for shareholders + uint64 paymentForQCAPHolders; // Payment amount for QCAP holders + uint64 paymentForReinvest; // Payment amount for reinvestment + uint64 amountOfBurn; // Amount to burn + uint64 circulatedSupply; // Circulating supply of QCAP + uint64 requiredFund; // Required fund amount + uint64 tmpAmount; // Temporary amount variable + uint64 paymentForQcapBurn; // Payment amount for QCAP burning + uint64 revenueForOneQcap; // Qus amount per one QCAP + uint32 numberOfVote; // Number of votes + sint32 _t, _r; // Loop counter variables + uint32 curDate; // Current date (packed) + uint8 year; // Current year + uint8 month; // Current month + uint8 day; // Current day + uint8 hour; // Current hour + uint8 minute; // Current minute + uint8 second; // Current second + }; + + /** + * Executes at the end of each epoch + * Distributes revenue to stakeholders, processes proposal results, and updates voting power + * Handles all proposal voting outcomes and state updates + */ + END_EPOCH_WITH_LOCALS() + { + locals.paymentForShareholders = div(state.totalEpochRevenue * state.shareholderDividend, 1000ULL); + locals.paymentForQCAPHolders = div(state.totalEpochRevenue * state.QCAPHolderPermille, 1000ULL); + locals.paymentForReinvest = div(state.totalEpochRevenue * state.reinvestingPermille, 1000ULL); + locals.paymentForQcapBurn = div(state.totalEpochRevenue * state.qcapBurnPermille, 1000ULL); + locals.amountOfBurn = div(state.totalEpochRevenue * state.burnPermille, 1000ULL); + + if (qpi.distributeDividends(div(locals.paymentForShareholders + state.proposalCreateFund, 676ULL))) + { + state.totalEpochRevenue -= locals.paymentForShareholders; + } + qpi.burn(locals.amountOfBurn); + + locals.QCAPId.assetName = QVAULT_QCAP_ASSETNAME; + locals.QCAPId.issuer = state.QCAP_ISSUER; + + locals.circulatedSupply = qpi.numberOfShares(locals.QCAPId) - qpi.numberOfShares(locals.QCAPId, AssetOwnershipSelect::byOwner(SELF), AssetPossessionSelect::byPossessor(SELF)) + state.totalStakedQcapAmount; + + locals.revenueForOneQcap = div(locals.paymentForQCAPHolders, locals.circulatedSupply * 1ULL); + state.totalEpochRevenue -= smul(locals.revenueForOneQcap, locals.circulatedSupply); + + state.revenueForOneQcapPerEpoch.set(qpi.epoch(), locals.revenueForOneQcap); + state.revenueForOneQvaultPerEpoch.set(qpi.epoch(), div(locals.paymentForShareholders + state.proposalCreateFund, 676ULL)); + state.revenueForReinvestPerEpoch.set(qpi.epoch(), locals.paymentForReinvest); + + state.reinvestingFund += locals.paymentForReinvest; + state.fundForBurn += locals.paymentForQcapBurn; + + state.totalEpochRevenue -= locals.paymentForReinvest + locals.paymentForQcapBurn + locals.amountOfBurn; + state.proposalCreateFund = 0; + + locals.iter.begin(locals.QCAPId); + while (!locals.iter.reachedEnd()) + { + locals.possessorPubkey = locals.iter.possessor(); + + if (locals.possessorPubkey != SELF) + { + qpi.transfer(locals.possessorPubkey, div(locals.paymentForQCAPHolders, locals.circulatedSupply) * locals.iter.numberOfPossessedShares()); + } + + locals.iter.next(); + } + + for (locals._t = 0 ; locals._t < (sint32)state.numberOfStaker; locals._t++) + { + qpi.transfer(state.staker.get(locals._t).stakerAddress, div(locals.paymentForQCAPHolders, locals.circulatedSupply) * state.staker.get(locals._t).amount); + } + + /* + General Proposal Result + */ + + for (locals._t = state.numberOfGP - 1; locals._t >= 0; locals._t--) + { + if (state.GP.get(locals._t).proposedEpoch == qpi.epoch()) + { + locals.updatedGProposal = state.GP.get(locals._t); + if (state.GP.get(locals._t).numberOfYes + state.GP.get(locals._t).numberOfNo < div(state.GP.get(locals._t).currentQuorumPercent * state.GP.get(locals._t).currentTotalVotingPower * 1ULL, 1000ULL)) + { + locals.updatedGProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QUORUM; + } + else if (state.GP.get(locals._t).numberOfYes <= state.GP.get(locals._t).numberOfNo) + { + locals.updatedGProposal.result = QVAULT_PROPOSAL_REJECTED; + } + else + { + locals.updatedGProposal.result = QVAULT_PROPOSAL_PASSED; + } + state.GP.set(locals._t, locals.updatedGProposal); + } + else + { + break; + } + } + + /* + Quorum Proposal Result + */ + + locals.numberOfVote = 0; + + for (locals._t = state.numberOfQCP - 1; locals._t >= 0; locals._t--) + { + if (state.QCP.get(locals._t).proposedEpoch == qpi.epoch()) + { + locals.updatedQCProposal = state.QCP.get(locals._t); + if (state.QCP.get(locals._t).numberOfYes + state.QCP.get(locals._t).numberOfNo < div(state.QCP.get(locals._t).currentQuorumPercent * state.QCP.get(locals._t).currentTotalVotingPower * 1ULL, 1000ULL)) + { + locals.updatedQCProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QUORUM; + } + else if (state.QCP.get(locals._t).numberOfYes <= state.QCP.get(locals._t).numberOfNo) + { + locals.updatedQCProposal.result = QVAULT_PROPOSAL_REJECTED; + } + else + { + locals.updatedQCProposal.result = QVAULT_PROPOSAL_PASSED; + if (locals.numberOfVote < state.QCP.get(locals._t).numberOfYes) + { + locals.numberOfVote = state.QCP.get(locals._t).numberOfYes; + } + + } + state.QCP.set(locals._t, locals.updatedQCProposal); + } + else + { + break; + } + } + + for (locals._t = state.numberOfQCP - 1; locals._t >= 0; locals._t--) + { + if (state.QCP.get(locals._t).proposedEpoch == qpi.epoch() && state.QCP.get(locals._t).numberOfYes != locals.numberOfVote) + { + locals.updatedQCProposal = state.QCP.get(locals._t); + locals.updatedQCProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_VOTING_POWER; + state.QCP.set(locals._t, locals.updatedQCProposal); + } + if (state.QCP.get(locals._t).proposedEpoch != qpi.epoch()) + { + break; + } + } + + /* + IPO Proposal Result + */ + + locals.requiredFund = 0; + + for (locals._t = state.numberOfIPOP - 1; locals._t >= 0; locals._t--) + { + if (state.IPOP.get(locals._t).proposedEpoch == qpi.epoch()) + { + locals.updatedIPOProposal = state.IPOP.get(locals._t); + if (state.IPOP.get(locals._t).numberOfYes + state.IPOP.get(locals._t).numberOfNo < div(state.IPOP.get(locals._t).currentQuorumPercent * state.IPOP.get(locals._t).currentTotalVotingPower * 1ULL, 1000ULL)) + { + locals.updatedIPOProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QUORUM; + } + else if (state.IPOP.get(locals._t).numberOfYes <= state.IPOP.get(locals._t).numberOfNo) + { + locals.updatedIPOProposal.result = QVAULT_PROPOSAL_REJECTED; + } + else + { + locals.updatedIPOProposal.result = QVAULT_PROPOSAL_PASSED; + locals.requiredFund += div(locals.updatedIPOProposal.totalWeight * 1ULL, locals.updatedIPOProposal.numberOfYes * 1ULL); + locals.updatedIPOProposal.assignedFund = div(locals.updatedIPOProposal.totalWeight * 1ULL, locals.updatedIPOProposal.numberOfYes * 1ULL); + } + state.IPOP.set(locals._t, locals.updatedIPOProposal); + } + else + { + break; + } + } + + locals.tmpAmount = 0; + + for (locals._t = state.numberOfIPOP - 1; locals._t >= 0; locals._t--) + { + if (state.IPOP.get(locals._t).proposedEpoch == qpi.epoch()) + { + if (state.IPOP.get(locals._t).result != 0) + { + continue; + } + locals.updatedIPOProposal = state.IPOP.get(locals._t); + if (state.reinvestingFund < locals.requiredFund) + { + locals.updatedIPOProposal.assignedFund = div(div(div(locals.updatedIPOProposal.totalWeight * 1ULL, locals.updatedIPOProposal.numberOfYes * 1ULL) * 1000, locals.requiredFund) * state.reinvestingFund, 1000ULL); + } + state.IPOP.set(locals._t, locals.updatedIPOProposal); + + for (locals._r = 675 ; locals._r >= 0; locals._r--) + { + if ((676 - locals._r) * (qpi.ipoBidPrice(locals.updatedIPOProposal.ipoContractIndex, locals._r) + 1) > (sint64)locals.updatedIPOProposal.assignedFund) + { + if (locals._r == 675) + { + break; + } + qpi.bidInIPO(locals.updatedIPOProposal.ipoContractIndex, qpi.ipoBidPrice(locals.updatedIPOProposal.ipoContractIndex, locals._r + 1) + 1, 675 - locals._r); + locals.tmpAmount += (qpi.ipoBidPrice(locals.updatedIPOProposal.ipoContractIndex, locals._r + 1) + 1) * (675 - locals._r); + break; + } + } + } + else + { + break; + } + } + + state.reinvestingFund -= locals.tmpAmount; + + /* + QEarn Proposal Result + */ + + locals.requiredFund = 0; + + for (locals._t = state.numberOfQEarnP - 1; locals._t >= 0; locals._t--) + { + if (state.QEarnP.get(locals._t).proposedEpoch == qpi.epoch()) + { + locals.updatedQEarnProposal = state.QEarnP.get(locals._t); + if (state.QEarnP.get(locals._t).numberOfYes + state.QEarnP.get(locals._t).numberOfNo < div(state.QEarnP.get(locals._t).currentQuorumPercent * state.QEarnP.get(locals._t).currentTotalVotingPower * 1ULL, 1000ULL)) + { + locals.updatedQEarnProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QUORUM; + } + else if (state.QEarnP.get(locals._t).numberOfYes <= state.QEarnP.get(locals._t).numberOfNo) + { + locals.updatedQEarnProposal.result = QVAULT_PROPOSAL_REJECTED; + } + else + { + locals.updatedQEarnProposal.result = QVAULT_PROPOSAL_PASSED; + locals.requiredFund += locals.updatedQEarnProposal.amountOfInvestPerEpoch * locals.updatedQEarnProposal.numberOfEpoch; + } + state.QEarnP.set(locals._t, locals.updatedQEarnProposal); + } + else + { + break; + } + } + + locals.tmpAmount = 0; + + if (state.reinvestingFund < locals.requiredFund) + { + for (locals._t = state.numberOfQEarnP - 1; locals._t >= 0; locals._t--) + { + if (state.QEarnP.get(locals._t).proposedEpoch == qpi.epoch()) + { + if (state.QEarnP.get(locals._t).result != 0) + { + continue; + } + locals.updatedQEarnProposal = state.QEarnP.get(locals._t); + locals.updatedQEarnProposal.assignedFundPerEpoch = div(div(div(locals.updatedQEarnProposal.numberOfEpoch * locals.updatedQEarnProposal.amountOfInvestPerEpoch * 1000, locals.requiredFund) * state.reinvestingFund, 1000ULL), locals.updatedQEarnProposal.numberOfEpoch * 1ULL); + locals.tmpAmount += locals.updatedQEarnProposal.assignedFundPerEpoch * locals.updatedQEarnProposal.numberOfEpoch; + state.QEarnP.set(locals._t, locals.updatedQEarnProposal); + } + else + { + break; + } + } + state.reinvestingFund -= locals.tmpAmount; + } + + else + { + state.reinvestingFund -= locals.requiredFund; + } + + /* + Fundrasing Proposal Result + */ + + for (locals._t = state.numberOfFundP - 1; locals._t >= 0; locals._t--) + { + if (state.FundP.get(locals._t).proposedEpoch == qpi.epoch()) + { + locals.updatedFundProposal = state.FundP.get(locals._t); + if (state.FundP.get(locals._t).numberOfYes + state.FundP.get(locals._t).numberOfNo < div(state.FundP.get(locals._t).currentQuorumPercent * state.FundP.get(locals._t).currentTotalVotingPower * 1ULL, 1000ULL)) + { + locals.updatedFundProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QUORUM; + } + else if (state.FundP.get(locals._t).numberOfYes <= state.FundP.get(locals._t).numberOfNo) + { + locals.updatedFundProposal.result = QVAULT_PROPOSAL_REJECTED; + } + else + { + locals.updatedFundProposal.result = QVAULT_PROPOSAL_PASSED; + + packQvaultDate(qpi.year(), qpi.month(), qpi.day(), qpi.hour(), qpi.minute(), qpi.second(), locals.curDate); + unpackQvaultDate(locals.year, locals.month, locals.day, locals.hour, locals.minute, locals.second, locals.curDate); + if (locals.year == 25 && state.qcapSoldAmount + locals.updatedFundProposal.amountOfQcap > QVAULT_2025MAX_QCAP_SALE_AMOUNT) + { + locals.updatedFundProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + } + else if (locals.year == 26 && state.qcapSoldAmount + locals.updatedFundProposal.amountOfQcap > QVAULT_2026MAX_QCAP_SALE_AMOUNT) + { + locals.updatedFundProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + } + else if (locals.year == 27 && state.qcapSoldAmount + locals.updatedFundProposal.amountOfQcap > QVAULT_2027MAX_QCAP_SALE_AMOUNT) + { + locals.updatedFundProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + } + else if (state.qcapSoldAmount + locals.updatedFundProposal.amountOfQcap > QVAULT_QCAP_MAX_SUPPLY) + { + locals.updatedFundProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + } + } + state.FundP.set(locals._t, locals.updatedFundProposal); + } + else + { + break; + } + } + + /* + Marketplace Proposal Result + */ + + for (locals._t = state.numberOfMKTP - 1; locals._t >= 0; locals._t--) + { + if (state.MKTP.get(locals._t).proposedEpoch == qpi.epoch()) + { + locals.updatedMKTProposal = state.MKTP.get(locals._t); + if (state.MKTP.get(locals._t).numberOfYes + state.MKTP.get(locals._t).numberOfNo < div(state.MKTP.get(locals._t).currentQuorumPercent * state.MKTP.get(locals._t).currentTotalVotingPower * 1ULL, 1000ULL)) + { + locals.updatedMKTProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QUORUM; + qpi.transferShareOwnershipAndPossession(locals.updatedMKTProposal.shareName, NULL_ID, SELF, SELF, locals.updatedMKTProposal.amountOfShare, locals.updatedMKTProposal.proposer); + } + else if (state.MKTP.get(locals._t).numberOfYes <= state.MKTP.get(locals._t).numberOfNo) + { + locals.updatedMKTProposal.result = QVAULT_PROPOSAL_REJECTED; + qpi.transferShareOwnershipAndPossession(locals.updatedMKTProposal.shareName, NULL_ID, SELF, SELF, locals.updatedMKTProposal.amountOfShare, locals.updatedMKTProposal.proposer); + } + else + { + locals.updatedMKTProposal.result = QVAULT_PROPOSAL_PASSED; + + packQvaultDate(qpi.year(), qpi.month(), qpi.day(), qpi.hour(), qpi.minute(), qpi.second(), locals.curDate); + unpackQvaultDate(locals.year, locals.month, locals.day, locals.hour, locals.minute, locals.second, locals.curDate); + if (locals.year == 25 && state.qcapSoldAmount + locals.updatedMKTProposal.amountOfQcap > QVAULT_2025MAX_QCAP_SALE_AMOUNT) + { + locals.updatedMKTProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + qpi.transferShareOwnershipAndPossession(locals.updatedMKTProposal.shareName, NULL_ID, SELF, SELF, locals.updatedMKTProposal.amountOfShare, locals.updatedMKTProposal.proposer); + } + else if (locals.year == 26 && state.qcapSoldAmount + locals.updatedMKTProposal.amountOfQcap > QVAULT_2026MAX_QCAP_SALE_AMOUNT) + { + locals.updatedMKTProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + qpi.transferShareOwnershipAndPossession(locals.updatedMKTProposal.shareName, NULL_ID, SELF, SELF, locals.updatedMKTProposal.amountOfShare, locals.updatedMKTProposal.proposer); + } + else if (locals.year == 27 && state.qcapSoldAmount + locals.updatedMKTProposal.amountOfQcap > QVAULT_2027MAX_QCAP_SALE_AMOUNT) + { + locals.updatedMKTProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + qpi.transferShareOwnershipAndPossession(locals.updatedMKTProposal.shareName, NULL_ID, SELF, SELF, locals.updatedMKTProposal.amountOfShare, locals.updatedMKTProposal.proposer); + } + else if (state.qcapSoldAmount + locals.updatedMKTProposal.amountOfQcap > QVAULT_QCAP_MAX_SUPPLY) + { + locals.updatedMKTProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + qpi.transferShareOwnershipAndPossession(locals.updatedMKTProposal.shareName, NULL_ID, SELF, SELF, locals.updatedMKTProposal.amountOfShare, locals.updatedMKTProposal.proposer); + } + else if (state.reinvestingFund < locals.updatedMKTProposal.amountOfQubic) + { + locals.updatedMKTProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QCAP; + qpi.transferShareOwnershipAndPossession(locals.updatedMKTProposal.shareName, NULL_ID, SELF, SELF, locals.updatedMKTProposal.amountOfShare, locals.updatedMKTProposal.proposer); + } + else + { + state.qcapSoldAmount += locals.updatedMKTProposal.amountOfQcap; + + qpi.transfer(locals.updatedMKTProposal.proposer, locals.updatedMKTProposal.amountOfQubic); + + state.reinvestingFund -= locals.updatedMKTProposal.amountOfQubic; + + locals.transferShareManagementRights_input.asset.assetName = QVAULT_QCAP_ASSETNAME; + locals.transferShareManagementRights_input.asset.issuer = state.QCAP_ISSUER; + locals.transferShareManagementRights_input.newManagingContractIndex = SELF_INDEX; + locals.transferShareManagementRights_input.numberOfShares = locals.updatedMKTProposal.amountOfQcap; + + INVOKE_OTHER_CONTRACT_PROCEDURE(QX, TransferShareManagementRights, locals.transferShareManagementRights_input, locals.transferShareManagementRights_output, 0); + + qpi.transferShareOwnershipAndPossession(QVAULT_QCAP_ASSETNAME, state.QCAP_ISSUER, SELF, SELF, locals.updatedMKTProposal.amountOfQcap, locals.updatedMKTProposal.proposer); + } + } + state.MKTP.set(locals._t, locals.updatedMKTProposal); + } + else + { + break; + } + } + + + /* + Allocation Proposal Result + */ + + locals.numberOfVote = 0; + + for (locals._t = state.numberOfAlloP - 1; locals._t >= 0; locals._t--) + { + if (state.AlloP.get(locals._t).proposedEpoch == qpi.epoch()) + { + locals.updatedAlloProposal = state.AlloP.get(locals._t); + if (state.AlloP.get(locals._t).numberOfYes + state.AlloP.get(locals._t).numberOfNo < div(state.AlloP.get(locals._t).currentQuorumPercent * state.AlloP.get(locals._t).currentTotalVotingPower * 1ULL, 1000ULL)) + { + locals.updatedAlloProposal.result = QVAULT_PROPOSAL_INSUFFICIENT_QUORUM; + } + else if (state.AlloP.get(locals._t).numberOfYes <= state.AlloP.get(locals._t).numberOfNo) + { + locals.updatedAlloProposal.result = QVAULT_PROPOSAL_REJECTED; + } + else + { + locals.updatedAlloProposal.result = QVAULT_PROPOSAL_PASSED; + if (locals.numberOfVote < state.AlloP.get(locals._t).numberOfYes) + { + locals.numberOfVote = state.AlloP.get(locals._t).numberOfYes; + } + + } + state.AlloP.set(locals._t, locals.updatedAlloProposal); + } + else + { + break; + } + } + + for (locals._t = state.numberOfAlloP - 1; locals._t >= 0; locals._t--) + { + if (state.AlloP.get(locals._t).proposedEpoch == qpi.epoch() && state.AlloP.get(locals._t).numberOfYes != locals.numberOfVote) + { + locals.updatedAlloProposal = state.AlloP.get(locals._t); + locals.updatedAlloProposal.result = 3; + state.AlloP.set(locals._t, locals.updatedAlloProposal); + } + if (state.AlloP.get(locals._t).proposedEpoch != qpi.epoch()) + { + break; + } + } + + state.totalVotingPower = 0; + for (locals._t = 0 ; locals._t < (sint32)state.numberOfStaker; locals._t++) + { + state.votingPower.set(locals._t, state.staker.get(locals._t)); + state.totalVotingPower += state.staker.get(locals._t).amount; + } + state.numberOfVotingPower = state.numberOfStaker; + + state.vote.reset(); + state.countOfVote.reset(); + + } + + /** + * Local variables for POST_INCOMING_TRANSFER function + */ + struct POST_INCOMING_TRANSFER_locals + { + AssetPossessionIterator iter; // Iterator for asset possession + Asset QCAPId; // QCAP asset identifier + id possessorPubkey; // Possessor public key + uint64 lockedFund; // Amount of locked funds + sint32 _t; // Loop counter variable + }; + + /** + * Handles incoming transfers to the contract + * Processes different transfer types (standard, QPI, IPO refunds, dividends) + * Updates revenue tracking and fund allocations accordingly + */ + POST_INCOMING_TRANSFER_WITH_LOCALS() + { + switch (input.type) + { + case TransferType::standardTransaction: + state.totalHistoryRevenue += input.amount; + state.revenueInQcapPerEpoch.set(qpi.epoch(), state.revenueInQcapPerEpoch.get(qpi.epoch()) + input.amount); + + state.totalEpochRevenue += input.amount; + break; + case TransferType::qpiTransfer: + if (input.sourceId.u64._0 == QEARN_CONTRACT_INDEX) + { + locals.lockedFund = 0; + for (locals._t = state.numberOfQEarnP - 1; locals._t >= 0; locals._t--) + { + if (state.QEarnP.get(locals._t).result == 0 && state.QEarnP.get(locals._t).proposedEpoch + 54 <= qpi.epoch() && state.QEarnP.get(locals._t).proposedEpoch + 54 + state.QEarnP.get(locals._t).numberOfEpoch > qpi.epoch()) + { + locals.lockedFund += state.QEarnP.get(locals._t).assignedFundPerEpoch; + } + } + state.reinvestingFund += locals.lockedFund; + state.totalEpochRevenue += input.amount - locals.lockedFund; + state.totalHistoryRevenue += input.amount - locals.lockedFund; + state.revenueInQcapPerEpoch.set(qpi.epoch(), state.revenueInQcapPerEpoch.get(qpi.epoch()) + input.amount - locals.lockedFund); + state.revenueByQearn += input.amount - locals.lockedFund; + + } + else + { + state.totalHistoryRevenue += input.amount; + state.revenueInQcapPerEpoch.set(qpi.epoch(), state.revenueInQcapPerEpoch.get(qpi.epoch()) + input.amount); + + state.totalEpochRevenue += input.amount; + } + break; + case TransferType::ipoBidRefund: + state.reinvestingFund += input.amount; + break; + case TransferType::qpiDistributeDividends: + state.totalHistoryRevenue += input.amount; + state.revenueInQcapPerEpoch.set(qpi.epoch(), state.revenueInQcapPerEpoch.get(qpi.epoch()) + input.amount); + state.revenuePerShare.set(input.sourceId.u64._0, state.revenuePerShare.get(input.sourceId.u64._0) + input.amount); + + state.totalEpochRevenue += input.amount; + break; + default: + break; + } + } + + /** + * Pre-acquire shares hook function + * Always allows share transfers to the contract + * Used for controlling share acquisition behavior + */ + PRE_ACQUIRE_SHARES() + { + output.allowTransfer = true; + } +}; +``` +
From ba40fe5c12b7d6f99c75b10e780f8080b164d855 Mon Sep 17 00:00:00 2001 From: KavataK <156964093+KavataK@users.noreply.github.com> Date: Tue, 10 Mar 2026 03:02:58 +0300 Subject: [PATCH 3/3] Update 2026-03-07-Upgrade-QVault.md Add PR link and update QCAP sold amount --- SmartContracts/2026-03-07-Upgrade-QVault.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SmartContracts/2026-03-07-Upgrade-QVault.md b/SmartContracts/2026-03-07-Upgrade-QVault.md index 6319cd7..f7fe46d 100644 --- a/SmartContracts/2026-03-07-Upgrade-QVault.md +++ b/SmartContracts/2026-03-07-Upgrade-QVault.md @@ -198,6 +198,10 @@ Important rule: Therefore, the remaining categories must **always sum to 97%**. --- +## Implementation + +Pull Request: +https://github.com/qubic/core/pull/365 ## Updated Smart Contract Code @@ -3229,7 +3233,7 @@ public: INITIALIZE() { state.QCAP_ISSUER = ID(_Q, _C, _A, _P, _W, _M, _Y, _R, _S, _H, _L, _B, _J, _H, _S, _T, _T, _Z, _Q, _V, _C, _I, _B, _A, _R, _V, _O, _A, _S, _K, _D, _E, _N, _A, _S, _A, _K, _N, _O, _B, _R, _G, _P, _F, _W, _W, _K, _R, _C, _U, _V, _U, _A, _X, _Y, _E); - state.qcapSoldAmount = 3130507; + state.qcapSoldAmount = 3230507; state.transferRightsFee = 100; state.quorumPercent = 670; state.qcapBurnPermille = 0;