@@ -58,6 +58,12 @@ impl From<OldConfig> for Config {
5858 fn from ( old : OldConfig ) -> Self {
5959 // Carry the deployed values; the attestation-storage fee is new in this release, so
6060 // it takes its default.
61+ //
62+ // `clean_invalid_attestations_tera_gas` is the deliberate exception: it is reset to
63+ // the new default rather than carried over. The deployed 10 TGas cannot fund the scan
64+ // `vote_reshared` requests, and the promise is detached, so every removal rolls back
65+ // unnoticed (#4035). Carrying it forward would leave the fix unreachable without a
66+ // governance config vote.
6167 Config {
6268 key_event_timeout_blocks : old. key_event_timeout_blocks ,
6369 tee_upgrade_deadline_duration_seconds : old. tee_upgrade_deadline_duration_seconds ,
@@ -73,7 +79,8 @@ impl From<OldConfig> for Config {
7379 fail_on_timeout_tera_gas : old. fail_on_timeout_tera_gas ,
7480 fail_attestation_submission_tera_gas : old. fail_attestation_submission_tera_gas ,
7581 clean_tee_status_tera_gas : old. clean_tee_status_tera_gas ,
76- clean_invalid_attestations_tera_gas : old. clean_invalid_attestations_tera_gas ,
82+ clean_invalid_attestations_tera_gas : Config :: default ( )
83+ . clean_invalid_attestations_tera_gas ,
7784 cleanup_orphaned_node_migrations_tera_gas : old
7885 . cleanup_orphaned_node_migrations_tera_gas ,
7986 remove_non_participant_update_votes_tera_gas : old
@@ -153,3 +160,131 @@ struct Metrics {
153160 sign_with_v1_payload_count : u64 ,
154161 sign_with_v2_payload_count : u64 ,
155162}
163+
164+ #[ cfg( test) ]
165+ #[ expect( non_snake_case) ]
166+ mod tests {
167+ use super :: * ;
168+
169+ /// Every field distinguishable from [`Config::default()`], so a carried-forward value
170+ /// cannot be mistaken for a defaulted one.
171+ fn deployed_config ( ) -> OldConfig {
172+ OldConfig {
173+ key_event_timeout_blocks : 1 ,
174+ tee_upgrade_deadline_duration_seconds : 2 ,
175+ contract_upgrade_deposit_tera_gas : 3 ,
176+ sign_call_gas_attachment_requirement_tera_gas : 4 ,
177+ ckd_call_gas_attachment_requirement_tera_gas : 5 ,
178+ return_signature_and_clean_state_on_success_call_tera_gas : 6 ,
179+ return_ck_and_clean_state_on_success_call_tera_gas : 7 ,
180+ fail_on_timeout_tera_gas : 8 ,
181+ fail_attestation_submission_tera_gas : 9 ,
182+ clean_tee_status_tera_gas : 10 ,
183+ clean_invalid_attestations_tera_gas : 11 ,
184+ cleanup_orphaned_node_migrations_tera_gas : 12 ,
185+ remove_non_participant_update_votes_tera_gas : 13 ,
186+ clean_foreign_chain_data_tera_gas : 14 ,
187+ remove_non_participant_tee_verifier_votes_tera_gas : 15 ,
188+ verifier_tera_gas : 16 ,
189+ resolve_verification_tera_gas : 17 ,
190+ launcher_hash_unused_ttl_seconds : 18 ,
191+ }
192+ }
193+
194+ /// The deployed budget cannot fund the scan [`crate::MpcContract::vote_reshared`] requests,
195+ /// and the promise is detached, so the sweep rolls back silently. Migration resets this one
196+ /// field so the fix lands on upgrade rather than needing a governance vote (#4035).
197+ #[ test]
198+ fn config_migration__should_reset_clean_invalid_attestations_gas_to_the_new_default ( ) {
199+ // given
200+ let old = deployed_config ( ) ;
201+ assert_ne ! (
202+ old. clean_invalid_attestations_tera_gas,
203+ Config :: default ( ) . clean_invalid_attestations_tera_gas
204+ ) ;
205+
206+ // when
207+ let migrated = Config :: from ( old) ;
208+
209+ // then
210+ assert_eq ! (
211+ migrated. clean_invalid_attestations_tera_gas,
212+ Config :: default ( ) . clean_invalid_attestations_tera_gas
213+ ) ;
214+ }
215+
216+ /// The reset must not leak into any other field an operator may have voted in.
217+ #[ test]
218+ fn config_migration__should_carry_every_other_deployed_value_forward ( ) {
219+ // given / when
220+ let old = deployed_config ( ) ;
221+ let migrated = Config :: from ( deployed_config ( ) ) ;
222+
223+ // then
224+ assert_eq ! (
225+ migrated. key_event_timeout_blocks,
226+ old. key_event_timeout_blocks
227+ ) ;
228+ assert_eq ! (
229+ migrated. tee_upgrade_deadline_duration_seconds,
230+ old. tee_upgrade_deadline_duration_seconds
231+ ) ;
232+ assert_eq ! (
233+ migrated. contract_upgrade_deposit_tera_gas,
234+ old. contract_upgrade_deposit_tera_gas
235+ ) ;
236+ assert_eq ! (
237+ migrated. sign_call_gas_attachment_requirement_tera_gas,
238+ old. sign_call_gas_attachment_requirement_tera_gas
239+ ) ;
240+ assert_eq ! (
241+ migrated. ckd_call_gas_attachment_requirement_tera_gas,
242+ old. ckd_call_gas_attachment_requirement_tera_gas
243+ ) ;
244+ assert_eq ! (
245+ migrated. return_signature_and_clean_state_on_success_call_tera_gas,
246+ old. return_signature_and_clean_state_on_success_call_tera_gas
247+ ) ;
248+ assert_eq ! (
249+ migrated. return_ck_and_clean_state_on_success_call_tera_gas,
250+ old. return_ck_and_clean_state_on_success_call_tera_gas
251+ ) ;
252+ assert_eq ! (
253+ migrated. fail_on_timeout_tera_gas,
254+ old. fail_on_timeout_tera_gas
255+ ) ;
256+ assert_eq ! (
257+ migrated. fail_attestation_submission_tera_gas,
258+ old. fail_attestation_submission_tera_gas
259+ ) ;
260+ assert_eq ! (
261+ migrated. clean_tee_status_tera_gas,
262+ old. clean_tee_status_tera_gas
263+ ) ;
264+ assert_eq ! (
265+ migrated. cleanup_orphaned_node_migrations_tera_gas,
266+ old. cleanup_orphaned_node_migrations_tera_gas
267+ ) ;
268+ assert_eq ! (
269+ migrated. remove_non_participant_update_votes_tera_gas,
270+ old. remove_non_participant_update_votes_tera_gas
271+ ) ;
272+ assert_eq ! (
273+ migrated. clean_foreign_chain_data_tera_gas,
274+ old. clean_foreign_chain_data_tera_gas
275+ ) ;
276+ assert_eq ! (
277+ migrated. remove_non_participant_tee_verifier_votes_tera_gas,
278+ old. remove_non_participant_tee_verifier_votes_tera_gas
279+ ) ;
280+ assert_eq ! ( migrated. verifier_tera_gas, old. verifier_tera_gas) ;
281+ assert_eq ! (
282+ migrated. resolve_verification_tera_gas,
283+ old. resolve_verification_tera_gas
284+ ) ;
285+ assert_eq ! (
286+ migrated. launcher_hash_unused_ttl_seconds,
287+ old. launcher_hash_unused_ttl_seconds
288+ ) ;
289+ }
290+ }
0 commit comments