From cdee078347f2a38562d7de38a2fd476b8353d90f Mon Sep 17 00:00:00 2001 From: sallymoc Date: Wed, 4 Mar 2026 22:42:00 +0100 Subject: [PATCH 1/3] feat: add protocol.json with transaction input types and fix environment switcher --- index.html | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 766ec90..fd112fb 100644 --- a/index.html +++ b/index.html @@ -618,16 +618,26 @@

Locales

// Update base URL displays document.querySelectorAll('.base-url').forEach(el => { - // Extract just the path after v1/ - const path = el.textContent.replace(/https:\/\/static\.qubic\.org\/(staging\/|dev\/)?v1\//, '').replace(/^v1\//, ''); - el.textContent = baseUrl + path; + const path = el.getAttribute('data-path'); + if (path) el.textContent = baseUrl + path; }); // Update all endpoint links document.querySelectorAll('.endpoints a').forEach(link => { - // Extract just the path after v1/ - const path = link.getAttribute('href').replace(/https:\/\/static\.qubic\.org\/(staging\/|dev\/)?v1\//, '').replace(/^v1\//, ''); - link.setAttribute('href', baseUrl + path); + const path = link.getAttribute('data-path'); + if (path) link.setAttribute('href', baseUrl + path); + }); + } + + // Store relative paths in data-path attributes on page load + function initPaths() { + document.querySelectorAll('.base-url').forEach(el => { + const path = el.textContent.replace(/https:\/\/static\.qubic\.org\/(staging\/|dev\/)?v1\//, '').replace(/^v1\//, ''); + el.setAttribute('data-path', path); + }); + document.querySelectorAll('.endpoints a').forEach(link => { + const path = link.getAttribute('href').replace(/^v1\//, ''); + link.setAttribute('data-path', path); }); } @@ -672,6 +682,7 @@

Locales

} // Initialize on page load + initPaths(); updateLinks(); addCopyButtons(); fetchVersion(); From 9c09462b4c713cb7543413a4041b297de8f497b7 Mon Sep 17 00:00:00 2001 From: sallymoc Date: Fri, 13 Mar 2026 01:45:14 +0100 Subject: [PATCH 2/3] fix: update script no longer overwrites fees and handles contract rewrites - Preserve manually set fees and procedure names across script runs - Detect rewritten contracts (e.g. QVault) and update procedures accordingly - Fix fee extraction for state.mut()/state.get() patterns --- data/smart_contracts.json | 447 ++++++++++++++++++++---------- scripts/update_smart_contracts.py | 229 ++++++++------- 2 files changed, 425 insertions(+), 251 deletions(-) diff --git a/data/smart_contracts.json b/data/smart_contracts.json index 68606f5..987afb6 100644 --- a/data/smart_contracts.json +++ b/data/smart_contracts.json @@ -10,33 +10,40 @@ "procedures": [ { "id": 1, - "name": "Issue Asset" + "name": "Issue Asset", + "sourceIdentifier": "IssueAsset" }, { "id": 2, "name": "Transfer Share Ownership and Possession", - "fee": 100 + "fee": 100, + "sourceIdentifier": "TransferShareOwnershipAndPossession" }, { "id": 5, - "name": "Add to Ask Order" + "name": "Add to Ask Order", + "sourceIdentifier": "AddToAskOrder" }, { "id": 6, - "name": "Add to Bid Order" + "name": "Add to Bid Order", + "sourceIdentifier": "AddToBidOrder" }, { "id": 7, - "name": "Remove from Ask Order" + "name": "Remove from Ask Order", + "sourceIdentifier": "RemoveFromAskOrder" }, { "id": 8, - "name": "Remove from Bid Order" + "name": "Remove from Bid Order", + "sourceIdentifier": "RemoveFromBidOrder" }, { "id": 9, "name": "Transfer Share Management Rights", - "fee": 0 + "fee": 0, + "sourceIdentifier": "TransferShareManagementRights" } ], "allowTransferShares": true, @@ -53,19 +60,23 @@ "procedures": [ { "id": 1, - "name": "Issue Bet" + "name": "Issue Bet", + "sourceIdentifier": "issueBet" }, { "id": 2, - "name": "Join Bet" + "name": "Join Bet", + "sourceIdentifier": "joinBet" }, { "id": 3, - "name": "Cancel Bet" + "name": "Cancel Bet", + "sourceIdentifier": "cancelBet" }, { "id": 4, - "name": "Publish Result" + "name": "Publish Result", + "sourceIdentifier": "publishResult" } ], "website": "https://quottery.org/", @@ -83,7 +94,8 @@ "procedures": [ { "id": 1, - "name": "Reveal and Commit" + "name": "Reveal and Commit", + "sourceIdentifier": "RevealAndCommit" } ], "allowTransferShares": false, @@ -100,35 +112,58 @@ "procedures": [ { "id": 1, - "name": "Send to Many v1" + "name": "Send to Many v1", + "sourceIdentifier": "SendToManyV1" }, { "id": 2, - "name": "Burn Qubic" + "name": "Burn Qubic", + "sourceIdentifier": "BurnQubic" }, { "id": 3, - "name": "Send to Many Benchmark" + "name": "Send to Many Benchmark", + "sourceIdentifier": "SendToManyBenchmark" }, { "id": 4, - "name": "Create Poll" + "name": "Create Poll", + "sourceIdentifier": "CreatePoll" }, { "id": 5, - "name": "Vote" + "name": "Vote", + "sourceIdentifier": "Vote" }, { "id": 6, - "name": "Cancel Poll" + "name": "Cancel Poll", + "sourceIdentifier": "CancelPoll" }, { "id": 7, - "name": "Distribute Qu to Shareholders" + "name": "Distribute Qu to Shareholders", + "sourceIdentifier": "DistributeQuToShareholders" }, { "id": 8, - "name": "Burn Qubic for Contract" + "name": "Burn Qubic for Contract", + "sourceIdentifier": "BurnQubicForContract" + }, + { + "id": 100, + "name": "Query Price Oracle", + "sourceIdentifier": "QueryPriceOracle" + }, + { + "id": 101, + "name": "Subscribe Price Oracle", + "sourceIdentifier": "SubscribePriceOracle" + }, + { + "id": 102, + "name": "Unsubscribe Oracle", + "sourceIdentifier": "UnsubscribeOracle" } ], "allowTransferShares": false, @@ -157,11 +192,13 @@ "procedures": [ { "id": 1, - "name": "Set Proposal" + "name": "Set Proposal", + "sourceIdentifier": "SetProposal" }, { "id": 2, - "name": "Vote" + "name": "Vote", + "sourceIdentifier": "Vote" } ], "allowTransferShares": false, @@ -191,11 +228,13 @@ "procedures": [ { "id": 1, - "name": "Set Proposal" + "name": "Set Proposal", + "sourceIdentifier": "SetProposal" }, { "id": 2, - "name": "Vote" + "name": "Vote", + "sourceIdentifier": "Vote" } ], "allowTransferShares": false, @@ -213,11 +252,13 @@ "procedures": [ { "id": 1, - "name": "Lock" + "name": "Lock", + "sourceIdentifier": "lock" }, { "id": 2, - "name": "Unlock" + "name": "Unlock", + "sourceIdentifier": "unlock" } ], "website": "https://qearn.org", @@ -236,54 +277,67 @@ "procedures": [ { "id": 1, - "name": "Submit Auth Address" + "name": "Stake", + "sourceIdentifier": "stake" }, { "id": 2, - "name": "Change Auth Address" + "name": "Unstake", + "sourceIdentifier": "unStake" }, { "id": 3, - "name": "Submit Distribution Permille" + "name": "Submit GP", + "sourceIdentifier": "submitGP" }, { "id": 4, - "name": "Change Distribution Permille" + "name": "Submit QCP", + "sourceIdentifier": "submitQCP" }, { "id": 5, - "name": "Submit Reinvesting Address" + "name": "Submit IPOP", + "sourceIdentifier": "submitIPOP" }, { "id": 6, - "name": "Change Reinvesting Address" + "name": "Submit QEarn P", + "sourceIdentifier": "submitQEarnP" }, { "id": 7, - "name": "Submit Admin Address" + "name": "Submit Fund P", + "sourceIdentifier": "submitFundP" }, { "id": 8, - "name": "Change Admin Address" + "name": "Submit MKTP", + "sourceIdentifier": "submitMKTP" }, { "id": 9, - "name": "Submit Banned Address" - }, - { - "id": 10, - "name": "Save Banned Address" + "name": "Submit Allo P", + "sourceIdentifier": "submitAlloP" }, { "id": 11, - "name": "Submit Unbanned Address" + "name": "Vote in Proposal", + "sourceIdentifier": "voteInProposal" }, { "id": 12, - "name": "Unblock Banned Address" + "name": "Buy Qcap", + "sourceIdentifier": "buyQcap" + }, + { + "id": 13, + "name": "Transfer Share Management Rights", + "sourceIdentifier": "TransferShareManagementRights", + "fee": 100 } ], - "allowTransferShares": false, + "allowTransferShares": true, "firstUseEpoch": 138, "sharesAuctionEpoch": 137 }, @@ -298,40 +352,49 @@ "procedures": [ { "id": 1, - "name": "Register Vault" + "name": "Register Vault", + "sourceIdentifier": "registerVault" }, { "id": 2, - "name": "Deposit" + "name": "Deposit", + "sourceIdentifier": "deposit" }, { "id": 3, - "name": "Release To" + "name": "Release To", + "sourceIdentifier": "releaseTo" }, { "id": 4, - "name": "Reset Release" + "name": "Reset Release", + "sourceIdentifier": "resetRelease" }, { "id": 13, - "name": "Vote Fee Change" + "name": "Vote Fee Change", + "sourceIdentifier": "voteFeeChange" }, { "id": 19, - "name": "Deposit Asset" + "name": "Deposit Asset", + "sourceIdentifier": "depositAsset" }, { "id": 20, - "name": "Release Asset To" + "name": "Release Asset To", + "sourceIdentifier": "releaseAssetTo" }, { "id": 21, - "name": "Reset Asset Release" + "name": "Reset Asset Release", + "sourceIdentifier": "resetAssetRelease" }, { "id": 25, "name": "Revoke Asset Management Rights", - "fee": 100 + "fee": 100, + "sourceIdentifier": "revokeAssetManagementRights" } ], "allowTransferShares": true, @@ -349,72 +412,89 @@ "procedures": [ { "id": 1, - "name": "Setting CFB and Qubic Price" + "name": "Setting CFB and Qubic Price", + "sourceIdentifier": "settingCFBAndQubicPrice" }, { "id": 2, - "name": "Create Collection" + "name": "Create Collection", + "sourceIdentifier": "createCollection" }, { "id": 3, - "name": "Mint" + "name": "Mint", + "sourceIdentifier": "mint" }, { "id": 4, - "name": "Mint of Drop" + "name": "Mint of Drop", + "sourceIdentifier": "mintOfDrop" }, { "id": 5, - "name": "Transfer" + "name": "Transfer", + "sourceIdentifier": "transfer" }, { "id": 6, - "name": "List in Market" + "name": "List in Market", + "sourceIdentifier": "listInMarket" }, { "id": 7, - "name": "Buy" + "name": "Buy", + "sourceIdentifier": "buy" }, { "id": 8, - "name": "Cancel Sale" + "name": "Cancel Sale", + "sourceIdentifier": "cancelSale" }, { "id": 9, - "name": "List in Exchange" + "name": "List in Exchange", + "sourceIdentifier": "listInExchange" }, { "id": 10, - "name": "Cancel Exchange" + "name": "Cancel Exchange", + "sourceIdentifier": "cancelExchange" }, { "id": 11, - "name": "Make Offer" + "name": "Make Offer", + "sourceIdentifier": "makeOffer" }, { "id": 12, - "name": "Accept Offer" + "name": "Accept Offer", + "sourceIdentifier": "acceptOffer" }, { "id": 13, - "name": "Cancel Offer" + "name": "Cancel Offer", + "sourceIdentifier": "cancelOffer" }, { "id": 14, - "name": "Create Traditional Auction" + "name": "Create Traditional Auction", + "sourceIdentifier": "createTraditionalAuction" }, { "id": 15, - "name": "Bid on Traditional Auction" + "name": "Bid on Traditional Auction", + "sourceIdentifier": "bidOnTraditionalAuction" }, { "id": 16, "name": "Transfer Share Management Rights", - "fee": 100 + "fee": 100, + "sourceIdentifier": "TransferShareManagementRights" }, { "id": 17, - "name": "Change Status of Market Place" + "name": "Change Status of Market Place", + "sourceIdentifier": "changeStatusOfMarketPlace" } ], "allowTransferShares": true, @@ -432,48 +512,59 @@ "procedures": [ { "id": 1, - "name": "Issue Asset" + "name": "Issue Asset", + "sourceIdentifier": "IssueAsset" }, { "id": 2, - "name": "Transfer Share Ownership and Possession" + "name": "Transfer Share Ownership and Possession", + "sourceIdentifier": "TransferShareOwnershipAndPossession" }, { "id": 3, - "name": "Create Pool" + "name": "Create Pool", + "sourceIdentifier": "CreatePool" }, { "id": 4, - "name": "Add Liquidity" + "name": "Add Liquidity", + "sourceIdentifier": "AddLiquidity" }, { "id": 5, - "name": "Remove Liquidity" + "name": "Remove Liquidity", + "sourceIdentifier": "RemoveLiquidity" }, { "id": 6, - "name": "Swap Exact Qu for Asset" + "name": "Swap Exact Qu for Asset", + "sourceIdentifier": "SwapExactQuForAsset" }, { "id": 7, - "name": "Swap Qu for Exact Asset" + "name": "Swap Qu for Exact Asset", + "sourceIdentifier": "SwapQuForExactAsset" }, { "id": 8, - "name": "Swap Exact Asset for Qu" + "name": "Swap Exact Asset for Qu", + "sourceIdentifier": "SwapExactAssetForQu" }, { "id": 9, - "name": "Swap Asset for Exact Qu" + "name": "Swap Asset for Exact Qu", + "sourceIdentifier": "SwapAssetForExactQu" }, { "id": 10, - "name": "Set Team Info" + "name": "Set Invest Rewards Info", + "sourceIdentifier": "SetInvestRewardsInfo" }, { "id": 11, "name": "Transfer Share Management Rights", - "fee": 100 + "fee": 100, + "sourceIdentifier": "TransferShareManagementRights" } ], "allowTransferShares": true, @@ -491,40 +582,49 @@ "procedures": [ { "id": 1, - "name": "Register in Tier" + "name": "Register in Tier", + "sourceIdentifier": "registerInTier" }, { "id": 2, - "name": "Logout from Tier" + "name": "Logout from Tier", + "sourceIdentifier": "logoutFromTier" }, { "id": 3, - "name": "Create Project" + "name": "Create Project", + "sourceIdentifier": "createProject" }, { "id": 4, - "name": "Vote in Project" + "name": "Vote in Project", + "sourceIdentifier": "voteInProject" }, { "id": 5, - "name": "Create Fundraising" + "name": "Create Fundraising", + "sourceIdentifier": "createFundraising" }, { "id": 6, - "name": "Invest in Project" + "name": "Invest in Project", + "sourceIdentifier": "investInProject" }, { "id": 7, - "name": "Claim Token" + "name": "Claim Token", + "sourceIdentifier": "claimToken" }, { "id": 8, - "name": "Upgrade Tier" + "name": "Upgrade Tier", + "sourceIdentifier": "upgradeTier" }, { "id": 9, "name": "Transfer Share Management Rights", - "fee": 100 + "fee": 100, + "sourceIdentifier": "TransferShareManagementRights" } ], "allowTransferShares": true, @@ -542,7 +642,8 @@ "procedures": [ { "id": 1, - "name": "Buy Ticket" + "name": "Buy Ticket", + "sourceIdentifier": "buyTicket" } ], "allowTransferShares": false, @@ -560,15 +661,18 @@ "procedures": [ { "id": 1, - "name": "Buy Ticket" + "name": "Buy Ticket", + "sourceIdentifier": "BuyTicket" }, { "id": 2, - "name": "Set Price" + "name": "Set Price", + "sourceIdentifier": "SetPrice" }, { "id": 3, - "name": "Set Schedule" + "name": "Set Schedule", + "sourceIdentifier": "SetSchedule" } ], "allowTransferShares": false, @@ -586,35 +690,43 @@ "procedures": [ { "id": 1, - "name": "Stake" + "name": "Stake", + "sourceIdentifier": "Stake" }, { "id": 2, - "name": "Transfer MBond Ownership and Possession" + "name": "Transfer MBond Ownership and Possession", + "sourceIdentifier": "TransferMBondOwnershipAndPossession" }, { "id": 3, - "name": "Add Ask Order" + "name": "Add Ask Order", + "sourceIdentifier": "AddAskOrder" }, { "id": 4, - "name": "Remove Ask Order" + "name": "Remove Ask Order", + "sourceIdentifier": "RemoveAskOrder" }, { "id": 5, - "name": "Add Bid Order" + "name": "Add Bid Order", + "sourceIdentifier": "AddBidOrder" }, { "id": 6, - "name": "Remove Bid Order" + "name": "Remove Bid Order", + "sourceIdentifier": "RemoveBidOrder" }, { "id": 7, - "name": "Burn QU" + "name": "Burn QU", + "sourceIdentifier": "BurnQU" }, { "id": 8, - "name": "Update CFA" + "name": "Update CFA", + "sourceIdentifier": "UpdateCFA" } ], "allowTransferShares": true, @@ -632,16 +744,19 @@ "procedures": [ { "id": 1, - "name": "Create ICO" + "name": "Create ICO", + "sourceIdentifier": "createICO" }, { "id": 2, - "name": "Buy Token" + "name": "Buy Token", + "sourceIdentifier": "buyToken" }, { "id": 3, "name": "Transfer Share Management Rights", - "fee": 100 + "fee": 100, + "sourceIdentifier": "TransferShareManagementRights" } ], "allowTransferShares": true, @@ -658,36 +773,44 @@ "procedures": [ { "id": 1, - "name": "Register in System" + "name": "Register in System", + "sourceIdentifier": "registerInSystem" }, { "id": 2, - "name": "Logout in System" + "name": "Logout in System", + "sourceIdentifier": "logoutInSystem" }, { "id": 3, - "name": "Submit Entry Amount" + "name": "Submit Entry Amount", + "sourceIdentifier": "submitEntryAmount" }, { "id": 4, - "name": "Submit Proposal" + "name": "Submit Proposal", + "sourceIdentifier": "submitProposal" }, { "id": 5, - "name": "Vote in Proposal" + "name": "Vote in Proposal", + "sourceIdentifier": "voteInProposal" }, { "id": 6, - "name": "Deposit in Qu Raffle" + "name": "Deposit in Qu Raffle", + "sourceIdentifier": "depositInQuRaffle" }, { "id": 7, - "name": "Deposit in Token Raffle" + "name": "Deposit in Token Raffle", + "sourceIdentifier": "depositInTokenRaffle" }, { "id": 8, "name": "Transfer Share Management Rights", - "fee": 100 + "fee": 100, + "sourceIdentifier": "TransferShareManagementRights" } ], "allowTransferShares": true, @@ -704,28 +827,34 @@ "procedures": [ { "id": 3, - "name": "Donate to Treasury" + "name": "Donate to Treasury", + "sourceIdentifier": "DonateToTreasury" }, { "id": 4, - "name": "Vote Gov Params" + "name": "Vote Gov Params", + "sourceIdentifier": "VoteGovParams" }, { "id": 5, - "name": "Create Asset Release Poll" + "name": "Create Asset Release Poll", + "sourceIdentifier": "CreateAssetReleasePoll" }, { "id": 6, - "name": "Vote Asset Release" + "name": "Vote Asset Release", + "sourceIdentifier": "VoteAssetRelease" }, { "id": 7, - "name": "Deposit General Asset" + "name": "Deposit General Asset", + "sourceIdentifier": "DepositGeneralAsset" }, { "id": 8, "name": "Revoke Asset Management Rights", - "fee": 100 + "fee": 100, + "sourceIdentifier": "RevokeAssetManagementRights" } ], "allowTransferShares": true, @@ -742,19 +871,23 @@ "procedures": [ { "id": 1, - "name": "Withdraw Reserve" + "name": "Withdraw Reserve", + "sourceIdentifier": "WithdrawReserve" }, { "id": 2, - "name": "Add Allowed SC" + "name": "Add Allowed SC", + "sourceIdentifier": "AddAllowedSC" }, { "id": 3, - "name": "Remove Allowed SC" + "name": "Remove Allowed SC", + "sourceIdentifier": "RemoveAllowedSC" }, { "id": 4, - "name": "Send Reserve" + "name": "Send Reserve", + "sourceIdentifier": "SendReserve" } ], "allowTransferShares": false, @@ -771,27 +904,33 @@ "procedures": [ { "id": 1, - "name": "Buy Ticket" + "name": "Buy Ticket", + "sourceIdentifier": "BuyTicket" }, { "id": 2, - "name": "Set Price" + "name": "Set Price", + "sourceIdentifier": "SetPrice" }, { "id": 3, - "name": "Set Schedule" + "name": "Set Schedule", + "sourceIdentifier": "SetSchedule" }, { "id": 4, - "name": "Set Target Jackpot" + "name": "Set Target Jackpot", + "sourceIdentifier": "SetTargetJackpot" }, { "id": 5, - "name": "Set Draw Hour" + "name": "Set Draw Hour", + "sourceIdentifier": "SetDrawHour" }, { "id": 6, - "name": "Sync Jackpot" + "name": "Sync Jackpot", + "sourceIdentifier": "SyncJackpot" } ], "allowTransferShares": false, @@ -808,31 +947,38 @@ "procedures": [ { "id": 1, - "name": "Create Room" + "name": "Create Room", + "sourceIdentifier": "CreateRoom" }, { "id": 2, - "name": "Connect to Room" + "name": "Connect to Room", + "sourceIdentifier": "ConnectToRoom" }, { "id": 3, - "name": "Set Percent Fees" + "name": "Set Percent Fees", + "sourceIdentifier": "SetPercentFees" }, { "id": 4, - "name": "Set TTL Hours" + "name": "Set TTL Hours", + "sourceIdentifier": "SetTTLHours" }, { "id": 5, - "name": "Deposit" + "name": "Deposit", + "sourceIdentifier": "Deposit" }, { "id": 6, - "name": "Withdraw" + "name": "Withdraw", + "sourceIdentifier": "Withdraw" }, { "id": 7, - "name": "Close Room" + "name": "Close Room", + "sourceIdentifier": "CloseRoom" } ], "allowTransferShares": false, @@ -850,47 +996,58 @@ "procedures": [ { "id": 1, - "name": "Buy Ticket" + "name": "Buy Ticket", + "sourceIdentifier": "BuyTicket" }, { "id": 2, - "name": "Set Price" + "name": "Set Price", + "sourceIdentifier": "SetPrice" }, { "id": 3, - "name": "Set Schedule" + "name": "Set Schedule", + "sourceIdentifier": "SetSchedule" }, { "id": 4, - "name": "Set Draw Hour" + "name": "Set Draw Hour", + "sourceIdentifier": "SetDrawHour" }, { "id": 5, - "name": "Set Fees" + "name": "Set Fees", + "sourceIdentifier": "SetFees" }, { "id": 6, - "name": "Set Qheart Hold Limit" + "name": "Set QHeart Hold Limit", + "sourceIdentifier": "SetQHeartHoldLimit" }, { "id": 7, - "name": "Buy Random Tickets" + "name": "Buy Random Tickets", + "sourceIdentifier": "BuyRandomTickets" }, { "id": 8, - "name": "Deposit Auto Participation" + "name": "Deposit Auto Participation", + "sourceIdentifier": "DepositAutoParticipation" }, { "id": 9, - "name": "Withdraw Auto Participation" + "name": "Withdraw Auto Participation", + "sourceIdentifier": "WithdrawAutoParticipation" }, { "id": 10, - "name": "Set Auto Config" + "name": "Set Auto Config", + "sourceIdentifier": "SetAutoConfig" }, { "id": 11, - "name": "Set Auto Limits" + "name": "Set Auto Limits", + "sourceIdentifier": "SetAutoLimits" } ], "firstUseEpoch": 204, diff --git a/scripts/update_smart_contracts.py b/scripts/update_smart_contracts.py index cdcb67f..47eec6a 100644 --- a/scripts/update_smart_contracts.py +++ b/scripts/update_smart_contracts.py @@ -75,12 +75,8 @@ r"constexpr\s+\w+\s+(?P\w+)\s*=\s*(?P\d+)", ) -STATE_FEE_RE = re.compile( - r"state\s*\.\s*transferRightsFee\s*=\s*(?P\d+)", -) - INVOCATION_REWARD_CMP_RE = re.compile( - r"qpi\s*\.\s*invocationReward\s*\(\s*\)\s*<\s*(?P[\w.]+)", + r"qpi\s*\.\s*invocationReward\s*\(\s*\)\s*<\s*(?P(?:state\s*\.\s*(?:mut|get)\s*\(\s*\)\s*\.\s*)?\w+)", ) BLOCK_COMMENT_RE = re.compile(r"/\*.*?\*/", re.DOTALL) @@ -188,6 +184,27 @@ def fetch_current_epoch() -> Optional[int]: # ---------------------------- Parse contract_def.h -------------------------- +def detect_rewritten_contracts(raw_text: str) -> set: + """ + Detect contracts that have been rewritten by looking for *_old.h includes. + Returns a set of base filenames (e.g., {'QVAULT.h'}) whose procedures should be + fully replaced instead of merged. + """ + rewritten: set = set() + old_re = re.compile(r'#\s*include\s*["<](?:[^">]*/)?(\w+)_old\.h[">]', re.IGNORECASE) + for m in old_re.finditer(raw_text): + stem = m.group(1) + # Find the actual non-old filename (case may differ) + actual_re = re.compile( + r'#\s*include\s*["<](?:[^">]*/)?(' + re.escape(stem) + r'\.h)[">]', + re.IGNORECASE, + ) + for am in actual_re.finditer(raw_text): + fname = Path(am.group(1)).name + if "_old" not in fname.lower(): + rewritten.add(fname) + return rewritten + def parse_contract_def_from_raw(raw_text: str, known_contract_basenames: Optional[set] = None) -> Dict[str, int]: lines = raw_text.splitlines() mapping: Dict[str, int] = {} @@ -200,7 +217,7 @@ def parse_contract_def_from_raw(raw_text: str, known_contract_basenames: Optiona continue cidx: Optional[int] = None - for j in range(i - 1, max(i - 6, -1), -1): + for j in range(i - 1, max(i - 10, -1), -1): m = CONTRACT_INDEX_RE.search(lines[j]) if m: cidx = int(m.group("num")) @@ -289,7 +306,7 @@ def should_skip_filename(fname: str) -> bool: return True if fname in {"math_lib.h", "qpi.h"}: return True - if fname.endswith("_old.h"): + if fname.lower().endswith("_old.h"): return True return False @@ -344,30 +361,16 @@ def _extract_last_arg(text: str, call_pos: int) -> Optional[str]: args.append("".join(current).strip()) return args[-1] if args else None -def _resolve_state_transfer_fee(text_nc: str) -> Optional[int]: - """Find state.transferRightsFee assignment, preferring BEGIN_EPOCH over INITIALIZE.""" - for method in ["BEGIN_EPOCH", "INITIALIZE"]: - pos = text_nc.find(method) - if pos == -1: - continue - body = _find_brace_block(text_nc, pos) - if not body: - continue - m = STATE_FEE_RE.search(body) - if m: - return int(m.group("value")) - return None - def _resolve_state_var_fee(text_nc: str, var_name: str) -> Optional[int]: """ Resolve a state variable fee by finding its literal assignment in INITIALIZE() or BEGIN_EPOCH(). Prefers BEGIN_EPOCH over INITIALIZE. Returns None if the assignment comes from an inter-contract call (CALL_OTHER_CONTRACT_FUNCTION) or is otherwise not a literal. """ - # Build regex for: state.varName = - # e.g. state._transferFee = 100 + # Build regex for: state[.mut()].varName = + # e.g. state.mut()._transferFee = 100 or state._transferFee = 100 assign_re = re.compile( - r"state\s*\.\s*" + re.escape(var_name) + r"\s*=\s*(?P\d+)" + r"state\s*\.(?:\s*(?:mut|get)\s*\(\s*\)\s*\.)?\s*" + re.escape(var_name) + r"\s*=\s*(?P\d+)" ) for method in ["BEGIN_EPOCH", "INITIALIZE"]: @@ -389,6 +392,19 @@ def _resolve_state_var_fee(text_nc: str, var_name: str) -> Optional[int]: return None +def _resolve_fee_from_value(text_nc: str, value_str: str, constants: Dict[str, int]) -> Optional[int]: + """Resolve a fee value from a literal, constant name, or state variable reference.""" + cleaned = re.sub(r'[UuLl]+$', '', value_str) + if cleaned.isdigit(): + return int(cleaned) + if cleaned in constants: + return constants[cleaned] + if cleaned.startswith("state."): + var_name = re.sub(r'^state\.(?:(?:mut|get)\(\)\.)?', '', cleaned) + if var_name: + return _resolve_state_var_fee(text_nc, var_name) + return None + FEE_PROCEDURES = { "transfersharemanagementrights", "revokeassetmanagementrights", @@ -411,9 +427,6 @@ def extract_procedure_fees(text: str) -> Dict[str, int]: for m in CONSTEXPR_RE.finditer(text_nc): constants[m.group("name")] = int(m.group("value")) - # Resolve state.transferRightsFee if used (for releaseShares pattern) - state_fee = _resolve_state_transfer_fee(text_nc) - fees: Dict[str, int] = {} for m in PROCEDURE_DEF_RE.finditer(text_nc): proc_name = m.group("name") @@ -431,29 +444,17 @@ def extract_procedure_fees(text: str) -> Dict[str, int]: if release_pos != -1: last_arg = _extract_last_arg(body, release_pos) if last_arg is not None: - cleaned = re.sub(r'[UuLl]+$', '', last_arg) - if cleaned.isdigit(): - fees[proc_name] = int(cleaned) - elif cleaned in constants: - fees[proc_name] = constants[cleaned] - elif cleaned.startswith("state.") and "transferRightsFee" in cleaned and state_fee is not None: - fees[proc_name] = state_fee + resolved = _resolve_fee_from_value(text_nc, last_arg, constants) + if resolved is not None: + fees[proc_name] = resolved continue # Strategy 2: qpi.invocationReward() < X — X is the fee cmp_m = INVOCATION_REWARD_CMP_RE.search(body) if cmp_m: - value_str = cmp_m.group("value") - if value_str.isdigit(): - fees[proc_name] = int(value_str) - elif value_str in constants: - fees[proc_name] = constants[value_str] - elif value_str.startswith("state."): - # Resolve the state variable from INITIALIZE/BEGIN_EPOCH - var_name = value_str.split(".", 1)[1] - resolved = _resolve_state_var_fee(text_nc, var_name) - if resolved is not None: - fees[proc_name] = resolved + resolved = _resolve_fee_from_value(text_nc, cmp_m.group("value"), constants) + if resolved is not None: + fees[proc_name] = resolved return fees @@ -463,26 +464,13 @@ def extract_allow_transfer_shares(text: str) -> bool: Returns True if output.allowTransfer = true, False otherwise (including if method doesn't exist). """ text_nc = strip_comments(text) - # Find the PRE_ACQUIRE_SHARES method marker = "PRE_ACQUIRE_SHARES" pos = text_nc.find(marker) if pos == -1: return False - # Find the method body - brace_start = text_nc.find("{", pos) - if brace_start == -1: + body = _find_brace_block(text_nc, pos) + if not body: return False - depth = 0 - end = brace_start - while end < len(text_nc): - if text_nc[end] == "{": - depth += 1 - elif text_nc[end] == "}": - depth -= 1 - if depth == 0: - break - end += 1 - body = text_nc[brace_start:end + 1] m = ALLOW_TRANSFER_RE.search(body) if m: return m.group("value") == "true" @@ -518,6 +506,36 @@ def index_to_base56(idx: int) -> str: return s + "A" * (56 - len(s)) +_JS_IDENTITY_PROGRAM = """ +(async () => { + const g = globalThis; + if (typeof g.self === 'undefined') g.self = g; + if (typeof g.window === 'undefined') g.window = g; + if (!g.crypto || !g.crypto.subtle) { + try { g.crypto = require('crypto').webcrypto; } catch (e) {} + } + if (typeof g.atob === 'undefined') g.atob = (s) => Buffer.from(s, 'base64').toString('binary'); + if (typeof g.btoa === 'undefined') g.btoa = (s) => Buffer.from(s, 'binary').toString('base64'); + + const libPath = process.env.QUBIC_JS_LIB; + const addr = process.env.QUBIC_ADDR56; + if (!libPath || !addr) throw new Error('Missing QUBIC_JS_LIB or QUBIC_ADDR56 env vars'); + + const mod = require(libPath); + let QubicHelper = null; + if (mod && typeof mod.QubicHelper === 'function') QubicHelper = mod.QubicHelper; + else if (mod && mod.default && typeof mod.default.QubicHelper === 'function') QubicHelper = mod.default.QubicHelper; + else if (typeof mod === 'function') QubicHelper = mod; + if (!QubicHelper) throw new Error('QubicHelper class not found in exports'); + + const helper = new QubicHelper(); + const publicKey = helper.getIdentityBytes(addr); + const identity = await helper.getIdentity(publicKey); + if (typeof identity !== 'string' || identity.length !== 60) throw new Error('Invalid identity length'); + process.stdout.write(identity); +})().catch(e => { console.error(String(e && e.stack || e)); process.exit(1); }); +""".strip() + def run_js_get_identity_from_index(cidx: int, js_lib_path: Path) -> Optional[str]: js_path = js_lib_path.resolve() if not js_path.exists(): @@ -525,41 +543,16 @@ def run_js_get_identity_from_index(cidx: int, js_lib_path: Path) -> Optional[str return None addr56 = index_to_base56(cidx) - - js_program = f""" - (async () => {{ - const g = globalThis; - if (typeof g.self === 'undefined') g.self = g; - if (typeof g.window === 'undefined') g.window = g; - if (!g.crypto || !g.crypto.subtle) {{ - try {{ g.crypto = require('crypto').webcrypto; }} catch (e) {{}} - }} - if (typeof g.atob === 'undefined') g.atob = (s) => Buffer.from(s, 'base64').toString('binary'); - if (typeof g.btoa === 'undefined') g.btoa = (s) => Buffer.from(s, 'binary').toString('base64'); - - const mod = require({json.dumps(str(js_path))}); - let QubicHelper = null; - if (mod && typeof mod.QubicHelper === 'function') QubicHelper = mod.QubicHelper; - else if (mod && mod.default && typeof mod.default.QubicHelper === 'function') QubicHelper = mod.default.QubicHelper; - else if (typeof mod === 'function') QubicHelper = mod; - if (!QubicHelper) throw new Error('QubicHelper class not found in exports'); - - const helper = new QubicHelper(); - - const addr = {json.dumps(addr56)}; - const publicKey = helper.getIdentityBytes(addr); - const identity = await helper.getIdentity(publicKey); - if (typeof identity !== 'string' || identity.length !== 60) throw new Error('Invalid identity length'); - process.stdout.write(identity); - }})().catch(e => {{ console.error(String(e && e.stack || e)); process.exit(1); }}); - """ + env = {**__import__("os").environ, "QUBIC_JS_LIB": str(js_path), "QUBIC_ADDR56": addr56} try: res = subprocess.run( - ["node", "-e", js_program], + ["node", "-e", _JS_IDENTITY_PROGRAM], capture_output=True, text=True, check=True, + env=env, + timeout=30, ) return res.stdout.strip() except FileNotFoundError: @@ -584,6 +577,8 @@ def normalize_procs_to_list(procs: Any) -> List[Dict[str, Any]]: if isinstance(obj, dict) and "id" in obj and "name" in obj: try: entry: Dict[str, Any] = {"id": int(obj["id"]), "name": obj["name"]} + if "sourceIdentifier" in obj: + entry["sourceIdentifier"] = obj["sourceIdentifier"] if "fee" in obj: entry["fee"] = obj["fee"] items[int(obj["id"])] = entry @@ -599,8 +594,13 @@ def index_by_filename(contracts: List[Dict[str, Any]]) -> Dict[str, Dict[str, An out[fn] = sc return out -def merge_contracts(existing: List[Dict[str, Any]], fresh: List[Dict[str, Any]]) -> List[Dict[str, Any]]: +def merge_contracts( + existing: List[Dict[str, Any]], + fresh: List[Dict[str, Any]], + rewritten_filenames: Optional[set] = None, +) -> List[Dict[str, Any]]: by_filename: Dict[str, Dict[str, Any]] = index_by_filename(existing) + rewritten = rewritten_filenames or set() for new in fresh: fname = new.get("filename") @@ -638,11 +638,23 @@ def merge_contracts(existing: List[Dict[str, Any]], fresh: List[Dict[str, Any]]) # Note: All other fields in 'ex' (like proposalUrl, etc.) are preserved automatically # since we're updating the existing dict in-place rather than replacing it - # Merge procedures: update authoritative fields (name, fee) from fresh data, - # preserve other custom fields from existing data - ex_procs = ex.get("procedures", []) new_list = normalize_procs_to_list(new.get("procedures", [])) + # Rewritten contract: replace procedures entirely, but only if the + # existing procedures don't already match (avoids re-replacing on every run + # since the _old.h include stays in contract_def.h permanently) + if fname in rewritten: + ex_procs = ex.get("procedures", []) + ex_ids = {p.get("sourceIdentifier") for p in ex_procs if isinstance(p, dict)} + new_ids = {p.get("sourceIdentifier") for p in new_list if isinstance(p, dict)} + if ex_ids != new_ids: + print(f" {fname}: rewritten contract detected, replacing procedures") + ex["procedures"] = new_list + continue + + # Normal merge: preserve manual edits, update what changed + ex_procs = ex.get("procedures", []) + # Build a map of existing procedures by id, preserving all fields ex_by_id: Dict[int, Dict[str, Any]] = {} for p in ex_procs: @@ -652,11 +664,6 @@ def merge_contracts(existing: List[Dict[str, Any]], fresh: List[Dict[str, Any]]) except (TypeError, ValueError): continue - # Build a map of fresh procedures for fee lookup - new_by_id: Dict[int, Dict[str, Any]] = {} - for p in new_list: - new_by_id[p["id"]] = p - merged_procs: List[Dict[str, Any]] = [] seen_ids: set[int] = set() @@ -664,12 +671,15 @@ def merge_contracts(existing: List[Dict[str, Any]], fresh: List[Dict[str, Any]]) pid = new_p["id"] if pid in ex_by_id: ex_p = ex_by_id[pid] - # Update fee from fresh data (authoritative) + # If the C++ identifier changed, update name; otherwise preserve manual edits + if ex_p.get("sourceIdentifier") != new_p.get("sourceIdentifier"): + ex_p["name"] = new_p["name"] + ex_p["sourceIdentifier"] = new_p.get("sourceIdentifier") + # Update fee from fresh data if extracted; preserve existing fee otherwise. + # Fees are NOT removed when absent from fresh data, because the script + # can't always extract them (e.g. dynamic fees) and some are set manually. if "fee" in new_p: ex_p["fee"] = new_p["fee"] - elif "fee" in ex_p: - # Fresh data has no fee for this procedure; remove stale fee - del ex_p["fee"] merged_procs.append(ex_p) else: # New procedure @@ -719,6 +729,10 @@ def main(): stripped = strip_comments(contract_def_text) + rewritten_filenames = detect_rewritten_contracts(contract_def_text) + if rewritten_filenames: + print(f"Rewritten contracts detected: {rewritten_filenames}") + all_basenames = set(Path(m.group("path")).name for m in INCLUDE_RE.finditer(stripped)) basenames = {b for b in all_basenames if not should_skip_filename(b)} idx_map = parse_contract_def_from_raw(stripped, basenames) @@ -757,7 +771,8 @@ def main(): if num in seen: continue seen.add(num) - proc_entry: Dict[str, Any] = {"id": num, "name": pretty_procedure_name(ident)} + pretty_name = pretty_procedure_name(ident) + proc_entry: Dict[str, Any] = {"id": num, "name": pretty_name, "sourceIdentifier": ident} if ident in proc_fees: proc_entry["fee"] = proc_fees[ident] procs.append(proc_entry) @@ -796,16 +811,18 @@ def main(): try: existing_top = json.loads(data_path.read_text(encoding="utf-8")) - except Exception: + except FileNotFoundError: existing_top = {} + except Exception as e: + raise SystemExit(f"Error reading {data_path}: {e}") if not isinstance(existing_top, dict): - existing_top = {} + raise SystemExit(f"Error: {data_path} does not contain a JSON object") existing_sc = existing_top.get("smart_contracts", []) if not isinstance(existing_sc, list): existing_sc = [] - merged_sc = merge_contracts(existing_sc, fresh_entries) + merged_sc = merge_contracts(existing_sc, fresh_entries, rewritten_filenames) merged_sc = sort_contracts(merged_sc) existing_top["smart_contracts"] = merged_sc From 65d6fbe645775735a52f1d40df5f2761399126e1 Mon Sep 17 00:00:00 2001 From: sallymoc Date: Fri, 13 Mar 2026 17:26:36 +0100 Subject: [PATCH 3/3] fix: update QVault procedure labels --- data/smart_contracts.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/data/smart_contracts.json b/data/smart_contracts.json index 987afb6..1f1dfc8 100644 --- a/data/smart_contracts.json +++ b/data/smart_contracts.json @@ -277,47 +277,47 @@ "procedures": [ { "id": 1, - "name": "Stake", + "name": "Lock Qcap", "sourceIdentifier": "stake" }, { "id": 2, - "name": "Unstake", + "name": "Unlock Qcap", "sourceIdentifier": "unStake" }, { "id": 3, - "name": "Submit GP", + "name": "Submit General Proposal", "sourceIdentifier": "submitGP" }, { "id": 4, - "name": "Submit QCP", + "name": "Submit Quorum Requirement Proposal", "sourceIdentifier": "submitQCP" }, { "id": 5, - "name": "Submit IPOP", + "name": "Submit IPO Participation Proposal", "sourceIdentifier": "submitIPOP" }, { "id": 6, - "name": "Submit QEarn P", + "name": "Submit Qearn Participation Proposal", "sourceIdentifier": "submitQEarnP" }, { "id": 7, - "name": "Submit Fund P", + "name": "Submit Fundraising Proposal", "sourceIdentifier": "submitFundP" }, { "id": 8, - "name": "Submit MKTP", + "name": "Submit Marketplace Proposal", "sourceIdentifier": "submitMKTP" }, { "id": 9, - "name": "Submit Allo P", + "name": "Submit Allocation Percentage", "sourceIdentifier": "submitAlloP" }, {