Skip to main content

Programs in Practice

Constant

Constants used in Qubic smart contracts should be defined with constexpr and follow the naming convention [CONTRACT_NAME]_VARIABLE.

using namespace QPI;

constexpr sint64 MYTEST_FEE = 1000;
constexpr uint32 MYTEST_INITIAL_TICK = 19'000;

struct MYTEST2
{
};

struct MYTEST : public ContractBase
{
};

User Defined DataType

User-defined data types must be declared inside the contract struct to avoid name conflicts between different contracts.

using namespace QPI;

struct MYTEST2
{
};

struct MYTEST : public ContractBase
{
struct User
{
// ...
};

struct Student
{
// ...
};
};

Contract Error

Declare contract-specific errors as an enum

enum ContractNameError
{
ERROR_1 = 1,
ERROR_2,
};

SELF And SELF_INDEX

  • SELF is your contract id (public key). Or you can generate it manually by id(CONTRACT_INDEX, 0, 0 , 0).

  • SELF_INDEX is your contract index.

Smart Contract Initialization

Always initialize all state variables explicitly in INITIALIZE() procedure.

INITIALIZE()
{
state.a = 0;
state.b = 1;
}

String Implementation

Using "" in smart contract is prohibited, use to represent string you can use this implementation.

String Implementation

String Implementation Extended

Advanced Data Types in QPI

Qubic's Programming Interface (QPI) provides high-performance, deterministic data structures optimized for smart contract execution, including:

HashMap (Key-Value Store)

Example:

// Token balance tracking
HashMap<id, sint64, 1024> balances;
balances.set(userId, 1000);

// Fast lookup
sint64 amount;
if (balances.get(userId, amount))
{
qpi.transfer(recipient, amount);
}

BitArray (Compact Boolean Storage)

Example:

// Track 256 permissions (uses only 32 bytes)
BitArray<256> userPermissions;

// Set bit at position 42 to 'true' (grant access)
userPermissions.set(42, true);

// Check permission status (returns 'bit' type)
bit hasAccess = userPermissions.get(42);

// Bulk operation: Count active permissions
uint64 activeCount = 0;
for (uint64 i = 0; i < 256; ++i) {
if (userPermissions.get(i)) activeCount++;
}

Collections (Priority Queues)

Example:

// Define a task with priority
struct Task
{
id requester;
uint64 dueTick;
Array<uint8, 32> data;
};

// Initialize collection (per PoV)
Collection<Task, 1024> taskQueue; // Holds 1024 tasks max

// Add task with priority (lower = higher priority)
sint64 addTask(const id& pov, const Task& task)
{
return taskQueue.add(pov, task, task.dueTick);
}

// Process highest-priority task (lowest dueTick)
Task getNextTask(const id& pov)
{
sint64 nextIdx = taskQueue.headIndex(pov, qpi.tick());
if (nextIdx != NULL_INDEX) {
return taskQueue.element(nextIdx);
}
return Task{}; // Null task
}

HashSet (Array Of Unique Elements)

Example:

// Initialize HashSet with 256 slots (max 204 items at 80% load factor)
HashSet<id, 256> whitelist;

// Add addresses to whitelist
whitelist.add(user1); // Returns true if added
whitelist.add(user2);

// Check membership (O(1) average)
if (whitelist.contains(user1))
{
// Grant access
}

// Remove entry
whitelist.remove(user2); // Marks for deletion
whitelist.cleanup(); // Reclaims space