Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
892ec3f
Implement QThirtyFour contract with ticket purchasing and winner sele…
N-010 Nov 8, 2025
5a6af96
Add QReservePool and QTF contract definitions with related functional…
N-010 Nov 9, 2025
785213a
Refactor QThirtyFour.h: reposition QTF2 struct definition for clarity
N-010 Nov 10, 2025
ff98674
Merge branch 'develop' into feature/2025-11-04-QThirtyFour
N-010 Nov 27, 2025
0c1e88b
Test version for QThirtyFour
N-010 Dec 4, 2025
28b7352
CalcReserveTopUp
N-010 Dec 7, 2025
53e4729
New structs
N-010 Dec 7, 2025
ed1c941
Fees
N-010 Dec 7, 2025
11ca689
Refactoring
N-010 Dec 8, 2025
821da1c
Remove profit transfer from k3 to k2 with FR enabled
N-010 Dec 9, 2025
5c4413a
Adds tests
N-010 Dec 10, 2025
6c266f8
Updates testes
N-010 Dec 11, 2025
9022807
Does not block the purchase of a ticket at a higher price
N-010 Dec 11, 2025
095556d
- src/contracts/QThirtyFour.h: ensure k4 reseed uses up-to-date QRP…
N-010 Dec 16, 2025
e2c9963
Removes SettlementLocals and aligned_storage_t
N-010 Dec 16, 2025
041433c
Updates tests
N-010 Dec 16, 2025
e0298f8
Update tests
N-010 Dec 16, 2025
c1e2829
Update tests
N-010 Dec 16, 2025
3758979
Merge branch 'refs/heads/develop' into feature/2025-11-04-QThirtyFour
N-010 Dec 16, 2025
9dca2f2
Updates index
N-010 Dec 16, 2025
094aa9d
Updates inde• Fix k=4 settlement: protect jackpot reseed from k2/k3 r…
N-010 Dec 19, 2025
f87354d
Fixes ContractVerify
N-010 Dec 19, 2025
e0c4432
Use RL::max
N-010 Dec 19, 2025
744545d
Fixes ContractVerify
N-010 Dec 19, 2025
deb0868
QDuel
N-010 Dec 25, 2025
b7d07f5
transfer dividends to RL shareholders
N-010 Dec 25, 2025
d8b83f1
Remove magic numbers
N-010 Dec 25, 2025
9d9153d
Move test file
N-010 Dec 25, 2025
baa2d1f
Changes public to protected
N-010 Dec 25, 2025
bf89227
Adds lock mechanism
N-010 Dec 26, 2025
fd642b1
Deposit
N-010 Dec 28, 2025
d2df84c
Return qus after end epoch
N-010 Jan 2, 2026
925db64
Adds check state in tick
N-010 Jan 2, 2026
79eddd6
Transferring rooms and players to the next epoch
N-010 Jan 2, 2026
23d3b9d
Create room in tick
N-010 Jan 2, 2026
39ff797
Transferring rooms and players to the next epoch
N-010 Jan 2, 2026
3565597
Base tests
N-010 Jan 3, 2026
3f7a5da
Adds tests
N-010 Jan 3, 2026
0fce190
Fixes
N-010 Jan 3, 2026
10cdae2
Add new test
N-010 Jan 3, 2026
054ab67
Adds cleanup for HashMaps
N-010 Jan 8, 2026
f363a9a
Renames Available to Allowed for SC
N-010 Jan 8, 2026
2db5e56
Merge branch 'refs/heads/develop' into feature/2025-12-25-QDuel
N-010 Jan 12, 2026
e6d19ad
Merge branch 'develop' into feature/2025-11-04-QThirtyFour
N-010 Jan 12, 2026
fde55f1
Fixes comment
N-010 Jan 12, 2026
df816f5
Merge branch 'develop' into feature/2025-11-04-QThirtyFour
N-010 Jan 14, 2026
fbb26c7
Merge branch 'develop' into feature/2025-12-25-QDuel
N-010 Jan 14, 2026
aa392d5
Update constructionEpoch
N-010 Jan 23, 2026
41c88c6
Update constructionEpoch
N-010 Jan 23, 2026
46e094b
Merge branch 'develop' into feature/2025-11-04-QThirtyFour
Franziska-Mueller Jan 26, 2026
177db71
Merge branch 'develop' into feature/2025-12-25-QDuel
N-010 Jan 26, 2026
fa65169
Fixes build and warnings
N-010 Jan 26, 2026
4dddfc5
Merge branch 'develop' into feature/2025-11-04-QThirtyFour
N-010 Jan 26, 2026
99f7268
Merge branch 'feature/2025-12-25-QDuel' into feature/2025-11-04-QThir…
N-010 Jan 26, 2026
3375d24
Fixes warnings, order
N-010 Jan 26, 2026
705d028
Merge branch 'develop' into feature/2025-11-04-QThirtyFour
N-010 Jan 26, 2026
066e8d8
Merge branch 'develop' into feature/2025-11-04-QThirtyFour
N-010 Feb 8, 2026
c0d2ad1
Adds SendReserve
N-010 Feb 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 53 additions & 16 deletions src/contracts/QReservePool.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using namespace QPI;

// Number of available smart contracts in the QRP contract.
constexpr uint16 QRP_ALLOWED_SC_NUM = 128;
constexpr uint64 QRP_QTF_INDEX = QRP_CONTRACT_INDEX + 1;
constexpr uint64 QRP_REMOVAL_THRESHOLD_PERCENT = 75;
Expand All @@ -13,11 +12,10 @@ struct QRP : ContractBase
{
enum class EReturnCode : uint8
{
SUCCESS = 0,
ACCESS_DENIED = 1,
INSUFFICIENT_RESERVE = 2,

MAX_VALUE = UINT8_MAX
SUCCESS,
ACCESS_DENIED,
INSUFFICIENT_RESERVE,
SC_NOT_FOUND,
};

static constexpr uint8 toReturnCode(const EReturnCode& code) { return static_cast<uint8>(code); };
Expand Down Expand Up @@ -94,6 +92,24 @@ struct QRP : ContractBase
uint64 arrayIndex;
};

struct SendReserve_input
{
uint64 scIndex;
uint64 amount;
};

struct SendReserve_output
{
uint8 returnCode;
};

struct SendReserve_locals
{
GetAvailableReserve_input getAvailableReserveInput;
GetAvailableReserve_output getAvailableReserveOutput;
id scId;
};

INITIALIZE()
{
// Set team/developer address (owner and team are the same for now)
Expand All @@ -111,13 +127,12 @@ struct QRP : ContractBase
REGISTER_USER_PROCEDURE(WithdrawReserve, 1);
REGISTER_USER_PROCEDURE(AddAllowedSC, 2);
REGISTER_USER_PROCEDURE(RemoveAllowedSC, 3);
REGISTER_USER_PROCEDURE(SendReserve, 4);
// Functions
REGISTER_USER_FUNCTION(GetAvailableReserve, 1);
REGISTER_USER_FUNCTION(GetAllowedSC, 2);
}

END_EPOCH() { state.allowedSmartContracts.cleanup(); }

PUBLIC_PROCEDURE_WITH_LOCALS(WithdrawReserve)
{
if (!state.allowedSmartContracts.contains(qpi.invocator()))
Expand Down Expand Up @@ -187,18 +202,40 @@ struct QRP : ContractBase
}
}

PUBLIC_PROCEDURE_WITH_LOCALS(SendReserve)
{
if (qpi.invocator() != state.ownerAddress)
{
output.returnCode = toReturnCode(EReturnCode::ACCESS_DENIED);
return;
}

locals.scId = id(input.scIndex, 0, 0, 0);

if (!state.allowedSmartContracts.contains(locals.scId))
{
output.returnCode = toReturnCode(EReturnCode::SC_NOT_FOUND);
return;
}

CALL(GetAvailableReserve, locals.getAvailableReserveInput, locals.getAvailableReserveOutput);
if (input.amount > locals.getAvailableReserveOutput.availableReserve)
{
output.returnCode = toReturnCode(EReturnCode::INSUFFICIENT_RESERVE);
return;
}

output.returnCode = toReturnCode(EReturnCode::SUCCESS);
qpi.transfer(locals.scId, input.amount);
}

protected:
/**
* @brief Address of the team managing the lottery contract.
* Initialized to a zero address.
*/
/** @brief Address of the team managing the lottery contract. */
id teamAddress;

/**
* @brief Address of the owner of the lottery contract.
* Initialized to a zero address.
*/
/** @brief Address of the owner of the lottery contract. */
id ownerAddress;

/** @brief Smart contract whitelist: only SCs in this list can call WithdrawReserve procedure to access the reserve funds, and receive transfers from SendReserve procedure. */
HashSet<id, QRP_ALLOWED_SC_NUM> allowedSmartContracts;
};
62 changes: 59 additions & 3 deletions test/contract_qrp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
constexpr uint16 QRP_PROC_GET_RESERVE = 1;
constexpr uint16 QRP_PROC_ADD_ALLOWED_SC = 2;
constexpr uint16 QRP_PROC_REMOVE_ALLOWED_SC = 3;
constexpr uint16 QRP_PROC_SEND_RESERVE = 4;

constexpr uint16 QRP_FUNC_GET_AVAILABLE_RESERVE = 1;
constexpr uint16 QRP_FUNC_GET_ALLOWED_SC = 2;

static const id QRP_CONTRACT_ID(QRP_CONTRACT_INDEX, 0, 0, 0);
static const id QRP_DEFAULT_SC_ID(QRP_QTF_INDEX, 0, 0, 0);
static const id QRP_OWNER_TEAM_ADDRESS =
ID(_Z, _T, _Z, _E, _A, _Q, _G, _U, _P, _I, _K, _T, _X, _F, _Y, _X, _Y, _E, _I, _T, _L, _A, _K, _F, _T, _D, _X, _C, _R, _L, _W, _E, _T, _H, _N, _G,
_H, _D, _Y, _U, _W, _E, _Y, _Q, _N, _Q, _S, _R, _H, _O, _W, _M, _U, _J, _L, _E);

class QRPChecker : public QRP
{
Expand Down Expand Up @@ -67,6 +65,14 @@ class ContractTestingQRP : protected ContractTesting
return output;
}

QRP::SendReserve_output sendReserve(const id& invocator, uint64 scIndex, uint64 amount)
{
QRP::SendReserve_input input{scIndex, amount};
QRP::SendReserve_output output{};
invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_SEND_RESERVE, input, output, invocator, 0);
return output;
}

QRP::GetAvailableReserve_output getAvailableReserve() const
{
QRP::GetAvailableReserve_input input{};
Expand Down Expand Up @@ -205,3 +211,53 @@ TEST(ContractQReservePool, OwnerAddRemove_IdempotencyAndBounds)
const auto rem2 = qrp.removeAllowedSC(state->owner(), newScIndex);
EXPECT_FALSE(state->hasAllowedSC(newScId));
}

TEST(ContractQReservePool, SendReserve_AllValidAndInvalidScenarios)
{
ContractTestingQRP qrp;
QRPChecker* state = qrp.state();
const id outsider(900, 0, 0, 0);
static constexpr uint64 recipientScIndex = 77;
const id recipientSc(recipientScIndex, 0, 0, 0);
static constexpr uint64 unknownScIndex = 999;
const id unknownSc(unknownScIndex, 0, 0, 0);

qrp.fund(state->owner(), 0);
qrp.fund(outsider, 0);
qrp.fund(recipientSc, 0);
qrp.fund(unknownSc, 0);

// ACCESS_DENIED: only owner can invoke SendReserve.
const QRP::SendReserve_output& denied = qrp.sendReserve(outsider, QRP_QTF_INDEX, 1);
EXPECT_EQ(denied.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED));

// SC_NOT_FOUND: owner invokes, but target SC is not whitelisted.
const QRP::SendReserve_output& notFound = qrp.sendReserve(state->owner(), unknownScIndex, 1);
EXPECT_EQ(notFound.returnCode, QRP::toReturnCode(QRP::EReturnCode::SC_NOT_FOUND));

// Add a new allowed SC to validate insufficient/success flows with a non-default recipient.
const QRP::AddAllowedSC_output& addSc = qrp.addAllowedSC(state->owner(), recipientScIndex);
EXPECT_EQ(addSc.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS));
EXPECT_TRUE(state->hasAllowedSC(recipientSc));

// INSUFFICIENT_RESERVE: reserve is 0.
const QRP::SendReserve_output& insufficientZeroReserve = qrp.sendReserve(state->owner(), recipientScIndex, 1);
EXPECT_EQ(insufficientZeroReserve.returnCode, QRP::toReturnCode(QRP::EReturnCode::INSUFFICIENT_RESERVE));
EXPECT_EQ(qrp.balanceQrp(), 0);
EXPECT_EQ(qrp.balanceOf(recipientSc), 0);

qrp.fundQrp(1000);
EXPECT_EQ(qrp.balanceQrp(), 1000);

// INSUFFICIENT_RESERVE: requested amount is above available reserve.
const QRP::SendReserve_output& insufficientAboveReserve = qrp.sendReserve(state->owner(), recipientScIndex, 1001);
EXPECT_EQ(insufficientAboveReserve.returnCode, QRP::toReturnCode(QRP::EReturnCode::INSUFFICIENT_RESERVE));
EXPECT_EQ(qrp.balanceQrp(), 1000);
EXPECT_EQ(qrp.balanceOf(recipientSc), 0);

// SUCCESS: exact available reserve should be transferable.
const QRP::SendReserve_output& successExact = qrp.sendReserve(state->owner(), recipientScIndex, 1000);
EXPECT_EQ(successExact.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS));
EXPECT_EQ(qrp.balanceQrp(), 0);
EXPECT_EQ(qrp.balanceOf(recipientSc), 1000);
}
Loading