# Kairan's Seat v2: independent bounded regression review Source: Rapha-btc/jing-contracts-v3, commit `cf4024cc43c6158704cd3b43d41be015ad48846b`. Bounty: https://aibtc.com/bounties/mvpsdbgk51aa6064f7a2 ## Result No new vulnerability established in this bounded v2 review. Twelve local Clarity 5 scenarios passed. This report does not claim duplicate-finding credit or a guaranteed bounty award. The bounty permits discretionary partial credit for a solid reasoned review with no findings. The contract source under test is unchanged. USDCx is an explicit local token fixture at the source's hardcoded principal, while sBTC is the simulator's boot contract. The fixture supplies controlled balances and an artificial recipient-failure switch to test rollback. That switch is not a claim about the deployed USDCx issuer. Tests never query RPCs, use wallet keys, send transactions to a chain, register accounts, or sign messages. ## Executed checks 1. False donor-terms acceptance returns u103, with no transfer. 2. A maximum-uint requested donation caps to seven remaining micro-USDCx without overflow or overbooking. 3. Unauthorized fundraiser and owner calls fail without moving funds. 4. Duplicate and unknown recipients in refund-many do not cause double refunds. 5. An artificial token-transfer failure after an earlier successful transfer rolls the whole batch back, including the earlier gift-map update. Retrying a valid subset pays the first donor once. 6. Claiming direct extra USDCx after all refunds preserves every donor's balance. 7. Claiming the extra before refunds gives the same final balances. 8. Successful seat release pays only LAB and subsequent refunds fail u114 despite historical gifts-map balances remaining visible. 9. A release containing a stale reviewed receipt fails u115 and leaves the target balance in the contract. 10. Reaching the target at global DEADLINE minus one preserves the full 12,960-block seat window beyond the global deadline. 11. The already-reported request boundary reproduces: failure-at minus 1,009 accepts; minus 1,008 rejects u113. No novelty claimed. 12. The round-one reserve-lockout fix reproduces: a fundraiser may deposit after the global deadline while no claim exists. ## Duplicate and disputed findings - Underfunded USDCx refund/sweep lockout was already reported by earlier round-two entrants. This review does not refile it. - The strict request-window boundary was already reported. It is reproduced as regression evidence only. - The zero reserve claim before fundraiser deposit is expressly described in the round-one README as an ordering caveat. No novelty claimed. - The assertion that a failed refund-many batch leaves earlier donors' gift records zeroed while withholding their refund does not hold in the local runtime: the enclosing response error reverts all prior transfers and map writes in the transaction. - Stale gifts-map data following success does not itself authorize another refund: check-refunds rejects the seat-claimed state before the fold. - Direct USDCx above unrefunded may be sent to LAB on failure, but donor obligations remain in the contract. Both orderings were executed. ## Reproduction With Node and the package-lock dependencies installed in kairan-local: ```sh node kairan-local/review-v2.mjs ``` The script writes `kairan-v2-local-results.json`. Exact packages are recorded in `kairan-local/package-lock.json`. These checks are a bounded local regression review, not a live-token audit or a proof that all possible paths are safe. ## Independent v3 manual review The same commit's 205-line v3 contract was independently read. No original finding was established. The source now includes a 90-day deadline and fundraiser recoup, whereas the original bounty text describes LAB-only/no-deadline behavior; earlier public entries already discuss this change, so no novelty is claimed. Post-claim direct sends and the strict 1,008-block boundary also appeared in earlier entries. This was manual source review, not executed v3 testing. The twelve v2 scenarios were independently rerun in a separate invocation and passed again. That repeats a bounded local result; it does not convert the mocked USDCx fixture into a mainnet-token test. ## Full local reproduction script Save the pinned unmodified v2 source as kairan-v2.clar beside kairan-local/. The test script reads ../kairan-v2.clar. ```javascript import { getSDK } from '@stacks/clarinet-sdk'; import { Cl, cvToString } from '@stacks/transactions'; import { readFileSync, writeFileSync } from 'node:fs'; import { createHash } from 'node:crypto'; import assert from 'node:assert/strict'; const source = readFileSync(new URL('../kairan-v2.clar', import.meta.url), 'utf8'); const OWNER = 'SP3JR7JXFT7ZM9JKSQPBQG1HPT0D365MA5TN0P12E'; const A = 'SP20GPDS5RYB2DV03KG4W08EG6HD11KYPK6FQJE1'; const B = 'SP3BB5BATNP0M9B7H9PW2NQBX3EW1T63Q1HV56H0A'; const LAB = 'SP2RGCKAQH0ZZD0WEVB38H128DZ1M2S5V3ST871NF'; const FUND = 'SP12AVHNBR1K5BRAXP699V66CWK7PQTJJBT2H174F'; const U = 'SP120SBRBQJ00MCWS7TM5R8WJNTTKD5K0HFRC2CNE.usdcx'; const S = 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token'; const C = `${OWNER}.kairan-v2`; const TARGET = 2_000_000_000n, DEADLINE = 1025386; const results=[]; function token(asset) { return `(define-fungible-token ${asset}) (define-data-var blocked (optional principal) none) (define-read-only (get-balance (who principal)) (ok (ft-get-balance ${asset} who))) (define-public (mint (amount uint) (who principal)) (ft-mint? ${asset} amount who)) (define-public (block-recipient (who principal)) (begin (var-set blocked (some who)) (ok true))) (define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34)))) (begin (asserts! (or (is-eq tx-sender sender) (is-eq contract-caller sender)) (err u4)) (asserts! (not (is-eq (var-get blocked) (some recipient))) (err u99)) (ft-transfer? ${asset} amount sender recipient)))`; } async function fixture(){ const n=await getSDK(); await n.initEmptySession(null); n.setEpoch('3.4'); for(const p of [OWNER,A,B,LAB,FUND]) n.mintSTX(p,1_000_000_000_000n); n.deployContract('usdcx',token('usdcx-token'),{clarityVersion:5},U.split('.')[0]); // The simulator includes the canonical sBTC boot contract at S. n.deployContract('kairan-v2',source,{clarityVersion:5},OWNER); const call=(fn,args=[],sender=OWNER)=>n.callPublicFn(C,fn,args,sender).result; const ro=(fn,args=[])=>n.callReadOnlyFn(C,fn,args,OWNER).result; const bal=who=>n.callReadOnlyFn(U,'get-balance',[Cl.principal(who)],OWNER).result.value.value; const mint=(who,amt)=>n.callPublicFn(U,'mint',[Cl.uint(amt),Cl.principal(who)],OWNER); const give=(who,amt)=>{mint(who,amt);return call('donate',[Cl.uint(amt),Cl.bool(true)],who)}; const refunds=who=>call('refund-many',[Cl.list(who.map(Cl.principal))],B); const to=h=>{if(h>n.burnBlockHeight)n.mineEmptyBurnBlocks(h-n.burnBlockHeight)}; return {n,call,ro,bal,mint,give,refunds,to}; } const eq=(a,b)=>assert.deepEqual(a,b); async function test(name,fn){let f;try{f=await fixture();await fn(f);results.push({name,status:'PASS'});console.log('PASS',name)}catch(e){results.push({name,status:'FAIL',error:String(e)});throw e}finally{f?.n.free()}} await test('Terms refusal transfers nothing',({call,mint,bal})=>{mint(A,100n);eq(call('donate',[Cl.uint(100),Cl.bool(false)],A),Cl.error(Cl.uint(103)));assert.equal(bal(A),100n);assert.equal(bal(C),0n)}); await test('Overlarge request caps at remaining target without overflow',({call,give,mint,bal})=>{give(A,TARGET-7n);mint(B,7n);eq(call('donate',[Cl.uint((1n<<128n)-1n),Cl.bool(true)],B),Cl.ok(Cl.uint(TARGET)));assert.equal(bal(C),TARGET)}); await test('Unauthorized fundraiser and owner calls cannot release or deposit',({call})=>{eq(call('deposit-reserve',[Cl.uint(1),Cl.uint(0)],A),Cl.error(Cl.uint(105)));eq(call('release-seat',[Cl.stringAscii('b'),Cl.stringAscii('r'),Cl.stringAscii('g')],A),Cl.error(Cl.uint(100)))}); await test('Duplicate and unknown refund recipients never double-pay',({give,refunds,to,bal})=>{give(A,100n);give(B,200n);to(DEADLINE);eq(refunds([A,A,OWNER,B,A]),Cl.ok(Cl.uint(300)));assert.equal(bal(A),100n);assert.equal(bal(B),200n);assert.equal(bal(C),0n);eq(refunds([A,B]),Cl.ok(Cl.uint(0)))}); await test('Forced transfer failure rolls entire batch back, including earlier map writes',({n,give,refunds,to,bal,ro})=>{give(A,100n);give(B,200n);to(DEADLINE);n.callPublicFn(U,'block-recipient',[Cl.principal(B)],OWNER);eq(refunds([A,B]),Cl.error(Cl.uint(99)));assert.equal(bal(A),0n);assert.equal(bal(C),300n);eq(ro('get-donor',[Cl.principal(A)]),Cl.some(Cl.uint(100)));eq(refunds([A]),Cl.ok(Cl.uint(100)));assert.equal(bal(A),100n)}); for(const reverse of [false,true])await test(`Refund/claim order preserves donors and direct gift; claim ${reverse?'before':'after'} refunds`,({n,give,mint,call,refunds,to,bal})=>{give(A,100n);give(B,200n);mint(C,17n);to(DEADLINE);const claim=()=>eq(call('claim-reserve',[Cl.stringAscii('https://example.org/pool'),Cl.bool(true)],LAB),Cl.ok(Cl.tuple({usdcx:Cl.uint(17),stx:Cl.uint(0),sats:Cl.uint(0)})));if(reverse)claim();eq(refunds(reverse?[B,A]:[A,B]),Cl.ok(Cl.uint(300)));if(!reverse)claim();assert.equal(bal(A),100n);assert.equal(bal(B),200n);assert.equal(bal(LAB),17n);assert.equal(bal(C),0n)}); await test('Seat success pays only LAB, locks refunds, and ignores stale gift-map balances',({give,call,refunds,to,bal})=>{give(A,TARGET);eq(call('request-seat',[Cl.stringAscii('builder'),Cl.stringAscii('repo'),Cl.stringAscii('grant'),Cl.bool(true)],LAB),Cl.ok(Cl.bool(true)));eq(call('release-seat',[Cl.stringAscii('builder'),Cl.stringAscii('repo'),Cl.stringAscii('grant')]),Cl.ok(Cl.tuple({usdcx:Cl.uint(TARGET),stx:Cl.uint(0),sats:Cl.uint(0)})));assert.equal(bal(LAB),TARGET);to(DEADLINE);eq(refunds([A]),Cl.error(Cl.uint(114)));assert.equal(bal(A),0n)}); await test('Reviewed receipt mismatch fails without moving assets',({give,call,bal})=>{give(A,TARGET);call('request-seat',[Cl.stringAscii('b'),Cl.stringAscii('r'),Cl.stringAscii('new'),Cl.bool(true)],LAB);eq(call('release-seat',[Cl.stringAscii('b'),Cl.stringAscii('r'),Cl.stringAscii('old')]),Cl.error(Cl.uint(115)));assert.equal(bal(C),TARGET);assert.equal(bal(LAB),0n)}); await test('Last-minute target completion receives full seat window past global deadline',({give,to,ro,call})=>{to(DEADLINE-1);give(A,TARGET);const reached=Number(ro('get-status').value['reached-at'].value.value);assert.equal(reached,DEADLINE-1);to(DEADLINE+100);eq(call('request-seat',[Cl.stringAscii('b'),Cl.stringAscii('r'),Cl.stringAscii('g'),Cl.bool(true)],LAB),Cl.ok(Cl.bool(true)));eq(ro('failure-at'),Cl.uint(reached+12960))}); await test('Known boundary: exactly 1008 blocks before failure rejects; 1009 accepts',({give,to,ro,call})=>{give(A,TARGET);const fail=Number(ro('failure-at').value);to(fail-1009);eq(call('request-seat',[Cl.stringAscii('b'),Cl.stringAscii('r'),Cl.stringAscii('g'),Cl.bool(true)],LAB),Cl.ok(Cl.bool(true)));to(fail-1008);eq(call('request-seat',[Cl.stringAscii('b'),Cl.stringAscii('r'),Cl.stringAscii('g'),Cl.bool(true)],LAB),Cl.error(Cl.uint(113)))}); await test('Round-one fixed behavior: fundraiser may deposit after global deadline before claim',({call,to})=>{to(DEADLINE+1);eq(call('deposit-reserve',[Cl.uint(1),Cl.uint(0)],FUND),Cl.ok(Cl.bool(true)))}); const report={commit:'cf4024cc43c6158704cd3b43d41be015ad48846b',sourceSha256:createHash('sha256').update(source).digest('hex'),scope:'Unmodified v2 contract; USDCx is an explicit local mock; sBTC is the simulator boot contract; no RPC, chain writes, account creation or signatures.',tests:results}; writeFileSync(new URL('../kairan-v2-local-results.json',import.meta.url),JSON.stringify(report,null,2)+'\n'); console.log(JSON.stringify(report,null,2)); ``` ## Dependency versions used ```json { "node_modules/@noble/hashes": "1.1.5", "node_modules/@noble/secp256k1": "1.7.1", "node_modules/@stacks/clarinet-sdk": "3.25.0", "node_modules/@stacks/clarinet-sdk-wasm": "3.25.0", "node_modules/@stacks/common": "7.6.0", "node_modules/@stacks/network": "7.6.0", "node_modules/@stacks/transactions": "7.6.0", "node_modules/base-x": "4.0.1", "node_modules/c32check": "2.0.0", "node_modules/cross-fetch": "3.2.0", "node_modules/lodash.clonedeep": "4.5.0", "node_modules/node-fetch": "2.7.0", "node_modules/tr46": "0.0.3", "node_modules/webidl-conversions": "3.0.1", "node_modules/whatwg-url": "5.0.0" } ```