@@ -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,94 @@ 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 psbt_input = psbt:: Input {
212+ non_witness_utxo : Some ( prev_tx) ,
213+ ..Default :: default ( )
214+ } ;
215+
216+ let utxo = Utxo :: Foreign {
217+ outpoint,
218+ sequence : Sequence :: ENABLE_RBF_NO_LOCKTIME ,
219+ psbt_input : Box :: new ( psbt_input) ,
220+ } ;
221+
222+ let txout = utxo. txout ( ) ;
223+ assert_eq ! ( txout. value, Amount :: from_sat( 2_000 ) ) ;
224+ }
225+
226+ #[ test]
227+ fn test_foreign_utxo_txout_with_invalid_vout_panics_with_message ( ) {
228+ let prev_tx = Transaction {
229+ version : Version :: TWO ,
230+ lock_time : LockTime :: ZERO ,
231+ input : vec ! [ ] ,
232+ output : vec ! [ TxOut {
233+ value: Amount :: from_sat( 1_000 ) ,
234+ script_pubkey: ScriptBuf :: new( ) ,
235+ } ] ,
236+ } ;
237+
238+ let outpoint = OutPoint {
239+ txid : prev_tx. compute_txid ( ) ,
240+ vout : 5 , // out of bounds: only 1 output
241+ } ;
242+
243+ let psbt_input = psbt:: Input {
244+ non_witness_utxo : Some ( prev_tx) ,
245+ ..Default :: default ( )
246+ } ;
247+
248+ let utxo = Utxo :: Foreign {
249+ outpoint,
250+ sequence : Sequence :: ENABLE_RBF_NO_LOCKTIME ,
251+ psbt_input : Box :: new ( psbt_input) ,
252+ } ;
253+
254+ let result = std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( || {
255+ utxo. txout ( ) ;
256+ } ) ) ;
257+ let err = result. expect_err ( "txout() must panic for out-of-bounds vout" ) ;
258+ let msg = err
259+ . downcast_ref :: < & str > ( )
260+ . copied ( )
261+ . or_else ( || err. downcast_ref :: < String > ( ) . map ( |s| s. as_str ( ) ) )
262+ . expect ( "panic payload must be a string" ) ;
263+ assert ! (
264+ msg. contains( "outpoint vout must be in bounds" ) ,
265+ "expected descriptive panic message, got: {msg}"
266+ ) ;
267+ }
268+ }
0 commit comments