ibc_client_cw/context/
mod.rs

1pub mod client_ctx;
2pub mod custom_ctx;
3
4use core::fmt::Display;
5use std::str::FromStr;
6
7use cosmwasm_std::{Binary, CustomQuery, Deps, DepsMut, Empty, Env, Order, Storage};
8use cw_storage_plus::{Bound, Map};
9use ibc_client_wasm_types::client_state::ClientState as WasmClientState;
10use ibc_core::client::context::client_state::ClientStateCommon;
11use ibc_core::client::types::Height;
12use ibc_core::host::types::error::HostError;
13use ibc_core::host::types::identifiers::ClientId;
14use ibc_core::host::types::path::{
15    ClientStatePath, ClientUpdateHeightPath, ClientUpdateTimePath, ITERATE_CONSENSUS_STATE_PREFIX,
16};
17use ibc_core::primitives::proto::{Any, Protobuf};
18use prost::Message;
19
20use crate::api::ClientType;
21use crate::types::{ContractError, HeightTravel, MigrationPrefix};
22use crate::utils::AnyCodec;
23
24/// - [`Height`] cannot be used directly as keys in the map,
25///   as it doesn't implement some cw_storage specific traits.
26/// - Only a sorted set is needed. So the value type is set to
27///   [`Empty`] following
28///   ([cosmwasm-book](https://book.cosmwasm.com/cross-contract/map-storage.html#maps-as-sets)).
29pub const CONSENSUS_STATE_HEIGHT_MAP: Map<(u64, u64), Empty> =
30    Map::new(ITERATE_CONSENSUS_STATE_PREFIX);
31
32/// Context is a wrapper around the deps and env that provides access
33/// to the methods under the ibc-rs Validation and Execution traits.
34pub struct Context<'a, C, Q = Empty>
35where
36    C: ClientType<'a>,
37    Q: CustomQuery,
38    <C::ClientState as TryFrom<Any>>::Error: Display,
39    <C::ConsensusState as TryFrom<Any>>::Error: Display,
40{
41    deps: Option<Deps<'a, Q>>,
42    deps_mut: Option<DepsMut<'a, Q>>,
43    env: Env,
44    client_id: ClientId,
45    checksum: Option<Binary>,
46    migration_prefix: MigrationPrefix,
47    client_type: std::marker::PhantomData<C>,
48}
49
50impl<'a, C, Q> Context<'a, C, Q>
51where
52    C: ClientType<'a>,
53    Q: CustomQuery,
54    <C::ClientState as TryFrom<Any>>::Error: Display,
55    <C::ConsensusState as TryFrom<Any>>::Error: Display,
56{
57    /// Constructs a new Context object with the given deps and env.
58    pub fn new_ref(deps: Deps<'a, Q>, env: Env) -> Result<Self, ContractError> {
59        let client_id = ClientId::from_str(env.contract.address.as_str())?;
60
61        Ok(Self {
62            deps: Some(deps),
63            deps_mut: None,
64            env,
65            client_id,
66            checksum: None,
67            migration_prefix: MigrationPrefix::None,
68            client_type: std::marker::PhantomData::<C>,
69        })
70    }
71
72    /// Constructs a new Context object with the given deps_mut and env.
73    pub fn new_mut(deps_mut: DepsMut<'a, Q>, env: Env) -> Result<Self, ContractError> {
74        let client_id = ClientId::from_str(env.contract.address.as_str())?;
75
76        Ok(Self {
77            deps: None,
78            deps_mut: Some(deps_mut),
79            env,
80            client_id,
81            checksum: None,
82            migration_prefix: MigrationPrefix::None,
83            client_type: std::marker::PhantomData::<C>,
84        })
85    }
86
87    /// Returns the env of the context.
88    pub fn env(&self) -> &Env {
89        &self.env
90    }
91
92    /// Logs the given message.
93    pub fn log(&self, msg: &str) -> Option<()> {
94        self.deps.map(|deps| deps.api.debug(msg))
95    }
96
97    /// Returns the client id of the context.
98    pub fn client_id(&self) -> ClientId {
99        self.client_id.clone()
100    }
101
102    /// Sets the checksum of the context.
103    pub fn set_checksum(&mut self, checksum: Binary) {
104        self.checksum = Some(checksum);
105    }
106
107    /// Enables the migration mode with the subject prefix.
108    pub fn set_subject_prefix(&mut self) {
109        self.migration_prefix = MigrationPrefix::Subject;
110    }
111
112    /// Enables the migration mode with the substitute prefix.
113    pub fn set_substitute_prefix(&mut self) {
114        self.migration_prefix = MigrationPrefix::Substitute;
115    }
116
117    /// Prefixes the given key with the migration prefix.
118    pub fn prefixed_key(&self, key: impl AsRef<[u8]>) -> Vec<u8> {
119        let mut prefixed_key = Vec::new();
120        prefixed_key.extend_from_slice(self.migration_prefix.key());
121        prefixed_key.extend_from_slice(key.as_ref());
122
123        prefixed_key
124    }
125
126    /// Retrieves the value of the given key.
127    pub fn retrieve(&self, key: impl AsRef<[u8]>) -> Result<Vec<u8>, HostError> {
128        let prefixed_key = self.prefixed_key(key);
129
130        let value =
131            self.storage_ref()
132                .get(prefixed_key.as_ref())
133                .ok_or(HostError::failed_to_retrieve(
134                    "key not found upon retrieval",
135                ))?;
136
137        Ok(value)
138    }
139
140    /// Inserts the given key-value pair.
141    pub fn insert(&mut self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) {
142        self.storage_mut().set(key.as_ref(), value.as_ref());
143    }
144
145    /// Removes the value of the given key.
146    pub fn remove(&mut self, key: impl AsRef<[u8]>) {
147        self.storage_mut().remove(key.as_ref());
148    }
149
150    /// Returns the storage of the context.
151    pub fn get_heights(&self) -> Result<Vec<Height>, HostError> {
152        CONSENSUS_STATE_HEIGHT_MAP
153            .keys(self.storage_ref(), None, None, Order::Ascending)
154            .map(|deserialized_result| {
155                let (rev_number, rev_height) =
156                    deserialized_result.map_err(HostError::failed_to_retrieve)?;
157                Height::new(rev_number, rev_height).map_err(HostError::invalid_state)
158            })
159            .collect()
160    }
161
162    /// Searches for either the earliest next or latest previous height based on
163    /// the given height and travel direction.
164    pub fn get_adjacent_height(
165        &self,
166        height: &Height,
167        travel: HeightTravel,
168    ) -> Result<Option<Height>, HostError> {
169        let iterator = match travel {
170            HeightTravel::Prev => CONSENSUS_STATE_HEIGHT_MAP.range(
171                self.storage_ref(),
172                None,
173                Some(Bound::exclusive((
174                    height.revision_number(),
175                    height.revision_height(),
176                ))),
177                Order::Descending,
178            ),
179            HeightTravel::Next => CONSENSUS_STATE_HEIGHT_MAP.range(
180                self.storage_ref(),
181                Some(Bound::exclusive((
182                    height.revision_number(),
183                    height.revision_height(),
184                ))),
185                None,
186                Order::Ascending,
187            ),
188        };
189
190        iterator
191            .map(|deserialized_result| {
192                let ((rev_number, rev_height), _) =
193                    deserialized_result.map_err(HostError::failed_to_retrieve)?;
194                Height::new(rev_number, rev_height).map_err(HostError::invalid_state)
195            })
196            .next()
197            .transpose()
198    }
199
200    /// Returns the key for the client update time.
201    pub fn client_update_time_key(&self, height: &Height) -> Vec<u8> {
202        let client_update_time_path = ClientUpdateTimePath::new(
203            self.client_id(),
204            height.revision_number(),
205            height.revision_height(),
206        );
207
208        client_update_time_path.leaf().into_bytes()
209    }
210
211    /// Returns the key for the client update height.
212    pub fn client_update_height_key(&self, height: &Height) -> Vec<u8> {
213        let client_update_height_path = ClientUpdateHeightPath::new(
214            self.client_id(),
215            height.revision_number(),
216            height.revision_height(),
217        );
218
219        client_update_height_path.leaf().into_bytes()
220    }
221
222    /// Returns the checksum of the current contract.
223    pub fn obtain_checksum(&self) -> Result<Binary, HostError> {
224        match &self.checksum {
225            Some(checksum) => Ok(checksum.clone()),
226            None => {
227                let client_state_value = self.retrieve(ClientStatePath::leaf())?;
228
229                let wasm_client_state: WasmClientState =
230                    Protobuf::<Any>::decode(client_state_value.as_slice())
231                        .map_err(HostError::invalid_state)?;
232
233                Ok(wasm_client_state.checksum.into())
234            }
235        }
236    }
237
238    /// Encodes the given client state into a byte vector.
239    pub fn encode_client_state(&self, client_state: C::ClientState) -> Result<Vec<u8>, HostError> {
240        let wasm_client_state = WasmClientState {
241            checksum: self.obtain_checksum()?.into(),
242            latest_height: client_state.latest_height(),
243            data: C::ClientState::encode_to_any_vec(client_state),
244        };
245
246        Ok(Any::from(wasm_client_state).encode_to_vec())
247    }
248}
249
250pub trait StorageRef {
251    fn storage_ref(&self) -> &dyn Storage;
252}
253
254impl<'a, C, Q> StorageRef for Context<'a, C, Q>
255where
256    C: ClientType<'a>,
257    Q: CustomQuery,
258    <C::ClientState as TryFrom<Any>>::Error: Display,
259    <C::ConsensusState as TryFrom<Any>>::Error: Display,
260{
261    fn storage_ref(&self) -> &dyn Storage {
262        match self.deps {
263            Some(ref deps) => deps.storage,
264            None => match self.deps_mut {
265                Some(ref deps) => deps.storage,
266                None => panic!("Either deps or deps_mut should be available"),
267            },
268        }
269    }
270}
271
272pub trait StorageMut: StorageRef {
273    fn storage_mut(&mut self) -> &mut dyn Storage;
274}
275
276impl<'a, C, Q> StorageMut for Context<'a, C, Q>
277where
278    C: ClientType<'a>,
279    Q: CustomQuery,
280    <C::ClientState as TryFrom<Any>>::Error: Display,
281    <C::ConsensusState as TryFrom<Any>>::Error: Display,
282{
283    fn storage_mut(&mut self) -> &mut dyn Storage {
284        match self.deps_mut {
285            Some(ref mut deps) => deps.storage,
286            None => panic!("deps_mut should be available"),
287        }
288    }
289}