diff --git a/lang/syn/src/parser/accounts/mod.rs b/lang/syn/src/parser/accounts/mod.rs index 895a25891b..f2cb49d9e6 100644 --- a/lang/syn/src/parser/accounts/mod.rs +++ b/lang/syn/src/parser/accounts/mod.rs @@ -387,6 +387,18 @@ fn constraints_cross_checks(fields: &[AccountField]) -> ParseResult<()> { } } + // MIGRATION + for field in fields { + if let AccountField::Field(f) = field { + if matches!(f.ty, Ty::Migration(_)) && !f.constraints.is_mutable() { + return Err(ParseError::new( + f.ident.span(), + "Migration accounts must be mutable. Add `#[account(mut)]`", + )); + } + } + } + Ok(()) } diff --git a/lang/syn/tests/migration_mut.rs b/lang/syn/tests/migration_mut.rs new file mode 100644 index 0000000000..9cc2ccea07 --- /dev/null +++ b/lang/syn/tests/migration_mut.rs @@ -0,0 +1,30 @@ +use anchor_syn::AccountsStruct; + +#[test] +fn test_migration_requires_mut_constraint() { + let result = syn::parse_str::( + r#" + pub struct MigrateTest<'info> { + pub state: Migration<'info, V1, V2>, + } + "#, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!(err.contains("Migration accounts must be mutable")); +} + +#[test] +fn test_migration_with_mut_constraint_succeeds() { + let result = syn::parse_str::( + r#" + pub struct MigrateTest<'info> { + #[account(mut)] + pub state: Migration<'info, V1, V2>, + } + "#, + ); + + assert!(result.is_ok()); +}