Skip to content

Commit 169d9ca

Browse files
authored
fix: pre-cw2 migrate validations (#117)
## Description <!-- Brief description of changes --> ## Checklist - [x] I have updated the [docs/SPEC.md](https://github.com/babylonlabs-io/rollup-bsn-contracts/blob/main/docs/SPEC.md) file if this change affects the specification - [x] I have updated the schema by running `cargo gen-schema`
1 parent 4088533 commit 169d9ca

File tree

5 files changed

+92
-45
lines changed

5 files changed

+92
-45
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
3939

4040
### Improvements
4141

42-
* [#114](https://github.com/babylonlabs-io/rollup-bsn-contracts/pull/114) feat: migration setup
42+
* [#114](https://github.com/babylonlabs-io/rollup-bsn-contracts/pull/114) feat:
43+
migration setup
44+
* [#117](https://github.com/babylonlabs-io/rollup-bsn-contracts/pull/117) fix:
45+
pre-cw2 migrate validations
4346

4447

4548
## v1.0.0-rc.0

contracts/finality/schema/finality.json

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -647,17 +647,8 @@
647647
"migrate": {
648648
"$schema": "http://json-schema.org/draft-07/schema#",
649649
"title": "MigrateMsg",
650-
"description": "Migration message for contract upgrades. This can be extended in the future to include migration-specific parameters.",
650+
"description": "Migration message for contract upgrades. Empty for non-state-breaking migrations. Can be extended in the future to include migration-specific parameters for state transformations.",
651651
"type": "object",
652-
"properties": {
653-
"version": {
654-
"description": "Optional version string for tracking migration",
655-
"type": [
656-
"string",
657-
"null"
658-
]
659-
}
660-
},
661652
"additionalProperties": false
662653
},
663654
"sudo": null,
Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
{
22
"$schema": "http://json-schema.org/draft-07/schema#",
33
"title": "MigrateMsg",
4-
"description": "Migration message for contract upgrades. This can be extended in the future to include migration-specific parameters.",
4+
"description": "Migration message for contract upgrades. Empty for non-state-breaking migrations. Can be extended in the future to include migration-specific parameters for state transformations.",
55
"type": "object",
6-
"properties": {
7-
"version": {
8-
"description": "Optional version string for tracking migration",
9-
"type": [
10-
"string",
11-
"null"
12-
]
13-
}
14-
},
156
"additionalProperties": false
167
}

contracts/finality/src/contract.rs

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use crate::state::public_randomness::{
2424
// Contract metadata for version tracking
2525
const CONTRACT_NAME: &str = "rollup-bsn/finality";
2626
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
27+
/// Placeholder version for contracts deployed before cw2 version tracking was implemented
28+
const LEGACY_VERSION: &str = "pre-cw2";
2729

2830
pub fn instantiate(
2931
mut deps: DepsMut<BabylonQuery>,
@@ -84,25 +86,42 @@ pub fn instantiate(
8486
}
8587

8688
/// Handle contract migration.
89+
///
8790
/// This function is called when the contract is migrated to a new version.
91+
/// It supports both:
92+
/// - Migrating from contracts with existing cw2 version info
93+
/// - Migrating from legacy contracts deployed without cw2 version tracking
94+
///
8895
/// For non-state-breaking migrations, this updates the contract version and logs the migration.
96+
/// State-breaking migrations would require additional logic in this function.
8997
pub fn migrate(
9098
deps: DepsMut<BabylonQuery>,
9199
_env: Env,
92100
_msg: MigrateMsg,
93101
) -> Result<Response<BabylonMsg>, ContractError> {
94-
// Get the current version stored in the contract
95-
let prev_version = cw2::get_contract_version(deps.storage)?;
96-
97-
// Validate that this is the expected contract
98-
if prev_version.contract != CONTRACT_NAME {
99-
return Err(ContractError::InvalidContractName {
100-
expected: CONTRACT_NAME.to_string(),
101-
actual: prev_version.contract,
102-
});
103-
}
102+
// Attempt to get the current version stored in the contract
103+
let prev_version = match cw2::get_contract_version(deps.storage) {
104+
Ok(version) => {
105+
// Contract has version info - validate it's the expected contract
106+
if version.contract != CONTRACT_NAME {
107+
return Err(ContractError::InvalidContractName {
108+
expected: CONTRACT_NAME.to_string(),
109+
actual: version.contract,
110+
});
111+
}
112+
version
113+
}
114+
Err(_) => {
115+
// No version info exists - this is a legacy contract
116+
// Create a placeholder version for logging purposes
117+
cw2::ContractVersion {
118+
contract: CONTRACT_NAME.to_string(),
119+
version: LEGACY_VERSION.to_string(),
120+
}
121+
}
122+
};
104123

105-
// Update to the new version
124+
// Update to the new version (this creates version info if it didn't exist)
106125
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
107126

108127
Ok(Response::new()
@@ -1638,6 +1657,7 @@ pub(crate) mod tests {
16381657

16391658
#[test]
16401659
fn test_migrate_basic() {
1660+
// Test 1: Migration from versioned contract
16411661
let mut deps = mock_deps_babylon();
16421662

16431663
// Simulate an older version being deployed initially (realistic migration scenario)
@@ -1655,12 +1675,36 @@ pub(crate) mod tests {
16551675
assert_eq!(res.attributes[1].key, "from_version");
16561676
assert_eq!(res.attributes[1].value, old_version); // Should be "0.9.0"
16571677
assert_eq!(res.attributes[2].key, "to_version");
1658-
assert_eq!(res.attributes[2].value, CONTRACT_VERSION); // Should be "1.0.0-rc.0"
1678+
assert_eq!(res.attributes[2].value, CONTRACT_VERSION);
16591679

16601680
// Verify the version was actually updated in storage
16611681
let stored_version = cw2::get_contract_version(deps.as_ref().storage).unwrap();
16621682
assert_eq!(stored_version.contract, CONTRACT_NAME);
16631683
assert_eq!(stored_version.version, CONTRACT_VERSION);
1684+
1685+
// Test 2: Migration from legacy contract (no version info)
1686+
let mut legacy_deps = mock_deps_babylon();
1687+
1688+
// Don't set any version info - simulates a legacy contract deployed without cw2
1689+
// This is the real-world scenario for testnet contracts
1690+
1691+
// Test migration from legacy contract
1692+
let migrate_msg = MigrateMsg {};
1693+
let res = migrate(legacy_deps.as_mut(), mock_env(), migrate_msg).unwrap();
1694+
1695+
// Check that migration response has correct attributes for legacy migration
1696+
assert_eq!(res.attributes.len(), 3);
1697+
assert_eq!(res.attributes[0].key, "action");
1698+
assert_eq!(res.attributes[0].value, "migrate");
1699+
assert_eq!(res.attributes[1].key, "from_version");
1700+
assert_eq!(res.attributes[1].value, LEGACY_VERSION); // Should be "pre-cw2"
1701+
assert_eq!(res.attributes[2].key, "to_version");
1702+
assert_eq!(res.attributes[2].value, CONTRACT_VERSION);
1703+
1704+
// Verify the version was set in storage (first time)
1705+
let stored_version = cw2::get_contract_version(legacy_deps.as_ref().storage).unwrap();
1706+
assert_eq!(stored_version.contract, CONTRACT_NAME);
1707+
assert_eq!(stored_version.version, CONTRACT_VERSION);
16641708
}
16651709

16661710
#[test]

docs/SPEC.md

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -477,24 +477,38 @@ in the contract's state and managed by the migration function.
477477
```rust
478478
/// Handle contract migration.
479479
/// This function is called when the contract is migrated to a new version.
480+
/// It supports both:
481+
/// - Migrating from contracts with existing cw2 version info
482+
/// - Migrating from legacy contracts deployed without cw2 version tracking
480483
/// For non-state-breaking migrations, this updates the contract version and logs the migration.
481484
pub fn migrate(
482485
deps: DepsMut<BabylonQuery>,
483486
_env: Env,
484487
_msg: MigrateMsg,
485488
) -> Result<Response<BabylonMsg>, ContractError> {
486-
// Get the current version stored in the contract
487-
let prev_version = cw2::get_contract_version(deps.storage)?;
488-
489-
// Validate that this is the expected contract
490-
if prev_version.contract != CONTRACT_NAME {
491-
return Err(ContractError::InvalidContractName {
492-
expected: CONTRACT_NAME.to_string(),
493-
actual: prev_version.contract,
494-
});
495-
}
496-
497-
// Update to the new version
489+
// Attempt to get the current version stored in the contract
490+
let prev_version = match cw2::get_contract_version(deps.storage) {
491+
Ok(version) => {
492+
// Contract has version info - validate it's the expected contract
493+
if version.contract != CONTRACT_NAME {
494+
return Err(ContractError::InvalidContractName {
495+
expected: CONTRACT_NAME.to_string(),
496+
actual: version.contract,
497+
});
498+
}
499+
version
500+
}
501+
Err(_) => {
502+
// No version info exists - this is a legacy contract
503+
// Create a placeholder version for logging purposes
504+
cw2::ContractVersion {
505+
contract: CONTRACT_NAME.to_string(),
506+
version: LEGACY_VERSION.to_string(),
507+
}
508+
}
509+
};
510+
511+
// Update to the new version (this creates version info if it didn't exist)
498512
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
499513

500514
Ok(Response::new()
@@ -507,6 +521,8 @@ pub fn migrate(
507521
**Migration Features:**
508522
- **Automatic Version Tracking**: Uses the `cw2` library for contract version
509523
management
524+
- **Legacy Contract Support**: Gracefully handles contracts deployed before
525+
`cw2` version tracking was implemented
510526
- **Contract Validation**: Ensures the contract name matches before migration
511527
- **Version Updates**: Automatically updates stored version information
512528
- **Detailed Logging**: Returns attributes showing the version transition
@@ -530,6 +546,8 @@ the expected name (`"rollup-bsn/finality"`).
530546
**Version Management:**
531547
- **Contract Name**: `"rollup-bsn/finality"` (constant)
532548
- **Contract Version**: Uses `CARGO_PKG_VERSION` from the package manifest
549+
- **Legacy Version**: `"pre-cw2"` (constant used for contracts deployed before
550+
version tracking)
533551
- **Storage**: Version information is stored using the `cw2` library
534552

535553
**Security Considerations:**

0 commit comments

Comments
 (0)