Skip to content

Commit ccb2907

Browse files
feat(bob): introduce light client (#4146)
2 parents 06aa6ba + ace274a commit ccb2907

File tree

14 files changed

+453
-7
lines changed

14 files changed

+453
-7
lines changed

Cargo.lock

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ members = [
9393

9494
"cosmwasm/ibc-union/lightclient/arbitrum",
9595
"cosmwasm/ibc-union/lightclient/berachain",
96+
"cosmwasm/ibc-union/lightclient/bob",
9697
"cosmwasm/ibc-union/lightclient/cometbls",
9798
"cosmwasm/ibc-union/lightclient/ethereum",
9899
"cosmwasm/ibc-union/lightclient/ethermint",

cosmwasm/cosmwasm.nix

+5
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ _: {
272272

273273
# directory => {}
274274
all-lightclients = [
275+
{
276+
name = "bob";
277+
dir = "bob";
278+
client-type = "bob";
279+
}
275280
{
276281
name = "arbitrum";
277282
dir = "arbitrum";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
name = "bob-light-client"
3+
version = "0.0.0"
4+
5+
authors = { workspace = true }
6+
edition = { workspace = true }
7+
license-file = "LICENSE"
8+
publish = { workspace = true }
9+
repository = { workspace = true }
10+
11+
[lints]
12+
workspace = true
13+
14+
[package.metadata.crane]
15+
test-include = []
16+
17+
[lib]
18+
crate-type = ["cdylib", "rlib"]
19+
20+
[dependencies]
21+
bob-light-client-types = { workspace = true, features = ["serde", "ethabi", "bincode"] }
22+
bob-verifier = { workspace = true }
23+
cosmwasm-std = { workspace = true, features = ["abort", "cosmwasm_2_1"] }
24+
embed-commit = { workspace = true }
25+
ethereum-light-client = { workspace = true, features = ["library"] }
26+
ethereum-light-client-types = { workspace = true, features = ["serde", "ethabi"] }
27+
evm-storage-verifier = { workspace = true }
28+
frissitheto = { workspace = true }
29+
ibc-union-light-client = { workspace = true }
30+
ibc-union-msg = { workspace = true }
31+
ics23 = { workspace = true }
32+
serde = { workspace = true, features = ["derive"] }
33+
thiserror = { workspace = true }
34+
unionlabs = { workspace = true }
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
2+
"Business Source License" is a trademark of MariaDB Corporation Ab.
3+
4+
Parameters
5+
6+
Licensor: Union.fi, Labs Inc.
7+
Licensed Work: All files under this license file's directory/subdirectories.
8+
The Licensed Work is (c) 2025 Union.fi, Labs Inc.
9+
Change Date: Four years from the date the Licensed Work is published.
10+
Change License: Apache-2.0
11+
12+
13+
For information about alternative licensing arrangements for the Licensed Work,
14+
please contact [email protected].
15+
16+
Notice
17+
18+
Business Source License 1.1
19+
20+
Terms
21+
22+
The Licensor hereby grants you the right to copy, modify, create derivative
23+
works, redistribute, and make non-production use of the Licensed Work. The
24+
Licensor may make an Additional Use Grant, above, permitting limited production use.
25+
26+
Effective on the Change Date, or the fourth anniversary of the first publicly
27+
available distribution of a specific version of the Licensed Work under this
28+
License, whichever comes first, the Licensor hereby grants you rights under
29+
the terms of the Change License, and the rights granted in the paragraph
30+
above terminate.
31+
32+
If your use of the Licensed Work does not comply with the requirements
33+
currently in effect as described in this License, you must purchase a
34+
commercial license from the Licensor, its affiliated entities, or authorized
35+
resellers, or you must refrain from using the Licensed Work.
36+
37+
All copies of the original and modified Licensed Work, and derivative works
38+
of the Licensed Work, are subject to this License. This License applies
39+
separately for each version of the Licensed Work and the Change Date may vary
40+
for each version of the Licensed Work released by Licensor.
41+
42+
You must conspicuously display this License on each original or modified copy
43+
of the Licensed Work. If you receive the Licensed Work in original or
44+
modified form from a third party, the terms and conditions set forth in this
45+
License apply to your use of that work.
46+
47+
Any use of the Licensed Work in violation of this License will automatically
48+
terminate your rights under this License for the current and all other
49+
versions of the Licensed Work.
50+
51+
This License does not grant you any right in any trademark or logo of
52+
Licensor or its affiliates (provided that you may use a trademark or logo of
53+
Licensor as expressly required by this License).
54+
55+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
56+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
57+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
58+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
59+
TITLE.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
use bob_light_client_types::{ClientStateV1, ConsensusState, Header};
2+
use cosmwasm_std::{Addr, Empty};
3+
use ethereum_light_client::client::EthereumLightClient;
4+
use ethereum_light_client_types::StorageProof;
5+
use ibc_union_light_client::{
6+
ClientCreationResult, IbcClient, IbcClientCtx, IbcClientError, StateUpdate,
7+
};
8+
use ibc_union_msg::lightclient::Status;
9+
use unionlabs::encoding::Bincode;
10+
11+
use crate::errors::Error;
12+
13+
pub struct BobLightClient;
14+
15+
impl IbcClient for BobLightClient {
16+
type Error = Error;
17+
18+
type Header = Header;
19+
20+
type Misbehaviour = ();
21+
22+
type ClientState = ClientStateV1;
23+
24+
type ConsensusState = ConsensusState;
25+
26+
type Encoding = Bincode;
27+
28+
type CustomQuery = Empty;
29+
30+
type StorageProof = StorageProof;
31+
32+
fn verify_membership(
33+
ctx: IbcClientCtx<Self>,
34+
height: u64,
35+
key: Vec<u8>,
36+
storage_proof: Self::StorageProof,
37+
value: Vec<u8>,
38+
) -> Result<(), IbcClientError<Self>> {
39+
let consensus_state = ctx.read_self_consensus_state(height)?;
40+
ethereum_light_client::client::verify_membership(
41+
key,
42+
consensus_state.ibc_storage_root,
43+
storage_proof,
44+
value,
45+
)
46+
.map_err(Into::<Error>::into)?;
47+
Ok(())
48+
}
49+
50+
fn verify_non_membership(
51+
ctx: IbcClientCtx<Self>,
52+
height: u64,
53+
key: Vec<u8>,
54+
storage_proof: Self::StorageProof,
55+
) -> Result<(), IbcClientError<Self>> {
56+
let consensus_state = ctx.read_self_consensus_state(height)?;
57+
ethereum_light_client::client::verify_non_membership(
58+
key,
59+
consensus_state.ibc_storage_root,
60+
storage_proof,
61+
)
62+
.map_err(Into::<Error>::into)?;
63+
Ok(())
64+
}
65+
66+
fn get_timestamp(consensus_state: &Self::ConsensusState) -> u64 {
67+
consensus_state.timestamp
68+
}
69+
70+
fn get_latest_height(client_state: &Self::ClientState) -> u64 {
71+
client_state.latest_height
72+
}
73+
74+
fn get_counterparty_chain_id(client_state: &Self::ClientState) -> String {
75+
client_state.chain_id.to_string()
76+
}
77+
78+
fn status(ctx: IbcClientCtx<Self>, client_state: &Self::ClientState) -> Status {
79+
let _ = client_state;
80+
let _ = ctx;
81+
// FIXME: expose the ctx to this call to allow threading this call to L1
82+
// client. generally, we want to thread if a client is an L2 so always
83+
// provide the ctx?
84+
Status::Active
85+
}
86+
87+
fn verify_creation(
88+
_caller: Addr,
89+
_client_state: &Self::ClientState,
90+
_consensus_state: &Self::ConsensusState,
91+
_relayer: Addr,
92+
) -> Result<ClientCreationResult<Self>, IbcClientError<Self>> {
93+
Ok(ClientCreationResult::new())
94+
}
95+
96+
fn verify_header(
97+
ctx: IbcClientCtx<Self>,
98+
_caller: Addr,
99+
header: Self::Header,
100+
_relayer: Addr,
101+
) -> Result<StateUpdate<Self>, IbcClientError<Self>> {
102+
let mut client_state = ctx.read_self_client_state()?;
103+
104+
let l1_consensus_state = ctx
105+
.read_consensus_state::<EthereumLightClient>(
106+
client_state.l1_client_id,
107+
header.l1_height,
108+
)
109+
.map_err(Into::<Error>::into)?;
110+
111+
bob_verifier::verify_header(
112+
&client_state,
113+
&header,
114+
l1_consensus_state.state_root,
115+
ctx.env.block.time.seconds(),
116+
)
117+
.map_err(Into::<Error>::into)?;
118+
119+
let update_height = header.l2_header.number.try_into().expect("impossible");
120+
121+
let consensus_state = ConsensusState {
122+
timestamp: header.l2_header.timestamp,
123+
state_root: header.l2_header.state_root,
124+
ibc_storage_root: header.l2_ibc_account_proof.storage_root,
125+
};
126+
127+
let state_update = StateUpdate::new(update_height, consensus_state);
128+
129+
if client_state.latest_height < update_height {
130+
client_state.latest_height = update_height;
131+
Ok(state_update.overwrite_client_state(client_state))
132+
} else {
133+
Ok(state_update)
134+
}
135+
}
136+
137+
fn misbehaviour(
138+
_ctx: IbcClientCtx<Self>,
139+
_caller: Addr,
140+
_misbehaviour: Self::Misbehaviour,
141+
_relayer: Addr,
142+
) -> Result<Self::ClientState, IbcClientError<Self>> {
143+
Err(Error::Unimplemented.into())
144+
}
145+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
2+
use frissitheto::UpgradeMsg;
3+
use ibc_union_light_client::{
4+
msg::{InitMsg, QueryMsg},
5+
IbcClientError,
6+
};
7+
8+
use crate::client::BobLightClient;
9+
10+
#[entry_point]
11+
pub fn instantiate(_: DepsMut, _: Env, _: MessageInfo, _: ()) -> StdResult<Response> {
12+
panic!("this contract cannot be instantiated directly, but must be migrated from an existing instantiated contract.");
13+
}
14+
15+
#[entry_point]
16+
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
17+
ibc_union_light_client::query::<BobLightClient>(deps, env, msg).map_err(Into::into)
18+
}
19+
20+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
21+
pub struct MigrateMsg {}
22+
23+
#[entry_point]
24+
pub fn migrate(
25+
deps: DepsMut,
26+
_env: Env,
27+
msg: UpgradeMsg<InitMsg, MigrateMsg>,
28+
) -> Result<Response, IbcClientError<BobLightClient>> {
29+
msg.run(
30+
deps,
31+
|deps, init_msg| {
32+
let res = ibc_union_light_client::init(deps, init_msg)?;
33+
34+
Ok((res, None))
35+
},
36+
|_deps, _migrate_msg, _current_version| Ok((Response::default(), None)),
37+
)
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use ethereum_light_client::client::EthereumLightClient;
2+
use ibc_union_light_client::IbcClientError;
3+
4+
use crate::client::BobLightClient;
5+
6+
#[derive(Debug, thiserror::Error)]
7+
pub enum Error {
8+
#[error("unimplemented")]
9+
Unimplemented,
10+
11+
#[error(transparent)]
12+
Verify(#[from] bob_verifier::Error),
13+
14+
#[error(transparent)]
15+
Evm(#[from] ethereum_light_client::errors::Error),
16+
17+
#[error(transparent)]
18+
EvmIbcClient(#[from] IbcClientError<EthereumLightClient>),
19+
}
20+
21+
// required for IbcClient trait
22+
impl From<Error> for IbcClientError<BobLightClient> {
23+
fn from(value: Error) -> Self {
24+
IbcClientError::ClientSpecific(value)
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod client;
2+
pub mod contract;
3+
pub mod errors;

lib/bob-light-client-types/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "0.0.0"
44

55
authors = { workspace = true }
66
edition = { workspace = true }
7-
license-file = { workspace = true }
7+
license-file = "LICENSE"
88
publish = { workspace = true }
99
repository = { workspace = true }
1010

0 commit comments

Comments
 (0)