@@ -8,7 +8,8 @@ use serde_json::{json, Map, Value};
88
99use crate :: ckb_acceptance:: { self , ArtifactRecord , CompileEvidence } ;
1010use crate :: ckb_devnet:: {
11- always_success_dep, decode_hex, deploy_code, out_point, resolve_ckb_bin, sha256_hex, CkbDevnet , ALWAYS_SUCCESS_CODE_HASH ,
11+ always_success_dep, decode_hex, deploy_code, funding_cells, out_point, resolve_ckb_bin, sha256_hex, CkbDevnet ,
12+ ALWAYS_SUCCESS_CODE_HASH ,
1213} ;
1314use crate :: production_evidence:: { ACTION_RUNS , EXPECTED_END_TO_END_STATEFUL_SCENARIOS , EXPECTED_EXAMPLES , LOCKS } ;
1415
@@ -183,7 +184,7 @@ impl Replayer<'_> {
183184 Ok ( tx)
184185 }
185186
186- fn balance_change_capacity ( & self , tx : & mut Value ) -> Result < ( ) > {
187+ fn balance_change_capacity ( & mut self , tx : & mut Value ) -> Result < ( ) > {
187188 let mut input_capacity = 0_u64 ;
188189 for input in tx[ "inputs" ] . as_array ( ) . context ( "transaction inputs missing" ) ? {
189190 let live = self . devnet . rpc ( "get_live_cell" , vec ! [ input[ "previous_output" ] . clone( ) , json!( false ) ] ) ?;
@@ -210,16 +211,35 @@ impl Replayer<'_> {
210211 && output[ "type" ] . is_null ( )
211212 && data. as_str ( ) . is_some_and ( |value| value == "0x" )
212213 } )
213- . map ( |( index, _) | index)
214- . context ( "rebound transaction is under-capacity and has no adjustable change output" ) ?;
215- let old_change = parse_hex_u64 ( & outputs[ candidate] [ "capacity" ] ) ?;
216- let fixed = output_capacity - old_change;
217- let new_change = input_capacity. checked_sub ( fixed) . context ( "rebound transaction inputs cannot fund fixed outputs" ) ?;
214+ . map ( |( index, _) | index) ;
218215 const ALWAYS_SUCCESS_EMPTY_OCCUPIED : u64 = 4_100_000_000 ;
219- if new_change < ALWAYS_SUCCESS_EMPTY_OCCUPIED {
220- bail ! ( "rebound change output would be under occupied capacity: {new_change}" ) ;
216+ if let Some ( candidate) = candidate {
217+ let old_change = parse_hex_u64 ( & outputs[ candidate] [ "capacity" ] ) ?;
218+ let fixed = output_capacity - old_change;
219+ if let Some ( new_change) = input_capacity. checked_sub ( fixed)
220+ && new_change >= ALWAYS_SUCCESS_EMPTY_OCCUPIED
221+ {
222+ tx[ "outputs" ] [ candidate] [ "capacity" ] = json ! ( format!( "0x{new_change:x}" ) ) ;
223+ return Ok ( ( ) ) ;
224+ }
225+ }
226+
227+ // Some recipe transactions intentionally have no disposable change
228+ // output: every output is a typed scenario cell. A replacement
229+ // cellbase input can be smaller than the original fixture input, so
230+ // add fresh always-success funding instead of mutating scenario state.
231+ let deficit = output_capacity - input_capacity;
232+ let funding = self . devnet . collect_spendable ( deficit) ?;
233+ let inputs = tx[ "inputs" ] . as_array_mut ( ) . context ( "transaction inputs missing" ) ?;
234+ for cell in funding_cells ( & funding) {
235+ inputs. push ( json ! ( {
236+ "previous_output" : out_point(
237+ cell[ "tx_hash" ] . as_str( ) . context( "funding transaction hash missing" ) ?,
238+ cell[ "index" ] . as_u64( ) . context( "funding output index missing" ) ?,
239+ ) ,
240+ "since" : "0x0" ,
241+ } ) ) ;
221242 }
222- tx[ "outputs" ] [ candidate] [ "capacity" ] = json ! ( format!( "0x{new_change:x}" ) ) ;
223243 Ok ( ( ) )
224244 }
225245}
0 commit comments