@@ -171,17 +171,15 @@ describe('Stacks: Contract Builder', function () {
171171 it ( 'an unsigned caller-allowlisted non-canonical pox-5 (dummy) contract call transaction' , async ( ) => {
172172 // The caller supplies the staking contract it is configured for — no SDK change needed.
173173 const builder = factoryProd . getContractBuilder ( ) ;
174- builder . allowedStakingContracts ( [
175- { name : 'pox-5-dummy-v1' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ,
176- ] ) ;
174+ builder . allowedStakingContracts ( [ { name : 'pox-5-test' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ] ) ;
177175 builder . fee ( { fee : '180' } ) ;
178176 builder . nonce ( 0 ) ;
179177 builder . contractAddress ( 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' ) ;
180- builder . contractName ( 'pox-5-dummy-v1 ' ) ;
178+ builder . contractName ( 'pox-5-test ' ) ;
181179 builder . functionName ( 'register-for-bond' ) ;
182180 builder . functionArgs ( [
183181 { type : 'uint128' , val : '210' } ,
184- { type : 'principal' , val : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY.signer-manager-stackslabs-dummy-v1 ' } ,
182+ { type : 'principal' , val : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY.signer-manager-stackslabs-test ' } ,
185183 { type : 'uint128' , val : '1005000' } ,
186184 { type : 'optional' } ,
187185 { type : 'optional' } ,
@@ -191,25 +189,25 @@ describe('Stacks: Contract Builder', function () {
191189
192190 const tx = await builder . build ( ) ;
193191 const txJson = tx . toJson ( ) ;
194- should . deepEqual ( txJson . payload . contractName , 'pox-5-dummy-v1 ' ) ;
192+ should . deepEqual ( txJson . payload . contractName , 'pox-5-test ' ) ;
195193 should . deepEqual ( txJson . payload . contractAddress , 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' ) ;
196194 should . deepEqual ( txJson . payload . functionName , 'register-for-bond' ) ;
197195 } ) ;
198196
199197 it ( 'rehydrates an allowlisted non-canonical contract call (sign / explain / fee-rebuild path)' , async ( ) => {
200198 // `signTransaction` / `explainTransaction` / the fee re-build all go through `factory.from(hex)`,
201199 // which re-creates the builder — re-parsing must not reject the allowlisted contract.
202- const allowlist = [ { name : 'pox-5-dummy-v1 ' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ] ;
200+ const allowlist = [ { name : 'pox-5-test ' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ] ;
203201 const builder = factoryProd . getContractBuilder ( ) ;
204202 builder . allowedStakingContracts ( allowlist ) ;
205203 builder . fee ( { fee : '180' } ) ;
206204 builder . nonce ( 0 ) ;
207205 builder . contractAddress ( 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' ) ;
208- builder . contractName ( 'pox-5-dummy-v1 ' ) ;
206+ builder . contractName ( 'pox-5-test ' ) ;
209207 builder . functionName ( 'register-for-bond' ) ;
210208 builder . functionArgs ( [
211209 { type : 'uint128' , val : '210' } ,
212- { type : 'principal' , val : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY.signer-manager-stackslabs-dummy-v1 ' } ,
210+ { type : 'principal' , val : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY.signer-manager-stackslabs-test ' } ,
213211 { type : 'uint128' , val : '1005000' } ,
214212 { type : 'optional' } ,
215213 { type : 'optional' } ,
@@ -223,55 +221,47 @@ describe('Stacks: Contract Builder', function () {
223221 reparsed . numberSignatures ( 1 ) ;
224222 const payload = ( await reparsed . build ( ) ) . toJson ( ) . payload ;
225223 should . deepEqual ( payload . contractAddress , 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' ) ;
226- should . deepEqual ( payload . contractName , 'pox-5-dummy-v1 ' ) ;
224+ should . deepEqual ( payload . contractName , 'pox-5-test ' ) ;
227225
228226 // Without the allowlist the rebuild is rejected — canonical validation is preserved (no relaxation).
229227 ( ( ) => factoryProd . from ( tx . toBroadcastFormat ( ) ) ) . should . throw ( ParseTransactionError ) ;
230228 } ) ;
231229
232230 it ( 'rejects an allowlisted name followed by a canonical address (order-independent pair check)' , function ( ) {
233231 const builder = factoryProd . getContractBuilder ( ) ;
234- builder . allowedStakingContracts ( [
235- { name : 'pox-5-dummy-v1' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ,
236- ] ) ;
237- builder . contractName ( 'pox-5-dummy-v1' ) ; // allowlisted name first
232+ builder . allowedStakingContracts ( [ { name : 'pox-5-test' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ] ) ;
233+ builder . contractName ( 'pox-5-test' ) ; // allowlisted name first
238234 ( ( ) => builder . contractAddress ( 'SP000000000000000000002Q6VF78' ) ) . should . throw ( / n o t a n a l l o w l i s t e d p a i r / ) ;
239235 } ) ;
240236
241237 it ( 'rejects an allowlisted address followed by a canonical name (order-independent pair check)' , function ( ) {
242238 const builder = factoryProd . getContractBuilder ( ) ;
243- builder . allowedStakingContracts ( [
244- { name : 'pox-5-dummy-v1' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ,
245- ] ) ;
239+ builder . allowedStakingContracts ( [ { name : 'pox-5-test' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ] ) ;
246240 builder . contractAddress ( 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' ) ; // allowlisted address first
247- ( ( ) => builder . contractName ( 'pox-5 ' ) ) . should . throw ( / n o t a n a l l o w l i s t e d p a i r / ) ;
241+ ( ( ) => builder . contractName ( 'pox-4 ' ) ) . should . throw ( / n o t a n a l l o w l i s t e d p a i r / ) ;
248242 } ) ;
249243
250244 it ( 'rejects duplicate allowlisted contract names' , function ( ) {
251245 const builder = factoryProd . getContractBuilder ( ) ;
252246 ( ( ) =>
253247 builder . allowedStakingContracts ( [
254- { name : 'pox-5-dummy-v1 ' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ,
255- { name : 'pox-5-dummy-v1 ' , address : 'SP000000000000000000002Q6VF78' } ,
248+ { name : 'pox-5-test ' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ,
249+ { name : 'pox-5-test ' , address : 'SP000000000000000000002Q6VF78' } ,
256250 ] ) ) . should . throw ( / D u p l i c a t e s t a k i n g c o n t r a c t n a m e / ) ;
257251 } ) ;
258252
259253 it ( 'rejects an allowlisted name paired with a different address' , function ( ) {
260254 const builder = factoryProd . getContractBuilder ( ) ;
261- builder . allowedStakingContracts ( [
262- { name : 'pox-5-dummy-v1' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ,
263- ] ) ;
255+ builder . allowedStakingContracts ( [ { name : 'pox-5-test' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ] ) ;
264256 builder . contractAddress ( 'SP000000000000000000002Q6VF78' ) ; // canonical address, wrong pair
265- ( ( ) => builder . contractName ( 'pox-5-dummy-v1 ' ) ) . should . throw ( / n o t a n a l l o w l i s t e d p a i r / ) ;
257+ ( ( ) => builder . contractName ( 'pox-5-test ' ) ) . should . throw ( / n o t a n a l l o w l i s t e d p a i r / ) ;
266258 } ) ;
267259
268260 it ( 'rejects a function that is not in the pox-5 set for an allowlisted contract' , function ( ) {
269261 const builder = factoryProd . getContractBuilder ( ) ;
270- builder . allowedStakingContracts ( [
271- { name : 'pox-5-dummy-v1' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ,
272- ] ) ;
262+ builder . allowedStakingContracts ( [ { name : 'pox-5-test' , address : 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' } ] ) ;
273263 builder . contractAddress ( 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' ) ;
274- builder . contractName ( 'pox-5-dummy-v1 ' ) ;
264+ builder . contractName ( 'pox-5-test ' ) ;
275265 // `stack-stx` is a pox-4 function, not part of the pox-5 ABI the dummy mirrors.
276266 ( ( ) => builder . functionName ( 'stack-stx' ) ) . should . throw ( / i s n o t s u p p o r t e d c o n t r a c t f u n c t i o n n a m e / ) ;
277267 } ) ;
@@ -288,6 +278,51 @@ describe('Stacks: Contract Builder', function () {
288278 ) ;
289279 } ) ;
290280
281+ describe ( 'canonical-only validation and the caller-supplied allowlist (SI-2089)' , function ( ) {
282+ // The PoX-5 test contract lives at a non-canonical address; the SDK has no network-level
283+ // allowance, so it is only accepted when the caller supplies it explicitly.
284+ const TEST_ADDRESS = 'SP3JVSE4ZB5GMDJP5XBPX7ZB1BG3QDFW7RCEQJDMY' ;
285+ const CANONICAL_ADDRESS = 'SP000000000000000000002Q6VF78' ;
286+
287+ it ( 'still accepts canonical pox-5 and pox-4 at the canonical address' , function ( ) {
288+ const pox5 = factoryProd . getContractBuilder ( ) ;
289+ pox5 . contractName ( 'pox-5' ) ;
290+ ( ( ) => pox5 . contractAddress ( CANONICAL_ADDRESS ) ) . should . not . throw ( ) ;
291+ const pox4 = factoryProd . getContractBuilder ( ) ;
292+ pox4 . contractName ( 'pox-4' ) ;
293+ ( ( ) => pox4 . contractAddress ( CANONICAL_ADDRESS ) ) . should . not . throw ( ) ;
294+ } ) ;
295+
296+ it ( 'rejects the test address without a caller allowlist (fail-closed)' , function ( ) {
297+ const builder = factoryProd . getContractBuilder ( ) ;
298+ builder . contractName ( 'pox-5' ) ;
299+ ( ( ) => builder . contractAddress ( TEST_ADDRESS ) ) . should . throw ( / I n v a l i d c o n t r a c t a d d r e s s / ) ;
300+ } ) ;
301+
302+ it ( 'rejects pox-4 and send-many-memo at the test address (no network allowance)' , function ( ) {
303+ const pox4 = factoryProd . getContractBuilder ( ) ;
304+ pox4 . contractName ( 'pox-4' ) ;
305+ ( ( ) => pox4 . contractAddress ( TEST_ADDRESS ) ) . should . throw ( / I n v a l i d c o n t r a c t a d d r e s s / ) ;
306+ const sendMany = factoryProd . getContractBuilder ( ) ;
307+ sendMany . contractName ( 'send-many-memo' ) ;
308+ ( ( ) => sendMany . contractAddress ( TEST_ADDRESS ) ) . should . throw ( / I n v a l i d c o n t r a c t a d d r e s s / ) ;
309+ } ) ;
310+
311+ it ( 'scopes a caller allowlist to the exact (name, address) pair' , function ( ) {
312+ const builder = factoryProd . getContractBuilder ( ) ;
313+ builder . allowedStakingContracts ( [ { name : 'pox-5' , address : TEST_ADDRESS } ] ) ;
314+ builder . contractName ( 'pox-4' ) ;
315+ ( ( ) => builder . contractAddress ( TEST_ADDRESS ) ) . should . throw ( / n o t a n a l l o w l i s t e d p a i r / ) ;
316+ } ) ;
317+
318+ it ( 'rejects an unknown address' , function ( ) {
319+ const builder = factoryProd . getContractBuilder ( ) ;
320+ ( ( ) => builder . contractAddress ( 'SP2TYC2AVC4GSB01F78H0XSVTH8WRGV95BXFG4BBR' ) ) . should . throw (
321+ / I n v a l i d c o n t r a c t a d d r e s s /
322+ ) ;
323+ } ) ;
324+ } ) ;
325+
291326 it ( 'a signed contract call with args' , async ( ) => {
292327 const builder = initTxBuilder ( ) ;
293328 builder . functionArgs ( [
0 commit comments