@@ -124,7 +124,10 @@ impl Utxo {
124124 ..
125125 } => {
126126 if let Some ( prev_tx) = & psbt_input. non_witness_utxo {
127- return & prev_tx. output [ outpoint. vout as usize ] ;
127+ return prev_tx
128+ . output
129+ . get ( outpoint. vout as usize )
130+ . expect ( "outpoint vout must be in bounds" ) ;
128131 }
129132
130133 if let Some ( txout) = & psbt_input. witness_utxo {
@@ -172,3 +175,90 @@ impl fmt::Display for IndexOutOfBoundsError {
172175}
173176
174177impl core:: error:: Error for IndexOutOfBoundsError { }
178+
179+ #[ cfg( test) ]
180+ mod test {
181+ use super :: * ;
182+ use alloc:: string:: String ;
183+
184+ use bitcoin:: absolute:: LockTime ;
185+ use bitcoin:: transaction:: { OutPoint , Sequence , TxOut , Version } ;
186+ use bitcoin:: { psbt, Amount , ScriptBuf , Transaction } ;
187+
188+ #[ test]
189+ fn test_foreign_utxo_txout_with_valid_vout ( ) {
190+ let prev_tx = Transaction {
191+ version : Version :: TWO ,
192+ lock_time : LockTime :: ZERO ,
193+ input : vec ! [ ] ,
194+ output : vec ! [
195+ TxOut {
196+ value: Amount :: from_sat( 1_000 ) ,
197+ script_pubkey: ScriptBuf :: new( ) ,
198+ } ,
199+ TxOut {
200+ value: Amount :: from_sat( 2_000 ) ,
201+ script_pubkey: ScriptBuf :: new( ) ,
202+ } ,
203+ ] ,
204+ } ;
205+
206+ let outpoint = OutPoint {
207+ txid : prev_tx. compute_txid ( ) ,
208+ vout : 1 ,
209+ } ;
210+
211+ let mut psbt_input = psbt:: Input :: default ( ) ;
212+ psbt_input. non_witness_utxo = Some ( prev_tx) ;
213+
214+ let utxo = Utxo :: Foreign {
215+ outpoint,
216+ sequence : Sequence :: ENABLE_RBF_NO_LOCKTIME ,
217+ psbt_input : Box :: new ( psbt_input) ,
218+ } ;
219+
220+ let txout = utxo. txout ( ) ;
221+ assert_eq ! ( txout. value, Amount :: from_sat( 2_000 ) ) ;
222+ }
223+
224+ #[ test]
225+ fn test_foreign_utxo_txout_with_invalid_vout_panics_with_message ( ) {
226+ let prev_tx = Transaction {
227+ version : Version :: TWO ,
228+ lock_time : LockTime :: ZERO ,
229+ input : vec ! [ ] ,
230+ output : vec ! [ TxOut {
231+ value: Amount :: from_sat( 1_000 ) ,
232+ script_pubkey: ScriptBuf :: new( ) ,
233+ } ] ,
234+ } ;
235+
236+ let outpoint = OutPoint {
237+ txid : prev_tx. compute_txid ( ) ,
238+ vout : 5 , // out of bounds: only 1 output
239+ } ;
240+
241+ let mut psbt_input = psbt:: Input :: default ( ) ;
242+ psbt_input. non_witness_utxo = Some ( prev_tx) ;
243+
244+ let utxo = Utxo :: Foreign {
245+ outpoint,
246+ sequence : Sequence :: ENABLE_RBF_NO_LOCKTIME ,
247+ psbt_input : Box :: new ( psbt_input) ,
248+ } ;
249+
250+ let result = std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( || {
251+ utxo. txout ( ) ;
252+ } ) ) ;
253+ let err = result. expect_err ( "txout() must panic for out-of-bounds vout" ) ;
254+ let msg = err
255+ . downcast_ref :: < & str > ( )
256+ . copied ( )
257+ . or_else ( || err. downcast_ref :: < String > ( ) . map ( |s| s. as_str ( ) ) )
258+ . expect ( "panic payload must be a string" ) ;
259+ assert ! (
260+ msg. contains( "outpoint vout must be in bounds" ) ,
261+ "expected descriptive panic message, got: {msg}"
262+ ) ;
263+ }
264+ }
0 commit comments