diff --git a/CMakeLists.txt b/CMakeLists.txt index b6a3bb2..5937818 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ SET(FILES ${CMAKE_SOURCE_DIR}/asset_utils.cpp ${CMAKE_SOURCE_DIR}/test_utils.cpp ${CMAKE_SOURCE_DIR}/wallet_utils.cpp ${CMAKE_SOURCE_DIR}/utils.cpp + ${CMAKE_SOURCE_DIR}/qsb.cpp ) SET(HEADER_FILES argparser.h @@ -55,6 +56,7 @@ SET(HEADER_FILES test_utils.h utils.h wallet_utils.h + qsb.h ) if(MSVC) diff --git a/README.md b/README.md index 103a3a1..e3172fe 100644 --- a/README.md +++ b/README.md @@ -435,7 +435,39 @@ Commands: -qbondgetusermbonds Get MBonds owned by the . -qbondgetcfa - Get list of commission free addresses. + Get list of commission free addresses. + +[QSB (QUBIC SOLANA BRIDGE) COMMANDS] + -qsblock + Lock QU to bridge to Solana. is the amount to lock, is the fee for relayer (must be < AMOUNT>), is the Solana recipient address as 128 hex characters (64 bytes), is the destination network ID, is a unique order nonce. + -qsboverridelock + Override a previously locked order. is the nonce of the locked order, is the new Solana recipient address (128 hex chars), is the new relayer fee. + -qsbunlock + Unlock funds from Solana (requires oracle signatures). This is a complex operation - use invokecontractprocedure for full control. + -qsbtransferadmin + Transfer admin role to a new identity. Admin only. + -qsbeditoraclethreshold + Set oracle threshold percentage (1-100). Admin only. + -qsbaddrole + Add role to an account. is 1 for Oracle or 2 for Pauser. Admin only. + -qsbremoverole + Remove role from an account. is 1 for Oracle or 2 for Pauser. Admin only. + -qsbpause + Pause the QSB contract. Admin or Pauser only. + -qsbunpause + Unpause the QSB contract. Admin or Pauser only. + -qsbeditfeeparameters + Edit fee parameters. and are identities (use empty string "" to skip), is basis points (0-10000), is percentage (0-100). Admin only. + -qsbgetconfig + Get QSB configuration (admin, fee recipients, fees, oracle parameters, paused state). + -qsbisoracle + Check if an account has Oracle role in QSB. + -qsbispauser + Check if an account has Pauser role in QSB. + -qsbgetlockedorder + Get information about a locked order by nonce. + -qsbisorderfilled + Check if an order hash has already been filled in QSB (64 hex chars). [TESTING COMMANDS] -testqpifunctionsoutput diff --git a/argparser.h b/argparser.h index 3a475bf..547cf95 100644 --- a/argparser.h +++ b/argparser.h @@ -488,6 +488,38 @@ void print_help() printf("\t-qbondgetcfa\n"); printf("\t\tGet list of commission free addresses.\n"); + printf("\n[QSB (QUBIC SOLANA BRIDGE) COMMANDS]\n"); + printf("\t-qsblock \n"); + printf("\t\tLock QU to bridge to Solana. is the amount to lock, is the fee for relayer (must be < AMOUNT>), is the Solana recipient address as 128 hex characters (64 bytes), is the destination network ID, is a unique order nonce.\n"); + printf("\t-qsboverridelock \n"); + printf("\t\tOverride a previously locked order. is the nonce of the locked order, is the new Solana recipient address (128 hex chars), is the new relayer fee.\n"); + printf("\t-qsbunlock \n"); + printf("\t\tUnlock funds from Solana (requires oracle signatures). This is a complex operation - use invokecontractprocedure for full control.\n"); + printf("\t-qsbtransferadmin \n"); + printf("\t\tTransfer admin role to a new identity. Admin only.\n"); + printf("\t-qsbeditoraclethreshold \n"); + printf("\t\tSet oracle threshold percentage (1-100). Admin only.\n"); + printf("\t-qsbaddrole \n"); + printf("\t\tAdd role to an account. is 1 for Oracle or 2 for Pauser. Admin only.\n"); + printf("\t-qsbremoverole \n"); + printf("\t\tRemove role from an account. is 1 for Oracle or 2 for Pauser. Admin only.\n"); + printf("\t-qsbpause\n"); + printf("\t\tPause the QSB contract. Admin or Pauser only.\n"); + printf("\t-qsbunpause\n"); + printf("\t\tUnpause the QSB contract. Admin or Pauser only.\n"); + printf("\t-qsbeditfeeparameters \n"); + printf("\t\tEdit fee parameters. and are identities (use empty string \"\" to skip), is basis points (0-10000), is percentage (0-100). Admin only.\n"); + printf("\t-qsbgetconfig\n"); + printf("\t\tGet QSB configuration (admin, fee recipients, fees, oracle parameters, paused state).\n"); + printf("\t-qsbisoracle \n"); + printf("\t\tCheck if an account has Oracle role in QSB.\n"); + printf("\t-qsbispauser \n"); + printf("\t\tCheck if an account has Pauser role in QSB.\n"); + printf("\t-qsbgetlockedorder \n"); + printf("\t\tGet information about a locked order by nonce.\n"); + printf("\t-qsbisorderfilled \n"); + printf("\t\tCheck if an order hash has already been filled in QSB (64 hex chars).\n"); + printf("\n[TESTING COMMANDS]\n"); printf("\t-testqpifunctionsoutput\n"); printf("\t\tTest that output of qpi functions matches TickData and quorum tick votes for 15 ticks in the future (as specified by scheduletick offset). Requires the TESTEXA SC to be enabled.\n"); @@ -554,6 +586,8 @@ static uint32_t getContractIndex(const char* str, bool enableTestContracts) idx = 17; else if (strcasecmp(str, "QIP") == 0) idx = 18; + else if (strcasecmp(str, "QSB") == 0) + idx = 20; else { unsigned int contractCount = CONTRACT_COUNT; @@ -2677,6 +2711,141 @@ void parseArgument(int argc, char** argv) break; } + /***************************************** + ***** QSB COMMANDS ***** + *****************************************/ + + if (strcmp(argv[i], "-qsblock") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(5) + g_cmd = QSB_LOCK_CMD; + g_qsb_amount = charToNumber(argv[i + 1]); + g_qsb_relayerFee = charToNumber(argv[i + 2]); + g_qsb_toAddressHex = argv[i + 3]; + g_qsb_networkOut = (uint32_t)charToNumber(argv[i + 4]); + g_qsb_nonce = (uint32_t)charToNumber(argv[i + 5]); + i += 6; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsboverridelock") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(3) + g_cmd = QSB_OVERRIDE_LOCK_CMD; + g_qsb_nonce = (uint32_t)charToNumber(argv[i + 1]); + g_qsb_toAddressHex = argv[i + 2]; + g_qsb_relayerFee = charToNumber(argv[i + 3]); + i += 4; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbtransferadmin") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QSB_TRANSFER_ADMIN_CMD; + g_qsb_newAdminIdentity = argv[i + 1]; + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbeditoraclethreshold") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QSB_EDIT_ORACLE_THRESHOLD_CMD; + g_qsb_oracleThreshold = (uint8_t)charToNumber(argv[i + 1]); + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbaddrole") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(2) + g_cmd = QSB_ADD_ROLE_CMD; + g_qsb_accountIdentity = argv[i + 1]; + g_qsb_role = (uint8_t)charToNumber(argv[i + 2]); + i += 3; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbremoverole") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(2) + g_cmd = QSB_REMOVE_ROLE_CMD; + g_qsb_accountIdentity = argv[i + 1]; + g_qsb_role = (uint8_t)charToNumber(argv[i + 2]); + i += 3; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbpause") == 0) + { + g_cmd = QSB_PAUSE_CMD; + i++; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbunpause") == 0) + { + g_cmd = QSB_UNPAUSE_CMD; + i++; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbeditfeeparameters") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(4) + g_cmd = QSB_EDIT_FEE_PARAMETERS_CMD; + g_qsb_protocolFeeRecipientIdentity = argv[i + 1]; + g_qsb_oracleFeeRecipientIdentity = argv[i + 2]; + g_qsb_bpsFee = (uint32_t)charToNumber(argv[i + 3]); + g_qsb_protocolFee = (uint32_t)charToNumber(argv[i + 4]); + i += 5; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbgetconfig") == 0) + { + g_cmd = QSB_GET_CONFIG_CMD; + i += 1; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbisoracle") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QSB_IS_ORACLE_CMD; + g_qsb_viewIdentity = argv[i + 1]; + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbispauser") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QSB_IS_PAUSER_CMD; + g_qsb_viewIdentity = argv[i + 1]; + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbgetlockedorder") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QSB_GET_LOCKED_ORDER_CMD; + g_qsb_viewNonce = (uint32_t)charToNumber(argv[i + 1]); + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qsbisorderfilled") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QSB_IS_ORDER_FILLED_CMD; + g_qsb_orderHashHex = argv[i + 1]; + i += 2; + CHECK_OVER_PARAMETERS + break; + } /***************************************** ***** SHAREHOLDER PROPOSAL COMMANDS ***** diff --git a/connection.cpp b/connection.cpp index f6e34e1..4abfca5 100644 --- a/connection.cpp +++ b/connection.cpp @@ -28,6 +28,7 @@ #include "nostromo.h" #include "qutil.h" #include "qbond.h" +#include "qsb.h" #define DEFAULT_TIMEOUT_MSEC 1000 @@ -410,3 +411,10 @@ template GetCurrentResult_output QubicConnection::receivePacketWithHeaderAs(); template GetCurrentPollId_output QubicConnection::receivePacketWithHeaderAs(); template GetPollInfo_output QubicConnection::receivePacketWithHeaderAs(); + +// QSB +template QSB_GetConfig_output QubicConnection::receivePacketWithHeaderAs(); +template QSB_IsOracle_output QubicConnection::receivePacketWithHeaderAs(); +template QSB_IsPauser_output QubicConnection::receivePacketWithHeaderAs(); +template QSB_GetLockedOrder_output QubicConnection::receivePacketWithHeaderAs(); +template QSB_IsOrderFilled_output QubicConnection::receivePacketWithHeaderAs(); \ No newline at end of file diff --git a/global.h b/global.h index 59ad68d..7b90561 100644 --- a/global.h +++ b/global.h @@ -255,3 +255,21 @@ int64_t g_qbond_mbondPrice = 0; int64_t g_qbond_burnAmount = 0; char* g_qbond_owner = nullptr; bool g_qbond_updateCFAOperation = false; + +// qsb +uint64_t g_qsb_amount = 0; +uint64_t g_qsb_relayerFee = 0; +char* g_qsb_toAddressHex = nullptr; +uint32_t g_qsb_networkOut = 0; +uint32_t g_qsb_nonce = 0; +char* g_qsb_newAdminIdentity = nullptr; +uint8_t g_qsb_oracleThreshold = 0; +char* g_qsb_accountIdentity = nullptr; +uint8_t g_qsb_role = 0; +char* g_qsb_protocolFeeRecipientIdentity = nullptr; +char* g_qsb_oracleFeeRecipientIdentity = nullptr; +uint32_t g_qsb_bpsFee = 0; +uint32_t g_qsb_protocolFee = 0; +char* g_qsb_viewIdentity = nullptr; +uint32_t g_qsb_viewNonce = 0; +char* g_qsb_orderHashHex = nullptr; \ No newline at end of file diff --git a/main.cpp b/main.cpp index b757d23..a09a9a9 100644 --- a/main.cpp +++ b/main.cpp @@ -21,6 +21,7 @@ #include "test_utils.h" #include "nostromo.h" #include "qbond.h" +#include "qsb.h" int run(int argc, char* argv[]) { @@ -1289,6 +1290,105 @@ int run(int argc, char* argv[]) qbondGetCFA(g_nodeIp, g_nodePort); break; } + case QSB_LOCK_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbLock(g_nodeIp, g_nodePort, g_seed, g_qsb_amount, g_qsb_relayerFee, + g_qsb_toAddressHex, g_qsb_networkOut, g_qsb_nonce, g_offsetScheduledTick); + break; + } + case QSB_OVERRIDE_LOCK_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbOverrideLock(g_nodeIp, g_nodePort, g_seed, g_qsb_nonce, + g_qsb_toAddressHex, g_qsb_relayerFee, g_offsetScheduledTick); + break; + } + case QSB_TRANSFER_ADMIN_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbTransferAdmin(g_nodeIp, g_nodePort, g_seed, g_qsb_newAdminIdentity, g_offsetScheduledTick); + break; + } + case QSB_EDIT_ORACLE_THRESHOLD_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbEditOracleThreshold(g_nodeIp, g_nodePort, g_seed, g_qsb_oracleThreshold, g_offsetScheduledTick); + break; + } + case QSB_ADD_ROLE_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbAddRole(g_nodeIp, g_nodePort, g_seed, g_qsb_accountIdentity, g_qsb_role, g_offsetScheduledTick); + break; + } + case QSB_REMOVE_ROLE_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbRemoveRole(g_nodeIp, g_nodePort, g_seed, g_qsb_accountIdentity, g_qsb_role, g_offsetScheduledTick); + break; + } + case QSB_PAUSE_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbPause(g_nodeIp, g_nodePort, g_seed, g_offsetScheduledTick); + break; + } + case QSB_UNPAUSE_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbUnpause(g_nodeIp, g_nodePort, g_seed, g_offsetScheduledTick); + break; + } + case QSB_EDIT_FEE_PARAMETERS_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + qsbEditFeeParameters(g_nodeIp, g_nodePort, g_seed, + g_qsb_protocolFeeRecipientIdentity, g_qsb_oracleFeeRecipientIdentity, + g_qsb_bpsFee, g_qsb_protocolFee, g_offsetScheduledTick); + break; + } + case QSB_GET_CONFIG_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + qsbGetConfig(g_nodeIp, g_nodePort); + break; + } + case QSB_IS_ORACLE_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckIdentity(g_qsb_viewIdentity); + qsbIsOracle(g_nodeIp, g_nodePort, g_qsb_viewIdentity); + break; + } + case QSB_IS_PAUSER_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckIdentity(g_qsb_viewIdentity); + qsbIsPauser(g_nodeIp, g_nodePort, g_qsb_viewIdentity); + break; + } + case QSB_GET_LOCKED_ORDER_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + qsbGetLockedOrder(g_nodeIp, g_nodePort, g_qsb_viewNonce); + break; + } + case QSB_IS_ORDER_FILLED_CMD: + { + sanityCheckNode(g_nodeIp, g_nodePort); + qsbIsOrderFilled(g_nodeIp, g_nodePort, g_qsb_orderHashHex); + break; + } case SHAREHOLDER_SET_PROPOSAL: sanityCheckNode(g_nodeIp, g_nodePort); sanityCheckSeed(g_seed); diff --git a/qsb.cpp b/qsb.cpp new file mode 100644 index 0000000..bfdfb8e --- /dev/null +++ b/qsb.cpp @@ -0,0 +1,416 @@ +#include "qsb.h" +#include "wallet_utils.h" +#include "key_utils.h" +#include "sanity_check.h" +#include "logger.h" +#include +#include +#include + +// Helper function to convert hex string to bytes +static bool hexStringToBytes(const char* hexStr, uint8_t* output, size_t outputSize) +{ + if (!hexStr || strlen(hexStr) != outputSize * 2) + { + return false; + } + + for (size_t i = 0; i < outputSize; ++i) + { + char c1 = hexStr[i * 2]; + char c2 = hexStr[i * 2 + 1]; + + uint8_t byte = 0; + if (c1 >= '0' && c1 <= '9') + byte |= (c1 - '0') << 4; + else if (c1 >= 'a' && c1 <= 'f') + byte |= (c1 - 'a' + 10) << 4; + else if (c1 >= 'A' && c1 <= 'F') + byte |= (c1 - 'A' + 10) << 4; + else + return false; + + if (c2 >= '0' && c2 <= '9') + byte |= (c2 - '0'); + else if (c2 >= 'a' && c2 <= 'f') + byte |= (c2 - 'a' + 10); + else if (c2 >= 'A' && c2 <= 'F') + byte |= (c2 - 'A' + 10); + else + return false; + + output[i] = byte; + } + return true; +} + +// Helper function to print order hash +static void printOrderHash(const QSB_OrderHash& hash) +{ + std::cout << "Order Hash: "; + for (size_t i = 0; i < 32; ++i) + { + std::cout << std::hex << std::setfill('0') << std::setw(2) << (int)hash.hash[i]; + } + std::cout << std::dec << std::endl; +} + +void qsbLock(const char* nodeIp, int nodePort, const char* seed, + uint64_t amount, uint64_t relayerFee, const char* toAddressHex, + uint32_t networkOut, uint32_t nonce, uint32_t scheduledTickOffset) +{ + QSB_Lock_input input; + memset(&input, 0, sizeof(input)); + + input.amount = amount; + input.relayerFee = relayerFee; + input.networkOut = networkOut; + input.nonce = nonce; + + // Convert hex string to bytes for toAddress + if (toAddressHex && strlen(toAddressHex) > 0) + { + if (!hexStringToBytes(toAddressHex, input.toAddress, 64)) + { + std::cout << "ERROR: Invalid toAddress hex string. Must be 128 hex characters (64 bytes)." << std::endl; + return; + } + } + + std::cout << "Locking " << amount << " QU (relayer fee: " << relayerFee + << ", network: " << networkOut << ", nonce: " << nonce << ")..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_LOCK, amount, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbOverrideLock(const char* nodeIp, int nodePort, const char* seed, + uint32_t nonce, const char* toAddressHex, uint64_t relayerFee, + uint32_t scheduledTickOffset) +{ + QSB_OverrideLock_input input; + memset(&input, 0, sizeof(input)); + + input.nonce = nonce; + input.relayerFee = relayerFee; + + // Convert hex string to bytes for toAddress + if (toAddressHex && strlen(toAddressHex) > 0) + { + if (!hexStringToBytes(toAddressHex, input.toAddress, 64)) + { + std::cout << "ERROR: Invalid toAddress hex string. Must be 128 hex characters (64 bytes)." << std::endl; + return; + } + } + + std::cout << "Overriding lock for nonce " << nonce + << " (relayer fee: " << relayerFee << ")..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_OVERRIDE_LOCK, 0, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbUnlock(const char* nodeIp, int nodePort, const char* seed, + const QSB_Order* order, uint32_t numSignatures, + const QSB_SignatureData* signatures, uint32_t scheduledTickOffset) +{ + if (numSignatures > 64) + { + std::cout << "ERROR: Too many signatures (max 64)." << std::endl; + return; + } + + QSB_Unlock_input input; + memset(&input, 0, sizeof(input)); + + memcpy(&input.order, order, sizeof(QSB_Order)); + input.numSignatures = numSignatures; + + for (uint32_t i = 0; i < numSignatures && i < 64; ++i) + { + memcpy(&input.signatures[i], &signatures[i], sizeof(QSB_SignatureData)); + } + + std::cout << "Unlocking order with " << numSignatures << " signature(s)..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_UNLOCK, 0, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbTransferAdmin(const char* nodeIp, int nodePort, const char* seed, + const char* newAdminIdentity, uint32_t scheduledTickOffset) +{ + QSB_TransferAdmin_input input; + memset(&input, 0, sizeof(input)); + + sanityCheckIdentity(newAdminIdentity); + getPublicKeyFromIdentity(newAdminIdentity, input.newAdmin); + + std::cout << "Transferring admin to " << newAdminIdentity << "..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_TRANSFER_ADMIN, 0, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbEditOracleThreshold(const char* nodeIp, int nodePort, const char* seed, + uint8_t newThreshold, uint32_t scheduledTickOffset) +{ + if (newThreshold == 0 || newThreshold > 100) + { + std::cout << "ERROR: Oracle threshold must be between 1 and 100." << std::endl; + return; + } + + QSB_EditOracleThreshold_input input; + memset(&input, 0, sizeof(input)); + input.newThreshold = newThreshold; + + std::cout << "Setting oracle threshold to " << (int)newThreshold << "%..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_EDIT_ORACLE_THRESHOLD, 0, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbAddRole(const char* nodeIp, int nodePort, const char* seed, + const char* accountIdentity, uint8_t role, uint32_t scheduledTickOffset) +{ + if (role != 1 && role != 2) + { + std::cout << "ERROR: Role must be 1 (Oracle) or 2 (Pauser)." << std::endl; + return; + } + + QSB_AddRole_input input; + memset(&input, 0, sizeof(input)); + + sanityCheckIdentity(accountIdentity); + getPublicKeyFromIdentity(accountIdentity, input.account); + input.role = role; + + const char* roleName = (role == 1) ? "Oracle" : "Pauser"; + std::cout << "Adding " << roleName << " role to " << accountIdentity << "..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_ADD_ROLE, 0, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbRemoveRole(const char* nodeIp, int nodePort, const char* seed, + const char* accountIdentity, uint8_t role, uint32_t scheduledTickOffset) +{ + if (role != 1 && role != 2) + { + std::cout << "ERROR: Role must be 1 (Oracle) or 2 (Pauser)." << std::endl; + return; + } + + QSB_RemoveRole_input input; + memset(&input, 0, sizeof(input)); + + sanityCheckIdentity(accountIdentity); + getPublicKeyFromIdentity(accountIdentity, input.account); + input.role = role; + + const char* roleName = (role == 1) ? "Oracle" : "Pauser"; + std::cout << "Removing " << roleName << " role from " << accountIdentity << "..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_REMOVE_ROLE, 0, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbPause(const char* nodeIp, int nodePort, const char* seed, + uint32_t scheduledTickOffset) +{ + QSB_Pause_input input; + memset(&input, 0, sizeof(input)); + + std::cout << "Pausing QSB contract..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_PAUSE, 0, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbUnpause(const char* nodeIp, int nodePort, const char* seed, + uint32_t scheduledTickOffset) +{ + QSB_Pause_input input; // Unpause uses same input structure + memset(&input, 0, sizeof(input)); + + std::cout << "Unpausing QSB contract..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_UNPAUSE, 0, + sizeof(input), &input, scheduledTickOffset); +} + +void qsbEditFeeParameters(const char* nodeIp, int nodePort, const char* seed, + const char* protocolFeeRecipientIdentity, const char* oracleFeeRecipientIdentity, + uint32_t bpsFee, uint32_t protocolFee, uint32_t scheduledTickOffset) +{ + QSB_EditFeeParameters_input input; + memset(&input, 0, sizeof(input)); + + input.bpsFee = bpsFee; + input.protocolFee = protocolFee; + + // Set protocol fee recipient if provided + if (protocolFeeRecipientIdentity && strlen(protocolFeeRecipientIdentity) > 0) + { + sanityCheckIdentity(protocolFeeRecipientIdentity); + getPublicKeyFromIdentity(protocolFeeRecipientIdentity, input.protocolFeeRecipient); + } + + // Set oracle fee recipient if provided + if (oracleFeeRecipientIdentity && strlen(oracleFeeRecipientIdentity) > 0) + { + sanityCheckIdentity(oracleFeeRecipientIdentity); + getPublicKeyFromIdentity(oracleFeeRecipientIdentity, input.oracleFeeRecipient); + } + + std::cout << "Editing fee parameters (BPS fee: " << bpsFee + << ", Protocol fee: " << protocolFee << "%)..." << std::endl; + + makeContractTransaction(nodeIp, nodePort, seed, + QSB_CONTRACT_INDEX, QSB_PROC_EDIT_FEE_PARAMETERS, 0, + sizeof(input), &input, scheduledTickOffset); +} + +// --------------------------------------------------------------------------- +// View / helper functions +// --------------------------------------------------------------------------- + +// Function numbers for view helpers (see contract REGISTER_USER_FUNCTION) +static constexpr uint16_t QSB_FUNC_GET_CONFIG = 1; +static constexpr uint16_t QSB_FUNC_IS_ORACLE = 2; +static constexpr uint16_t QSB_FUNC_IS_PAUSER = 3; +static constexpr uint16_t QSB_FUNC_GET_LOCKED = 4; +static constexpr uint16_t QSB_FUNC_IS_ORDER_FILLED = 5; + +void qsbGetConfig(const char* nodeIp, int nodePort) +{ + QSB_GetConfig_output result; + if (!runContractFunction(nodeIp, nodePort, QSB_CONTRACT_INDEX, QSB_FUNC_GET_CONFIG, + nullptr, 0, &result, sizeof(result))) + { + LOG("Failed to receive QSB config.\n"); + return; + } + + char adminId[128] = {0}; + char protoId[128] = {0}; + char oracleId[128] = {0}; + + getIdentityFromPublicKey(result.admin, adminId, false); + getIdentityFromPublicKey(result.protocolFeeRecipient, protoId, false); + getIdentityFromPublicKey(result.oracleFeeRecipient, oracleId, false); + + std::cout << "QSB Configuration\n"; + std::cout << "-----------------\n"; + std::cout << "Admin: " << adminId << "\n"; + std::cout << "Protocol fee recipient: " << protoId << "\n"; + std::cout << "Oracle fee recipient: " << oracleId << "\n"; + std::cout << "bpsFee: " << result.bpsFee << "\n"; + std::cout << "protocolFee: " << result.protocolFee << "\n"; + std::cout << "oracleCount: " << result.oracleCount << "\n"; + std::cout << "oracleThreshold: " << (int)result.oracleThreshold << "%\n"; + std::cout << "paused: " << (result.paused ? "yes" : "no") << "\n"; +} + +void qsbIsOracle(const char* nodeIp, int nodePort, const char* accountIdentity) +{ + QSB_IsRole_input input{}; + getPublicKeyFromIdentity(accountIdentity, input.account); + + QSB_IsOracle_output result; + if (!runContractFunction(nodeIp, nodePort, QSB_CONTRACT_INDEX, QSB_FUNC_IS_ORACLE, + &input, sizeof(input), &result, sizeof(result))) + { + LOG("Failed to receive QSB IsOracle result.\n"); + return; + } + + std::cout << (result.isOracle ? "true" : "false") << "\n"; +} + +void qsbIsPauser(const char* nodeIp, int nodePort, const char* accountIdentity) +{ + QSB_IsRole_input input{}; + getPublicKeyFromIdentity(accountIdentity, input.account); + + QSB_IsPauser_output result; + if (!runContractFunction(nodeIp, nodePort, QSB_CONTRACT_INDEX, QSB_FUNC_IS_PAUSER, + &input, sizeof(input), &result, sizeof(result))) + { + LOG("Failed to receive QSB IsPauser result.\n"); + return; + } + + std::cout << (result.isPauser ? "true" : "false") << "\n"; +} + +void qsbGetLockedOrder(const char* nodeIp, int nodePort, uint32_t nonce) +{ + QSB_GetLockedOrder_input input{}; + input.nonce = nonce; + + QSB_GetLockedOrder_output result; + if (!runContractFunction(nodeIp, nodePort, QSB_CONTRACT_INDEX, QSB_FUNC_GET_LOCKED, + &input, sizeof(input), &result, sizeof(result))) + { + LOG("Failed to receive QSB locked order.\n"); + return; + } + + if (!result.exists) + { + std::cout << "No locked order found for nonce " << nonce << ".\n"; + return; + } + + char senderId[128] = {0}; + getIdentityFromPublicKey(result.order.sender, senderId, false); + + std::cout << "Locked order for nonce " << nonce << ":\n"; + std::cout << " sender: " << senderId << "\n"; + std::cout << " amount: " << result.order.amount << "\n"; + std::cout << " relayerFee: " << result.order.relayerFee << "\n"; + std::cout << " networkOut: " << result.order.networkOut << "\n"; + std::cout << " active: " << (result.order.active ? "yes" : "no") << "\n"; + + std::cout << " orderHash: "; + printOrderHash(result.order.orderHash); +} + +void qsbIsOrderFilled(const char* nodeIp, int nodePort, const char* orderHashHex) +{ + if (!orderHashHex) + { + LOG("order hash must be provided.\n"); + return; + } + + QSB_IsOrderFilled_input input{}; + if (!hexStringToBytes(orderHashHex, input.hash.hash, 32)) + { + std::cout << "ERROR: Invalid order hash hex string. Must be 64 hex characters (32 bytes)." << std::endl; + return; + } + + QSB_IsOrderFilled_output result; + if (!runContractFunction(nodeIp, nodePort, QSB_CONTRACT_INDEX, QSB_FUNC_IS_ORDER_FILLED, + &input, sizeof(input), &result, sizeof(result))) + { + LOG("Failed to receive QSB IsOrderFilled result.\n"); + return; + } + + std::cout << (result.filled ? "true" : "false") << "\n"; +} diff --git a/qsb.h b/qsb.h new file mode 100644 index 0000000..1a56719 --- /dev/null +++ b/qsb.h @@ -0,0 +1,313 @@ +#pragma once + +#include "structs.h" +#include +#include + +// QSB Contract Index - needs to be set when contract is deployed +// For now, using a placeholder that can be configured +#define QSB_CONTRACT_INDEX 19 // Update this when contract is deployed + +// Procedure numbers (matching the contract) +#define QSB_PROC_LOCK 1 +#define QSB_PROC_OVERRIDE_LOCK 2 +#define QSB_PROC_UNLOCK 3 +#define QSB_PROC_TRANSFER_ADMIN 10 +#define QSB_PROC_EDIT_ORACLE_THRESHOLD 11 +#define QSB_PROC_ADD_ROLE 12 +#define QSB_PROC_REMOVE_ROLE 13 +#define QSB_PROC_PAUSE 14 +#define QSB_PROC_UNPAUSE 15 +#define QSB_PROC_EDIT_FEE_PARAMETERS 16 + +// Structs matching the contract I/O structures +struct QSB_Order +{ + uint8_t fromAddress[32]; // sender on source chain (mirrored id) + uint8_t toAddress[32]; // Qubic recipient when minting/unlocking + uint64_t tokenIn; // identifier for incoming asset + uint64_t tokenOut; // identifier for outgoing asset + uint64_t amount; // total bridged amount (Qubic units) + uint64_t relayerFee; // fee paid to relayer (subset of amount) + uint32_t destinationChainId; // e.g. Solana chain-id as used off-chain + uint32_t networkIn; // incoming network id + uint32_t networkOut; // outgoing network id + uint32_t nonce; // unique order nonce +}; + +struct QSB_OrderHash +{ + uint8_t hash[32]; // K12 digest +}; + +struct QSB_SignatureData +{ + uint8_t signer[32]; // oracle id (public key) + int8_t signature[64]; // raw 64-byte signature +}; + +// Lock input/output +struct QSB_Lock_input +{ + uint64_t amount; + uint64_t relayerFee; + uint8_t toAddress[64]; // Recipient on Solana (fixed-size buffer, zero-padded) + uint32_t networkOut; + uint32_t nonce; +}; + +struct QSB_Lock_output +{ + QSB_OrderHash orderHash; + uint8_t success; + uint8_t _padding[7]; +}; + +// OverrideLock input/output +struct QSB_OverrideLock_input +{ + uint8_t toAddress[64]; + uint64_t relayerFee; + uint32_t nonce; +}; + +struct QSB_OverrideLock_output +{ + QSB_OrderHash orderHash; + uint8_t success; + uint8_t _padding[7]; +}; + +// Unlock input/output +struct QSB_Unlock_input +{ + QSB_Order order; + uint32_t numSignatures; + QSB_SignatureData signatures[64]; // QSB_MAX_ORACLES +}; + +struct QSB_Unlock_output +{ + QSB_OrderHash orderHash; + uint8_t success; + uint8_t _padding[7]; +}; + +// TransferAdmin input/output +struct QSB_TransferAdmin_input +{ + uint8_t newAdmin[32]; +}; + +struct QSB_TransferAdmin_output +{ + uint8_t success; + uint8_t _padding[7]; +}; + +// EditOracleThreshold input/output +struct QSB_EditOracleThreshold_input +{ + uint8_t newThreshold; + uint8_t _padding[7]; +}; + +struct QSB_EditOracleThreshold_output +{ + uint8_t oldThreshold; + uint8_t success; + uint8_t _padding[6]; +}; + +// AddRole input/output +struct QSB_AddRole_input +{ + uint8_t account[32]; + uint8_t role; // 1 = Oracle, 2 = Pauser + uint8_t _padding[7]; +}; + +struct QSB_AddRole_output +{ + uint8_t success; + uint8_t _padding[7]; +}; + +// RemoveRole input/output +struct QSB_RemoveRole_input +{ + uint8_t account[32]; + uint8_t role; // 1 = Oracle, 2 = Pauser + uint8_t _padding[7]; +}; + +struct QSB_RemoveRole_output +{ + uint8_t success; + uint8_t _padding[7]; +}; + +// Pause/Unpause input/output +struct QSB_Pause_input +{ + uint8_t _padding[8]; +}; + +struct QSB_Pause_output +{ + uint8_t success; + uint8_t _padding[7]; +}; + +// EditFeeParameters input/output +struct QSB_EditFeeParameters_input +{ + uint8_t protocolFeeRecipient[32]; // updated when not zero-id + uint8_t oracleFeeRecipient[32]; // updated when not zero-id + uint32_t bpsFee; // basis points fee (0..10000) + uint32_t protocolFee; // share of BPS fee for protocol (0..100) +}; + +struct QSB_EditFeeParameters_output +{ + uint8_t success; + uint8_t _padding[7]; +}; + +// --------------------------------------------------------------------------- +// View function structs +// --------------------------------------------------------------------------- + +// GetConfig() +struct QSB_GetConfig_output +{ + uint8_t admin[32]; + uint8_t protocolFeeRecipient[32]; + uint8_t oracleFeeRecipient[32]; + uint32_t bpsFee; + uint32_t protocolFee; + uint32_t oracleCount; + uint8_t oracleThreshold; + uint8_t paused; + + static constexpr unsigned char type() + { + return RespondContractFunction::type(); + } +}; + +// IsOracle() / IsPauser() +struct QSB_IsRole_input +{ + uint8_t account[32]; +}; + +struct QSB_IsOracle_output +{ + uint8_t isOracle; + + static constexpr unsigned char type() + { + return RespondContractFunction::type(); + } +}; + +struct QSB_IsPauser_output +{ + uint8_t isPauser; + + static constexpr unsigned char type() + { + return RespondContractFunction::type(); + } +}; + +// LockedOrderEntry used by GetLockedOrder() +struct QSB_LockedOrderEntry +{ + uint8_t sender[32]; + uint64_t amount; + uint64_t relayerFee; + uint32_t networkOut; + uint32_t nonce; + uint8_t toAddress[64]; + QSB_OrderHash orderHash; + uint8_t active; + uint8_t _padding[7]; +}; + +struct QSB_GetLockedOrder_input +{ + uint32_t nonce; +}; + +struct QSB_GetLockedOrder_output +{ + uint8_t exists; + uint8_t _padding[7]; + QSB_LockedOrderEntry order; + + static constexpr unsigned char type() + { + return RespondContractFunction::type(); + } +}; + +// IsOrderFilled() +struct QSB_IsOrderFilled_input +{ + QSB_OrderHash hash; +}; + +struct QSB_IsOrderFilled_output +{ + uint8_t filled; + uint8_t _padding[7]; + + static constexpr unsigned char type() + { + return RespondContractFunction::type(); + } +}; + +// Function declarations +void qsbLock(const char* nodeIp, int nodePort, const char* seed, + uint64_t amount, uint64_t relayerFee, const char* toAddressHex, + uint32_t networkOut, uint32_t nonce, uint32_t scheduledTickOffset); + +void qsbOverrideLock(const char* nodeIp, int nodePort, const char* seed, + uint32_t nonce, const char* toAddressHex, uint64_t relayerFee, + uint32_t scheduledTickOffset); + +void qsbUnlock(const char* nodeIp, int nodePort, const char* seed, + const QSB_Order* order, uint32_t numSignatures, + const QSB_SignatureData* signatures, uint32_t scheduledTickOffset); + +void qsbTransferAdmin(const char* nodeIp, int nodePort, const char* seed, + const char* newAdminIdentity, uint32_t scheduledTickOffset); + +void qsbEditOracleThreshold(const char* nodeIp, int nodePort, const char* seed, + uint8_t newThreshold, uint32_t scheduledTickOffset); + +void qsbAddRole(const char* nodeIp, int nodePort, const char* seed, + const char* accountIdentity, uint8_t role, uint32_t scheduledTickOffset); + +void qsbRemoveRole(const char* nodeIp, int nodePort, const char* seed, + const char* accountIdentity, uint8_t role, uint32_t scheduledTickOffset); + +void qsbPause(const char* nodeIp, int nodePort, const char* seed, + uint32_t scheduledTickOffset); + +void qsbUnpause(const char* nodeIp, int nodePort, const char* seed, + uint32_t scheduledTickOffset); + +void qsbEditFeeParameters(const char* nodeIp, int nodePort, const char* seed, + const char* protocolFeeRecipientIdentity, const char* oracleFeeRecipientIdentity, + uint32_t bpsFee, uint32_t protocolFee, uint32_t scheduledTickOffset); + +// View / helper functions +void qsbGetConfig(const char* nodeIp, int nodePort); +void qsbIsOracle(const char* nodeIp, int nodePort, const char* accountIdentity); +void qsbIsPauser(const char* nodeIp, int nodePort, const char* accountIdentity); +void qsbGetLockedOrder(const char* nodeIp, int nodePort, uint32_t nonce); +void qsbIsOrderFilled(const char* nodeIp, int nodePort, const char* orderHashHex); diff --git a/structs.h b/structs.h index 6acbea7..f68adfc 100644 --- a/structs.h +++ b/structs.h @@ -206,6 +206,21 @@ enum COMMAND SHAREHOLDER_GET_VOTING_RESULTS, SET_EXECUTION_FEE_MULTIPLIER, GET_EXECUTION_FEE_MULTIPLIER, + QSB_LOCK_CMD, + QSB_OVERRIDE_LOCK_CMD, + QSB_UNLOCK_CMD, + QSB_TRANSFER_ADMIN_CMD, + QSB_EDIT_ORACLE_THRESHOLD_CMD, + QSB_ADD_ROLE_CMD, + QSB_REMOVE_ROLE_CMD, + QSB_PAUSE_CMD, + QSB_UNPAUSE_CMD, + QSB_EDIT_FEE_PARAMETERS_CMD, + QSB_GET_CONFIG_CMD, + QSB_IS_ORACLE_CMD, + QSB_IS_PAUSER_CMD, + QSB_GET_LOCKED_ORDER_CMD, + QSB_IS_ORDER_FILLED_CMD, TOTAL_COMMAND // DO NOT CHANGE THIS };