@@ -18,6 +18,7 @@ import {
1818 BadNonceRejection ,
1919 CONTRACT_ABI_PATH ,
2020 ClarityAbi ,
21+ Pc ,
2122 READONLY_FUNCTION_CALL_PATH ,
2223 StacksWireType ,
2324 TRANSACTION_FEE_ESTIMATE_PATH ,
@@ -83,8 +84,11 @@ import {
8384 AddressHashMode ,
8485 AuthType ,
8586 ClarityVersion ,
87+ FungibleConditionCode ,
8688 PayloadType ,
8789 PostConditionMode ,
90+ PostConditionPrincipalId ,
91+ PostConditionType ,
8892 PubKeyEncoding ,
8993 TxRejectedReason ,
9094} from '../src/constants' ;
@@ -98,6 +102,7 @@ import {
98102 transactionToHex ,
99103} from '../src/transaction' ;
100104import { cloneDeep , randomBytes } from '../src/utils' ;
105+ import { FungiblePostConditionWire , STXPostConditionWire } from '../src/wire/types' ;
101106
102107function setSignature (
103108 unsignedTransaction : StacksTransactionWire ,
@@ -1951,6 +1956,294 @@ test('Transaction broadcast fails', async () => {
19511956 await expect ( broadcastTransaction ( { transaction } ) ) . rejects . toThrow ( ) ;
19521957} ) ;
19531958
1959+ test ( 'Make contract-call with serialized hex post conditions' , async ( ) => {
1960+ const contractAddress = 'ST3KC0MTNW34S1ZXD36JYKFD3JJMWA01M55DSJ4JE' ;
1961+ const contractName = 'kv-store' ;
1962+ const functionName = 'get-value' ;
1963+ const buffer = bufferCV ( utf8ToBytes ( 'foo' ) ) ;
1964+ const senderKey = 'e494f188c2d35887531ba474c433b1e41fadd8eb824aca983447fd4bb8b277a801' ;
1965+ const fee = 0 ;
1966+
1967+ // These are serialized post conditions
1968+ const stxPostConditionHex = '00021a5dd8ff3545259925b982524807686567eec2933f03000000000000000a' ; // STX post condition for ST1EXHZSN8MJSJ9DSG994G1V8CNKYXGMK7Z4SA6DH, gte, 10
1969+ const ftPostConditionHex =
1970+ '01021a5dd8ff3545259925b982524807686567eec2933f1ac989ba53bbb27a76ef5e8499e65f69c7798fd5d113746573742d61737365742d636f6e74726163740f746573742d61737365742d6e616d650400000000000003e8' ; // FT post condition
1971+
1972+ const postConditions = [ stxPostConditionHex , ftPostConditionHex ] ;
1973+
1974+ const transaction = await makeContractCall ( {
1975+ contractAddress,
1976+ contractName,
1977+ functionName,
1978+ functionArgs : [ buffer ] ,
1979+ senderKey,
1980+ fee,
1981+ nonce : 1 ,
1982+ network : STACKS_TESTNET ,
1983+ postConditions,
1984+ postConditionMode : 'deny' ,
1985+ } ) ;
1986+
1987+ expect ( ( ) => transaction . verifyOrigin ( ) ) . not . toThrow ( ) ;
1988+
1989+ // Re-serialize and check if the serialized post conditions were correctly handled
1990+ const serializedTx = transaction . serialize ( ) ;
1991+ const bytesReader = new BytesReader ( hexToBytes ( serializedTx ) ) ;
1992+ const deserializedTx = deserializeTransaction ( bytesReader ) ;
1993+
1994+ // Should have 2 post conditions
1995+ expect ( deserializedTx . postConditions . values . length ) . toBe ( 2 ) ;
1996+
1997+ const firstPC = deserializedTx . postConditions . values [ 0 ] ;
1998+ expect ( firstPC . conditionType ) . toBe ( PostConditionType . STX ) ;
1999+ expect ( ( firstPC as STXPostConditionWire ) . amount . toString ( ) ) . toBe ( '10' ) ;
2000+
2001+ const secondPC = deserializedTx . postConditions . values [ 1 ] ;
2002+ expect ( secondPC . conditionType ) . toBe ( PostConditionType . Fungible ) ;
2003+ expect ( ( secondPC as FungiblePostConditionWire ) . amount . toString ( ) ) . toBe ( '1000' ) ;
2004+ } ) ;
2005+
2006+ test ( 'Make contract deploy with mixed post conditions (objects and serialized hex)' , async ( ) => {
2007+ const contractName = 'kv-store' ;
2008+ const codeBody = fs . readFileSync ( './tests/contracts/kv-store.clar' ) . toString ( ) ;
2009+ const senderKey = 'e494f188c2d35887531ba474c433b1e41fadd8eb824aca983447fd4bb8b277a801' ;
2010+ const fee = 0 ;
2011+ const nonce = 0 ;
2012+
2013+ // Object post condition
2014+ const stxPostCondition : StxPostCondition = {
2015+ type : 'stx-postcondition' ,
2016+ address : 'ST1EXHZSN8MJSJ9DSG994G1V8CNKYXGMK7Z4SA6DH' ,
2017+ condition : 'eq' ,
2018+ amount : 5000 ,
2019+ } ;
2020+
2021+ // Serialized hex post condition
2022+ const serializedPostConditionHex =
2023+ '00021a5dd8ff3545259925b982524807686567eec2933f03000000000000000a' ; // STX post condition for the same address, gte, 10
2024+
2025+ const transaction = await makeContractDeploy ( {
2026+ contractName,
2027+ codeBody,
2028+ senderKey,
2029+ fee,
2030+ nonce,
2031+ network : STACKS_TESTNET ,
2032+ postConditions : [ stxPostCondition , serializedPostConditionHex ] ,
2033+ postConditionMode : 'deny' ,
2034+ } ) ;
2035+
2036+ expect ( ( ) => transaction . verifyOrigin ( ) ) . not . toThrow ( ) ;
2037+
2038+ // Re-serialize and check if both post conditions were correctly handled
2039+ const serializedTx = transaction . serialize ( ) ;
2040+ const bytesReader = new BytesReader ( hexToBytes ( serializedTx ) ) ;
2041+ const deserializedTx = deserializeTransaction ( bytesReader ) ;
2042+
2043+ // Should have 2 post conditions
2044+ expect ( deserializedTx . postConditions . values . length ) . toBe ( 2 ) ;
2045+
2046+ const firstPC = deserializedTx . postConditions . values [ 0 ] ;
2047+ expect ( firstPC . conditionType ) . toBe ( PostConditionType . STX ) ;
2048+ expect ( ( firstPC as STXPostConditionWire ) . amount ) . toBe ( 5000n ) ;
2049+
2050+ const secondPC = deserializedTx . postConditions . values [ 1 ] ;
2051+ expect ( secondPC . conditionType ) . toBe ( PostConditionType . STX ) ;
2052+ expect ( ( secondPC as STXPostConditionWire ) . amount ) . toBe ( 10n ) ;
2053+ } ) ;
2054+
2055+ test ( 'Comprehensive post condition test for contract call and deploy' , async ( ) => {
2056+ // Common test variables
2057+ const senderKey = 'e494f188c2d35887531ba474c433b1e41fadd8eb824aca983447fd4bb8b277a801' ;
2058+ const fee = 0 ;
2059+ const nonce = 0 ;
2060+
2061+ // Test data - Post Conditions in different formats
2062+
2063+ // 1. Normal post condition objects
2064+ const stxPostCondition : StxPostCondition = {
2065+ type : 'stx-postcondition' ,
2066+ address : 'ST1EXHZSN8MJSJ9DSG994G1V8CNKYXGMK7Z4SA6DH' ,
2067+ condition : 'eq' ,
2068+ amount : 5000 ,
2069+ } ;
2070+
2071+ const ftPostCondition : FungiblePostCondition = {
2072+ type : 'ft-postcondition' ,
2073+ address : 'ST1EXHZSN8MJSJ9DSG994G1V8CNKYXGMK7Z4SA6DH' ,
2074+ condition : 'gte' ,
2075+ asset : 'ST34RKEJKQES7MXQFBT29KSJZD73QK3YNT5N56C6X.test-asset-contract::test-asset-name' ,
2076+ amount : 1000 ,
2077+ } ;
2078+
2079+ // 2. Wire format post conditions
2080+ const stxPostConditionWire : STXPostConditionWire = {
2081+ type : StacksWireType . PostCondition ,
2082+ conditionType : PostConditionType . STX ,
2083+ principal : {
2084+ type : StacksWireType . Principal ,
2085+ prefix : PostConditionPrincipalId . Standard ,
2086+ address : {
2087+ type : StacksWireType . Address ,
2088+ version : 22 ,
2089+ hash160 : '5dd8ff3545259925b982524807686567eec2933f' ,
2090+ } ,
2091+ } ,
2092+ conditionCode : FungibleConditionCode . Less ,
2093+ amount : 7500n ,
2094+ } ;
2095+
2096+ // 3. Hex serialized post conditions
2097+ const hexSerializedStxPostCondition =
2098+ '00021a5dd8ff3545259925b982524807686567eec2933f03000000000000000a' ; // STX post condition for ST1EXHZSN8MJSJ9DSG994G1V8CNKYXGMK7Z4SA6DH, gte, 10
2099+
2100+ const hexSerializedFtPostCondition =
2101+ '01021a5dd8ff3545259925b982524807686567eec2933f1ac989ba53bbb27a76ef5e8499e65f69c7798fd5d113746573742d61737365742d636f6e74726163740f746573742d61737365742d6e616d650400000000000003e8' ; // FT post condition
2102+
2103+ // Create an array with all post condition types
2104+ const allPostConditions = [
2105+ stxPostCondition ,
2106+ ftPostCondition ,
2107+ stxPostConditionWire ,
2108+ hexSerializedStxPostCondition ,
2109+ hexSerializedFtPostCondition ,
2110+ ] ;
2111+
2112+ // Define expected post condition values after deserialization for easier testing
2113+ const expectedPostConditions = [
2114+ {
2115+ conditionType : PostConditionType . STX ,
2116+ amount : 5000n ,
2117+ conditionCode : FungibleConditionCode . Equal ,
2118+ } ,
2119+ {
2120+ conditionType : PostConditionType . Fungible ,
2121+ amount : 1000n ,
2122+ conditionCode : FungibleConditionCode . GreaterEqual ,
2123+ } ,
2124+ {
2125+ conditionType : PostConditionType . STX ,
2126+ amount : 7500n ,
2127+ conditionCode : FungibleConditionCode . Less ,
2128+ } ,
2129+ {
2130+ conditionType : PostConditionType . STX ,
2131+ amount : 10n ,
2132+ conditionCode : FungibleConditionCode . GreaterEqual ,
2133+ } ,
2134+ {
2135+ conditionType : PostConditionType . Fungible ,
2136+ amount : 1000n ,
2137+ conditionCode : FungibleConditionCode . Less ,
2138+ } ,
2139+ ] ;
2140+
2141+ // Test Contract Deploy with all post condition types
2142+ const contractName = 'test-contract' ;
2143+ const codeBody = fs . readFileSync ( './tests/contracts/kv-store.clar' ) . toString ( ) ;
2144+
2145+ const deployTx = await makeContractDeploy ( {
2146+ contractName,
2147+ codeBody,
2148+ senderKey,
2149+ fee,
2150+ nonce,
2151+ network : STACKS_TESTNET ,
2152+ postConditions : allPostConditions ,
2153+ postConditionMode : 'deny' ,
2154+ } ) ;
2155+
2156+ // Verify transaction and post conditions
2157+ expect ( ( ) => deployTx . verifyOrigin ( ) ) . not . toThrow ( ) ;
2158+
2159+ // Re-serialize and check if post conditions were correctly handled
2160+ let serializedTx = deployTx . serialize ( ) ;
2161+ let deserializedTx = deserializeTransaction ( serializedTx ) ;
2162+
2163+ // Should have all 5 post conditions
2164+ expect ( deserializedTx . postConditions . values . length ) . toBe ( 5 ) ;
2165+
2166+ // Verify all post conditions from deserialized transaction match expected values
2167+ const deployPCs = deserializedTx . postConditions . values ;
2168+
2169+ // Validate all post conditions at once with a loop
2170+ expectedPostConditions . forEach ( ( expected , index ) => {
2171+ const actual = deployPCs [ index ] ;
2172+ expect ( actual . conditionType ) . toBe ( expected . conditionType ) ;
2173+
2174+ if ( actual . conditionType === PostConditionType . STX ) {
2175+ expect ( ( actual as STXPostConditionWire ) . amount ) . toBe ( expected . amount ) ;
2176+ expect ( ( actual as STXPostConditionWire ) . conditionCode ) . toBe ( expected . conditionCode ) ;
2177+ } else if ( actual . conditionType === PostConditionType . Fungible ) {
2178+ expect ( ( actual as FungiblePostConditionWire ) . amount ) . toBe ( expected . amount ) ;
2179+ expect ( ( actual as FungiblePostConditionWire ) . conditionCode ) . toBe ( expected . conditionCode ) ;
2180+ }
2181+ } ) ;
2182+
2183+ // Test Contract Call with same post conditions
2184+ const contractAddress = 'ST3KC0MTNW34S1ZXD36JYKFD3JJMWA01M55DSJ4JE' ;
2185+ const contractCallName = 'kv-store' ;
2186+ const functionName = 'get-value' ;
2187+ const buffer = bufferCV ( utf8ToBytes ( 'test-key' ) ) ;
2188+
2189+ const callTx = await makeContractCall ( {
2190+ contractAddress,
2191+ contractName : contractCallName ,
2192+ functionName,
2193+ functionArgs : [ buffer ] ,
2194+ senderKey,
2195+ fee,
2196+ nonce,
2197+ network : STACKS_TESTNET ,
2198+ postConditions : allPostConditions ,
2199+ postConditionMode : 'deny' ,
2200+ } ) ;
2201+
2202+ // Verify transaction and post conditions
2203+ expect ( ( ) => callTx . verifyOrigin ( ) ) . not . toThrow ( ) ;
2204+
2205+ // Re-serialize and check if post conditions were correctly handled
2206+ serializedTx = callTx . serialize ( ) ;
2207+ deserializedTx = deserializeTransaction ( serializedTx ) ;
2208+
2209+ // Should have all 5 post conditions
2210+ expect ( deserializedTx . postConditions . values . length ) . toBe ( 5 ) ;
2211+
2212+ // Verify all post conditions from deserialized transaction match expected values
2213+ const callPCs = deserializedTx . postConditions . values ;
2214+
2215+ // Validate all post conditions at once with a loop (same expected values)
2216+ expectedPostConditions . forEach ( ( expected , index ) => {
2217+ const actual = callPCs [ index ] ;
2218+ expect ( actual . conditionType ) . toBe ( expected . conditionType ) ;
2219+
2220+ if ( actual . conditionType === PostConditionType . STX ) {
2221+ expect ( ( actual as STXPostConditionWire ) . amount ) . toBe ( expected . amount ) ;
2222+ expect ( ( actual as STXPostConditionWire ) . conditionCode ) . toBe ( expected . conditionCode ) ;
2223+ } else if ( actual . conditionType === PostConditionType . Fungible ) {
2224+ expect ( ( actual as FungiblePostConditionWire ) . amount ) . toBe ( expected . amount ) ;
2225+ expect ( ( actual as FungiblePostConditionWire ) . conditionCode ) . toBe ( expected . conditionCode ) ;
2226+ }
2227+ } ) ;
2228+
2229+ // Test conversion back to JSON format using Pc.fromHex
2230+ const expectedPcFromHex = {
2231+ type : 'stx-postcondition' ,
2232+ address : 'ST1EXHZSN8MJSJ9DSG994G1V8CNKYXGMK7Z4SA6DH' ,
2233+ condition : 'gte' ,
2234+ amount : '10' ,
2235+ } ;
2236+
2237+ // Check that Pc.fromHex correctly converts the serialized hex post condition
2238+ const pcFromHex = Pc . fromHex ( hexSerializedStxPostCondition ) as StxPostCondition ;
2239+ expect ( {
2240+ type : pcFromHex . type ,
2241+ address : pcFromHex . address ,
2242+ condition : pcFromHex . condition ,
2243+ amount : pcFromHex . amount ,
2244+ } ) . toEqual ( expectedPcFromHex ) ;
2245+ } ) ;
2246+
19542247test ( 'Make contract-call with network ABI validation' , async ( ) => {
19552248 const contractAddress = 'ST3KC0MTNW34S1ZXD36JYKFD3JJMWA01M55DSJ4JE' ;
19562249 const contractName = 'kv-store' ;
0 commit comments