Skip to content

feat: Add QSurv contract and tests#771

Open
IanLaFlair wants to merge 9 commits intoqubic:developfrom
IanLaFlair:feature/QSurv
Open

feat: Add QSurv contract and tests#771
IanLaFlair wants to merge 9 commits intoqubic:developfrom
IanLaFlair:feature/QSurv

Conversation

@IanLaFlair
Copy link

@IanLaFlair IanLaFlair commented Feb 19, 2026

Description

This PR introduces QSurv (Qubic Survey), a decentralized and verifiable survey smart contract integrated into the Qubic Core.

QSurv enables entities to create and fund surveys directly on the Qubic network. Respondents are then rewarded autonomously upon successful survey completion. Central to the contract is its Oracle integration, ensuring that off-chain survey data or respondent verification is securely bridged on-chain.

Key Features

  • Survey Creation: Entities can instantiate new surveys (createSurvey) by allocating a rewardPool, defining a max targetRespondents, and providing an IPFS hash containing the survey metadata/questions.
  • Oracle Governance: A trusted Oracle is assigned via setOracle to act as the ultimate judge for survey completions and reward distributions.
  • Automated Payouts: The payout function automatically distributes the base reward, platform fees (1%), and optional referral/bonus payouts to verified respondents.
  • Slot Reusability: Designed with MAX_SURVEYS limits in mind, the contract actively reuses inactive slots to prevent indefinite storage creep.

Updates (Addressing PR Feedback)

  • Resolved division edge-cases and precise routing for platform, referral, and base rewards to ensure exactly 100% of funds are accounted for without leaking or deflating the _surveys balance.
  • Patched an oracle hijacking vulnerability by immediately assigning _oracleAddress during the contract's INITIALIZE() routine.
  • Updated test/contract_qsurv.cpp to include comprehensive Google Tests covering success conditions, insufficient funds, permission rejections, and zero-value bounds.
  • Formatted all .h and .cpp files to adhere strictly to Qubic's Allman brace style guidelines.

@IanLaFlair
Copy link
Author

ok

@Franziska-Mueller Franziska-Mueller changed the base branch from main to develop February 19, 2026 15:21
@Franziska-Mueller
Copy link
Collaborator

please use develop as base for your changes and make sure your code compiles without any warnings.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure you keep the existing format of the files. The new lines introduce a lot of noise in the changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please make sure you leave the #include order as is.


#endif

// new contracts should be added above this line
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep these guards as is as they will be removed in the development branch when we update it.


#endif

#define QSURV_CONTRACT_INDEX 25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You miss the #undef, see the other contracts above for the correct schema.

Copy link
Contributor

@fnordspace fnordspace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall a good start but there some major points that need to be addressed:

  • Please do not change the formating of files
  • Use the style described in https://github.com/qubic/core/blob/main/doc/contributing.md#style-guidelines for your Contract
  • The PR description should clearly describe the SC functionality or link to the proposal with the description.
  • src/Qubic.vcxproj and src/Qubic.vcxproj.filters: Adding the SC header file to the Qubic project in the VS Solution.
  • test/test.vcxproj and test/test.vcxproj.filters: Adding the SC test file to the test project in the VS Solution.
  • Make sure the UEFI and test build run without Errors and use the develop branch as base branch
  • Also make sure you check your contract with the Contract Verify tool https://github.com/qubic/core/blob/main/doc/contracts.md#review-and-tests, at the moment there are no problems in this regards.

#define CONTRACT_STATE2_TYPE QSURV2
#include "contracts/QSurv.h"

#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This #endif has no coresponding #if probably a copy paste error from pulse. Leave the compile time switch away for the moment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please make sure you leave the #include order as is.

}

// Verify invocation reward matches rewardPool
if (qpi.invocationReward() < input.rewardPool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user send more Qus than needed, everything is kept. Can be ok but worth thinking about refund the excess back to the user.


PUBLIC_PROCEDURE(setOracle) {
// Only allow setting oracle if not already set, or by current oracle
if (state._oracleAddress == NULL_ID ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first caller to this procedures can set the oracleAddress. Probably better to set the inital _oracleAddress in INITIALIZE so that no one can try to be faster and 'steal' the oracleAddress by setting it.


// Calculate reward splits using QPI::div (no / operator allowed)
locals.totalReward = locals.tempSurvey.rewardPerRespondent;
locals.baseReward =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to see a bit more detail how the payout is planned. At the moment the total amount only accounts for 90% (60% Base, 25% Referral, 5% Platform) but 100% are deducted from the survey balance.

Also it is not clear to me how the bonus system works, in its current form it might deplete the contracts Qus over time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth to check if you want to have a way to cancel surveys if not enough people are answering. Also at the moment there will be a maximum of 1024 contracts ever before the contract is rendered useless completed surveys are never cleaned and the slot remains occupied.

Please also make sure your contract follows the style guide https://github.com/qubic/core/blob/main/doc/contributing.md#style-guidelines

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great that you test, test could be more thorough tho. Some ideas on top of my mind:

  • Verify the expected survey and contract balance after payout
  • Survey competition works as expected e.g., isActive => 0
  • createSurvey with maxRespondents = 0 or rewardPool = 0
  • Set oracle from non oracle id (should fail)
  • Payout when currentRespondents >= maxRespondents (should fail)

@IanLaFlair
Copy link
Author

Hi @fnordspace, I have updated the PR to address the feedback. I've patched the potential vulnerabilities in

setOracle, updated the exact payout logic to prevent deflation/division issues, thoroughly expanded the test suites in contract_qsurv.cpp, and adhered to the Allman formatting guidelines. I also resolved the recent merge conflicts

@fnordspace
Copy link
Contributor

fnordspace commented Feb 21, 2026

@IanLaFlair Thanks for the update. I'll take a look later. However the last commit also includes your build directory and hence adds around 200 files. Please do not commit these. Also see my other comments about changing the format of, for example, contract_def.h.

Copy link
Contributor

@fnordspace fnordspace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR is not in a state for a proper review. The code will not compile in it current version and style restrictions are not followed. Please first fix all the issues, make sure it compiles with MSVC (not cmake/clang) and that all tests runs.

I strongly suggest you test your SC functionality thoroughly as in the moment I don't think it is doing what you expect. To test your SC you can use the Qubic Core-Lite https://github.com/qubic/core-lite. This node you can run in single node mode and test your SC in a ticking network.

"Size of contract state " #contractName " is too large!"); \
}

<<<<<<< HEAD static void initializeContracts() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are git conflict marker, should not be here

#endif
REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(QSURV);
// new contracts should be added above this line
== == == =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(QTF);
REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(QDUEL);
// new contracts should be added above this line
>>>>>>> upstream/develop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

// ============================================
public:
INITIALIZE() {
state._oracleAddress = qpi.invocator();
Copy link
Contributor

@fnordspace fnordspace Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

INITIALIZE() has no invocator hence it probably will set the oracleAddress to null leaving the same problem open.

@Franziska-Mueller
Copy link
Collaborator

Franziska-Mueller commented Feb 23, 2026

@IanLaFlair Do not force-push after we've started reviewing/writing comments. The force-push destroys the association of the comments with the code and we're unable to see the changes you introduce with new commits.

@IanLaFlair
Copy link
Author

@Franziska-Mueller Hi Franziska, thanks for the guidance!
I've addressed all the feedback and pushed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants