Skip to content

Commit

Permalink
fix: delegate-stx burn-op parsing and test fix (#1939)
Browse files Browse the repository at this point in the history
* fix: delegate-stx burn-op parsing and test fix

* chore: remove console.log

* chore: lint fix

* chore: use explicit burnchain-op event data
  • Loading branch information
zone117x authored Apr 12, 2024
1 parent 9e9a464 commit 73ec0db
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 37 deletions.
31 changes: 30 additions & 1 deletion src/event-stream/core-node-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,36 @@ export interface BurnchainOpStackStx {
};
}

type BurnchainOp = BurnchainOpRegisterAssetNft | BurnchainOpRegisterAssetFt | BurnchainOpStackStx;
export interface BurnchainOpDelegateStx {
delegate_stx: {
burn_block_height: number; // 121;
burn_header_hash: string; // '54feff1b7edc52311de1f4a54ccc0cf786274cdd2e2ca95ab73569a622f43e35';
burn_txid: string; // '15700f75e675181f79ab66219746b501e276006d53a8874cc3123d8317c6ed8b';
delegate_to: {
address: string; // 'ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y';
address_hash_bytes: string; // '0x43596b5386f466863e25658ddf94bd0fadab0048';
address_version: number; // 26;
};
delegated_ustx: number; // 4500432000000000;
reward_addr: [
number, // 1,
string // 'tb1pf4x64urhdsdmadxxhv2wwjv6e3evy59auu2xaauu3vz3adxtskfschm453'
];
sender: {
address: string; // 'ST1Z7V02CJRY3G5R2RDG7SFAZA8VGH0Y44NC2NAJN';
address_hash_bytes: string; // '0x7e7d804c963c381702c3607cbd5f52370883c425';
address_version: number; // 26;
};
until_burn_height: number; // 200;
vtxindex: number; // 3;
};
}

type BurnchainOp =
| BurnchainOpRegisterAssetNft
| BurnchainOpRegisterAssetFt
| BurnchainOpStackStx
| BurnchainOpDelegateStx;

export type CoreNodeEvent =
| SmartContractEvent
Expand Down
93 changes: 91 additions & 2 deletions src/event-stream/reader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
BurnchainOpDelegateStx,
BurnchainOpRegisterAssetFt,
BurnchainOpRegisterAssetNft,
BurnchainOpStackStx,
Expand Down Expand Up @@ -518,6 +519,78 @@ function createTransactionFromCoreBtcStxLockEventPox4(
return tx;
}

function createTransactionFromCoreBtcDelegateStxEventPox4(
chainId: ChainID,
contractEvent: SmartContractEvent,
decodedEvent: DbPoxSyntheticDelegateStxEvent,
burnOpData: BurnchainOpDelegateStx,
txResult: string,
txId: string
): DecodedTxResult {
const resultCv = decodeClarityValue<ClarityValueResponse>(txResult);
if (resultCv.type_id !== ClarityTypeID.ResponseOk) {
throw new Error(`Unexpected tx result Clarity type ID: ${resultCv.type_id}`);
}
const senderAddress = decodeStacksAddress(burnOpData.delegate_stx.sender.address);
const poxContractAddressString =
getChainIDNetwork(chainId) === 'mainnet'
? BootContractAddress.mainnet
: BootContractAddress.testnet;
const poxContractAddress = decodeStacksAddress(poxContractAddressString);
const contractName = contractEvent.contract_event.contract_identifier?.split('.')?.[1] ?? 'pox';

const legacyClarityVals = [
uintCV(burnOpData.delegate_stx.delegated_ustx), // amount-ustx
principalCV(burnOpData.delegate_stx.delegate_to.address), // delegate-to
someCV(uintCV(burnOpData.delegate_stx.until_burn_height)), // until-burn-ht
someCV(poxAddressToTuple(burnOpData.delegate_stx.reward_addr[1])), // pox-addr
];
const fnLenBuffer = Buffer.alloc(4);
fnLenBuffer.writeUInt32BE(legacyClarityVals.length);
const serializedClarityValues = legacyClarityVals.map(c => serializeCV(c));
const rawFnArgs = bufferToHex(Buffer.concat([fnLenBuffer, ...serializedClarityValues]));
const clarityFnArgs = decodeClarityValueList(rawFnArgs);

const tx: DecodedTxResult = {
tx_id: txId,
version:
getChainIDNetwork(chainId) === 'mainnet'
? TransactionVersion.Mainnet
: TransactionVersion.Testnet,
chain_id: chainId,
auth: {
type_id: PostConditionAuthFlag.Standard,
origin_condition: {
hash_mode: TxSpendingConditionSingleSigHashMode.P2PKH,
signer: {
address_version: senderAddress[0],
address_hash_bytes: senderAddress[1],
address: decodedEvent.stacker,
},
nonce: '0',
tx_fee: '0',
key_encoding: TxPublicKeyEncoding.Compressed,
signature: '0x',
},
},
anchor_mode: AnchorModeID.Any,
post_condition_mode: PostConditionModeID.Allow,
post_conditions: [],
post_conditions_buffer: '0x0100000000',
payload: {
type_id: TxPayloadTypeID.ContractCall,
address: poxContractAddressString,
address_version: poxContractAddress[0],
address_hash_bytes: poxContractAddress[1],
contract_name: contractName,
function_name: 'delegate-stx',
function_args: clarityFnArgs,
function_args_buffer: rawFnArgs,
},
};
return tx;
}

/*
;; Delegate to `delegate-to` the ability to stack from a given address.
;; This method _does not_ lock the funds, rather, it allows the delegate
Expand Down Expand Up @@ -547,8 +620,8 @@ function createTransactionFromCoreBtcDelegateStxEvent(
const senderAddress = decodeStacksAddress(decodedEvent.stacker);
const poxContractAddressString =
getChainIDNetwork(chainId) === 'mainnet'
? 'SP000000000000000000002Q6VF78'
: 'ST000000000000000000002AMW42H';
? BootContractAddress.mainnet
: BootContractAddress.testnet;
const poxContractAddress = decodeStacksAddress(poxContractAddressString);
const contractName = contractEvent.contract_event.contract_identifier?.split('.')?.[1] ?? 'pox';

Expand Down Expand Up @@ -795,6 +868,22 @@ export function parseMessageTransaction(
stxStacksPoxEvent
);
txSender = stxLockEvent.stx_lock_event.locked_address;
} else if (
poxEvent &&
poxEvent.decodedEvent.name === SyntheticPoxEventName.DelegateStx &&
poxEvent.contractEvent.contract_event.contract_identifier?.split('.')?.[1] === 'pox-4' &&
coreTx.burnchain_op &&
'delegate_stx' in coreTx.burnchain_op
) {
rawTx = createTransactionFromCoreBtcDelegateStxEventPox4(
chainId,
poxEvent.contractEvent,
poxEvent.decodedEvent,
coreTx.burnchain_op,
coreTx.raw_result,
coreTx.txid
);
txSender = coreTx.burnchain_op.delegate_stx.sender.address;
} else if (poxEvent && poxEvent.decodedEvent.name === SyntheticPoxEventName.DelegateStx) {
rawTx = createTransactionFromCoreBtcDelegateStxEvent(
chainId,
Expand Down
20 changes: 0 additions & 20 deletions src/test-utils/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,26 +612,6 @@ export async function stackStxWithRosetta(opts: {
};
}

export function decodePoxAddrArg(argHex: string): {
btcAddr: string;
stxAddr: string;
hash160: string;
} {
const pox_address_cv = decodeClarityValue(argHex);
expect(pox_address_cv.type_id).toBe(ClarityTypeID.Tuple);
const addressCV = pox_address_cv as ClarityValueTuple<{
version: ClarityValueBuffer;
hashbytes: ClarityValueBuffer;
}>;
const btcAddr = poxAddressToBtcAddress(
hexToBuffer(addressCV.data.version.buffer)[0],
hexToBuffer(addressCV.data.hashbytes.buffer),
'mocknet'
);
const stxAddr = b58ToC32(btcAddr);
return { btcAddr, stxAddr, hash160: addressCV.data.hashbytes.buffer };
}

/** Client-side nonce tracking */
export class NonceJar {
nonceMap = new Map<string, number>();
Expand Down
44 changes: 30 additions & 14 deletions src/tests-2.5/pox-4-burnchain-delegate-stx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import {
ContractCallTransaction,
TransactionEventsResponse,
TransactionEventStxLock,
TransactionResults,
} from '@stacks/stacks-blockchain-api-types';
import {
AnchorMode,
Cl,
makeContractCall,
makeSTXTokenTransfer,
SomeCV,
standardPrincipalCV,
uintCV,
} from '@stacks/transactions';
Expand All @@ -19,7 +22,6 @@ import { BootContractAddress } from '../helpers';
import {
Account,
accountFromKey,
decodePoxAddrArg,
fetchGet,
getRosettaAccountBalance,
standByForTxSuccess,
Expand All @@ -37,7 +39,7 @@ import { RPCClient } from 'rpc-bitcoin';
import * as supertest from 'supertest';
import { PoxContractIdentifier } from '../pox-helpers';
import { ClarityValueUInt, decodeClarityValue } from 'stacks-encoding-native-js';
import { decodeBtcAddress } from '@stacks/stacking';
import { decodeBtcAddress, poxAddressToBtcAddress } from '@stacks/stacking';
import { timeout } from '@hirosystems/api-toolkit';

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

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

const txResp = await supertest(api.server).get(`/extended/v1/tx/${event1.tx_id}`).expect(200);
const txObj = txResp.body as ContractCallTransaction;
const addrTxsReq = await supertest(api.server)
.get(`/extended/v1/address/${account.stxAddr}/transactions`)
.expect(200);
const addrTxs = addrTxsReq.body as TransactionResults;
const txObj = addrTxs.results.find(
tx => tx.sender_address === account.stxAddr
) as ContractCallTransaction;
expect(txObj).toBeDefined();

expect(txObj.tx_type).toBe('contract_call');
expect(txObj.tx_status).toBe('success');
expect(txObj.sender_address).toBe(account.stxAddr);
expect(txObj.contract_call.contract_id).toBe(PoxContractIdentifier.pox2.testnet);
expect(txObj.contract_call.function_name).toBe('stack-stx');
expect(txObj.contract_call.contract_id).toBe(PoxContractIdentifier.pox4.testnet);
expect(txObj.contract_call.function_name).toBe('delegate-stx');

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

const callArg2 = txObj.contract_call.function_args![1];
expect(callArg2.name).toBe('pox-addr');
const callArg2Addr = decodePoxAddrArg(callArg2.hex);
expect(callArg2Addr.stxAddr).toBe(account.stxAddr);
expect(callArg2Addr.btcAddr).toBe(account.btcAddr);
expect(callArg2.name).toBe('delegate-to');
expect(callArg2.repr).toBe(`'${delegatorAccount.stxAddr}`);

const callArg3 = txObj.contract_call.function_args![2];
expect(callArg3.name).toBe('until-burn-ht');
expect(callArg3.repr).toBe(`(some u${untilBurnHeight})`);

const callArg4 = txObj.contract_call.function_args![3];
expect(callArg4.name).toBe('pox-addr');
const callArg2Addr = poxAddressToBtcAddress(
Cl.deserialize<SomeCV>(callArg4.hex).value,
'testnet'
);
expect(callArg2Addr).toBe(poxAddrPayoutAccount.btcTestnetAddr);
});
});

0 comments on commit 73ec0db

Please sign in to comment.