Skip to content

Commit 73ec0db

Browse files
authored
fix: delegate-stx burn-op parsing and test fix (#1939)
* fix: delegate-stx burn-op parsing and test fix * chore: remove console.log * chore: lint fix * chore: use explicit burnchain-op event data
1 parent 9e9a464 commit 73ec0db

File tree

4 files changed

+151
-37
lines changed

4 files changed

+151
-37
lines changed

src/event-stream/core-node-message.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,36 @@ export interface BurnchainOpStackStx {
188188
};
189189
}
190190

191-
type BurnchainOp = BurnchainOpRegisterAssetNft | BurnchainOpRegisterAssetFt | BurnchainOpStackStx;
191+
export interface BurnchainOpDelegateStx {
192+
delegate_stx: {
193+
burn_block_height: number; // 121;
194+
burn_header_hash: string; // '54feff1b7edc52311de1f4a54ccc0cf786274cdd2e2ca95ab73569a622f43e35';
195+
burn_txid: string; // '15700f75e675181f79ab66219746b501e276006d53a8874cc3123d8317c6ed8b';
196+
delegate_to: {
197+
address: string; // 'ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y';
198+
address_hash_bytes: string; // '0x43596b5386f466863e25658ddf94bd0fadab0048';
199+
address_version: number; // 26;
200+
};
201+
delegated_ustx: number; // 4500432000000000;
202+
reward_addr: [
203+
number, // 1,
204+
string // 'tb1pf4x64urhdsdmadxxhv2wwjv6e3evy59auu2xaauu3vz3adxtskfschm453'
205+
];
206+
sender: {
207+
address: string; // 'ST1Z7V02CJRY3G5R2RDG7SFAZA8VGH0Y44NC2NAJN';
208+
address_hash_bytes: string; // '0x7e7d804c963c381702c3607cbd5f52370883c425';
209+
address_version: number; // 26;
210+
};
211+
until_burn_height: number; // 200;
212+
vtxindex: number; // 3;
213+
};
214+
}
215+
216+
type BurnchainOp =
217+
| BurnchainOpRegisterAssetNft
218+
| BurnchainOpRegisterAssetFt
219+
| BurnchainOpStackStx
220+
| BurnchainOpDelegateStx;
192221

193222
export type CoreNodeEvent =
194223
| SmartContractEvent

src/event-stream/reader.ts

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
BurnchainOpDelegateStx,
23
BurnchainOpRegisterAssetFt,
34
BurnchainOpRegisterAssetNft,
45
BurnchainOpStackStx,
@@ -518,6 +519,78 @@ function createTransactionFromCoreBtcStxLockEventPox4(
518519
return tx;
519520
}
520521

522+
function createTransactionFromCoreBtcDelegateStxEventPox4(
523+
chainId: ChainID,
524+
contractEvent: SmartContractEvent,
525+
decodedEvent: DbPoxSyntheticDelegateStxEvent,
526+
burnOpData: BurnchainOpDelegateStx,
527+
txResult: string,
528+
txId: string
529+
): DecodedTxResult {
530+
const resultCv = decodeClarityValue<ClarityValueResponse>(txResult);
531+
if (resultCv.type_id !== ClarityTypeID.ResponseOk) {
532+
throw new Error(`Unexpected tx result Clarity type ID: ${resultCv.type_id}`);
533+
}
534+
const senderAddress = decodeStacksAddress(burnOpData.delegate_stx.sender.address);
535+
const poxContractAddressString =
536+
getChainIDNetwork(chainId) === 'mainnet'
537+
? BootContractAddress.mainnet
538+
: BootContractAddress.testnet;
539+
const poxContractAddress = decodeStacksAddress(poxContractAddressString);
540+
const contractName = contractEvent.contract_event.contract_identifier?.split('.')?.[1] ?? 'pox';
541+
542+
const legacyClarityVals = [
543+
uintCV(burnOpData.delegate_stx.delegated_ustx), // amount-ustx
544+
principalCV(burnOpData.delegate_stx.delegate_to.address), // delegate-to
545+
someCV(uintCV(burnOpData.delegate_stx.until_burn_height)), // until-burn-ht
546+
someCV(poxAddressToTuple(burnOpData.delegate_stx.reward_addr[1])), // pox-addr
547+
];
548+
const fnLenBuffer = Buffer.alloc(4);
549+
fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
550+
const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
551+
const rawFnArgs = bufferToHex(Buffer.concat([fnLenBuffer, ...serializedClarityValues]));
552+
const clarityFnArgs = decodeClarityValueList(rawFnArgs);
553+
554+
const tx: DecodedTxResult = {
555+
tx_id: txId,
556+
version:
557+
getChainIDNetwork(chainId) === 'mainnet'
558+
? TransactionVersion.Mainnet
559+
: TransactionVersion.Testnet,
560+
chain_id: chainId,
561+
auth: {
562+
type_id: PostConditionAuthFlag.Standard,
563+
origin_condition: {
564+
hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
565+
signer: {
566+
address_version: senderAddress[0],
567+
address_hash_bytes: senderAddress[1],
568+
address: decodedEvent.stacker,
569+
},
570+
nonce: '0',
571+
tx_fee: '0',
572+
key_encoding: TxPublicKeyEncoding.Compressed,
573+
signature: '0x',
574+
},
575+
},
576+
anchor_mode: AnchorModeID.Any,
577+
post_condition_mode: PostConditionModeID.Allow,
578+
post_conditions: [],
579+
post_conditions_buffer: '0x0100000000',
580+
payload: {
581+
type_id: TxPayloadTypeID.ContractCall,
582+
address: poxContractAddressString,
583+
address_version: poxContractAddress[0],
584+
address_hash_bytes: poxContractAddress[1],
585+
contract_name: contractName,
586+
function_name: 'delegate-stx',
587+
function_args: clarityFnArgs,
588+
function_args_buffer: rawFnArgs,
589+
},
590+
};
591+
return tx;
592+
}
593+
521594
/*
522595
;; Delegate to `delegate-to` the ability to stack from a given address.
523596
;; This method _does not_ lock the funds, rather, it allows the delegate
@@ -547,8 +620,8 @@ function createTransactionFromCoreBtcDelegateStxEvent(
547620
const senderAddress = decodeStacksAddress(decodedEvent.stacker);
548621
const poxContractAddressString =
549622
getChainIDNetwork(chainId) === 'mainnet'
550-
? 'SP000000000000000000002Q6VF78'
551-
: 'ST000000000000000000002AMW42H';
623+
? BootContractAddress.mainnet
624+
: BootContractAddress.testnet;
552625
const poxContractAddress = decodeStacksAddress(poxContractAddressString);
553626
const contractName = contractEvent.contract_event.contract_identifier?.split('.')?.[1] ?? 'pox';
554627

@@ -795,6 +868,22 @@ export function parseMessageTransaction(
795868
stxStacksPoxEvent
796869
);
797870
txSender = stxLockEvent.stx_lock_event.locked_address;
871+
} else if (
872+
poxEvent &&
873+
poxEvent.decodedEvent.name === SyntheticPoxEventName.DelegateStx &&
874+
poxEvent.contractEvent.contract_event.contract_identifier?.split('.')?.[1] === 'pox-4' &&
875+
coreTx.burnchain_op &&
876+
'delegate_stx' in coreTx.burnchain_op
877+
) {
878+
rawTx = createTransactionFromCoreBtcDelegateStxEventPox4(
879+
chainId,
880+
poxEvent.contractEvent,
881+
poxEvent.decodedEvent,
882+
coreTx.burnchain_op,
883+
coreTx.raw_result,
884+
coreTx.txid
885+
);
886+
txSender = coreTx.burnchain_op.delegate_stx.sender.address;
798887
} else if (poxEvent && poxEvent.decodedEvent.name === SyntheticPoxEventName.DelegateStx) {
799888
rawTx = createTransactionFromCoreBtcDelegateStxEvent(
800889
chainId,

src/test-utils/test-helpers.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -612,26 +612,6 @@ export async function stackStxWithRosetta(opts: {
612612
};
613613
}
614614

615-
export function decodePoxAddrArg(argHex: string): {
616-
btcAddr: string;
617-
stxAddr: string;
618-
hash160: string;
619-
} {
620-
const pox_address_cv = decodeClarityValue(argHex);
621-
expect(pox_address_cv.type_id).toBe(ClarityTypeID.Tuple);
622-
const addressCV = pox_address_cv as ClarityValueTuple<{
623-
version: ClarityValueBuffer;
624-
hashbytes: ClarityValueBuffer;
625-
}>;
626-
const btcAddr = poxAddressToBtcAddress(
627-
hexToBuffer(addressCV.data.version.buffer)[0],
628-
hexToBuffer(addressCV.data.hashbytes.buffer),
629-
'mocknet'
630-
);
631-
const stxAddr = b58ToC32(btcAddr);
632-
return { btcAddr, stxAddr, hash160: addressCV.data.hashbytes.buffer };
633-
}
634-
635615
/** Client-side nonce tracking */
636616
export class NonceJar {
637617
nonceMap = new Map<string, number>();

src/tests-2.5/pox-4-burnchain-delegate-stx.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import {
44
ContractCallTransaction,
55
TransactionEventsResponse,
66
TransactionEventStxLock,
7+
TransactionResults,
78
} from '@stacks/stacks-blockchain-api-types';
89
import {
910
AnchorMode,
11+
Cl,
1012
makeContractCall,
1113
makeSTXTokenTransfer,
14+
SomeCV,
1215
standardPrincipalCV,
1316
uintCV,
1417
} from '@stacks/transactions';
@@ -19,7 +22,6 @@ import { BootContractAddress } from '../helpers';
1922
import {
2023
Account,
2124
accountFromKey,
22-
decodePoxAddrArg,
2325
fetchGet,
2426
getRosettaAccountBalance,
2527
standByForTxSuccess,
@@ -37,7 +39,7 @@ import { RPCClient } from 'rpc-bitcoin';
3739
import * as supertest from 'supertest';
3840
import { PoxContractIdentifier } from '../pox-helpers';
3941
import { ClarityValueUInt, decodeClarityValue } from 'stacks-encoding-native-js';
40-
import { decodeBtcAddress } from '@stacks/stacking';
42+
import { decodeBtcAddress, poxAddressToBtcAddress } from '@stacks/stacking';
4143
import { timeout } from '@hirosystems/api-toolkit';
4244

4345
// Perform Delegate-STX operation on Bitcoin.
@@ -439,17 +441,14 @@ describe('PoX-4 - Stack using Bitcoin-chain delegate ops', () => {
439441
);
440442
});
441443

442-
// TODO: unable to parse this synthetic `delegate-stx` tx due to missing events,
443-
// see https://github.com/stacks-network/stacks-blockchain/issues/3465
444-
test.skip('Test synthetic STX tx', async () => {
444+
test('Test synthetic STX tx', async () => {
445445
const coreNodeBalance = await client.getAccount(account.stxAddr);
446446
const addressEventsResp = await supertest(api.server)
447447
.get(`/extended/v1/tx/events?address=${account.stxAddr}`)
448448
.expect(200);
449449
const delegatorAddressEventsResp = await supertest(api.server)
450450
.get(`/extended/v1/tx/events?address=${delegatorAccount.stxAddr}`)
451451
.expect(200);
452-
console.log(delegatorAddressEventsResp);
453452
const addressEvents = addressEventsResp.body.events as TransactionEventsResponse['results'];
454453
const event1 = addressEvents[0] as TransactionEventStxLock;
455454
expect(event1.event_type).toBe('stx_lock');
@@ -458,22 +457,39 @@ describe('PoX-4 - Stack using Bitcoin-chain delegate ops', () => {
458457
expect(BigInt(event1.stx_lock_event.locked_amount)).toBe(testStackAmount);
459458
expect(BigInt(event1.stx_lock_event.locked_amount)).toBe(BigInt(coreNodeBalance.locked));
460459

461-
const txResp = await supertest(api.server).get(`/extended/v1/tx/${event1.tx_id}`).expect(200);
462-
const txObj = txResp.body as ContractCallTransaction;
460+
const addrTxsReq = await supertest(api.server)
461+
.get(`/extended/v1/address/${account.stxAddr}/transactions`)
462+
.expect(200);
463+
const addrTxs = addrTxsReq.body as TransactionResults;
464+
const txObj = addrTxs.results.find(
465+
tx => tx.sender_address === account.stxAddr
466+
) as ContractCallTransaction;
467+
expect(txObj).toBeDefined();
468+
463469
expect(txObj.tx_type).toBe('contract_call');
464470
expect(txObj.tx_status).toBe('success');
465471
expect(txObj.sender_address).toBe(account.stxAddr);
466-
expect(txObj.contract_call.contract_id).toBe(PoxContractIdentifier.pox2.testnet);
467-
expect(txObj.contract_call.function_name).toBe('stack-stx');
472+
expect(txObj.contract_call.contract_id).toBe(PoxContractIdentifier.pox4.testnet);
473+
expect(txObj.contract_call.function_name).toBe('delegate-stx');
468474

469475
const callArg1 = txObj.contract_call.function_args![0];
470476
expect(callArg1.name).toBe('amount-ustx');
471477
expect(BigInt(decodeClarityValue<ClarityValueUInt>(callArg1.hex).value)).toBe(testStackAmount);
472478

473479
const callArg2 = txObj.contract_call.function_args![1];
474-
expect(callArg2.name).toBe('pox-addr');
475-
const callArg2Addr = decodePoxAddrArg(callArg2.hex);
476-
expect(callArg2Addr.stxAddr).toBe(account.stxAddr);
477-
expect(callArg2Addr.btcAddr).toBe(account.btcAddr);
480+
expect(callArg2.name).toBe('delegate-to');
481+
expect(callArg2.repr).toBe(`'${delegatorAccount.stxAddr}`);
482+
483+
const callArg3 = txObj.contract_call.function_args![2];
484+
expect(callArg3.name).toBe('until-burn-ht');
485+
expect(callArg3.repr).toBe(`(some u${untilBurnHeight})`);
486+
487+
const callArg4 = txObj.contract_call.function_args![3];
488+
expect(callArg4.name).toBe('pox-addr');
489+
const callArg2Addr = poxAddressToBtcAddress(
490+
Cl.deserialize<SomeCV>(callArg4.hex).value,
491+
'testnet'
492+
);
493+
expect(callArg2Addr).toBe(poxAddrPayoutAccount.btcTestnetAddr);
478494
});
479495
});

0 commit comments

Comments
 (0)