Skip to content

Commit 0a5315c

Browse files
sanityclaude
andcommitted
refactor: make ContractKey.code non-optional
BREAKING CHANGE: ContractKey now always contains a CodeHash. Changes: - Remove `ContractKey::from_id()` - created incomplete keys - Remove `From<ContractInstanceId> for ContractKey` - lost code hash - `code_hash()` now returns `&CodeHash` instead of `Option<&CodeHash>` - `encoded_code_hash()` now returns `String` instead of `Option<String>` - `TryFromFbs` now errors if code hash is missing in wire format This eliminates a class of bugs where incomplete ContractKeys (missing code hash) were passed to operations requiring complete keys, causing runtime failures. Now the type system enforces completeness at compile time. Use `ContractInstanceId` for operations that only need the instance ID (routing, client lookups), and `ContractKey` for operations that need the full specification (storage, execution). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 48e3d23 commit 0a5315c

File tree

2 files changed

+26
-48
lines changed

2 files changed

+26
-48
lines changed

rust/src/client_api/client_events.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -958,9 +958,7 @@ impl HostResponse {
958958
},
959959
);
960960

961-
let code = key
962-
.code_hash()
963-
.map(|code| builder.create_vector(code.0.as_ref()));
961+
let code = Some(builder.create_vector(&key.code_hash().0));
964962
let key_offset = FbsContractKey::create(
965963
&mut builder,
966964
&ContractKeyArgs {
@@ -1004,9 +1002,7 @@ impl HostResponse {
10041002
},
10051003
);
10061004

1007-
let code = key
1008-
.code_hash()
1009-
.map(|code| builder.create_vector(code.0.as_ref()));
1005+
let code = Some(builder.create_vector(&key.code_hash().0));
10101006

10111007
let key_offset = FbsContractKey::create(
10121008
&mut builder,
@@ -1058,7 +1054,7 @@ impl HostResponse {
10581054
},
10591055
);
10601056

1061-
let code = key.code_hash().map(|code| builder.create_vector(&code.0));
1057+
let code = Some(builder.create_vector(&key.code_hash().0));
10621058
let key_offset = FbsContractKey::create(
10631059
&mut builder,
10641060
&ContractKeyArgs {
@@ -1075,10 +1071,7 @@ impl HostResponse {
10751071
&ContractInstanceIdArgs { data: Some(data) },
10761072
);
10771073

1078-
let code = contract
1079-
.key()
1080-
.code_hash()
1081-
.map(|code| builder.create_vector(&code.0));
1074+
let code = Some(builder.create_vector(&contract.key().code_hash().0));
10821075
let contract_key_offset = FbsContractKey::create(
10831076
&mut builder,
10841077
&ContractKeyArgs {
@@ -1164,9 +1157,7 @@ impl HostResponse {
11641157
},
11651158
);
11661159

1167-
let code = key
1168-
.code_hash()
1169-
.map(|code| builder.create_vector(code.0.as_ref()));
1160+
let code = Some(builder.create_vector(&key.code_hash().0));
11701161
let key_offset = FbsContractKey::create(
11711162
&mut builder,
11721163
&ContractKeyArgs {

rust/src/contract_interface/key.rs

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
//! Contract key types and identifiers.
22
//!
33
//! This module provides the core types for identifying contracts:
4-
//! - `ContractInstanceId`: The hash of contract code and parameters
5-
//! - `ContractKey`: A complete key specification with optional code hash
4+
//! - `ContractInstanceId`: The hash of contract code and parameters (use for routing/lookup)
5+
//! - `ContractKey`: A complete key specification with code hash (use for storage/execution)
66
77
use std::borrow::Borrow;
88
use std::fmt::Display;
@@ -104,6 +104,11 @@ impl std::fmt::Debug for ContractInstanceId {
104104
}
105105

106106
/// A complete key specification, that represents a cryptographic hash that identifies the contract.
107+
///
108+
/// This type always contains both the instance ID and the code hash.
109+
/// Use `ContractInstanceId` for operations that only need to identify the contract
110+
/// (routing, client requests), and `ContractKey` for operations that need the full
111+
/// specification (storage, execution).
107112
#[serde_as]
108113
#[derive(Debug, Eq, Copy, Clone, Serialize, Deserialize)]
109114
#[cfg_attr(
@@ -112,7 +117,7 @@ impl std::fmt::Debug for ContractInstanceId {
112117
)]
113118
pub struct ContractKey {
114119
instance: ContractInstanceId,
115-
code: Option<CodeHash>,
120+
code: CodeHash,
116121
}
117122

118123
impl ContractKey {
@@ -122,39 +127,28 @@ impl ContractKey {
122127
) -> Self {
123128
let code = wasm_code.borrow();
124129
let id = generate_id(params.borrow(), code);
125-
let code_hash = code.hash();
130+
let code_hash = *code.hash();
126131
Self {
127132
instance: id,
128-
code: Some(*code_hash),
133+
code: code_hash,
129134
}
130135
}
131136

132-
/// Builds a partial [`ContractKey`](ContractKey), the contract code part is unspecified.
133-
pub fn from_id(instance: impl Into<String>) -> Result<Self, bs58::decode::Error> {
134-
let instance = ContractInstanceId::try_from(instance.into())?;
135-
Ok(Self {
136-
instance,
137-
code: None,
138-
})
139-
}
140-
141137
/// Gets the whole spec key hash.
142138
pub fn as_bytes(&self) -> &[u8] {
143139
self.instance.0.as_ref()
144140
}
145141

146-
/// Returns the hash of the contract code only, if the key is fully specified.
147-
pub fn code_hash(&self) -> Option<&CodeHash> {
148-
self.code.as_ref()
142+
/// Returns the hash of the contract code.
143+
pub fn code_hash(&self) -> &CodeHash {
144+
&self.code
149145
}
150146

151-
/// Returns the encoded hash of the contract code, if the key is fully specified.
152-
pub fn encoded_code_hash(&self) -> Option<String> {
153-
self.code.as_ref().map(|c| {
154-
bs58::encode(c.0)
155-
.with_alphabet(bs58::Alphabet::BITCOIN)
156-
.into_string()
157-
})
147+
/// Returns the encoded hash of the contract code.
148+
pub fn encoded_code_hash(&self) -> String {
149+
bs58::encode(self.code.0)
150+
.with_alphabet(bs58::Alphabet::BITCOIN)
151+
.into_string()
158152
}
159153

160154
/// Returns the contract key from the encoded hash of the contract code and the given
@@ -177,7 +171,7 @@ impl ContractKey {
177171
spec.copy_from_slice(&full_key_arr);
178172
Ok(Self {
179173
instance: ContractInstanceId(spec),
180-
code: Some(CodeHash(code_key)),
174+
code: CodeHash(code_key),
181175
})
182176
}
183177

@@ -203,14 +197,6 @@ impl std::hash::Hash for ContractKey {
203197
}
204198
}
205199

206-
impl From<ContractInstanceId> for ContractKey {
207-
fn from(instance: ContractInstanceId) -> Self {
208-
Self {
209-
instance,
210-
code: None,
211-
}
212-
}
213-
}
214200

215201
impl From<ContractKey> for ContractInstanceId {
216202
fn from(key: ContractKey) -> Self {
@@ -238,7 +224,8 @@ impl<'a> TryFromFbs<&FbsContractKey<'a>> for ContractKey {
238224
let instance = ContractInstanceId::new(key_bytes);
239225
let code = key
240226
.code()
241-
.map(|code_hash| CodeHash::from_code(code_hash.bytes()));
227+
.map(|code_hash| CodeHash::from_code(code_hash.bytes()))
228+
.ok_or_else(|| WsApiError::deserialization("ContractKey missing code hash".into()))?;
242229
Ok(ContractKey { instance, code })
243230
}
244231
}

0 commit comments

Comments
 (0)