11import { BigNumber } from "bignumber.js" ;
2+ import { NotEnoughBalance } from "@ledgerhq/ledger-wallet-framework/errors" ;
23import { getTransactionStatus } from "./getTransactionStatus" ;
34import { prepareTransaction } from "./prepareTransaction" ;
45import {
@@ -9,10 +10,14 @@ import {
910 resolveTransparentUtxos ,
1011} from "./statusHelpers" ;
1112import { TRANSPARENT_OUTPUT_DUST_THRESHOLD , ZIP317_MINIMUM_FEE } from "../logic/coin-selection" ;
12- import { ZcashAmountBelowDustThreshold } from "../types/errors" ;
13+ import { ZcashAmountBelowDustThreshold , ZcashSendTooLarge } from "../types/errors" ;
1314import type { BitcoinOutput , Transaction , ZcashAccount , ZcashTransferType } from "../types/bridge" ;
1415import type { SpendableNote } from "../network/types" ;
15- import { ZCASH_SHIELDED_SPENDABILITY_DELAY_BLOCKS } from "../constants" ;
16+ import {
17+ ZCASH_MAX_IRONWOOD_ACTIONS ,
18+ ZCASH_MAX_TRANSPARENT_INPUTS ,
19+ ZCASH_SHIELDED_SPENDABILITY_DELAY_BLOCKS ,
20+ } from "../constants" ;
1621
1722const T_ADDRESS = "t1b1Rbw2shhJkP6MCnCyxCPuyFedHrwKty8" ;
1823const U_ADDRESS =
@@ -489,6 +494,122 @@ describe("getTransactionStatus, transparent-input flows", () => {
489494 } ) ;
490495} ) ;
491496
497+ describe ( "getTransactionStatus, bounded-selection shortfall (ZcashSendTooLarge)" , ( ) => {
498+ const FEE = 10_000 ;
499+
500+ it . each ( [ "transparent" , "transparent-to-shielded" ] as ZcashTransferType [ ] ) (
501+ "resolves without an amount error for a %s send within the bounded max, from an account above the bound" ,
502+ async transferType => {
503+ const utxoCount = ZCASH_MAX_TRANSPARENT_INPUTS + 5 ;
504+ const acc = account ( { utxos : Array ( utxoCount ) . fill ( 100_000 ) } ) ;
505+ const boundedBalance = ZCASH_MAX_TRANSPARENT_INPUTS * 100_000 ;
506+ const recipient = transferType === "transparent" ? T_ADDRESS : U_ADDRESS ;
507+ const tx = transaction ( {
508+ transferType,
509+ recipient,
510+ amount : new BigNumber ( boundedBalance - FEE ) ,
511+ zcashFee : new BigNumber ( FEE ) ,
512+ } ) ;
513+
514+ const status = await getTransactionStatus ( acc , tx ) ;
515+
516+ expect ( status . errors . amount ) . toBeUndefined ( ) ;
517+ } ,
518+ ) ;
519+
520+ it . each ( [ "transparent" , "transparent-to-shielded" ] as ZcashTransferType [ ] ) (
521+ "rejects a %s send between the bounded max and the full balance with ZcashSendTooLarge" ,
522+ async transferType => {
523+ const utxoCount = ZCASH_MAX_TRANSPARENT_INPUTS + 5 ;
524+ const acc = account ( { utxos : Array ( utxoCount ) . fill ( 100_000 ) } ) ;
525+ const boundedBalance = ZCASH_MAX_TRANSPARENT_INPUTS * 100_000 ;
526+ const fullBalance = utxoCount * 100_000 ;
527+ const recipient = transferType === "transparent" ? T_ADDRESS : U_ADDRESS ;
528+ // amount + FEE sits strictly between boundedBalance and fullBalance.
529+ const tx = transaction ( {
530+ transferType,
531+ recipient,
532+ amount : new BigNumber ( boundedBalance ) ,
533+ zcashFee : new BigNumber ( FEE ) ,
534+ } ) ;
535+
536+ const status = await getTransactionStatus ( acc , tx ) ;
537+
538+ expect ( status . errors . amount ) . toBeInstanceOf ( ZcashSendTooLarge ) ;
539+ expect ( status . errors . amount ) . not . toBeInstanceOf ( NotEnoughBalance ) ;
540+ expect ( boundedBalance + FEE ) . toBeLessThanOrEqual ( fullBalance ) ;
541+ } ,
542+ ) ;
543+
544+ it ( "keeps reporting NotEnoughBalance for a genuine shortfall, on an account below the bound" , async ( ) => {
545+ const acc = account ( { utxos : [ 10_000 , 10_000 , 10_000 ] } ) ; // full balance 30_000, well below the bound
546+ const tx = transaction ( {
547+ amount : new BigNumber ( 100_000 ) ,
548+ zcashFee : new BigNumber ( FEE ) ,
549+ } ) ;
550+
551+ const status = await getTransactionStatus ( acc , tx ) ;
552+
553+ expect ( status . errors . amount ) . toEqual ( new NotEnoughBalance ( ) ) ;
554+ expect ( status . errors . amount ) . not . toBeInstanceOf ( ZcashSendTooLarge ) ;
555+ } ) ;
556+
557+ it ( "resolves without an amount error for a shielded send within the bounded max, from a pool above the bound" , async ( ) => {
558+ const noteCount = ZCASH_MAX_IRONWOOD_ACTIONS + 5 ;
559+ const acc = account ( { ironwoodNotes : Array ( noteCount ) . fill ( 100_000 ) } ) ;
560+ const boundedTotal = ZCASH_MAX_IRONWOOD_ACTIONS * 100_000 ;
561+ const selectedNotes = Array . from ( { length : ZCASH_MAX_IRONWOOD_ACTIONS } , ( _ , i ) =>
562+ note ( 100_000 , i ) ,
563+ ) ;
564+ const tx = transaction ( {
565+ transferType : "shielded" ,
566+ recipient : U_ADDRESS ,
567+ amount : new BigNumber ( boundedTotal - FEE ) ,
568+ selectedNotes,
569+ zcashFee : new BigNumber ( FEE ) ,
570+ } ) ;
571+
572+ const status = await getTransactionStatus ( acc , tx ) ;
573+
574+ expect ( status . errors . amount ) . toBeUndefined ( ) ;
575+ } ) ;
576+
577+ it ( "rejects a shielded send between the bounded and full pool with ZcashSendTooLarge, not the generic insufficiency error" , async ( ) => {
578+ const noteCount = ZCASH_MAX_IRONWOOD_ACTIONS + 5 ;
579+ const acc = account ( { ironwoodNotes : Array ( noteCount ) . fill ( 100_000 ) } ) ;
580+ const boundedTotal = ZCASH_MAX_IRONWOOD_ACTIONS * 100_000 ;
581+ const fullTotal = noteCount * 100_000 ;
582+ const tx = transaction ( {
583+ transferType : "shielded" ,
584+ recipient : U_ADDRESS ,
585+ amount : new BigNumber ( boundedTotal ) ,
586+ zcashFee : new BigNumber ( FEE ) ,
587+ } ) ;
588+
589+ const status = await getTransactionStatus ( acc , tx ) ;
590+
591+ expect ( status . errors . amount ) . toBeInstanceOf ( ZcashSendTooLarge ) ;
592+ expect ( status . errors . amount ) . not . toEqual ( new Error ( "Insufficient shielded balance" ) ) ;
593+ expect ( boundedTotal + FEE ) . toBeLessThanOrEqual ( fullTotal ) ;
594+ } ) ;
595+
596+ it ( "keeps reporting the generic insufficiency error for a genuine shielded shortfall, on a pool below the bound" , async ( ) => {
597+ const acc = account ( { ironwoodNotes : [ 10_000 , 10_000 ] } ) ; // pool 20_000, well below the bound
598+ const tx = transaction ( {
599+ transferType : "shielded" ,
600+ recipient : U_ADDRESS ,
601+ amount : new BigNumber ( 100_000 ) ,
602+ selectedNotes : [ note ( 20_000 ) ] ,
603+ zcashFee : new BigNumber ( FEE ) ,
604+ } ) ;
605+
606+ const status = await getTransactionStatus ( acc , tx ) ;
607+
608+ expect ( status . errors . amount ) . toEqual ( new Error ( "Insufficient shielded balance" ) ) ;
609+ expect ( status . errors . amount ) . not . toBeInstanceOf ( ZcashSendTooLarge ) ;
610+ } ) ;
611+ } ) ;
612+
492613describe ( "getTransactionStatus, note-spending flows" , ( ) => {
493614 it ( "refuses to price anything before the shielded scan has run" , async ( ) => {
494615 const status = await getTransactionStatus (
0 commit comments