@@ -25,18 +25,44 @@ pub struct SplitInitializedEvent {
2525 pub timestamp : u64 ,
2626}
2727
28+ /// Domain-separated authorization payload for `initialize_split`.
29+ /// Passed to `require_auth_for_args` to bind the authorization to the
30+ /// specific operation parameters, preventing replay across different calls.
31+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
32+ #[ contracttype]
33+ pub struct SplitAuthPayload {
34+ pub domain_id : Symbol ,
35+ pub network_id : BytesN < 32 > ,
36+ pub contract_addr : Address ,
37+ pub owner_addr : Address ,
38+ pub nonce_val : u64 ,
39+ pub usdc_contract : Address ,
40+ pub spending_percent : u32 ,
41+ pub savings_percent : u32 ,
42+ pub bills_percent : u32 ,
43+ pub insurance_percent : u32 ,
44+ }
45+
2846#[ contracterror]
2947#[ derive( Copy , Clone , Debug , Eq , PartialEq , PartialOrd , Ord ) ]
3048#[ repr( u32 ) ]
3149pub enum RemittanceSplitError {
3250 AlreadyInitialized = 1 ,
51+ /// The contract has not been initialized yet; `initialize_split` must be called first.
3352 NotInitialized = 2 ,
53+ /// One or more split percentages are invalid: either a field exceeds 100 or the four
54+ /// fields do not sum to exactly 100.
3455 InvalidPercentages = 3 ,
3556 InvalidAmount = 4 ,
3657 Overflow = 5 ,
58+ /// The caller is not authorized to perform this operation.
3759 Unauthorized = 6 ,
3860 InvalidNonce = 7 ,
61+ /// The snapshot's `schema_version` is outside the supported range
62+ /// `[MIN_SUPPORTED_SCHEMA_VERSION, SCHEMA_VERSION]`.
3963 UnsupportedVersion = 8 ,
64+ /// The snapshot's `checksum` field does not match the value computed from its contents;
65+ /// the payload may have been tampered with or corrupted.
4066 ChecksumMismatch = 9 ,
4167 InvalidDueDate = 10 ,
4268 ScheduleNotFound = 11 ,
@@ -1022,20 +1048,36 @@ impl RemittanceSplit {
10221048 } ) )
10231049 }
10241050
1025- /// Import a previously exported snapshot after validating version and checksum.
1051+ /// Import a previously exported snapshot, restoring the full `SplitConfig` and
1052+ /// associated `RemittanceSchedule` list to on-chain storage after running the
1053+ /// complete validation pipeline.
10261054 ///
10271055 /// # Arguments
10281056 /// * `caller` - Split owner address (must authorize)
10291057 /// * `nonce` - Replay-protection nonce (must equal `get_nonce(caller)`)
10301058 /// * `snapshot` - Serialized configuration snapshot to restore
10311059 ///
10321060 /// # Errors
1033- /// - `Unauthorized` if `caller` is not the split owner or the contract is paused
1034- /// - `InvalidNonce` if the replay-protection nonce does not match
1035- /// - `UnsupportedVersion` if the snapshot schema version is not supported
1036- /// - `ChecksumMismatch` if the snapshot checksum is invalid
1037- /// - `PercentagesDoNotSumTo100` if the imported configuration is malformed
1038- /// - `NotInitialized` if no existing configuration is present to authorize the caller
1061+ /// * `UnsupportedVersion` — `snapshot.schema_version` is outside
1062+ /// `[MIN_SUPPORTED_SCHEMA_VERSION, SCHEMA_VERSION]`.
1063+ /// * `ChecksumMismatch` — `snapshot.checksum` does not match the value computed
1064+ /// by `compute_checksum` over the snapshot fields.
1065+ /// * `SnapshotNotInitialized` — `snapshot.config.initialized` is `false`; the
1066+ /// snapshot represents an incomplete or factory-default configuration.
1067+ /// * `InvalidPercentageRange` — at least one of `spending_percent`,
1068+ /// `savings_percent`, `bills_percent`, or `insurance_percent` exceeds `100`.
1069+ /// * `InvalidPercentages` — all four percentage fields are within `[0, 100]` but
1070+ /// their sum is not equal to `100`.
1071+ /// * `InvalidAmount` — `snapshot.config.timestamp` is greater than the current
1072+ /// ledger timestamp, indicating a future-dated or replayed payload.
1073+ /// * `Unauthorized` — `caller` is not the current on-chain owner stored in
1074+ /// instance storage, or the contract is paused.
1075+ /// * `OwnerMismatch` — `snapshot.config.owner` does not equal `caller`, which
1076+ /// would silently transfer ownership if allowed.
1077+ /// * `NotInitialized` — no existing `SplitConfig` is present in instance storage
1078+ /// (the contract has not been initialized).
1079+ /// * `InvalidNonce` — the provided `nonce` has already been used or does not
1080+ /// match the expected replay-protection value for `caller`.
10391081 pub fn import_snapshot (
10401082 env : Env ,
10411083 caller : Address ,
@@ -1084,7 +1126,7 @@ impl RemittanceSplit {
10841126 + snapshot. config . insurance_percent ;
10851127 if total != 100 {
10861128 Self :: append_audit ( & env, symbol_short ! ( "import" ) , & caller, false ) ;
1087- return Err ( RemittanceSplitError :: InvalidPercentages ) ;
1129+ return Err ( e ) ;
10881130 }
10891131
10901132 // 6. Timestamp sanity — reject payloads whose timestamps are in the future.
@@ -1157,29 +1199,41 @@ impl RemittanceSplit {
11571199
11581200 /// Verify snapshot integrity without importing it.
11591201 ///
1160- /// Runs the same checks as `import_snapshot` (version boundary, checksum,
1161- /// initialized flag, per-field range, sum constraint, and timestamp sanity)
1162- /// **without** modifying any contract state. Use this as a pre-flight check
1163- /// before calling `import_snapshot`.
1202+ /// Runs the same validation pipeline as `import_snapshot` steps 2–7 — schema
1203+ /// version boundary, checksum integrity, initialized flag, per-field percentage
1204+ /// range, percentage sum constraint, and timestamp sanity — **without** modifying
1205+ /// any contract state. Use this as a read-only pre-flight check before calling
1206+ /// `import_snapshot`.
11641207 ///
1165- /// Returns `Ok(true)` when the snapshot is valid and ready to import.
1166- /// Returns an error variant describing the first failing check.
1208+ /// Returns `Ok(true)` when all checks pass and the snapshot is ready to import.
1209+ ///
1210+ /// # Errors
1211+ /// - `UnsupportedVersion` — `snapshot.schema_version` is outside
1212+ /// `[MIN_SUPPORTED_SCHEMA_VERSION, SCHEMA_VERSION]`.
1213+ /// - `ChecksumMismatch` — the stored checksum does not match the freshly
1214+ /// computed digest over the snapshot fields.
1215+ /// - `SnapshotNotInitialized` — `snapshot.config.initialized` is `false`.
1216+ /// - `InvalidPercentageRange` — at least one of the four percentage fields
1217+ /// individually exceeds `100`.
1218+ /// - `InvalidPercentages` — all four fields are ≤ 100 but their sum is not `100`.
1219+ /// - `InvalidAmount` — `snapshot.config.timestamp` is greater than the current
1220+ /// ledger timestamp (future-dated payload).
11671221 ///
11681222 /// # Note
1169- /// This function does **not** verify ownership mapping (that requires knowing
1170- /// which address will perform the import) or nonce validity .
1223+ /// This function does **not** check ownership mapping or nonce validity; those
1224+ /// require a specific caller context and are only enforced by `import_snapshot` .
11711225 pub fn verify_snapshot (
11721226 env : Env ,
11731227 snapshot : ExportSnapshot ,
11741228 ) -> Result < bool , RemittanceSplitError > {
1175- // 1. Version boundary
1229+ // Step 2. Schema version boundary
11761230 if snapshot. schema_version < MIN_SUPPORTED_SCHEMA_VERSION
11771231 || snapshot. schema_version > SCHEMA_VERSION
11781232 {
11791233 return Err ( RemittanceSplitError :: UnsupportedVersion ) ;
11801234 }
11811235
1182- // 2 . Checksum
1236+ // Step 3 . Checksum integrity
11831237 let expected = Self :: compute_checksum (
11841238 snapshot. schema_version ,
11851239 & snapshot. config ,
@@ -1189,7 +1243,7 @@ impl RemittanceSplit {
11891243 return Err ( RemittanceSplitError :: ChecksumMismatch ) ;
11901244 }
11911245
1192- // 3 . Initialized flag
1246+ // Step 4 . Initialized flag
11931247 if !snapshot. config . initialized {
11941248 return Err ( RemittanceSplitError :: NotInitialized ) ;
11951249 }
0 commit comments