diff --git a/README.md b/README.md index f656f14..c744bdf 100644 --- a/README.md +++ b/README.md @@ -194,24 +194,58 @@ Commands: You need to own/possess the shares to do this (seed required). [QTRY COMMANDS] - -qtrygetbasicinfo - Show qtry basic info from a node. - -qtryissuebet - Issue a bet (prompt mode) - -qtrygetactivebet - Show all active bet id. - -qtrygetactivebetbycreator - Show all active bet id of an ID. - -qtrygetbetinfo - Get meta information of a bet - -qtrygetbetdetail - Get a list of IDs that bet on of the bet - -qtryjoinbet - Join a bet - -qtrypublishresult - (Oracle providers only) publish a result for a bet - -qtrycancelbet - (Game operator only) cancel a bet + Format: -qtry [subcommand for QTRY] [other params] + -qtry getbasicinfo + Show Quottery basic info from a node (fees, revenues, game operator, anti-spam amount, etc.). + -qtry getactiveeventsid + Show recent active event IDs. + -qtry createevent [EVENT_DESC] [OPTION_0_DESC] [OPTION_1_DESC] [END_DATE] [TAG_ID] + Create a new Quottery event. EVENT_DESC max 126 bytes, OPTION_*_DESC max 64 bytes each. Game operator only. + END_DATE format: \"YYYY-MM-DD hh:mm:ss\" (UTC). TAG_ID is a uint16 category tag. + -qtry order add/remove bid/ask [EVENT_ID] [OPTION] [AMOUNT] [PRICE] + Place or remove a bid/ask order on Quottery. OPTION must be 0 or 1. + -qtry getorder bid/ask [EVENT_ID] [OPTION] [OFFSET] + Get bid/ask orders for an event option, paginated by OFFSET. + -qtry getposition [IDENTITY] + Get all positions held by an identity on Quottery. + -qtry geteventinfo [EVENT_ID] + Get detailed info for a single event (description, options, dates, result, dispute state). + -qtry geteventinfobatch [EVENT_ID_1] [EVENT_ID_2] ... [EVENT_ID_N] + Get detailed info for up to 64 events in one request. + -qtry publishresult [EVENT_ID] [OPTION_ID] + Publish the result for an event. OPTION_ID must be 0 or 1. Game operator only. + Requires the event's end date to have passed. Locks the dispute deposit amount. + -qtry tryfinalizeevent [EVENT_ID] + Finalize an event after the dispute window (~1000 ticks past publish). Game operator only. + -qtry dispute [EVENT_ID] + Dispute a published result. Requires the dispute deposit amount; only valid before finalization. + -qtry resolvedispute [EVENT_ID] [VOTE] + Resolve a dispute by voting (VOTE: 0 = No, 1 = Yes). Computor only. + Requires a small invocation reward (refunded if caller is a computor). + -qtry claimreward [EVENT_ID] + Claim reward for a winning position in a finalized event. Requires a 1,000,000 qu fee + (refunded only if you have a winning position; otherwise lost as anti-spam). + -qtry forceclaimreward [EVENT_ID] [IDENTITY_1] [IDENTITY_2] ... [IDENTITY_16] + Force-claim rewards on behalf of up to 16 identities. Game operator only. + -qtry transfersharemgmt [ISSUER_IDENTITY] [ASSET_NAME] [NUMBER_OF_SHARES] [NEW_MANAGING_CONTRACT_INDEX] + Transfer asset share management rights from Quottery to another contract. + -qtry cleanmemory + Finalize events with published results past dispute window and clean up state. Game operator only. + -qtry transferqusd [RECEIVER_IDENTITY] [AMOUNT] + Transfer QUSD (GARTH) via the Quottery contract. + -qtry transferqtrygov [RECEIVER_IDENTITY] [AMOUNT] + Transfer QTRYGOV tokens via the Quottery contract. AMOUNT must be positive. + -qtry updatediscount [USER_IDENTITY] set [NEW_FEE_RATE] + -qtry updatediscount [USER_IDENTITY] remove + Set or remove a fee discount for a user. Game operator only. + NEW_FEE_RATE is in tenths of a percent (max 1000 = 100%%). + -qtry proposalvote [OPERATION_FEE] [SHAREHOLDER_FEE] [BURN_FEE] [FEE_PER_DAY] [DEPOSIT_FOR_DISPUTE] [OPERATION_ID_IDENTITY] + Cast a governance proposal vote with the proposed parameters. Costs the anti-spam amount. QTRYGOV holder only. + Fees are in tenths of a percent (e.g. 50 = 5.0%%). + -qtry getapprovedamount [IDENTITY] + Get the approved QUSD amount for an identity. + -qtry gettopproposals + Get the top governance proposals (up to 3) for the current epoch. [GENERAL QUORUM PROPOSAL COMMANDS] -gqmpropsetproposal diff --git a/argparser.h b/argparser.h index b61da06..585687c 100644 --- a/argparser.h +++ b/argparser.h @@ -216,24 +216,58 @@ void print_help() printf("\t\tTransfer asset management rights of shares from QX to another contract. can be given as name or index. You need to own/possess the shares to do this (seed required).\n"); printf("\n[QTRY COMMANDS]\n"); - printf("\t-qtrygetbasicinfo\n"); - printf("\t\tShow qtry basic info from a node.\n"); - printf("\t-qtryissuebet\n"); - printf("\t\tIssue a bet (prompt mode)\n"); - printf("\t-qtrygetactivebet\n"); - printf("\t\tShow all active bet id.\n"); - printf("\t-qtrygetactivebetbycreator \n"); - printf("\t\tShow all active bet id of an ID.\n"); - printf("\t-qtrygetbetinfo \n"); - printf("\t\tGet meta information of a bet\n"); - printf("\t-qtrygetbetdetail \n"); - printf("\t\tGet a list of IDs that bet on of the bet \n"); - printf("\t-qtryjoinbet \n"); - printf("\t\tJoin a bet\n"); - printf("\t-qtrypublishresult \n"); - printf("\t\t(Oracle providers only) publish a result for a bet\n"); - printf("\t-qtrycancelbet \n"); - printf("\t\t(Game operator only) cancel a bet\n"); + printf("\tFormat: -qtry [subcommand for QTRY] [other params]\n"); + printf("\t-qtry getbasicinfo\n"); + printf("\t\tShow Quottery basic info from a node (fees, revenues, game operator, anti-spam amount, etc.).\n"); + printf("\t-qtry getactiveeventsid\n"); + printf("\t\tShow recent active event IDs.\n"); + printf("\t-qtry createevent [EVENT_DESC] [OPTION_0_DESC] [OPTION_1_DESC] [END_DATE] [TAG_ID]\n"); + printf("\t\tCreate a new Quottery event. EVENT_DESC max 126 bytes, OPTION_*_DESC max 64 bytes each. Game operator only.\n"); + printf("\t\tEND_DATE format: \"YYYY-MM-DD hh:mm:ss\" (UTC). TAG_ID is a uint16 category tag.\n"); + printf("\t-qtry order add/remove bid/ask [EVENT_ID] [OPTION] [AMOUNT] [PRICE]\n"); + printf("\t\tPlace or remove a bid/ask order on Quottery. OPTION must be 0 or 1.\n"); + printf("\t-qtry getorder bid/ask [EVENT_ID] [OPTION] [OFFSET]\n"); + printf("\t\tGet bid/ask orders for an event option, paginated by OFFSET.\n"); + printf("\t-qtry getposition [IDENTITY]\n"); + printf("\t\tGet all positions held by an identity on Quottery.\n"); + printf("\t-qtry geteventinfo [EVENT_ID]\n"); + printf("\t\tGet detailed info for a single event (description, options, dates, result, dispute state).\n"); + printf("\t-qtry geteventinfobatch [EVENT_ID_1] [EVENT_ID_2] ... [EVENT_ID_N]\n"); + printf("\t\tGet detailed info for up to 64 events in one request.\n"); + printf("\t-qtry publishresult [EVENT_ID] [OPTION_ID]\n"); + printf("\t\tPublish the result for an event. OPTION_ID must be 0 or 1. Game operator only.\n"); + printf("\t\tRequires the event's end date to have passed. Locks the dispute deposit amount.\n"); + printf("\t-qtry tryfinalizeevent [EVENT_ID]\n"); + printf("\t\tFinalize an event after the dispute window (~1000 ticks past publish). Game operator only.\n"); + printf("\t-qtry dispute [EVENT_ID]\n"); + printf("\t\tDispute a published result. Requires the dispute deposit amount; only valid before finalization.\n"); + printf("\t-qtry resolvedispute [EVENT_ID] [VOTE]\n"); + printf("\t\tResolve a dispute by voting (VOTE: 0 = No, 1 = Yes). Computor only.\n"); + printf("\t\tRequires a small invocation reward (refunded if caller is a computor).\n"); + printf("\t-qtry claimreward [EVENT_ID]\n"); + printf("\t\tClaim reward for a winning position in a finalized event. Requires a 1,000,000 qu fee\n"); + printf("\t\t(refunded only if you have a winning position; otherwise lost as anti-spam).\n"); + printf("\t-qtry forceclaimreward [EVENT_ID] [IDENTITY_1] [IDENTITY_2] ... [IDENTITY_16]\n"); + printf("\t\tForce-claim rewards on behalf of up to 16 identities. Game operator only.\n"); + printf("\t-qtry transfersharemgmt [ISSUER_IDENTITY] [ASSET_NAME] [NUMBER_OF_SHARES] [NEW_MANAGING_CONTRACT_INDEX]\n"); + printf("\t\tTransfer asset share management rights from Quottery to another contract.\n"); + printf("\t-qtry cleanmemory\n"); + printf("\t\tFinalize events with published results past dispute window and clean up state. Game operator only.\n"); + printf("\t-qtry transferqusd [RECEIVER_IDENTITY] [AMOUNT]\n"); + printf("\t\tTransfer QUSD (GARTH) via the Quottery contract.\n"); + printf("\t-qtry transferqtrygov [RECEIVER_IDENTITY] [AMOUNT]\n"); + printf("\t\tTransfer QTRYGOV tokens via the Quottery contract. AMOUNT must be positive.\n"); + printf("\t-qtry updatediscount [USER_IDENTITY] set [NEW_FEE_RATE]\n"); + printf("\t-qtry updatediscount [USER_IDENTITY] remove\n"); + printf("\t\tSet or remove a fee discount for a user. Game operator only.\n"); + printf("\t\tNEW_FEE_RATE is in tenths of a percent (max 1000 = 100%%).\n"); + printf("\t-qtry proposalvote [OPERATION_FEE] [SHAREHOLDER_FEE] [BURN_FEE] [FEE_PER_DAY] [DEPOSIT_FOR_DISPUTE] [OPERATION_ID_IDENTITY]\n"); + printf("\t\tCast a governance proposal vote with the proposed parameters. Costs the anti-spam amount. QTRYGOV holder only.\n"); + printf("\t\tFees are in tenths of a percent (e.g. 50 = 5.0%%).\n"); + printf("\t-qtry getapprovedamount [IDENTITY]\n"); + printf("\t\tGet the approved QUSD amount for an identity.\n"); + printf("\t-qtry gettopproposals\n"); + printf("\t\tGet the top governance proposals (up to 3) for the current epoch.\n"); printf("\n[GENERAL QUORUM PROPOSAL COMMANDS]\n"); printf("\t-gqmpropsetproposal \n"); @@ -1398,85 +1432,11 @@ void parseArgument(int argc, char** argv) /************************* ***** QTRY COMMANDS ***** *************************/ - - if (strcmp(argv[i], "-qtryissuebet") == 0) - { - g_cmd = QUOTTERY_ISSUE_BET; - i+=1; - CHECK_OVER_PARAMETERS - break; - } - if (strcmp(argv[i], "-qtryjoinbet") == 0) - { - CHECK_NUMBER_OF_PARAMETERS(4) - g_cmd = QUOTTERY_JOIN_BET; - g_quottery_betId = uint32_t(charToNumber(argv[i + 1])); - g_quottery_numberBetSlot = charToNumber(argv[i+2]); - g_quottery_amountPerBetSlot = charToNumber(argv[i+3]); - g_quottery_pickedOption = uint32_t(charToNumber(argv[i+4])); - i+=5; - CHECK_OVER_PARAMETERS - break; - } - if (strcmp(argv[i], "-qtrygetbetinfo") == 0) - { - CHECK_NUMBER_OF_PARAMETERS(1) - g_cmd = QUOTTERY_GET_BET_INFO; - g_quottery_betId = uint32_t(charToNumber(argv[i + 1])); - i+=2; - CHECK_OVER_PARAMETERS - break; - } - if (strcmp(argv[i], "-qtrygetbetdetail") == 0) - { - CHECK_NUMBER_OF_PARAMETERS(2) - g_cmd = QUOTTERY_GET_BET_DETAIL; - g_quottery_betId = uint32_t(charToNumber(argv[i + 1])); - g_quottery_optionId = uint32_t(charToNumber(argv[i + 2])); - i+=3; - CHECK_OVER_PARAMETERS - break; - } - if (strcmp(argv[i], "-qtrygetactivebet") == 0) - { - g_cmd = QUOTTERY_GET_ACTIVE_BET; - i+=1; - CHECK_OVER_PARAMETERS - break; - } - if (strcmp(argv[i], "-qtrygetactivebetbycreator") == 0) - { - CHECK_NUMBER_OF_PARAMETERS(1) - g_cmd = QUOTTERY_GET_ACTIVE_BET_BY_CREATOR; - g_quottery_creatorId = argv[i+1]; - i+=2; - CHECK_OVER_PARAMETERS - break; - } - if (strcmp(argv[i], "-qtrygetbasicinfo") == 0) - { - g_cmd = QUOTTERY_GET_BASIC_INFO; - i+=1; - CHECK_OVER_PARAMETERS - break; - } - if (strcmp(argv[i], "-qtrypublishresult") == 0) - { - CHECK_NUMBER_OF_PARAMETERS(2) - g_cmd = QUOTTERY_PUBLISH_RESULT; - g_quottery_betId = uint32_t(charToNumber(argv[i + 1])); - g_quottery_optionId = uint32_t(charToNumber(argv[i + 2])); - i+=3; - CHECK_OVER_PARAMETERS - break; - } - if (strcmp(argv[i], "-qtrycancelbet") == 0) - { - CHECK_NUMBER_OF_PARAMETERS(1) - g_cmd = QUOTTERY_CANCEL_BET; - g_quottery_betId = uint32_t(charToNumber(argv[i + 1])); - i+=2; - CHECK_OVER_PARAMETERS + if (strcmp(argv[i], "-qtry") == 0) { + g_cmd = QUOTTERY_COMMAND; + int c = 0; + while (i < 128 && i < argc) g_quottery_subcmd[c++] = argv[i++]; + g_quottery_subcmd_count = c; break; } diff --git a/connection.cpp b/connection.cpp index de23462..867a114 100644 --- a/connection.cpp +++ b/connection.cpp @@ -326,12 +326,16 @@ template BroadcastComputors QubicConnection::receivePacketWithHeaderAs(); template std::vector QubicConnection::getLatestVectorPacketAs(); template SpecialCommandExecutionFeeMultiplierRequestAndResponse QubicConnection::receivePacketWithHeaderAs(); +template RespondedEntity QubicConnection::receivePacketWithHeaderAs(); // QUOTTERY template qtryBasicInfo_output QubicConnection::receivePacketWithHeaderAs(); -template getBetInfo_output QubicConnection::receivePacketWithHeaderAs(); -template getBetOptionDetail_output QubicConnection::receivePacketWithHeaderAs(); -template getActiveBet_output QubicConnection::receivePacketWithHeaderAs(); -template getActiveBetByCreator_output QubicConnection::receivePacketWithHeaderAs(); +template getEventInfo_output QubicConnection::receivePacketWithHeaderAs(); +template qtryGetOrders_output QubicConnection::receivePacketWithHeaderAs(); +template getUserPosition_output QubicConnection::receivePacketWithHeaderAs(); +template getActiveEvent_output QubicConnection::receivePacketWithHeaderAs(); +template GetEventInfoBatch_output QubicConnection::receivePacketWithHeaderAs(); +template getApprovedAmount_output QubicConnection::receivePacketWithHeaderAs(); +template getTopProposals_output QubicConnection::receivePacketWithHeaderAs(); // QX template QxFees_output QubicConnection::receivePacketWithHeaderAs(); template qxGetAssetOrder_output QubicConnection::receivePacketWithHeaderAs(); diff --git a/global.h b/global.h index 84acbac..b427bc7 100644 --- a/global.h +++ b/global.h @@ -83,12 +83,27 @@ char* g_qx_assetTransferAssetName = nullptr; char* g_qx_assetTransferIssuerInHex = nullptr; // quottery -uint32_t g_quottery_betId = 0; +char* g_quottery_subcmd[128] = { nullptr }; +int g_quottery_subcmd_count = 0; +char* g_event_desc = nullptr; +char* g_opt0_desc = nullptr; +char* g_opt1_desc = nullptr; +char* g_end_date_desc = nullptr; +uint32_t g_quottery_eventId = 0; uint32_t g_quottery_optionId = 0; char* g_quottery_creatorId = nullptr; uint64_t g_quottery_numberBetSlot = 0; uint64_t g_quottery_amountPerBetSlot = 0; uint32_t g_quottery_pickedOption = 0; +uint64_t g_quottery_option = 0; +uint64_t g_quottery_isBid = 0; +uint64_t g_quottery_amount = 0; +int64_t g_quottery_price = 0; +uint64_t g_quottery_antiSpamAmount = 0; +uint64_t g_quottery_offset = 0; +const char* g_quottery_command1 = nullptr; +const char* g_quottery_command2 = nullptr; +const char* g_quottery_positionIdentity = nullptr; // qutil char* g_qutil_sendToManyV1PayoutListFile = nullptr; diff --git a/main.cpp b/main.cpp index fc7a584..b3b2748 100644 --- a/main.cpp +++ b/main.cpp @@ -286,46 +286,10 @@ int run(int argc, char* argv[]) sanityCheckNode(g_nodeIp, g_nodePort); printActiveIPOs(g_nodeIp, g_nodePort); break; - case QUOTTERY_ISSUE_BET: + case QUOTTERY_COMMAND: sanityCheckNode(g_nodeIp, g_nodePort); sanityCheckSeed(g_seed); - quotteryIssueBet(g_nodeIp, g_nodePort, g_seed, g_offsetScheduledTick); - break; - case QUOTTERY_GET_BET_INFO: - sanityCheckNode(g_nodeIp, g_nodePort); - quotteryPrintBetInfo(g_nodeIp, g_nodePort, g_quottery_betId); - break; - case QUOTTERY_JOIN_BET: - sanityCheckNode(g_nodeIp, g_nodePort); - sanityCheckSeed(g_seed); - quotteryJoinBet(g_nodeIp, g_nodePort, g_seed, g_quottery_betId, int(g_quottery_numberBetSlot), g_quottery_amountPerBetSlot, g_quottery_pickedOption, g_offsetScheduledTick); - break; - case QUOTTERY_GET_BET_DETAIL: - sanityCheckNode(g_nodeIp, g_nodePort); - quotteryPrintBetOptionDetail(g_nodeIp, g_nodePort, g_quottery_betId, g_quottery_optionId); - break; - case QUOTTERY_GET_ACTIVE_BET: - sanityCheckNode(g_nodeIp, g_nodePort); - quotteryPrintActiveBet(g_nodeIp, g_nodePort); - break; - case QUOTTERY_GET_ACTIVE_BET_BY_CREATOR: - sanityCheckNode(g_nodeIp, g_nodePort); - sanityCheckIdentity(g_quottery_creatorId); - quotteryPrintActiveBetByCreator(g_nodeIp, g_nodePort, g_quottery_creatorId); - break; - case QUOTTERY_GET_BASIC_INFO: - sanityCheckNode(g_nodeIp, g_nodePort); - quotteryPrintBasicInfo(g_nodeIp, g_nodePort); - break; - case QUOTTERY_PUBLISH_RESULT: - sanityCheckNode(g_nodeIp, g_nodePort); - sanityCheckSeed(g_seed); - quotteryPublishResult(g_nodeIp, g_nodePort, g_seed, g_quottery_betId, g_quottery_optionId, g_offsetScheduledTick); - break; - case QUOTTERY_CANCEL_BET: - sanityCheckNode(g_nodeIp, g_nodePort); - sanityCheckSeed(g_seed); - quotteryCancelBet(g_nodeIp, g_nodePort, g_seed, g_quottery_betId, g_offsetScheduledTick); + quotteryEntryPoint(g_quottery_subcmd_count, g_quottery_subcmd, g_nodeIp, g_nodePort, g_seed, g_offsetScheduledTick); break; case TOOGLE_MAIN_AUX: sanityCheckNode(g_nodeIp, g_nodePort); diff --git a/quottery.cpp b/quottery.cpp index 29af549..1ac9521 100644 --- a/quottery.cpp +++ b/quottery.cpp @@ -13,28 +13,92 @@ #include "connection.h" #include "logger.h" #include "wallet_utils.h" - constexpr int QUOTTERY_CONTRACT_ID = 2; - -enum quotteryViewId +/** + * @return pack DateAndTime data from year, month, day, hour, minute, second, millisec, microsec to a uint64_t + * Bit layout: year(18) | month(4) | day(5) | hour(5) | minute(6) | second(6) | millisec(10) | microsec(10) + */ +static void packDateTime(uint32_t _year, uint32_t _month, uint32_t _day, uint32_t _hour, uint32_t _minute, uint32_t _second, uint32_t _millisec, uint32_t _microsec, uint64_t& res) { - fee = 1, - betInfo = 2, - betDetail = 3, - activeBet = 4, - activeBetByCreator = 5 -}; + res = ((uint64_t)_year << 46) | ((uint64_t)_month << 42) | ((uint64_t)_day << 37) | ((uint64_t)_hour << 32) + | ((uint64_t)_minute << 26) | ((uint64_t)_second << 20) | ((uint64_t)_millisec << 10) | (uint64_t)_microsec; +} -enum quotteryFuncId +#define DATETIME_GET_YEAR(data) ((data >> 46)) +#define DATETIME_GET_MONTH(data) ((data >> 42) & 0b1111) +#define DATETIME_GET_DAY(data) ((data >> 37) & 0b11111) +#define DATETIME_GET_HOUR(data) ((data >> 32) & 0b11111) +#define DATETIME_GET_MINUTE(data) ((data >> 26) & 0b111111) +#define DATETIME_GET_SECOND(data) ((data >> 20) & 0b111111) +#define DATETIME_GET_MILLISEC(data) ((data >> 10) & 0b1111111111) +#define DATETIME_GET_MICROSEC(data) ((data) & 0b1111111111) + +/** +* @return unpack DateAndTime from uint64 to year, month, day, hour, minute, second, millisec, microsec +*/ +void unpackDateTime(uint32_t& _year, uint8_t& _month, uint8_t& _day, uint8_t& _hour, uint8_t& _minute, uint8_t& _second, uint16_t& _millisec, uint16_t& _microsec, uint64_t data) { - issue = 1, - join = 2, - cancelBet = 3, - publishResult = 4 -}; + _year = DATETIME_GET_YEAR(data); // 18 bits + _month = DATETIME_GET_MONTH(data); // 4 bits + _day = DATETIME_GET_DAY(data); // 5 bits + _hour = DATETIME_GET_HOUR(data); // 5 bits + _minute = DATETIME_GET_MINUTE(data); // 6 bits + _second = DATETIME_GET_SECOND(data); // 6 bits + _millisec = DATETIME_GET_MILLISEC(data); // 10 bits + _microsec = DATETIME_GET_MICROSEC(data); // 10 bits +} + +// QTRY PROCEDURES +#define QTRY_CREATE_EVENT 1 +#define QTRY_ADD_ASK_ORDER 2 +#define QTRY_REMOVE_ASK_ORDER 3 +#define QTRY_ADD_BID_ORDER 4 +#define QTRY_REMOVE_BID_ORDER 5 +#define QTRY_PUBLISH_RESULT 6 +#define QTRY_TRY_FINALIZE_EVENT 7 +#define QTRY_DISPUTE 8 +#define QTRY_RESOLVE_DISPUTE 9 +#define QTRY_USER_CLAIM_REWARD 10 +#define QTRY_GO_FORCE_CLAIM_REWARD 11 +#define QTRY_TRANSFER_QUSD 12 +#define QTRY_TRANSFER_SHARE_MANAGEMENT_RIGHTS 13 +#define QTRY_CLEAN_MEMORY 14 +#define QTRY_TRANSFER_QTRYGOV 15 +#define QTRY_UPDATE_FEE_DISCOUNT_LIST 20 +#define QTRY_PROPOSAL_VOTE 100 +// QTRY FUNCTIONS +#define QTRY_GET_BASIC 1 +#define QTRY_GET_EVENT 2 +#define QTRY_GET_ORDERS 3 +#define QTRY_GET_ACTIVE_EVENTS 4 +#define QTRY_GET_EVENT_BATCH 5 +#define QUOTTERY_GET_USER_POSITION 6 +#define QTRY_GET_APPROVED_AMOUNT 7 +#define QTRY_GET_TOP_PROPOSALS 8 + +#define QUOTTERY_EO_GET_OPTION(eo) ((eo) >> 63) +#define QUOTTERY_EO_GET_EVENTID(eo) ((eo) & 0x3FFFFFFFFFFFFFFFULL) + +static int64_t getBalanceNumber(QCPtr& qc, const uint8_t* publicKey) { + struct { + RequestResponseHeader header; + RequestedEntity req; + } packet; + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(REQUEST_ENTITY); + memcpy(packet.req.publicKey, publicKey, 32); + qc->sendData((uint8_t*)&packet, packet.header.size()); + auto result = qc->receivePacketWithHeaderAs(); + return result.entity.incomingAmount - result.entity.outgoingAmount; +} void quotteryGetBasicInfo(QCPtr& qc, qtryBasicInfo_output& result) { + memset(&result, 0, sizeof(result)); + // Note: the QCPtr overload is preserved so we can pass an already-open connection. + // We reuse runContractFunction by extracting nodeIp/nodePort would not be possible here; + // for callers that have only nodeIp/nodePort, prefer quotteryGetBasicInfoByIp(). struct { RequestResponseHeader header; RequestContractFunction rcf; @@ -43,9 +107,9 @@ void quotteryGetBasicInfo(QCPtr& qc, qtryBasicInfo_output& result) packet.header.randomizeDejavu(); packet.header.setType(RequestContractFunction::type()); packet.rcf.inputSize = 0; - packet.rcf.inputType = quotteryViewId::fee; + packet.rcf.inputType = QTRY_GET_BASIC; packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; - qc->sendData((uint8_t *) &packet, packet.header.size()); + qc->sendData((uint8_t*)&packet, packet.header.size()); try { @@ -59,38 +123,63 @@ void quotteryGetBasicInfo(QCPtr& qc, qtryBasicInfo_output& result) void quotteryPrintBasicInfo(const char* nodeIp, const int nodePort) { - qtryBasicInfo_output result; - memset(&result, 1, sizeof(qtryBasicInfo_output)); - auto qc = make_qc(nodeIp, nodePort); - quotteryGetBasicInfo(qc, result); - LOG("Fee per slot per hour: %" PRIu64 " qu\n", result.feePerSlotPerHour); - LOG("Minimum amount of qus per bet slot: %" PRIu64 " qu\n", result.minBetSlotAmount); - LOG("Game operator Fee: %.2f%%\n", result.gameOperatorFee/100.0); - LOG("Shareholders fee: %.2f%%\n", result.shareholderFee/100.0); - LOG("Burn fee: %.2f%%\n", result.burnFee/100.0); - LOG("================\n"); - LOG("Number of issued bet: %" PRIu64 "\n", result.nIssuedBet); - LOG("moneyFlow: %" PRIu64 "\n", result.moneyFlow); - LOG("moneyFlow through issueBet: %" PRIu64 "\n", result.moneyFlowThroughIssueBet); - LOG("moneyFlow through joinBet: %" PRIu64 "\n", result.moneyFlowThroughJoinBet); - LOG("moneyFlow through finalizeBet: %" PRIu64 "\n", result.moneyFlowThroughFinalizeBet); + qtryBasicInfo_output result{}; + if (!runContractFunction(nodeIp, nodePort, QUOTTERY_CONTRACT_ID, QTRY_GET_BASIC, nullptr, 0, &result, sizeof(result))) + { + LOG("Failed to get basic info\n"); + return; + } + LOG("Operation Fee: %.2f%%\n", result.operationFee / 10.0); + LOG("Shareholders fee: %.2f%%\n", result.shareholderFee / 10.0); + LOG("Burn fee: %.2f%%\n", result.burnFee / 10.0); LOG("================\n"); - LOG("earned amount for shareholders: %" PRIu64 "\n", result.earnedAmountForShareHolder); - LOG("earned amount for winners: %" PRIu64 "\n", result.earnedAmountForBetWinner); - LOG("distributed amount: %" PRIu64 "\n", result.distributedAmount); - LOG("burned amount: %" PRIu64 "\n", result.burnedAmount); - char buf[64] = {0}; + LOG("Number of issued events: %" PRIu64 "\n", result.nIssuedEvent); + LOG("Shareholders revenue: %" PRIu64 "\n", result.shareholdersRevenue); + LOG("Operation revenue: %" PRIu64 "\n", result.operationRevenue); + LOG("Burned amount: %" PRIu64 "\n", result.burnedAmount); + LOG("feePerDay: %" PRIu64 "\n", result.feePerDay); + LOG("antiSpamAmount: %" PRIu64 "\n", result.antiSpamAmount); + LOG("depositAmountForDispute: %" PRIu64 "\n", result.depositAmountForDispute); + char buf[64] = { 0 }; getIdentityFromPublicKey(result.gameOperator, buf, false); LOG("Game operator ID: %s\n", buf); } -/** - * @return pack qtry datetime data from year, month, day, hour, minute, second to a uint32_t - * year is counted from 24 (2024) - */ -static void packQuotteryDate(uint32_t _year, uint32_t _month, uint32_t _day, uint32_t _hour, uint32_t _minute, uint32_t _second, uint32_t& res) +void quotteryGetActiveEvents(const char* nodeIp, int nodePort, getActiveEvent_output& result) +{ + memset(&result, 0, sizeof(result)); + if (!runContractFunction(nodeIp, nodePort, QUOTTERY_CONTRACT_ID, QTRY_GET_ACTIVE_EVENTS, nullptr, 0, &result, sizeof(result))) + { + memset(&result, 0, sizeof(result)); + } +} + +void quotteryPrintActiveEvents(const char* nodeIp, int nodePort) { - res = ((_year - 24) << 26) | (_month << 22) | (_day << 17) | (_hour << 12) | (_minute << 6) | (_second); + getActiveEvent_output result{}; + quotteryGetActiveEvents(nodeIp, nodePort, result); + + if (isArrayZero(reinterpret_cast(&result), sizeof(result))) + { + LOG("Failed to get recent active events\n"); + return; + } + + LOG("Recent active event IDs:\n"); + bool hasAny = false; + for (size_t i = 0; i < QUOTTERY_MAX_CONCURRENT_EVENT; ++i) + { + if (result.recentActiveEvent[i] == uint64_t(-1)) + continue; + + LOG("%" PRIu64 "\n", result.recentActiveEvent[i]); + hasAny = true; + } + + if (!hasAny) + { + LOG("(none)\n"); + } } #define QTRY_GET_YEAR(data) ((data >> 26)+24) @@ -113,613 +202,1855 @@ void unpackQuotteryDate(uint8_t& _year, uint8_t& _month, uint8_t& _day, uint8_t& _second = QTRY_GET_SECOND(data); //6bits } -static void accumulatedDay(int month, uint64_t& res) -{ - switch (month) - { - case 1: res = 0; break; - case 2: res = 31; break; - case 3: res = 59; break; - case 4: res = 90; break; - case 5: res = 120; break; - case 6: res = 151; break; - case 7: res = 181; break; - case 8: res = 212; break; - case 9: res = 243; break; - case 10:res = 273; break; - case 11:res = 304; break; - case 12:res = 334; break; - } -} -static int dateCompare(uint32_t& A, uint32_t& B, uint32_t& i) +struct QuotteryCreateEvent_input { - if (A == B) return 0; - if (A < B) return -1; - return 1; -} + uint64_t eid; + uint64_t openDate; // submitted date + uint64_t endDate; // stop receiving result from OPs + uint8_t desc[128]; + uint8_t option0Desc[64]; + uint8_t option1Desc[64]; +}; -// return diff in number of second, A must be smaller than or equal B to have valid value -static void diffDate(uint32_t& A, uint32_t& B, uint32_t& i, uint64_t& dayA, uint64_t& dayB, uint64_t& res) +void quotteryCreateEvent(const char* nodeIp, int nodePort, const char* seed, + const std::string eventDesc, + const std::string opt0Desc, + const std::string opt1Desc, + const std::string endDate, + uint16_t tagId, + uint32_t scheduledTickOffset) { - if (dateCompare(A, B, i) >= 0) - { - res = 0; - return; - } - // TODO: convert local variables to locals struct when finalizing - accumulatedDay(QTRY_GET_MONTH(A), dayA); - dayA += QTRY_GET_DAY(A); - accumulatedDay(QTRY_GET_MONTH(B), dayB); - dayB += (QTRY_GET_YEAR(B) - QTRY_GET_YEAR(A)) * 365ULL + QTRY_GET_DAY(B); + QuotteryCreateEvent_input cei{}; + + // Copy desc text, but cap at 128 bytes; pack tagId as uint16 LE into desc[126:128] + memcpy(cei.desc, eventDesc.c_str(), std::min(int(eventDesc.size()), 128)); + cei.desc[126] = (uint8_t)(tagId & 0xFF); + cei.desc[127] = (uint8_t)((tagId >> 8) & 0xFF); + memcpy(cei.option0Desc, opt0Desc.c_str(), std::min(int(opt0Desc.size()), 64)); + memcpy(cei.option1Desc, opt1Desc.c_str(), std::min(int(opt1Desc.size()), 64)); - // handling leap-year: only store last 2 digits of year here, don't care about mod 100 & mod 400 case - for (i = QTRY_GET_YEAR(A); i < QTRY_GET_YEAR(B); i++) { - if (i%4 == 0) - { - dayB++; + auto buff = endDate.data(); + if (strlen(buff) != 19 || buff[4] != '-' || buff[7] != '-' || buff[10] != ' ' || buff[13] != ':' || + buff[16] != ':' || + !isdigit(buff[0]) || !isdigit(buff[1]) || !isdigit(buff[2]) || !isdigit(buff[3]) || + !isdigit(buff[5]) || !isdigit(buff[6]) || !isdigit(buff[8]) || !isdigit(buff[9]) || + !isdigit(buff[11]) || !isdigit(buff[12]) || !isdigit(buff[14]) || !isdigit(buff[15]) || + !isdigit(buff[17]) || !isdigit(buff[18])) { + LOG("Error: Invalid date-time format. Please follow the format: YYYY-MM-DD hh:mm:ss\n"); + exit(EXIT_FAILURE); } + + uint32_t year = (buff[0] - 48) * 1000 + (buff[1] - 48) * 100 + (buff[2] - 48) * 10 + (buff[3] - 48); + uint8_t month = (buff[5] - 48) * 10 + (buff[6] - 48); + uint8_t day = (buff[8] - 48) * 10 + (buff[9] - 48); + uint8_t hour = (buff[11] - 48) * 10 + (buff[12] - 48); + uint8_t minute = (buff[14] - 48) * 10 + (buff[15] - 48); + uint8_t sec = (buff[17] - 48) * 10 + (buff[18] - 48); + packDateTime(year, month, day, hour, minute, sec, 0, 0, cei.endDate); } - if (int(QTRY_GET_YEAR(A))% 4 == 0 && (QTRY_GET_MONTH(A) > 2)) dayA++; - if (int(QTRY_GET_YEAR(B))% 4 == 0 && (QTRY_GET_MONTH(B) > 2)) dayB++; - res = (dayB - dayA)*3600ULL*24; - res += (QTRY_GET_HOUR(B) * 3600 + QTRY_GET_MINUTE(B) * 60 + QTRY_GET_SECOND(B)); - res -= (QTRY_GET_HOUR(A) * 3600 + QTRY_GET_MINUTE(A) * 60 + QTRY_GET_SECOND(A)); + + // eid and openDate are set by the SC + cei.eid = 0; + cei.openDate = 0; + + LOG("Crafting transaction...\n"); + LOG("Tag ID: %u\n", tagId); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_CREATE_EVENT, + /*amount=*/0, + sizeof(cei), &cei, + scheduledTickOffset); } -void quotteryIssueBet(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset) +void _quotteryGetEventInfo(QCPtr& qc, uint64_t eventId, getEventInfo_output& result) { - uint8_t privateKey[32] = {0}; - uint8_t sourcePublicKey[32] = {0}; - uint8_t destPublicKey[32] = {0}; - uint8_t subseed[32] = {0}; - uint8_t digest[32] = {0}; - uint8_t signature[64] = {0}; - char publicIdentity[128] = {0}; - char txHash[128] = {0}; - getSubseedFromSeed((uint8_t*)seed, subseed); - getPrivateKeyFromSubSeed(subseed, privateKey); - getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); - const bool isLowerCase = false; - getIdentityFromPublicKey(sourcePublicKey, publicIdentity, isLowerCase); - ((uint64_t*)destPublicKey)[0] = QUOTTERY_CONTRACT_ID; - ((uint64_t*)destPublicKey)[1] = 0; - ((uint64_t*)destPublicKey)[2] = 0; - ((uint64_t*)destPublicKey)[3] = 0; - + // Kept for callers that already hold a QCPtr. struct { RequestResponseHeader header; - Transaction transaction; - QuotteryissueBet_input ibi; - unsigned char signature[64]; + RequestContractFunction rcf; + getEventInfo_input input; } packet; - memset(&packet.ibi, 0, sizeof(QuotteryissueBet_input)); + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(RequestContractFunction::type()); + packet.rcf.inputSize = sizeof(getEventInfo_input); + packet.rcf.inputType = QTRY_GET_EVENT; + packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; + packet.input.eventId = eventId; + qc->sendData((uint8_t*)&packet, packet.header.size()); - char buff[128] = {0}; - promptStdin("Enter bet description (32 chars)", buff, 32); - memcpy(packet.ibi.betDesc, buff, 32); - promptStdin("Enter number of options (valid [2-8])", buff, 1); - packet.ibi.numberOfOption = buff[0] - 48; - for (uint32_t i = 0; i < packet.ibi.numberOfOption; i++) + try { - char buff2[128] = {0}; - snprintf(buff2, 128, "Enter option #%d description (32 chars)", i); - promptStdin(buff2, buff, 32); - memcpy(packet.ibi.optionDesc + i*32, buff, 32); + result = qc->receivePacketWithHeaderAs(); } - promptStdin("Enter number of oracle provider (valid [1-8])", buff, 1); - int numberOP = buff[0] - 48; - for (int i = 0; i < numberOP; i++) + catch (std::logic_error) { - char buff2[128] = {0}; - uint8_t buf3[32] = {0}; - snprintf(buff2, 128, "Enter oracle provider #%d ID (60 chars)", i); - promptStdin(buff2, buff, 60); - getPublicKeyFromIdentity(buff, buf3); - memcpy(packet.ibi.oracleProviderId + i * 32, buf3, 32); + memset(&result, 0, sizeof(getEventInfo_output)); + result.resultByGO = -1; } - for (int i = 0; i < numberOP; i++) +} + +void quotteryGetEventInfo(const char* nodeIp, const int nodePort, uint64_t eventId, getEventInfo_output& result) +{ + getEventInfo_input input{}; + input.eventId = eventId; + memset(&result, 0, sizeof(result)); + if (!runContractFunction(nodeIp, nodePort, QUOTTERY_CONTRACT_ID, QTRY_GET_EVENT, + &input, sizeof(input), &result, sizeof(result))) { - char buff2[128] = {0}; - snprintf(buff2, 128, "Enter fee for oracle provider #%d ID [4 digits number, format ABCD (meaning AB.CD%%)]", i); - promptStdin(buff2, buff, 4); - uint32_t op_fee = std::atoi(buff); - packet.ibi.oracleFees[i] = op_fee; + memset(&result, 0, sizeof(result)); + result.resultByGO = -1; } +} + +static void quotteryPrintEventMetaData(const QtryEventInfo& result, uint64_t requestedEventId) +{ + if (result.eid != requestedEventId) { - promptStdin("Enter bet close date (stop receiving bet date) (Format: YY-MM-DD hh:mm:ss)", buff, 17); - uint8_t year = (buff[0]-48)*10 + (buff[1]-48); - uint8_t month = (buff[3]-48)*10 + (buff[4]-48); - uint8_t day = (buff[6]-48)*10 + (buff[7]-48); + LOG("EventId #%" PRIu64 " doesn't exist\n", requestedEventId); + return; + } - uint8_t hour = (buff[9]-48)*10 + (buff[10]-48); - uint8_t minute = (buff[12]-48)*10 + (buff[13]-48); - uint8_t sec = (buff[15]-48)*10 + (buff[16]-48); - packQuotteryDate(year, month, day, hour, minute, sec, packet.ibi.closeDate); + char buf[128] = { 0 }; + LOG("Event Id: %" PRIu64 "\n", result.eid); + { + memset(buf, 0, sizeof(buf)); + memcpy(buf, result.desc, sizeof(result.desc)); + LOG("Event description: %s\n", buf); } { - promptStdin("Enter bet end date (finalize bet date) (Format: YY-MM-DD hh:mm:ss)", buff, 17); - uint8_t year = (buff[0]-48)*10 + (buff[1]-48); - uint8_t month = (buff[3]-48)*10 + (buff[4]-48); - uint8_t day = (buff[6]-48)*10 + (buff[7]-48); - - uint8_t hour = (buff[9]-48)*10 + (buff[10]-48); - uint8_t minute = (buff[12]-48)*10 + (buff[13]-48); - uint8_t sec = (buff[15]-48)*10 + (buff[16]-48); - packQuotteryDate(year, month, day, hour, minute, sec, packet.ibi.endDate); + memset(buf, 0, sizeof(buf)); + memcpy(buf, result.option0Desc, sizeof(result.option0Desc)); + LOG("Option 0: %s\n", buf); } { - promptStdin("Enter amount of qus per bet slot", buff, 16); - packet.ibi.amountPerSlot = std::atoi(buff); + memset(buf, 0, sizeof(buf)); + memcpy(buf, result.option1Desc, sizeof(result.option1Desc)); + LOG("Option 1: %s\n", buf); } { - promptStdin("Enter max number of bet slot per option", buff, 16); - packet.ibi.maxBetSlotPerOption = std::atoi(buff); + uint32_t year; + uint8_t month, day, hour, minute, second; + uint16_t _millisec; + uint16_t _microsec; + unpackDateTime(year, month, day, hour, minute, second, _millisec, _microsec, result.openDate); + LOG("Open date: %04u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); + + unpackDateTime(year, month, day, hour, minute, second, _millisec, _microsec, result.endDate); + LOG("End date: %04u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); } - LOG("Crafting transaction...\n"); - memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); - memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); - auto qc = make_qc(nodeIp, nodePort); - LOG("Established connection...\n"); - { - qtryBasicInfo_output quotteryBasicInfo; - LOG("Getting QTRY info...\n"); - quotteryGetBasicInfo(qc, quotteryBasicInfo); - LOG("feePerSlotPerHour: %" PRIu64 "\n", quotteryBasicInfo.feePerSlotPerHour); - std::time_t now = time(0); - std::tm *gmtm = gmtime(&now); - uint8_t year = gmtm->tm_year % 100; - uint8_t month = gmtm->tm_mon + 1; // NOTE: tm_mon is zero-based [0-11] => [Jan-Dec]. Ref: https://cplusplus.com/reference/ctime/tm/ - uint8_t day = gmtm->tm_mday; - uint8_t hour = gmtm->tm_hour; - uint8_t minute = gmtm->tm_min; - uint8_t second = gmtm->tm_sec; - uint32_t curDate; - packQuotteryDate(year, month, day, hour, minute, second, curDate); - uint64_t diffhour = 0, tmp0, tmp1; - uint32_t tmp; - diffDate(curDate, packet.ibi.endDate, tmp, tmp0, tmp1, diffhour); - diffhour = (diffhour+3599)/3600; - packet.transaction.amount = packet.ibi.maxBetSlotPerOption * packet.ibi.numberOfOption * quotteryBasicInfo.feePerSlotPerHour * diffhour; - } - - uint32_t currentTick = getTickNumberFromNode(qc); - LOG("Getting tick info, latest tick is: %u\n", currentTick); - packet.transaction.tick = currentTick + scheduledTickOffset; - packet.transaction.inputType = quotteryFuncId::issue; - packet.transaction.inputSize = sizeof(QuotteryissueBet_input); - KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(QuotteryissueBet_input), - digest, - 32); - LOG("Signing tx packet...\n"); - sign(subseed, sourcePublicKey, digest, signature); - memcpy(packet.signature, signature, 64); - packet.header.setSize(sizeof(packet)); - packet.header.zeroDejavu(); - packet.header.setType(BROADCAST_TRANSACTION); - LOG("Sending data...\n"); - qc->sendData((uint8_t *) &packet, packet.header.size()); - LOG("Sent data...\n"); - KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(QuotteryissueBet_input) + SIGNATURE_SIZE, - digest, - 32); // recompute digest for txhash - getTxHashFromDigest(digest, txHash); - LOG("Bet creation has been sent!\n"); - printReceipt(packet.transaction, txHash, nullptr); - LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); - LOG("to check your tx confirmation status\n"); -} - -void quotteryJoinBet(const char* nodeIp, int nodePort, const char* seed, uint32_t betId, int numberOfBetSlot, uint64_t amountPerSlot, uint8_t option, uint32_t scheduledTickOffset) -{ - auto qc = make_qc(nodeIp, nodePort); - uint8_t privateKey[32] = {0}; - uint8_t sourcePublicKey[32] = {0}; - uint8_t destPublicKey[32] = {0}; - uint8_t subseed[32] = {0}; - uint8_t digest[32] = {0}; - uint8_t signature[64] = {0}; - char publicIdentity[128] = {0}; - char txHash[128] = {0}; - getSubseedFromSeed((uint8_t*)seed, subseed); - getPrivateKeyFromSubSeed(subseed, privateKey); - getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); - const bool isLowerCase = false; - getIdentityFromPublicKey(sourcePublicKey, publicIdentity, isLowerCase); - ((uint64_t*)destPublicKey)[0] = QUOTTERY_CONTRACT_ID; - ((uint64_t*)destPublicKey)[1] = 0; - ((uint64_t*)destPublicKey)[2] = 0; - ((uint64_t*)destPublicKey)[3] = 0; +} - struct { - RequestResponseHeader header; - Transaction transaction; - QuotteryjoinBet_input jbi; - unsigned char signature[64]; - } packet; - memset(&packet.jbi, 0, sizeof(QuotteryjoinBet_input)); - packet.jbi.betId = betId; - packet.jbi.numberOfSlot = numberOfBetSlot; - packet.jbi.option = option; - memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); - memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); - packet.transaction.amount = amountPerSlot*numberOfBetSlot; - uint32_t currentTick = getTickNumberFromNode(qc); - packet.transaction.tick = currentTick + scheduledTickOffset; - packet.transaction.inputType = quotteryFuncId::join; - packet.transaction.inputSize = sizeof(QuotteryjoinBet_input); - KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(QuotteryjoinBet_input), - digest, - 32); - sign(subseed, sourcePublicKey, digest, signature); - memcpy(packet.signature, signature, 64); - packet.header.setSize(sizeof(packet)); - packet.header.zeroDejavu(); - packet.header.setType(BROADCAST_TRANSACTION); - qc->sendData((uint8_t *) &packet, packet.header.size()); - KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(QuotteryjoinBet_input) + SIGNATURE_SIZE, - digest, - 32); // recompute digest for txhash - getTxHashFromDigest(digest, txHash); - LOG("Joining bet tx has been sent!\n"); - printReceipt(packet.transaction, txHash, nullptr); - LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); - LOG("to check your tx confirmation status\n"); -} - -void quotteryGetBetInfo(const char* nodeIp, const int nodePort, int betId, getBetInfo_output& result) +static void quotteryPrintEventInfoRecord(const getEventInfo_output& result, uint64_t requestedEventId) { - auto qc = make_qc(nodeIp, nodePort); - struct { - RequestResponseHeader header; - RequestContractFunction rcf; - getBetInfo_input input; - } packet; - packet.header.setSize(sizeof(packet)); - packet.header.randomizeDejavu(); - packet.header.setType(RequestContractFunction::type()); - packet.rcf.inputSize = sizeof(getBetInfo_input); - packet.rcf.inputType = quotteryViewId::betInfo; - packet.rcf.contractIndex = 2; - packet.input.betId = betId; - qc->sendData((uint8_t *) &packet, packet.header.size()); + if (result.qei.eid != requestedEventId) + { + LOG("EventId #%" PRIu64 " doesn't exist\n", requestedEventId); + return; + } + quotteryPrintEventMetaData(result.qei, requestedEventId); - try + LOG("Result by GO: %" PRId32 "\n", result.resultByGO); + if (result.resultByGO != -1) { - result = qc->receivePacketWithHeaderAs(); + if (result.publishTickTime == 0xffffffffu) { + LOG("This event is already finalized and waiting for cleanup\n"); + } + else { + LOG("Publish tick time: %" PRIu32 "\n", result.publishTickTime); + } } - catch (std::logic_error) + + if (!isZeroPubkey(result.disputerInfo.pubkey)) { - memset(&result, 0, sizeof(getBetInfo_output)); + char disputerId[128] = { 0 }; + getIdentityFromPublicKey(result.disputerInfo.pubkey, disputerId, false); + LOG("Disputer: %s\n", disputerId); + LOG("Dispute amount: %" PRIu64 "\n", result.disputerInfo.amount); + LOG("Computors vote 0: %" PRIu32 "\n", result.computorsVote0); + LOG("Computors vote 1: %" PRIu32 "\n", result.computorsVote1); } } -void quotteryPrintBetInfo(const char* nodeIp, const int nodePort, int betId) +void quotteryPrintEventInfo(const char* nodeIp, const int nodePort, uint64_t eventId) { - getBetInfo_output result; - memset(&result, 0, sizeof(getBetInfo_output)); - LOG("Getting betId #%d info...\n", betId); - quotteryGetBetInfo(nodeIp, nodePort, betId, result); - if (isArrayZero((uint8_t*)&result, sizeof(getBetInfo_output))) + getEventInfo_output result; + memset(&result, 0, sizeof(getEventInfo_output)); + LOG("Getting eventId #%" PRIu64 " info...\n", eventId); + quotteryGetEventInfo(nodeIp, nodePort, eventId, result); + if (isArrayZero((uint8_t*)&result, sizeof(getEventInfo_output))) { LOG("Failed to get\n"); return; } - if (result.betId == -1) - { - LOG("BetId #%d doesn't exist\n", betId); - return; - } - char buf[128] = {0}; - LOG("Bet Id: %u\n", result.betId); // uint32_t betId; - LOG("Number of options: %u\n", result.nOption); // uint8_t nOption; // options number - getIdentityFromPublicKey(result.creator, buf, false); - LOG("Creator: %s\n", buf); + + quotteryPrintEventInfoRecord(result, eventId); +} + +void quotteryGetEventInfoBatch(const char* nodeIp, int nodePort, const uint64_t* eventIds, GetEventInfoBatch_output& result) +{ + GetEventInfoBatch_input input{}; + for (size_t j = 0; j < 64; ++j) { - memset(buf, 0 , 128); - memcpy(buf, result.betDesc, 32); - LOG("Bet descriptions: %s\n", buf); + input.eventIds[j] = eventIds[j]; } - for (uint32_t i = 0; i < result.nOption; i++) + + memset(&result, 0, sizeof(result)); + if (!runContractFunction(nodeIp, nodePort, QUOTTERY_CONTRACT_ID, QTRY_GET_EVENT_BATCH, + &input, sizeof(input), &result, sizeof(result))) { - memset(buf, 0 , 128); - memcpy(buf, result.optionDesc + i * 32, 32); - LOG("Option #%d: %s\n", i, buf); + memset(&result, 0, sizeof(result)); } +} + +void quotteryPrintEventInfoBatch(const char* nodeIp, int nodePort, const uint64_t* eventIds, size_t count) +{ + if (count == 0) { - LOG("Current state:\n"); - for (uint32_t i = 0; i < result.nOption; i++) - { - LOG("Option #%d: %d | ", i, result.currentBetState[i]); - } - LOG("\n"); + LOG("Error: no event ids provided\n"); + return; } + + uint64_t paddedEventIds[64] = {}; + for (size_t i = 0; i < count && i < 64; ++i) { - LOG("Minimum bet amount: %" PRIu64 "\n", result.minBetAmount); - LOG("Maximum slot per option: %" PRIu32 "\n", result.maxBetSlotPerOption); - uint8_t year, month, day, hour, minute, second; - unpackQuotteryDate(year, month, day, hour, minute, second, result.openDate); - LOG("OpenDate: %02u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); - unpackQuotteryDate(year, month, day, hour, minute, second, result.closeDate); - LOG("CloseDate: %02u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); - unpackQuotteryDate(year, month, day, hour, minute, second, result.endDate); - LOG("EndDate: %02u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); + paddedEventIds[i] = eventIds[i]; } - LOG("Oracle IDs\n"); - int nOP = 0; - for (int i = 0; i < 8; i++) + + GetEventInfoBatch_output result{}; + memset(&result, 2, sizeof(GetEventInfoBatch_output)); + LOG("Getting %zu event(s) info in batch...\n", count); + quotteryGetEventInfoBatch(nodeIp, nodePort, paddedEventIds, result); + + if (isArrayZero(reinterpret_cast(&result), sizeof(result))) { - if (!isZeroPubkey(result.oracleProviderId+i*32)) - { - nOP++; - memset(buf, 0 , 128); - getIdentityFromPublicKey(result.oracleProviderId+i*32, buf, false); - uint32_t fee_u32 = result.oracleFees[i]; - double fee = (fee_u32)/100.0; - LOG("%s\tFee: %.2f%%\n", buf, fee); - } + LOG("Failed to get batch event info\n"); + return; } - LOG("Current votes result:\n"); - for (int i = 0; i < 8; i++) + + for (size_t i = 0; i < count && i < 64; ++i) { - if (result.betResultWonOption[i] != -1 && result.betResultOPId[i] != -1) - { - LOG("OP #%d: voted for option #%d\n", result.betResultOPId[i], result.betResultWonOption[i]); - } + LOG("\n================\n"); + LOG("Requested eventId: %" PRIu64 "\n", paddedEventIds[i]); + quotteryPrintEventMetaData(result.aqei[i], paddedEventIds[i]); } } -// getBetOptionDetail 3 -bool quotteryGetBetOptionDetail(const char* nodeIp, const int nodePort, uint32_t betId, uint32_t betOption, getBetOptionDetail_output& result) +struct qtryOrderAction_input +{ + uint64_t eventId; + uint64_t option; + uint64_t amount; + uint64_t price; +}; +template +void qtryOrderAction(const char* nodeIp, int nodePort, + const char* seed, + uint64_t eventId, uint64_t option, uint64_t amount, int64_t price, + uint64_t antiSpamAmount, + uint32_t scheduledTickOffset) { auto qc = make_qc(nodeIp, nodePort); - struct { - RequestResponseHeader header; - RequestContractFunction rcf; - getBetOptionDetail_input bo_inp; - } packet; - packet.header.setSize(sizeof(packet)); - packet.header.randomizeDejavu(); - packet.header.setType(RequestContractFunction::type()); - packet.rcf.inputSize = sizeof(getBetOptionDetail_input); - packet.rcf.inputType = quotteryViewId::betDetail; - packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; - packet.bo_inp.betId = betId; - packet.bo_inp.betOption = betOption; - qc->sendData((uint8_t *) &packet, packet.header.size()); - try - { - result = qc->receivePacketWithHeaderAs(); - return true; - } - catch (std::logic_error) - { - memset(&result, 0, sizeof(getBetOptionDetail_output)); - return false; - } + qtryBasicInfo_output qbi{}; + quotteryGetBasicInfo(qc, qbi); + antiSpamAmount = qbi.antiSpamAmount; + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY order action - functionNumber: %d\n", functionNumber); + LOG("eventId: %" PRIu64 "\n", eventId); + LOG("option: %" PRIu64 "\n", option); + LOG("amount: %" PRIu64 "\n", amount); + LOG("price: %" PRId64 "\n", price); + LOG("antiSpamAmount: %" PRIu64 "\n", antiSpamAmount); + LOG("\n-------------------------------------\n\n"); + + qtryOrderAction_input input{}; + input.eventId = eventId; + input.option = option; + input.amount = amount; + input.price = price; + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + functionNumber, + /*amount=*/(int64_t)antiSpamAmount, + sizeof(input), &input, + scheduledTickOffset, + &qc); +} + +void qtryAddToAskOrder(const char* nodeIp, int nodePort, const char* seed, + uint64_t eventId, uint64_t option, uint64_t amount, int64_t price, + uint64_t antiSpamAmount, uint32_t scheduledTickOffset) +{ + qtryOrderAction(nodeIp, nodePort, seed, eventId, option, amount, price, antiSpamAmount, scheduledTickOffset); +} + +void qtryAddToBidOrder(const char* nodeIp, int nodePort, const char* seed, + uint64_t eventId, uint64_t option, uint64_t amount, int64_t price, + uint64_t antiSpamAmount, uint32_t scheduledTickOffset) +{ + qtryOrderAction(nodeIp, nodePort, seed, eventId, option, amount, price, antiSpamAmount, scheduledTickOffset); } -// showing which ID bet for an option -void quotteryPrintBetOptionDetail(const char* nodeIp, const int nodePort, uint32_t betId, uint32_t betOption) +void qtryRemoveAskOrder(const char* nodeIp, int nodePort, const char* seed, + uint64_t eventId, uint64_t option, uint64_t amount, int64_t price, + uint64_t antiSpamAmount, uint32_t scheduledTickOffset) { - getBetOptionDetail_output result; - memset(&result, 0, sizeof(getBetOptionDetail_output)); - if (!quotteryGetBetOptionDetail(nodeIp, nodePort, betId, betOption, result)) + qtryOrderAction(nodeIp, nodePort, seed, eventId, option, amount, price, antiSpamAmount, scheduledTickOffset); +} + +void qtryRemoveBidOrder(const char* nodeIp, int nodePort, const char* seed, + uint64_t eventId, uint64_t option, uint64_t amount, int64_t price, + uint64_t antiSpamAmount, uint32_t scheduledTickOffset) +{ + qtryOrderAction(nodeIp, nodePort, seed, eventId, option, amount, price, antiSpamAmount, scheduledTickOffset); +} + +void qtryGetOrders(const char* nodeIp, int nodePort, + uint64_t eventId, uint64_t option, uint64_t isBid, uint64_t offset, + qtryGetOrders_output& result) +{ + qtryGetOrders_input input{}; + input.eventId = eventId; + input.option = option; + input.isBid = isBid; + input.offset = offset; + + memset(&result, 0, sizeof(result)); + if (!runContractFunction(nodeIp, nodePort, QUOTTERY_CONTRACT_ID, QTRY_GET_ORDERS, + &input, sizeof(input), &result, sizeof(result))) { - LOG("Failed to get\n"); - return; + memset(&result, 0, sizeof(result)); } - LOG("List of IDs bet option #%d on betID %d\n", betOption, betId); - char buf[128] = {0}; - for (int i = 0; i < 1024; i++) +} + +void quotteryPrintOrders(const char* nodeIp, int nodePort, + uint64_t eventId, uint64_t option, uint64_t isBid, uint64_t offset) +{ + qtryGetOrders_output result; + memset(&result, 0, sizeof(qtryGetOrders_output)); + qtryGetOrders(nodeIp, nodePort, eventId, option, isBid, offset, result); + + int N = sizeof(result.orders) / sizeof(result.orders[0]); + LOG("%s orders for eventId %" PRIu64 " option %" PRIu64 " (offset %" PRIu64 "):\n", + isBid ? "Bid" : "Ask", eventId, option, offset); + LOG("Entity\t\t\t\t\t\t\t\tPrice\tAmount\n"); + for (int i = 0; i < N; i++) { - if (!isZeroPubkey(result.bettor + i*32)) + if (isZeroPubkey(result.orders[i].qo.entity)) { - memset(buf, 0, 128); - getIdentityFromPublicKey(result.bettor + i * 32, buf, false); - LOG("%s\n", buf); + break; } + char iden[128] = { 0 }; + getIdentityFromPublicKey(result.orders[i].qo.entity, iden, false); + LOG("%s\t%" PRId64 "\t%" PRIu64 "\n", iden, result.orders[i].price, result.orders[i].qo.amount); } } -// getActiveBet 4 -void quotteryGetActiveBet(const char* nodeIp, const int nodePort, getActiveBet_output& result) +struct getUserPosition_input { - auto qc = make_qc(nodeIp, nodePort); - struct { - RequestResponseHeader header; - RequestContractFunction rcf; - } packet; - packet.header.setSize(sizeof(packet)); - packet.header.randomizeDejavu(); - packet.header.setType(RequestContractFunction::type()); - packet.rcf.inputSize = 0; - packet.rcf.inputType = quotteryViewId::activeBet; - packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; - qc->sendData((uint8_t *) &packet, packet.header.size()); + uint8_t uid[32]; +}; - try - { - result = qc->receivePacketWithHeaderAs(); - } - catch (std::logic_error) +void quotteryGetUserPosition(const char* nodeIp, int nodePort, const char* identity, getUserPosition_output& result) +{ + getUserPosition_input input{}; + getPublicKeyFromIdentity(identity, input.uid); + + memset(&result, 0, sizeof(result)); + if (!runContractFunction(nodeIp, nodePort, QUOTTERY_CONTRACT_ID, QUOTTERY_GET_USER_POSITION, + &input, sizeof(input), &result, sizeof(result))) { - memset(&result, 0, sizeof(getActiveBet_output)); + memset(&result, 0, sizeof(result)); } } -// showing which ID bet for an option -void quotteryPrintActiveBet(const char* nodeIp, const int nodePort) +void quotteryPrintUserPosition(const char* nodeIp, int nodePort, const char* identity) { - getActiveBet_output result; - memset(&result, 0, sizeof(getActiveBet_output)); - quotteryGetActiveBet(nodeIp, nodePort, result); - LOG("List of active bet (%d):\n", result.count); - for (uint32_t i = 0; i < result.count; i++) + getUserPosition_output result; + memset(&result, 0, sizeof(getUserPosition_output)); + quotteryGetUserPosition(nodeIp, nodePort, identity, result); + + LOG("Positions for %s (count: %" PRId64 "):\n", identity, result.count); + LOG("EventId\tOption\tAmount\n"); + for (int64_t i = 0; i < result.count; i++) { - LOG("%u, ", result.betId[i]); + uint64_t eventId = QUOTTERY_EO_GET_EVENTID(result.p[i].eo); + uint64_t option = QUOTTERY_EO_GET_OPTION(result.p[i].eo); + LOG("%" PRIu64 "\t%" PRIu64 "\t%" PRId64 "\n", eventId, option, result.p[i].amount); } - LOG("\n"); } -// getBetByCreator 5 -void quotteryGetActiveBetByCreator(const char* nodeIp, const int nodePort, getActiveBetByCreator_output& result, const uint8_t* creator) +struct qtryPublishResult_input +{ + uint64_t eventId; + uint64_t option; +}; + +struct qtryTryFinalizeEvent_input +{ + uint64_t eventId; +}; + +static bool isCurrentUtcAfterPackedDateTime(uint64_t packedDateTime) +{ + uint32_t endYear; + uint8_t endMonth, endDay, endHour, endMinute, endSecond; + uint16_t endMillisec, endMicrosec; + unpackDateTime(endYear, endMonth, endDay, endHour, endMinute, endSecond, endMillisec, endMicrosec, packedDateTime); + + std::time_t nowTs = std::time(nullptr); + std::tm nowUtc{}; +#if defined(_WIN32) + gmtime_s(&nowUtc, &nowTs); +#else + gmtime_r(&nowTs, &nowUtc); +#endif + + const uint32_t nowYear = static_cast(nowUtc.tm_year + 1900); + const uint8_t nowMonth = static_cast(nowUtc.tm_mon + 1); + const uint8_t nowDay = static_cast(nowUtc.tm_mday); + const uint8_t nowHour = static_cast(nowUtc.tm_hour); + const uint8_t nowMinute = static_cast(nowUtc.tm_min); + const uint8_t nowSecond = static_cast(nowUtc.tm_sec); + + uint64_t nowPacked = 0; + packDateTime(nowYear, nowMonth, nowDay, nowHour, nowMinute, nowSecond, 0, 0, nowPacked); + + return nowPacked >= packedDateTime; +} + +void qtryPublishResult(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset, uint64_t eventId, uint64_t result) { + if (result != 0 && result != 1) + { + LOG("Error: result can only be 0 or 1\n"); + return; + } + auto qc = make_qc(nodeIp, nodePort); - struct { - RequestResponseHeader header; - RequestContractFunction rcf; - getActiveBetByCreator_input abi; - } packet; - packet.header.setSize(sizeof(packet)); - packet.header.randomizeDejavu(); - packet.header.setType(RequestContractFunction::type()); - packet.rcf.inputSize = sizeof(getActiveBetByCreator_input); - packet.rcf.inputType = quotteryViewId::activeBetByCreator; - packet.rcf.contractIndex = QUOTTERY_CONTRACT_ID; - memcpy(packet.abi.creator, creator, 32); - qc->sendData((uint8_t *) &packet, packet.header.size()); - try + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + char sourceIdentity[128] = { 0 }; + char goIdentity[128] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + getIdentityFromPublicKey(sourcePublicKey, sourceIdentity, false); + + qtryBasicInfo_output basic{}; + quotteryGetBasicInfo(qc, basic); + + if (isArrayZero((uint8_t*)&basic, sizeof(basic))) { - result = qc->receivePacketWithHeaderAs(); + LOG("Error: failed to get Quottery basic info\n"); + return; } - catch (std::logic_error) + + if (memcmp(sourcePublicKey, basic.gameOperator, 32) != 0) { - memset(&result, 0, sizeof(getActiveBetByCreator_output)); + getIdentityFromPublicKey(basic.gameOperator, goIdentity, false); + LOG("Error: seed is not the game operator\n"); + LOG("Current identity: %s\n", sourceIdentity); + LOG("Game operator: %s\n", goIdentity); + return; } -} -void quotteryPrintActiveBetByCreator(const char* nodeIp, const int nodePort, const char* identity) -{ - uint8_t creatorPubkey[32] = {0}; - getPublicKeyFromIdentity(identity, creatorPubkey); - getActiveBetByCreator_output result; - memset(&result, 0, sizeof(getActiveBet_output)); - quotteryGetActiveBetByCreator(nodeIp, nodePort, result, creatorPubkey); - LOG("List of active bet (%d):\n", result.count); - for (uint32_t i = 0; i < result.count; i++) + getEventInfo_output eventInfo{}; + _quotteryGetEventInfo(qc, eventId, eventInfo); + + if (isArrayZero((uint8_t*)&eventInfo, sizeof(eventInfo))) + { + LOG("Error: failed to get event info for eventId %" PRIu64 "\n", eventId); + return; + } + + if (eventInfo.qei.eid == (uint64_t)-1) { - LOG("%u, ", result.betId[i]); + LOG("Error: eventId %" PRIu64 " does not exist\n", eventId); + return; + } + + if (!isCurrentUtcAfterPackedDateTime(eventInfo.qei.endDate)) + { + uint32_t year; + uint8_t month, day, hour, minute, second; + uint16_t millisec, microsec; + unpackDateTime(year, month, day, hour, minute, second, millisec, microsec, eventInfo.qei.endDate); + LOG("Error: event %" PRIu64 " has not ended yet\n", eventId); + LOG("End date (UTC): %04u-%02u-%02u %02u:%02u:%02u\n", year, month, day, hour, minute, second); + return; } - LOG("\n"); + + qtryPublishResult_input input{}; + input.eventId = eventId; + input.option = result; + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY publish result\n"); + LOG("eventId: %" PRIu64 "\n", eventId); + LOG("result: %" PRIu64 "\n", result); + LOG("depositAmountForDispute: %" PRIu64 "\n", basic.depositAmountForDispute); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_PUBLISH_RESULT, + /*amount=*/(int64_t)basic.depositAmountForDispute, + sizeof(input), &input, + scheduledTickOffset, + &qc); } -void quotteryCancelBet(const char* nodeIp, const int nodePort, const char* seed, const uint32_t betId, const uint32_t scheduledTickOffset) +void qtryTryFinalizeEvent(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset, uint64_t eventId) { auto qc = make_qc(nodeIp, nodePort); - uint8_t privateKey[32] = {0}; - uint8_t sourcePublicKey[32] = {0}; - uint8_t destPublicKey[32] = {0}; - uint8_t subseed[32] = {0}; - uint8_t digest[32] = {0}; - uint8_t signature[64] = {0}; - char publicIdentity[128] = {0}; - char txHash[128] = {0}; - getSubseedFromSeed((uint8_t*)seed, subseed); - getPrivateKeyFromSubSeed(subseed, privateKey); - getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); - const bool isLowerCase = false; - getIdentityFromPublicKey(sourcePublicKey, publicIdentity, isLowerCase); - ((uint64_t*)destPublicKey)[0] = QUOTTERY_CONTRACT_ID; - ((uint64_t*)destPublicKey)[1] = 0; - ((uint64_t*)destPublicKey)[2] = 0; - ((uint64_t*)destPublicKey)[3] = 0; - struct { - RequestResponseHeader header; - Transaction transaction; - cancelBet_input cbi; - unsigned char signature[64]; - } packet; - packet.cbi.betId = betId; - memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); - memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); - packet.transaction.amount = 0; - uint32_t currentTick = getTickNumberFromNode(qc); - packet.transaction.tick = currentTick + scheduledTickOffset; - packet.transaction.inputType = quotteryFuncId::cancelBet; - packet.transaction.inputSize = sizeof(cancelBet_input); - KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(cancelBet_input), - digest, - 32); - sign(subseed, sourcePublicKey, digest, signature); - memcpy(packet.signature, signature, 64); - packet.header.setSize(sizeof(packet)); - packet.header.zeroDejavu(); - packet.header.setType(BROADCAST_TRANSACTION); - qc->sendData((uint8_t *) &packet, packet.header.size()); - KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(cancelBet_input) + SIGNATURE_SIZE, - digest, - 32); // recompute digest for txhash - getTxHashFromDigest(digest, txHash); - LOG("Cancel bet tx has been sent!\n"); - printReceipt(packet.transaction, txHash, nullptr); - LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); - LOG("to check your tx confirmation status\n"); -} - -void quotteryPublishResult(const char* nodeIp, const int nodePort, const char* seed, const uint32_t betId, const uint32_t winOption, const uint32_t scheduledTickOffset) -{ - auto qc = make_qc(nodeIp, nodePort); - uint8_t privateKey[32] = {0}; - uint8_t sourcePublicKey[32] = {0}; - uint8_t destPublicKey[32] = {0}; - uint8_t subseed[32] = {0}; - uint8_t digest[32] = {0}; - uint8_t signature[64] = {0}; - char publicIdentity[128] = {0}; - char txHash[128] = {0}; - getSubseedFromSeed((uint8_t*)seed, subseed); - getPrivateKeyFromSubSeed(subseed, privateKey); + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + char sourceIdentity[128] = { 0 }; + char goIdentity[128] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); - const bool isLowerCase = false; - getIdentityFromPublicKey(sourcePublicKey, publicIdentity, isLowerCase); - ((uint64_t*)destPublicKey)[0] = QUOTTERY_CONTRACT_ID; - ((uint64_t*)destPublicKey)[1] = 0; - ((uint64_t*)destPublicKey)[2] = 0; - ((uint64_t*)destPublicKey)[3] = 0; + getIdentityFromPublicKey(sourcePublicKey, sourceIdentity, false); - struct { - RequestResponseHeader header; - Transaction transaction; - publishResult_input pri; - unsigned char signature[64]; - } packet; - packet.pri.betId = betId; - packet.pri.winOption = winOption; - memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); - memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); - packet.transaction.amount = 0; - uint32_t currentTick = getTickNumberFromNode(qc); - packet.transaction.tick = currentTick + scheduledTickOffset; - packet.transaction.inputType = quotteryFuncId::publishResult; - packet.transaction.inputSize = sizeof(publishResult_input); - KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(publishResult_input), - digest, - 32); - sign(subseed, sourcePublicKey, digest, signature); - memcpy(packet.signature, signature, 64); - packet.header.setSize(sizeof(packet)); - packet.header.zeroDejavu(); - packet.header.setType(BROADCAST_TRANSACTION); - qc->sendData((uint8_t *) &packet, packet.header.size()); - KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(publishResult_input) + SIGNATURE_SIZE, - digest, - 32); // recompute digest for txhash - getTxHashFromDigest(digest, txHash); - LOG("Publishing result tx has been sent!\n"); - printReceipt(packet.transaction, txHash, nullptr); - LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); - LOG("to check your tx confirmation status\n"); + qtryBasicInfo_output basic{}; + quotteryGetBasicInfo(qc, basic); + + if (isArrayZero((uint8_t*)&basic, sizeof(basic))) + { + LOG("Error: failed to get Quottery basic info\n"); + return; + } + + if (memcmp(sourcePublicKey, basic.gameOperator, 32) != 0) + { + getIdentityFromPublicKey(basic.gameOperator, goIdentity, false); + LOG("Error: seed is not the game operator\n"); + LOG("Current identity: %s\n", sourceIdentity); + LOG("Game operator: %s\n", goIdentity); + return; + } + + getEventInfo_output eventInfo{}; + _quotteryGetEventInfo(qc, eventId, eventInfo); + + if (isArrayZero((uint8_t*)&eventInfo, sizeof(eventInfo))) + { + LOG("Error: failed to get event info for eventId %" PRIu64 "\n", eventId); + return; + } + + if (eventInfo.qei.eid != eventId) + { + LOG("Error: eventId %" PRIu64 " does not exist\n", eventId); + return; + } + + if (eventInfo.resultByGO == -1) + { + LOG("Error: event %" PRIu64 " does not have a published result yet\n", eventId); + return; + } + + if (!isZeroPubkey(eventInfo.disputerInfo.pubkey)) + { + LOG("Error: event %" PRIu64 " is under dispute and cannot be finalized\n", eventId); + return; + } + + const uint32_t currentTick = getTickNumberFromNode(qc); + const uint32_t scheduledTick = currentTick + scheduledTickOffset; + + if (eventInfo.publishTickTime + 1000 > scheduledTick) + { + LOG("Error: event %" PRIu64 " cannot be finalized yet\n", eventId); + LOG("Publish tick: %" PRIu32 "\n", eventInfo.publishTickTime); + LOG("Earliest finalize tick: %" PRIu32 "\n", eventInfo.publishTickTime + 1000); + LOG("Scheduled tick: %" PRIu32 "\n", scheduledTick); + return; + } + + qtryTryFinalizeEvent_input input{}; + input.eventId = eventId; + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY try finalize event\n"); + LOG("eventId: %" PRIu64 "\n", eventId); + LOG("publishTickTime: %" PRIu32 "\n", eventInfo.publishTickTime); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_TRY_FINALIZE_EVENT, + /*amount=*/0, + sizeof(input), &input, + scheduledTickOffset, + &qc); +} + +struct qtryDispute_input +{ + uint64_t eventId; +}; + +void qtryDispute(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset, uint64_t eventId) +{ + auto qc = make_qc(nodeIp, nodePort); + + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + + qtryBasicInfo_output basic{}; + quotteryGetBasicInfo(qc, basic); + + if (isArrayZero((uint8_t*)&basic, sizeof(basic))) + { + LOG("Error: failed to get Quottery basic info\n"); + return; + } + + const uint64_t depositAmount = basic.depositAmountForDispute; + + // Fetch event info to validate dispute preconditions + getEventInfo_output eventInfo{}; + _quotteryGetEventInfo(qc, eventId, eventInfo); + + if (isArrayZero((uint8_t*)&eventInfo, sizeof(eventInfo))) + { + LOG("Error: failed to get event info for eventId %" PRIu64 "\n", eventId); + return; + } + + if (eventInfo.qei.eid != eventId) + { + LOG("Error: eventId %" PRIu64 " does not exist\n", eventId); + return; + } + + if (eventInfo.resultByGO == -1) + { + LOG("Error: event %" PRIu64 " does not have a published result yet. Nothing to dispute.\n", eventId); + return; + } + + if (eventInfo.publishTickTime == 0xffffffffu) + { + LOG("Error: event %" PRIu64 " is already finalized. Cannot dispute.\n", eventId); + return; + } + + if (!isZeroPubkey(eventInfo.disputerInfo.pubkey)) + { + char existingDisputer[128] = { 0 }; + getIdentityFromPublicKey(eventInfo.disputerInfo.pubkey, existingDisputer, false); + LOG("Error: event %" PRIu64 " is already being disputed by %s\n", eventId, existingDisputer); + return; + } + + const uint32_t currentTick = getTickNumberFromNode(qc); + const uint32_t scheduledTick = currentTick + scheduledTickOffset; + + if (eventInfo.publishTickTime + 1000 <= scheduledTick) + { + LOG("Warning: dispute window may have passed for event %" PRIu64 "\n", eventId); + LOG("Publish tick: %" PRIu32 ", finalize eligible at tick: %" PRIu32 ", scheduled tick: %" PRIu32 "\n", + eventInfo.publishTickTime, eventInfo.publishTickTime + 1000, scheduledTick); + LOG("The event may already be finalized by the time this transaction executes.\n"); + } + + { + long long balance = getBalanceNumber(qc, sourcePublicKey); + if (balance < 0) + { + LOG("Error: failed to query balance\n"); + return; + } + if (static_cast(balance) < depositAmount) + { + LOG("Error: insufficient balance for dispute deposit\n"); + LOG("Required: %" PRIu64 ", available: %lld\n", depositAmount, balance); + return; + } + } + + qtryDispute_input input{}; + input.eventId = eventId; + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY Dispute\n"); + LOG("eventId: %" PRIu64 "\n", eventId); + LOG("depositAmountForDispute: %" PRIu64 "\n", depositAmount); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_DISPUTE, + /*amount=*/(int64_t)depositAmount, + sizeof(input), &input, + scheduledTickOffset, + &qc); +} + +struct qtryResolveDispute_input +{ + uint64_t eventId; + int64_t vote; +}; + +void qtryResolveDispute(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset, uint64_t eventId, int64_t vote) +{ + if (vote != 0 && vote != 1) + { + LOG("Error: vote must be 0 or 1\n"); + return; + } + + auto qc = make_qc(nodeIp, nodePort); + + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + char sourceIdentity[128] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + getIdentityFromPublicKey(sourcePublicKey, sourceIdentity, false); + + getEventInfo_output eventInfo{}; + _quotteryGetEventInfo(qc, eventId, eventInfo); + + if (isArrayZero((uint8_t*)&eventInfo, sizeof(eventInfo))) + { + LOG("Error: failed to get event info for eventId %" PRIu64 "\n", eventId); + return; + } + + if (eventInfo.qei.eid != eventId) + { + LOG("Error: eventId %" PRIu64 " does not exist\n", eventId); + return; + } + + if (isZeroPubkey(eventInfo.disputerInfo.pubkey)) + { + LOG("Error: event %" PRIu64 " is not under dispute\n", eventId); + return; + } + + constexpr int64_t MIN_INVOCATION_REWARD = 10000000; + + { + long long balance = getBalanceNumber(qc, sourcePublicKey); + if (balance < 0) + { + LOG("Error: failed to query balance\n"); + return; + } + if (static_cast(balance) < static_cast(MIN_INVOCATION_REWARD)) + { + LOG("Error: insufficient balance\n"); + LOG("Required (refunded if computor): %" PRId64 ", available: %lld\n", MIN_INVOCATION_REWARD, balance); + return; + } + } + + qtryResolveDispute_input input{}; + input.eventId = eventId; + input.vote = vote; + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY ResolveDispute\n"); + LOG("Caller: %s\n", sourceIdentity); + LOG("eventId: %" PRIu64 "\n", eventId); + LOG("vote: %" PRId64 " (%s)\n", vote, vote == 0 ? "No" : "Yes"); + LOG("invocationReward: %" PRId64 " (refunded if caller is a computor)\n", MIN_INVOCATION_REWARD); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_RESOLVE_DISPUTE, + /*amount=*/MIN_INVOCATION_REWARD, + sizeof(input), &input, + scheduledTickOffset, + &qc); + LOG("Note: only computors can resolve disputes. If the caller is not a computor, the invocation reward will NOT be refunded.\n"); +} + +struct qtryUserClaimReward_input +{ + uint64_t eventId; +}; + +void qtryUserClaimReward(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset, uint64_t eventId) +{ + auto qc = make_qc(nodeIp, nodePort); + + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + char sourceIdentity[128] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + getIdentityFromPublicKey(sourcePublicKey, sourceIdentity, false); + + getEventInfo_output eventInfo{}; + _quotteryGetEventInfo(qc, eventId, eventInfo); + + if (isArrayZero((uint8_t*)&eventInfo, sizeof(eventInfo))) + { + LOG("Error: failed to get event info for eventId %" PRIu64 "\n", eventId); + return; + } + + if (eventInfo.qei.eid != eventId) + { + LOG("Error: eventId %" PRIu64 " does not exist\n", eventId); + return; + } + + if (eventInfo.resultByGO == -1) + { + LOG("Error: event %" PRIu64 " does not have a result yet. Cannot claim reward.\n", eventId); + return; + } + + if (eventInfo.publishTickTime != 0xffffffffu) + { + if (!isZeroPubkey(eventInfo.disputerInfo.pubkey)) + { + LOG("Warning: event %" PRIu64 " is still under dispute. The result may change.\n", eventId); + } + else + { + LOG("Warning: event %" PRIu64 " may not be finalized yet (publishTickTime: %" PRIu32 ").\n", + eventId, eventInfo.publishTickTime); + LOG("The SC will reject the claim if the result is not set.\n"); + } + } + + { + getUserPosition_output posResult{}; + quotteryGetUserPosition(nodeIp, nodePort, sourceIdentity, posResult); + + bool hasPosition = false; + for (int64_t idx = 0; idx < posResult.count; idx++) + { + uint64_t posEventId = QUOTTERY_EO_GET_EVENTID(posResult.p[idx].eo); + if (posEventId == eventId) + { + uint64_t posOption = QUOTTERY_EO_GET_OPTION(posResult.p[idx].eo); + LOG("Found position in event %" PRIu64 ": option %" PRIu64 ", amount %" PRId64 "\n", + eventId, posOption, posResult.p[idx].amount); + hasPosition = true; + break; + } + } + + if (!hasPosition) + { + LOG("Error: no position found for %s in event %" PRIu64 "\n", sourceIdentity, eventId); + LOG("You must have a position in this event to claim a reward.\n"); + return; + } + } + + constexpr int64_t CLAIM_INVOCATION_REWARD = 1000000; + + { + long long balance = getBalanceNumber(qc, sourcePublicKey); + if (balance < 0) + { + LOG("Error: failed to query balance\n"); + return; + } + if (static_cast(balance) < static_cast(CLAIM_INVOCATION_REWARD)) + { + LOG("Error: insufficient balance\n"); + LOG("Required: %" PRId64 " (refunded if you have a winning position), available: %lld\n", + CLAIM_INVOCATION_REWARD, balance); + return; + } + } + + qtryUserClaimReward_input input{}; + input.eventId = eventId; + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY UserClaimReward\n"); + LOG("Caller: %s\n", sourceIdentity); + LOG("eventId: %" PRIu64 "\n", eventId); + LOG("invocationReward: %" PRId64 " (refunded if winning position exists)\n", CLAIM_INVOCATION_REWARD); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_USER_CLAIM_REWARD, + /*amount=*/CLAIM_INVOCATION_REWARD, + sizeof(input), &input, + scheduledTickOffset, + &qc); + LOG("Note: the 1,000,000 qu fee is only refunded if you have a winning position. Otherwise it is lost (anti-spam).\n"); +} + +struct qtryGOForceClaimReward_input +{ + uint64_t eventId; + uint8_t pubkeys[16][32]; // Array +}; + +void qtryGOForceClaimReward(const char* nodeIp, int nodePort, const char* seed, + uint32_t scheduledTickOffset, uint64_t eventId, + const char* identities[], int identityCount) +{ + if (identityCount <= 0 || identityCount > 16) + { + LOG("Error: must provide between 1 and 16 public key identities\n"); + return; + } + + auto qc = make_qc(nodeIp, nodePort); + + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + char sourceIdentity[128] = { 0 }; + char goIdentity[128] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + getIdentityFromPublicKey(sourcePublicKey, sourceIdentity, false); + + qtryBasicInfo_output basic{}; + quotteryGetBasicInfo(qc, basic); + + if (isArrayZero((uint8_t*)&basic, sizeof(basic))) + { + LOG("Error: failed to get Quottery basic info\n"); + return; + } + + if (memcmp(sourcePublicKey, basic.gameOperator, 32) != 0) + { + getIdentityFromPublicKey(basic.gameOperator, goIdentity, false); + LOG("Error: seed is not the game operator\n"); + LOG("Current identity: %s\n", sourceIdentity); + LOG("Game operator: %s\n", goIdentity); + return; + } + + getEventInfo_output eventInfo{}; + _quotteryGetEventInfo(qc, eventId, eventInfo); + + if (isArrayZero((uint8_t*)&eventInfo, sizeof(eventInfo))) + { + LOG("Error: failed to get event info for eventId %" PRIu64 "\n", eventId); + return; + } + + if (eventInfo.qei.eid != eventId) + { + LOG("Error: eventId %" PRIu64 " does not exist\n", eventId); + return; + } + + if (eventInfo.resultByGO == -1) + { + LOG("Error: event %" PRIu64 " does not have a result yet. Cannot force claim.\n", eventId); + return; + } + + qtryGOForceClaimReward_input input{}; + input.eventId = eventId; + memset(input.pubkeys, 0, sizeof(input.pubkeys)); + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY GOForceClaimReward\n"); + LOG("Caller (GO): %s\n", sourceIdentity); + LOG("eventId: %" PRIu64 "\n", eventId); + LOG("Number of pubkeys: %d\n", identityCount); + + for (int idx = 0; idx < identityCount; idx++) + { + getPublicKeyFromIdentity(identities[idx], input.pubkeys[idx]); + char verifyId[128] = { 0 }; + getIdentityFromPublicKey(input.pubkeys[idx], verifyId, false); + LOG(" [%d] %s\n", idx, verifyId); + } + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_GO_FORCE_CLAIM_REWARD, + /*amount=*/0, + sizeof(input), &input, + scheduledTickOffset, + &qc); +} + +struct qtryTransferShareManagementRights_input +{ + uint8_t issuer[32]; // Asset.issuer (id = 32 bytes public key) + uint64_t assetName; // Asset.assetName (uint64 encoded) + int64_t numberOfShares; + uint32_t newManagingContractIndex; + uint32_t _padding; // align to 8 bytes +}; + +static uint64_t encodeAssetName(const char* name) +{ + uint64_t result = 0; + for (int i = 0; i < 7 && name[i] != '\0'; i++) + { + char c = name[i]; + // Qubic asset names are uppercase A-Z mapped to values 1-26 + if (c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + if (c < 'A' || c > 'Z') + { + LOG("Error: invalid character '%c' in asset name. Only A-Z allowed.\n", name[i]); + return 0; + } + result |= (static_cast(c - 'A' + 1)) << (i * 8); + } + return result; +} + +void qtryTransferShareManagementRights(const char* nodeIp, int nodePort, const char* seed, + uint32_t scheduledTickOffset, + const char* issuerIdentity, const char* assetName, + int64_t numberOfShares, uint32_t newManagingContractIndex) +{ + if (numberOfShares <= 0) + { + LOG("Error: numberOfShares must be positive\n"); + return; + } + + uint64_t encodedAssetName = encodeAssetName(assetName); + if (encodedAssetName == 0) + { + LOG("Error: failed to encode asset name '%s'\n", assetName); + return; + } + + qtryTransferShareManagementRights_input input{}; + getPublicKeyFromIdentity(issuerIdentity, input.issuer); + input.assetName = encodedAssetName; + input.numberOfShares = numberOfShares; + input.newManagingContractIndex = newManagingContractIndex; + + char issuerBuf[128] = { 0 }; + getIdentityFromPublicKey(input.issuer, issuerBuf, false); + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY TransferShareManagementRights\n"); + LOG("Asset issuer: %s\n", issuerBuf); + LOG("Asset name: %s (encoded: %" PRIu64 ")\n", assetName, encodedAssetName); + LOG("Number of shares: %" PRId64 "\n", numberOfShares); + LOG("New managing contract index: %" PRIu32 "\n", newManagingContractIndex); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_TRANSFER_SHARE_MANAGEMENT_RIGHTS, + /*amount=*/0, + sizeof(input), &input, + scheduledTickOffset); +} + +void qtryCleanMemory(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset) +{ + auto qc = make_qc(nodeIp, nodePort); + + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + char sourceIdentity[128] = { 0 }; + char goIdentity[128] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + getIdentityFromPublicKey(sourcePublicKey, sourceIdentity, false); + + qtryBasicInfo_output basic{}; + quotteryGetBasicInfo(qc, basic); + + if (isArrayZero((uint8_t*)&basic, sizeof(basic))) + { + LOG("Error: failed to get Quottery basic info\n"); + return; + } + + if (memcmp(sourcePublicKey, basic.gameOperator, 32) != 0) + { + getIdentityFromPublicKey(basic.gameOperator, goIdentity, false); + LOG("Error: seed is not the game operator\n"); + LOG("Current identity: %s\n", sourceIdentity); + LOG("Game operator: %s\n", goIdentity); + return; + } + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY CleanMemory\n"); + LOG("Caller (GO): %s\n", sourceIdentity); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_CLEAN_MEMORY, + /*amount=*/0, + /*extraDataSize=*/0, nullptr, + scheduledTickOffset, + &qc); + LOG("This will finalize events with published results (past dispute window) and clean up state.\n"); +} + +struct qtryTransferQUSD_input +{ + uint8_t receiver[32]; + int64_t amount; +}; + +void qtryTransferQUSD(const char* nodeIp, int nodePort, const char* seed, + const char* receiverIdentity, int64_t amount, + uint32_t scheduledTickOffset) +{ + qtryTransferQUSD_input input{}; + getPublicKeyFromIdentity(receiverIdentity, input.receiver); + input.amount = amount; + + char receiverBuf[128] = { 0 }; + getIdentityFromPublicKey(input.receiver, receiverBuf, false); + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY TransferQUSD (GARTH transfer via Quottery contract)\n"); + LOG("Receiver: %s\n", receiverBuf); + LOG("Amount: %" PRId64 "\n", amount); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_TRANSFER_QUSD, + /*amount=*/0, + sizeof(input), &input, + scheduledTickOffset); +} + +// TransferQTRYGOV (proc 15) +struct qtryTransferQTRYGOV_input +{ + uint8_t receiver[32]; + int64_t amount; +}; + +void qtryTransferQTRYGOV(const char* nodeIp, int nodePort, const char* seed, + const char* receiverIdentity, int64_t amount, + uint32_t scheduledTickOffset) +{ + if (amount <= 0) + { + LOG("Error: amount must be positive\n"); + return; + } + + qtryTransferQTRYGOV_input input{}; + getPublicKeyFromIdentity(receiverIdentity, input.receiver); + input.amount = amount; + + char receiverBuf[128] = { 0 }; + getIdentityFromPublicKey(input.receiver, receiverBuf, false); + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY TransferQTRYGOV\n"); + LOG("Receiver: %s\n", receiverBuf); + LOG("Amount: %" PRId64 "\n", amount); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_TRANSFER_QTRYGOV, + /*amount=*/0, + sizeof(input), &input, + scheduledTickOffset); +} + +// UpdateFeeDiscountList (proc 20) +struct qtryUpdateFeeDiscountList_input +{ + uint8_t userId[32]; + uint64_t newFeeRate; + uint64_t ops; // 0 = remove, 1 = set +}; + +void qtryUpdateFeeDiscountList(const char* nodeIp, int nodePort, const char* seed, + uint32_t scheduledTickOffset, + const char* userIdentity, uint64_t newFeeRate, uint64_t ops) +{ + if (ops != 0 && ops != 1) + { + LOG("Error: ops must be 0 (remove) or 1 (set)\n"); + return; + } + + if (ops == 1 && newFeeRate > 1000) + { + LOG("Error: newFeeRate must be <= 1000 (100%%)\n"); + return; + } + + auto qc = make_qc(nodeIp, nodePort); + + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + char sourceIdentity[128] = { 0 }; + char goIdentity[128] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + getIdentityFromPublicKey(sourcePublicKey, sourceIdentity, false); + + qtryBasicInfo_output basic{}; + quotteryGetBasicInfo(qc, basic); + + if (memcmp(sourcePublicKey, basic.gameOperator, 32) != 0) + { + getIdentityFromPublicKey(basic.gameOperator, goIdentity, false); + LOG("Error: seed is not the game operator\n"); + LOG("Current identity: %s\n", sourceIdentity); + LOG("Game operator: %s\n", goIdentity); + return; + } + + qtryUpdateFeeDiscountList_input input{}; + getPublicKeyFromIdentity(userIdentity, input.userId); + input.newFeeRate = newFeeRate; + input.ops = ops; + + char userBuf[128] = { 0 }; + getIdentityFromPublicKey(input.userId, userBuf, false); + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY UpdateFeeDiscountList\n"); + LOG("User: %s\n", userBuf); + LOG("Operation: %s\n", ops == 0 ? "remove" : "set"); + if (ops == 1) LOG("New fee rate: %" PRIu64 " (%.1f%%)\n", newFeeRate, newFeeRate / 10.0); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_UPDATE_FEE_DISCOUNT_LIST, + /*amount=*/0, + sizeof(input), &input, + scheduledTickOffset, + &qc); +} + +// ProposalVote (proc 100) +struct qtryProposalVote_input +{ + QtryGOV_cli proposed; +}; + +void qtryProposalVote(const char* nodeIp, int nodePort, const char* seed, + uint32_t scheduledTickOffset, + uint64_t operationFee, uint64_t shareholderFee, uint64_t burnFee, + int64_t feePerDay, int64_t depositAmountForDispute, + const char* operationIdIdentity) +{ + auto qc = make_qc(nodeIp, nodePort); + + uint8_t privateKey[32] = { 0 }; + uint8_t sourcePublicKey[32] = { 0 }; + uint8_t subSeed[32] = { 0 }; + char sourceIdentity[128] = { 0 }; + + getSubseedFromSeed((uint8_t*)seed, subSeed); + getPrivateKeyFromSubSeed(subSeed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + getIdentityFromPublicKey(sourcePublicKey, sourceIdentity, false); + + qtryBasicInfo_output basic{}; + quotteryGetBasicInfo(qc, basic); + const int64_t txAmount = static_cast(basic.antiSpamAmount); + + qtryProposalVote_input input{}; + input.proposed.mOperationFee = operationFee; + input.proposed.mShareHolderFee = shareholderFee; + input.proposed.mBurnFee = burnFee; + input.proposed.feePerDay = feePerDay; + input.proposed.mDepositAmountForDispute = depositAmountForDispute; + getPublicKeyFromIdentity(operationIdIdentity, input.proposed.mOperationId); + + char opIdBuf[128] = { 0 }; + getIdentityFromPublicKey(input.proposed.mOperationId, opIdBuf, false); + + LOG("\n-------------------------------------\n\n"); + LOG("Sending QTRY ProposalVote\n"); + LOG("Caller: %s\n", sourceIdentity); + LOG("Proposed params:\n"); + LOG(" operationFee: %" PRIu64 " (%.1f%%)\n", operationFee, operationFee / 10.0); + LOG(" shareholderFee: %" PRIu64 " (%.1f%%)\n", shareholderFee, shareholderFee / 10.0); + LOG(" burnFee: %" PRIu64 " (%.1f%%)\n", burnFee, burnFee / 10.0); + LOG(" feePerDay: %" PRId64 "\n", feePerDay); + LOG(" depositAmountForDispute: %" PRId64 "\n", depositAmountForDispute); + LOG(" operationId: %s\n", opIdBuf); + LOG("\n-------------------------------------\n\n"); + + makeContractTransaction(nodeIp, nodePort, seed, + QUOTTERY_CONTRACT_ID, + QTRY_PROPOSAL_VOTE, + /*amount=*/txAmount, + sizeof(input), &input, + scheduledTickOffset, + &qc); +} + +// GetApprovedAmount (fn 7) +void quotteryGetApprovedAmount(const char* nodeIp, int nodePort, const char* identity) +{ + getApprovedAmount_input input{}; + getPublicKeyFromIdentity(identity, input.pk); + + getApprovedAmount_output result{}; + if (runContractFunction(nodeIp, nodePort, QUOTTERY_CONTRACT_ID, QTRY_GET_APPROVED_AMOUNT, + &input, sizeof(input), &result, sizeof(result))) + { + LOG("Approved QUSD amount for %s: %" PRIu64 "\n", identity, result.amount); + } + else + { + LOG("Failed to get approved amount\n"); + } +} + +// GetTopProposals (fn 8) +void quotteryGetTopProposals(const char* nodeIp, int nodePort) +{ + getTopProposals_output result{}; + if (!runContractFunction(nodeIp, nodePort, QUOTTERY_CONTRACT_ID, QTRY_GET_TOP_PROPOSALS, + nullptr, 0, &result, sizeof(result))) + { + LOG("Failed to get top proposals\n"); + return; + } + + LOG("Top governance proposals (%" PRId32 " unique this epoch):\n", result.uniqueCount); + for (int i = 0; i < 3 && i < result.uniqueCount; i++) + { + const auto& p = result.top[i]; + if (p.totalVotes == 0) break; + char opId[128] = { 0 }; + getIdentityFromPublicKey(p.proposed.mOperationId, opId, false); + LOG("\n #%d (%" PRId64 " votes):\n", i + 1, p.totalVotes); + LOG(" operationFee: %" PRIu64 " (%.1f%%)\n", p.proposed.mOperationFee, p.proposed.mOperationFee / 10.0); + LOG(" shareholderFee: %" PRIu64 " (%.1f%%)\n", p.proposed.mShareHolderFee, p.proposed.mShareHolderFee / 10.0); + LOG(" burnFee: %" PRIu64 " (%.1f%%)\n", p.proposed.mBurnFee, p.proposed.mBurnFee / 10.0); + LOG(" feePerDay: %" PRId64 "\n", p.proposed.feePerDay); + LOG(" depositAmountForDispute: %" PRId64 "\n", p.proposed.mDepositAmountForDispute); + LOG(" operationId: %s\n", opId); + } + if (result.uniqueCount == 0) LOG(" (none)\n"); +} + +static bool hasRequiredParameters(int currentIndex, int argc, int requiredCount, const char* command) +{ + const int expectedArgc = currentIndex + requiredCount + 1; + if (expectedArgc != argc) + { + LOG("Error: %s expects %d parameter(s)\n", command, requiredCount); + return false; + } + return true; +} + +static bool hasAtLeastRequiredParameters(int currentIndex, int argc, int requiredCount, const char* command) +{ + if (currentIndex + requiredCount >= argc) + { + LOG("Error: %s expects at least %d parameter(s)\n", command, requiredCount); + return false; + } + return true; +} + +static bool tryParseUint64Arg(const char* text, uint64_t& value, const char* fieldName) +{ + if (text == nullptr || *text == '\0') + { + LOG("Error: %s is empty\n", fieldName); + return false; + } + + char* endPtr = nullptr; + const unsigned long long parsed = std::strtoull(text, &endPtr, 10); + if (*endPtr != '\0') + { + LOG("Error: invalid numeric value for %s: %s\n", fieldName, text); + return false; + } + + value = static_cast(parsed); + return true; +} + +void quotteryEntryPoint(int argc, char** argv, const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset) +{ + constexpr uint64_t defaultAntiSpamAmount = 0; + + int i = 0; + while (i < argc) + { + if (strcmp(argv[i], "getbasicinfo") == 0) + { + if (!hasRequiredParameters(i, argc, 0, "getbasicinfo")) + return; + quotteryPrintBasicInfo(nodeIp, nodePort); + return; + } + + if (strcmp(argv[i], "getactiveeventsid") == 0) + { + if (!hasRequiredParameters(i, argc, 0, "getactiveeventsid")) + return; + + quotteryPrintActiveEvents(nodeIp, nodePort); + return; + } + + if (strcmp(argv[i], "createevent") == 0) + { + if (!hasRequiredParameters(i, argc, 5, "createevent")) + return; + + const std::string eventDesc = argv[i + 1]; + const std::string opt0Desc = argv[i + 2]; + const std::string opt1Desc = argv[i + 3]; + const std::string endDate = argv[i + 4]; + + uint64_t tagIdRaw = 0; + if (!tryParseUint64Arg(argv[i + 5], tagIdRaw, "tagId")) + return; + uint16_t tagId = static_cast(tagIdRaw); + + quotteryCreateEvent(nodeIp, nodePort, seed, + eventDesc, + opt0Desc, + opt1Desc, + endDate, + tagId, + scheduledTickOffset); + return; + } + + if (strcmp(argv[i], "order") == 0) + { + if (!hasRequiredParameters(i, argc, 6, "order")) + return; + + const char* action = argv[i + 1]; + const char* side = argv[i + 2]; + + uint64_t eventId = 0; + uint64_t option = 0; + uint64_t amount = 0; + uint64_t price = 0; + + if (!tryParseUint64Arg(argv[i + 3], eventId, "eventId")) + return; + if (!tryParseUint64Arg(argv[i + 4], option, "option")) + return; + if (!tryParseUint64Arg(argv[i + 5], amount, "amount")) + return; + if (!tryParseUint64Arg(argv[i + 6], price, "price")) + return; + + if (strcmp(action, "add") == 0 && strcmp(side, "ask") == 0) + { + qtryAddToAskOrder(nodeIp, nodePort, seed, eventId, option, amount, + static_cast(price), defaultAntiSpamAmount, scheduledTickOffset); + } + else if (strcmp(action, "add") == 0 && strcmp(side, "bid") == 0) + { + qtryAddToBidOrder(nodeIp, nodePort, seed, eventId, option, amount, + static_cast(price), defaultAntiSpamAmount, scheduledTickOffset); + } + else if (strcmp(action, "remove") == 0 && strcmp(side, "ask") == 0) + { + qtryRemoveAskOrder(nodeIp, nodePort, seed, eventId, option, amount, + static_cast(price), defaultAntiSpamAmount, scheduledTickOffset); + } + else if (strcmp(action, "remove") == 0 && strcmp(side, "bid") == 0) + { + qtryRemoveBidOrder(nodeIp, nodePort, seed, eventId, option, amount, + static_cast(price), defaultAntiSpamAmount, scheduledTickOffset); + } + else + { + LOG("Invalid qtryorder command: %s %s. Expected: add/remove bid/ask\n", action, side); + } + return; + } + + if (strcmp(argv[i], "getorder") == 0) + { + if (!hasRequiredParameters(i, argc, 4, "getorder")) + return; + + const char* side = argv[i + 1]; + uint64_t eventId = 0; + uint64_t option = 0; + uint64_t offset = 0; + + if (!tryParseUint64Arg(argv[i + 2], eventId, "eventId")) + return; + if (!tryParseUint64Arg(argv[i + 3], option, "option")) + return; + if (!tryParseUint64Arg(argv[i + 4], offset, "offset")) + return; + + const uint64_t isBid = (strcmp(side, "bid") == 0) ? 1 : 0; + if (strcmp(side, "bid") != 0 && strcmp(side, "ask") != 0) + { + LOG("Invalid qtrygetorder side: %s. Expected: bid/ask\n", side); + return; + } + + quotteryPrintOrders(nodeIp, nodePort, eventId, option, isBid, offset); + return; + } + + if (strcmp(argv[i], "getposition") == 0) + { + if (!hasRequiredParameters(i, argc, 1, "getposition")) + return; + + const char* identity = argv[i + 1]; + + quotteryPrintUserPosition(nodeIp, nodePort, identity); + return; + } + + if (strcmp(argv[i], "geteventinfo") == 0) + { + if (!hasRequiredParameters(i, argc, 1, "geteventinfo")) + return; + + uint64_t eventId = 0; + if (!tryParseUint64Arg(argv[i + 1], eventId, "eventId")) + return; + + quotteryPrintEventInfo(nodeIp, nodePort, eventId); + return; + } + + if (strcmp(argv[i], "geteventinfobatch") == 0) + { + if (!hasAtLeastRequiredParameters(i, argc, 1, "geteventinfobatch")) + return; + + uint64_t eventIds[64] = {}; + size_t count = 0; + int j = i + 1; + + while (j < argc && count < 64) + { + if (!tryParseUint64Arg(argv[j], eventIds[count], "eventId")) + return; + ++count; + ++j; + } + + if (j < argc) + { + LOG("Error: geteventinfobatch supports at most 64 event ids\n"); + return; + } + + quotteryPrintEventInfoBatch(nodeIp, nodePort, eventIds, count); + return; + } + + if (strcmp(argv[i], "publishresult") == 0) { + if (!hasRequiredParameters(i, argc, 2, "publishresult")) + return; + + uint64_t eventId = 0; + uint64_t optionId = 0; + if (!tryParseUint64Arg(argv[i + 1], eventId, "eventId")) + return; + if (!tryParseUint64Arg(argv[i + 2], optionId, "optionId")) + return; + + qtryPublishResult(nodeIp, nodePort, seed, scheduledTickOffset, eventId, optionId); + return; + } + + if (strcmp(argv[i], "tryfinalizeevent") == 0) + { + if (!hasRequiredParameters(i, argc, 1, "tryfinalizeevent")) + return; + + uint64_t eventId = 0; + if (!tryParseUint64Arg(argv[i + 1], eventId, "eventId")) + return; + + qtryTryFinalizeEvent(nodeIp, nodePort, seed, scheduledTickOffset, eventId); + return; + } + + if (strcmp(argv[i], "dispute") == 0) + { + if (!hasRequiredParameters(i, argc, 1, "dispute")) + return; + + uint64_t eventId = 0; + if (!tryParseUint64Arg(argv[i + 1], eventId, "eventId")) + return; + + qtryDispute(nodeIp, nodePort, seed, scheduledTickOffset, eventId); + return; + } + + if (strcmp(argv[i], "resolvedispute") == 0) + { + if (!hasRequiredParameters(i, argc, 2, "resolvedispute")) + return; + + uint64_t eventId = 0; + uint64_t voteRaw = 0; + if (!tryParseUint64Arg(argv[i + 1], eventId, "eventId")) + return; + if (!tryParseUint64Arg(argv[i + 2], voteRaw, "vote")) + return; + + if (voteRaw != 0 && voteRaw != 1) + { + LOG("Error: vote must be 0 (No) or 1 (Yes)\n"); + return; + } + + qtryResolveDispute(nodeIp, nodePort, seed, scheduledTickOffset, eventId, static_cast(voteRaw)); + return; + } + + if (strcmp(argv[i], "claimreward") == 0) + { + if (!hasRequiredParameters(i, argc, 1, "claimreward")) + return; + + uint64_t eventId = 0; + if (!tryParseUint64Arg(argv[i + 1], eventId, "eventId")) + return; + + qtryUserClaimReward(nodeIp, nodePort, seed, scheduledTickOffset, eventId); + return; + } + + if (strcmp(argv[i], "forceclaimreward") == 0) + { + // Usage: forceclaimreward [identity2] ... [identity16] + if (!hasAtLeastRequiredParameters(i, argc, 2, "forceclaimreward")) + return; + + uint64_t eventId = 0; + if (!tryParseUint64Arg(argv[i + 1], eventId, "eventId")) + return; + + const char* identities[16] = {}; + int identityCount = 0; + int j = i + 2; + + while (j < argc && identityCount < 16) + { + identities[identityCount] = argv[j]; + ++identityCount; + ++j; + } + + if (j < argc) + { + LOG("Error: forceclaimreward supports at most 16 identities\n"); + return; + } + + if (identityCount == 0) + { + LOG("Error: forceclaimreward requires at least one identity\n"); + return; + } + + qtryGOForceClaimReward(nodeIp, nodePort, seed, scheduledTickOffset, + eventId, identities, identityCount); + return; + } + + if (strcmp(argv[i], "transfersharemgmt") == 0) + { + // Usage: transfersharemgmt + if (!hasRequiredParameters(i, argc, 4, "transfersharemgmt")) + return; + + const char* issuerIdentity = argv[i + 1]; + const char* assetName = argv[i + 2]; + + uint64_t sharesRaw = 0; + if (!tryParseUint64Arg(argv[i + 3], sharesRaw, "numberOfShares")) + return; + + uint64_t contractIndexRaw = 0; + if (!tryParseUint64Arg(argv[i + 4], contractIndexRaw, "newManagingContractIndex")) + return; + + qtryTransferShareManagementRights(nodeIp, nodePort, seed, scheduledTickOffset, + issuerIdentity, assetName, + static_cast(sharesRaw), + static_cast(contractIndexRaw)); + return; + } + + if (strcmp(argv[i], "cleanmemory") == 0) + { + if (!hasRequiredParameters(i, argc, 0, "cleanmemory")) + return; + + qtryCleanMemory(nodeIp, nodePort, seed, scheduledTickOffset); + return; + } + + if (strcmp(argv[i], "transferqtrygov") == 0) + { + if (!hasRequiredParameters(i, argc, 2, "transferqtrygov")) + return; + + const char* receiverIdentity = argv[i + 1]; + uint64_t amount = 0; + if (!tryParseUint64Arg(argv[i + 2], amount, "amount")) + return; + + qtryTransferQTRYGOV(nodeIp, nodePort, seed, receiverIdentity, + static_cast(amount), scheduledTickOffset); + return; + } + + if (strcmp(argv[i], "updatediscount") == 0) + { + // Usage: updatediscount [newFeeRate] + if (!hasAtLeastRequiredParameters(i, argc, 2, "updatediscount")) + return; + + const char* userIdentity = argv[i + 1]; + const char* action = argv[i + 2]; + + if (strcmp(action, "remove") == 0) + { + if (!hasRequiredParameters(i, argc, 2, "updatediscount")) + return; + qtryUpdateFeeDiscountList(nodeIp, nodePort, seed, scheduledTickOffset, + userIdentity, 0, 0); + } + else if (strcmp(action, "set") == 0) + { + if (!hasRequiredParameters(i, argc, 3, "updatediscount set")) + return; + uint64_t newFeeRate = 0; + if (!tryParseUint64Arg(argv[i + 3], newFeeRate, "newFeeRate")) + return; + qtryUpdateFeeDiscountList(nodeIp, nodePort, seed, scheduledTickOffset, + userIdentity, newFeeRate, 1); + } + else + { + LOG("Error: updatediscount action must be 'set' or 'remove'\n"); + return; + } + return; + } + + if (strcmp(argv[i], "proposalvote") == 0) + { + // Usage: proposalvote + if (!hasRequiredParameters(i, argc, 6, "proposalvote")) + return; + + uint64_t opFee = 0, shFee = 0, burnFee = 0; + uint64_t feePerDayRaw = 0, depositRaw = 0; + if (!tryParseUint64Arg(argv[i + 1], opFee, "operationFee")) return; + if (!tryParseUint64Arg(argv[i + 2], shFee, "shareholderFee")) return; + if (!tryParseUint64Arg(argv[i + 3], burnFee, "burnFee")) return; + if (!tryParseUint64Arg(argv[i + 4], feePerDayRaw, "feePerDay")) return; + if (!tryParseUint64Arg(argv[i + 5], depositRaw, "depositAmountForDispute")) return; + const char* opIdIdentity = argv[i + 6]; + + qtryProposalVote(nodeIp, nodePort, seed, scheduledTickOffset, + opFee, shFee, burnFee, + static_cast(feePerDayRaw), + static_cast(depositRaw), + opIdIdentity); + return; + } + + if (strcmp(argv[i], "getapprovedamount") == 0) + { + if (!hasRequiredParameters(i, argc, 1, "getapprovedamount")) + return; + + const char* identity = argv[i + 1]; + + quotteryGetApprovedAmount(nodeIp, nodePort, identity); + return; + } + + if (strcmp(argv[i], "gettopproposals") == 0) + { + if (!hasRequiredParameters(i, argc, 0, "gettopproposals")) + return; + + quotteryGetTopProposals(nodeIp, nodePort); + return; + } + + if (strcmp(argv[i], "transferqusd") == 0) + { + if (!hasRequiredParameters(i, argc, 2, "transferqusd")) + return; + + const char* receiverIdentity = argv[i + 1]; + uint64_t amount = 0; + if (!tryParseUint64Arg(argv[i + 2], amount, "amount")) + return; + + qtryTransferQUSD(nodeIp, nodePort, seed, receiverIdentity, + static_cast(amount), scheduledTickOffset); + return; + } + + ++i; + } + + LOG("Error: no valid Quottery command provided\n"); } diff --git a/quottery.h b/quottery.h index bd69127..44dbf6d 100644 --- a/quottery.h +++ b/quottery.h @@ -1,47 +1,56 @@ #pragma once #include "structs.h" +#define QUOTTERY_MAX_CONCURRENT_EVENT 4096 +struct qtryBasicInfo_output +{ + uint64_t operationFee; // 4 digit number ABCD means AB.CD% | 1234 is 12.34% + uint64_t shareholderFee; // 4 digit number ABCD means AB.CD% | 1234 is 12.34% + uint64_t burnFee; // percentage + uint64_t nIssuedEvent; // number of issued event + uint64_t shareholdersRevenue; + uint64_t operationRevenue; + uint64_t burnedAmount; + uint64_t feePerDay; + uint64_t antiSpamAmount; + uint64_t depositAmountForDispute; + uint8_t gameOperator[32]; + + static constexpr unsigned char type() + { + return RespondContractFunction::type(); + } +}; -// SC structs -struct QuotteryjoinBet_input +struct getEventInfo_input { - uint32_t betId; - int numberOfSlot; - uint32_t option; - uint32_t _placeHolder; + uint64_t eventId; }; -struct QuotteryissueBet_input +struct QtryEventInfo { - uint8_t betDesc[32]; - uint8_t optionDesc[32*8]; - uint8_t oracleProviderId[32*8]; - uint32_t oracleFees[8]; - uint32_t closeDate; - uint32_t endDate; - uint64_t amountPerSlot; - uint32_t maxBetSlotPerOption; - uint32_t numberOfOption; + uint64_t eid; + uint64_t openDate; // submitted date + uint64_t endDate; // stop receiving result from OPs + uint8_t desc[128]; + uint8_t option0Desc[64]; + uint8_t option1Desc[64]; }; -struct qtryBasicInfo_output +struct DepositInfo { - uint64_t feePerSlotPerHour; // Amount of qus - uint64_t gameOperatorFee; // 4 digit number ABCD means AB.CD% | 1234 is 12.34% - uint64_t shareholderFee; // 4 digit number ABCD means AB.CD% | 1234 is 12.34% - uint64_t minBetSlotAmount; // amount of qus - uint64_t burnFee; // percentage - uint64_t nIssuedBet; // number of issued bet - uint64_t moneyFlow; - uint64_t moneyFlowThroughIssueBet; - uint64_t moneyFlowThroughJoinBet; - uint64_t moneyFlowThroughFinalizeBet; - uint64_t earnedAmountForShareHolder; - uint64_t paidAmountForShareHolder; - uint64_t earnedAmountForBetWinner; - uint64_t distributedAmount; - uint64_t burnedAmount; - uint8_t gameOperator[32]; + uint8_t pubkey[32]; + uint64_t amount; +}; + +struct getEventInfo_output +{ + QtryEventInfo qei; + int32_t resultByGO; // -1 = NOT_SET, 0 = NO, 1 = YES + uint32_t publishTickTime; // ignore if resultByGO is not set + DepositInfo disputerInfo; // zero / null if no dispute + uint32_t computorsVote0; + uint32_t computorsVote1; static constexpr unsigned char type() { @@ -49,45 +58,49 @@ struct qtryBasicInfo_output } }; -struct getBetInfo_input +void unpackQuotteryDate(uint8_t& _year, uint8_t& _month, uint8_t& _day, uint8_t& _hour, uint8_t& _minute, uint8_t& _second, uint32_t data); + + +struct qtryGetOrders_input { - uint32_t betId; + uint64_t eventId; + uint64_t option; + uint64_t isBid; + uint64_t offset; }; -struct getBetInfo_output + +struct QtryOrder { - // meta data info - uint32_t betId; - uint32_t nOption; // options number - uint8_t creator[32]; - uint8_t betDesc[32]; // 32 bytes - uint8_t optionDesc[8*32]; // 8x(32)=256bytes - uint8_t oracleProviderId[8*32]; // 256x8=2048bytes - uint32_t oracleFees[8]; // 4x8 = 32 bytes + uint8_t entity[32]; + uint64_t amount; +}; - uint32_t openDate; // creation date, start to receive bet - uint32_t closeDate; // stop receiving bet date - uint32_t endDate; // result date - // Amounts and numbers - uint64_t minBetAmount; - uint32_t maxBetSlotPerOption; - uint32_t currentBetState[8]; // how many bet slots have been filled on each option - char betResultWonOption[8]; - char betResultOPId[8]; +struct QtryOrderWithPrice +{ + QtryOrder qo; + int64_t price; +}; + +struct qtryGetOrders_output +{ + QtryOrderWithPrice orders[256]; static constexpr unsigned char type() { return RespondContractFunction::type(); - } + } }; -struct getBetOptionDetail_input +struct PositionInfo { - uint32_t betId; - uint32_t betOption; + uint64_t eo; // packed: bit 0 = option, bits 1+ = eventId + int64_t amount; }; -struct getBetOptionDetail_output + +struct getUserPosition_output { - uint8_t bettor[32*1024]; + int64_t count; + PositionInfo p[1024]; static constexpr unsigned char type() { @@ -95,66 +108,67 @@ struct getBetOptionDetail_output } }; -struct getActiveBet_output -{ - uint32_t count; - uint32_t betId[1024]; +struct getActiveEvent_output +{ + uint64_t recentActiveEvent[QUOTTERY_MAX_CONCURRENT_EVENT]; static constexpr unsigned char type() { return RespondContractFunction::type(); } }; -struct getActiveBetByCreator_input +struct GetEventInfoBatch_input { - uint8_t creator[32]; + uint64_t eventIds[64]; }; -struct getActiveBetByCreator_output +struct GetEventInfoBatch_output { - uint32_t count; - uint32_t betId[1024]; - + QtryEventInfo aqei[64]; static constexpr unsigned char type() { return RespondContractFunction::type(); - } + } }; -struct publishResult_input + +struct getApprovedAmount_input { - uint32_t betId; - uint32_t winOption; + uint8_t pk[32]; }; - -#pragma pack(push, 1) -struct cancelBet_input +struct getApprovedAmount_output { - uint32_t betId; + uint64_t amount; + static constexpr unsigned char type() + { + return RespondContractFunction::type(); + } }; -#pragma pack(pop) - -void quotteryIssueBet(const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset); -void quotteryPrintBetInfo(const char* nodeIp, const int nodePort, int betId); -void quotteryPrintBasicInfo(const char* nodeIp, const int nodePort); -void quotteryJoinBet(const char* nodeIp, int nodePort, const char* seed, uint32_t betId, int numberOfBetSlot, uint64_t amountPerSlot, uint8_t option, uint32_t scheduledTickOffset); -void quotteryPrintBetOptionDetail(const char* nodeIp, const int nodePort, uint32_t betId, uint32_t betOption); -void quotteryPrintActiveBet(const char* nodeIp, const int nodePort); -void quotteryPrintActiveBetByCreator(const char* nodeIp, const int nodePort, const char* identity); -void quotteryCancelBet(const char* nodeIp, const int nodePort, const char* seed, const uint32_t betId, const uint32_t scheduledTickOffset); -void quotteryPublishResult(const char* nodeIp, const int nodePort, const char* seed, const uint32_t betId, const uint32_t winOption, const uint32_t scheduledTickOffset); -// Get the basic information of quottery -void quotteryGetBasicInfo(const char* nodeIp, const int nodePort, qtryBasicInfo_output& result); +struct QtryGOV_cli +{ + uint64_t mShareHolderFee; + uint64_t mBurnFee; + uint64_t mOperationFee; + int64_t feePerDay; + int64_t mDepositAmountForDispute; + uint8_t mOperationId[32]; +}; -// Core function -void quotteryGetActiveBet(const char* nodeIp, const int nodePort, getActiveBet_output& result); +struct ProposalInfo_cli +{ + QtryGOV_cli proposed; + int64_t totalVotes; +}; -// Get detail information of a bet ID -void quotteryGetBetInfo( - const char* nodeIp, - const int nodePort, - int betId, - getBetInfo_output& result); +struct getTopProposals_output +{ + ProposalInfo_cli top[4]; // top 3 (index 3 unused) + int32_t uniqueCount; + static constexpr unsigned char type() + { + return RespondContractFunction::type(); + } +}; -void unpackQuotteryDate(uint8_t& _year, uint8_t& _month, uint8_t& _day, uint8_t& _hour, uint8_t& _minute, uint8_t& _second, uint32_t data); +void quotteryEntryPoint(int argc, char** argv, const char* nodeIp, int nodePort, const char* seed, uint32_t scheduledTickOffset); \ No newline at end of file diff --git a/structs.h b/structs.h index a4f8d7a..be31f0d 100644 --- a/structs.h +++ b/structs.h @@ -36,15 +36,7 @@ enum COMMAND MAKE_IPO_BID, GET_IPO_STATUS, GET_ACTIVE_IPOS, - QUOTTERY_ISSUE_BET, - QUOTTERY_JOIN_BET, - QUOTTERY_GET_BET_INFO, - QUOTTERY_GET_BET_DETAIL, - QUOTTERY_GET_ACTIVE_BET, - QUOTTERY_GET_ACTIVE_BET_BY_CREATOR, - QUOTTERY_GET_BASIC_INFO, - QUOTTERY_PUBLISH_RESULT, - QUOTTERY_CANCEL_BET, + QUOTTERY_COMMAND, TOOGLE_MAIN_AUX, SET_SOLUTION_THRESHOLD, REFRESH_PEER_LIST, @@ -319,12 +311,16 @@ struct Entity unsigned int latestIncomingTransferTick, latestOutgoingTransferTick; }; -typedef struct +typedef struct RespondedEntity { Entity entity; unsigned int tick; int spectrumIndex; unsigned char siblings[SPECTRUM_DEPTH][32]; + static constexpr unsigned char type() + { + return 32; + } } RespondedEntity; typedef struct