Skip to content

Commit 899e1de

Browse files
feat(docs): union ibc spec
1 parent a9767d8 commit 899e1de

File tree

4 files changed

+332
-16
lines changed

4 files changed

+332
-16
lines changed

docs/astro.config.ts

+6
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ export default defineConfig({
155155
label: "Overview",
156156
link: "/protocol/overview"
157157
},
158+
{
159+
label: "Specifications",
160+
autogenerate: {
161+
directory: "/protocol/specifications"
162+
}
163+
},
158164
{
159165
label: "Channels",
160166
autogenerate: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
---
2+
title: "Union IBC"
3+
sidebar:
4+
badge:
5+
text: live
6+
variant: note
7+
---
8+
9+
import Mermaid from "#/components/Mermaid.astro";
10+
11+
# Overview
12+
13+
[`IBC`](https://ibcprotocol.org) is a blockchain interoperability protocol for secure general message passing between blockchains. At Union, we use a specialized in-house version (more EVM friendly) that slightly deviates from the _canonical_ [ibc-go](https://github.com/cosmos/ibc-go) and [ibc](https://github.com/cosmos/ibc). This document is an attempt at specifying the implementation.
14+
15+
The semantic of the core protocol can be found in the [ibc](https://github.com/cosmos/ibc/tree/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core) repository. Our implementation is deviating from the semantic in few places that we will describe. Most of the changes are specializations/optimizations targeting the EVM.
16+
17+
The protocol assumes the execution happens within a smart contract engine (contract addresses **MUST** be unique).
18+
19+
# Protocol
20+
21+
## ICS-002 Client
22+
23+
:::note
24+
25+
[Read more on the canonical specification](https://github.com/cosmos/ibc/tree/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-002-client-semantics)
26+
27+
:::
28+
29+
- [`verifyMembership`](https://github.com/cosmos/ibc/tree/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-002-client-semantics#state-verification) no longer takes a `delayPeriodTime` and `delayPeriodBlocks`, the specific light clients SHOULD implement such verification if necessary.
30+
- [`verifyNonMembership`](https://github.com/cosmos/ibc/tree/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-002-client-semantics#state-verification)
31+
- `delayPeriodTime` has been removed.
32+
- `delayPeriodBlocks` has been removed.
33+
- [`clientId`](https://github.com/cosmos/ibc/tree/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-002-client-semantics#identifier-validation) is a unique `uint32`. The client type **MUST** be stored and indexable with the `clientId`.
34+
35+
### Additions
36+
37+
- `registerClient` **MUST** be implemented and called before being able to call `createClient` on a light client. Only one client type **MUST** exist for a given light client:
38+
```haskell
39+
registerClient Implementations ClientType Address Implementations
40+
registerClient impls clientType lightClient = do
41+
assert (getImplementation impls clientType null)
42+
setImplementation impls clientType lightClient
43+
```
44+
45+
## ICS-003 Connection
46+
47+
:::note
48+
49+
[Read more on the canonical specification](https://github.com/cosmos/ibc/blob/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-003-connection-semantics/README.md).
50+
51+
:::
52+
53+
:::caution
54+
55+
The protocol version is considered hardcoded. We support the `Ordered` and `Unordered` features for channels only.
56+
57+
:::
58+
59+
- [`connectionId`](https://github.com/cosmos/ibc/tree/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-003-connection-semantics#identifier-validation) is no longer a string but a unique `uint32`.
60+
- [`ConnectionEnd`](https://github.com/cosmos/ibc/tree/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-003-connection-semantics#data-structures)
61+
- `counterpartyPrefix` has been removed.
62+
- `version` has been removed.
63+
- `delayPeriodTime` has been removed.
64+
- `delayPeriodBlocks` has been removed.
65+
```haskell
66+
type ClientId = Uint32
67+
type ConnectionId = Uint32
68+
69+
class ConnectionEnd where
70+
state ConnectionState
71+
counterpartyConnectionId ConnectionId
72+
clientId ClientId
73+
counterpartyClientId ClientId
74+
```
75+
76+
77+
## ICS-004 Channel and Packet
78+
79+
:::note
80+
81+
[Read more on the canonical specification](https://github.com/cosmos/ibc/blob/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-004-channel-and-packet-semantics/README.md)
82+
83+
:::
84+
85+
### Channel
86+
87+
:::caution
88+
89+
We do not support the [Channel Upgrade](https://github.com/cosmos/ibc/blob/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md) feature.
90+
We only support `Ordered` and `Unordered` channels.
91+
92+
:::
93+
94+
- [`channelId`](https://github.com/cosmos/ibc/blob/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-004-channel-and-packet-semantics/README.md#identifier-validation) is no longer a string but a unique `uint32`.
95+
- [`ChannelEnd`](https://github.com/cosmos/ibc/blob/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-004-channel-and-packet-semantics/README.md#definitions):
96+
- `connectionHops` has been renamed to `connectionId` and it's type changed from `[connectionIdentifer]` to `connectionId`.
97+
- `upgradeSequence` has been removed.
98+
```haskell
99+
type ChannelId = Uint32
100+
data ChannelOrder = Ordered | Unordered
101+
data ChannelState = Init | TryOpen | Open | Closed
102+
103+
104+
class ChannelEnd where
105+
state ChannelState
106+
ordering ChannelOrder
107+
connectionId ConnectionId
108+
counterpartyChannelId ChannelId
109+
counterpartyPortId String
110+
version String
111+
```
112+
113+
### Packet
114+
115+
:::caution
116+
117+
The [ICS-05 port allocation](https://github.com/cosmos/ibc/blob/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-005-port-allocation/README.md) module has been removed, channel identifiers uniquely identify protocols and the implementor **MUST** ensure there is an existing indirection from `channelId -> protocolAddress`.
118+
119+
:::
120+
121+
- [`Packet`](https://github.com/cosmos/ibc/blob/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-004-channel-and-packet-semantics/README.md#definitions)
122+
- `sourcePort` has been removed.
123+
- `destinationPort` has been removed.
124+
- `timeoutHeight` type has been changed from `(uint64, uint64)` to `uint64`.
125+
```haskell
126+
class Packet where
127+
sequence Uint64
128+
sourceChannel ChannelId
129+
destinationChannel ChannelId
130+
payload Bytes
131+
timeoutHeight Uint64
132+
timeoutTimestamp Uint64
133+
```
134+
135+
## ICS-024 Host
136+
137+
:::note
138+
139+
[Read more on the canonical specification](https://github.com/cosmos/ibc/blob/41a8caa54d8691b7fa98795d31f401e1df31e18c/spec/core/ics-024-host-requirements/README.md#synopsis)
140+
141+
:::
142+
143+
### Path-space
144+
145+
Union approach to the path-space is deviating from the canonical implementation. Since we no longer use string for client/connection/channel identifiers, the commitment paths are binary.
146+
147+
```haskell
148+
type Prefix = Uint256
149+
150+
clientStatePrefix Prefix
151+
clientStatePrefix = 0x00
152+
153+
consensusStatePrefix Prefix
154+
consensusStatePrefix = 0x01
155+
156+
connectionsPrefix Prefix
157+
connectionsPrefix = 0x02
158+
159+
channelsPrefix Prefix
160+
channelsPrefix = 0x03
161+
162+
packetsPrefix Prefix
163+
packetsPrefix = 0x04
164+
165+
packetAcksPrefix Prefix
166+
packetAcksPrefix = 0x05
167+
168+
nextSeqSendPrefix Prefix
169+
nextSeqSendPrefix = 0x06
170+
171+
nextSeqRecvPrefix Prefix
172+
nextSeqRecvPrefix = 0x07
173+
174+
nextSeqAckPrefix Prefix
175+
nextSeqAckPrefix = 0x08
176+
177+
commit AbiEncode a a Bytes32
178+
commit x = keccak256 (abiEncode x)
179+
180+
clientStateKey ClientId Bytes32
181+
clientStateKey clientId = commit (clientStatePrefix, clientId)
182+
183+
consensusStateKey ClientId Uint64 Bytes32
184+
consensusStateKey clientId height = commit (consensusStatePrefix, clientId, height)
185+
186+
connectionKey ConnectionId Bytes32
187+
connectionKey connectionId = commit (connectionsPrefix, connectionId)
188+
189+
channelKey ChannelId Bytes32
190+
channelKey channelId = commit (channelsPrefix, channelId)
191+
192+
packetKey ChannelId Bytes32 Bytes32
193+
packetKey channelId packetHash = commit (packetsPrefix, channelId, packetHash)
194+
195+
packetReceiptKey ChannelId Bytes32 Bytes32
196+
packetReceiptKey channelId packetHash = commit (packetAcksPrefix, channelId, packetHash)
197+
198+
nextSequenceSendKey ChannelId Bytes32
199+
nextSequenceSendKey channelId = commit (nextSeqSendPrefix, channelId)
200+
201+
nextSequenceRecvKey ChannelId Bytes32
202+
nextSequenceRecvKey channelId = commit (nextSeqRecvPrefix, channelId)
203+
204+
nextSequenceAckKey ChannelId Bytes32
205+
nextSequenceAckKey channelId = commit (nextSeqAckPrefix, channelId)
206+
```
207+
208+
### Commitments
209+
210+
After all preconditions are met, the protocol commits a succinct digest of the structures we need to prove on the counterparty. This commitments are encoded differently than in the canonical implementation, instead of protobuf, we use the [solidity contract ABI encoding](https://docs.soliditylang.org/en/develop/abi-spec.html#formal-specification-of-the-encoding) encoding.
211+
212+
Let's define the `setCommitment` and it's associated `commit` functions to update a store.
213+
214+
```haskell
215+
setCommitment Store Bytes32 Bytes32 Store
216+
217+
commit AbiEncode a a Bytes32
218+
commit x = keccak256 (abiEncode x)
219+
```
220+
221+
#### Client
222+
223+
```haskell
224+
commitClientState Store ClientId ClientState Store
225+
commitClientState store clientId clientState =
226+
setCommitment (clientStateKey clientId) (commit clientState)
227+
228+
commitConsensusState Store ClientId ConsensusState Store
229+
commitConsensusState store clientId consensusState =
230+
setCommitment (clientStateKey clientId) (commit consensusState)
231+
```
232+
233+
#### Connection
234+
```haskell
235+
commitConnection Store ConnectionId ConnectionEnd Store
236+
commitConnection store connectionId connection =
237+
setCommitment (connectionKey connectionId) (commit connection)
238+
```
239+
240+
#### Channel
241+
```haskell
242+
commitChannel Store ChannelId ChannelEnd Store
243+
commitChannel store channelId channel =
244+
setCommitment (channelKey channelId) (commit channel)
245+
```
246+
247+
#### Packet
248+
:::tip
249+
250+
The `commitmentMagic` value and `mergeAck` functions are present for the receipt and the acknowledgement hash to share the same commitment slot. This drastically lower the gas cost on EVM.
251+
252+
To avoid replay attacks, a receipt (`commitmentMagic` here) is written when a packet is received. Acknowledgements are asynchronous and written when the execution of a received packet completes, possibly in a future transaction.
253+
254+
:::
255+
256+
```haskell
257+
commitmentMagic Bytes32
258+
commitmentMagic = 0x0100000000000000000000000000000000000000000000000000000000000000
259+
260+
commitPacket Store ChannelId Packet Store
261+
commitPacket store channelId packet =
262+
setCommitment (packetKey channelId (commit packet)) commitmentMagic
263+
264+
mergeAck Bytes32 Bytes32
265+
mergeAck ack =
266+
commitmentMagic |
267+
(ack & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
268+
269+
commitReceipt Store ChannelId Packet Store
270+
commitReceipt store channelId packet =
271+
setCommitment (packetReceiptKey channelId (commit packet)) commitmentMagic
272+
273+
type Acknowledgement = Bytes
274+
275+
commitAck Store ChannelId Packet Acknowledgement Store
276+
commitAck store channelId packet ack =
277+
setCommitment (packetReceiptKey channelId (commit packet)) (mergeAck (keccak256 ack))
278+
```
279+
280+
## Extensions
281+
282+
### ICS-004 Packet
283+
284+
The `packetKey` and `packetReceiptKey` are special commitments that no longer takes a `sequence` but the whole packet hash. This allows us to extend the protocol with batching for sent packets and written acknowledgements.
285+
286+
```haskell
287+
commitmentMagic Bytes32
288+
commitmentMagic = 0x0100000000000000000000000000000000000000000000000000000000000000
289+
290+
setCommitment Store Bytes32 Bytes32 Store
291+
getCommitment Store Bytes32 Bytes32
292+
293+
mergeAck Bytes32 Bytes32
294+
mergeAck ack =
295+
commitmentMagic |
296+
(ack & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
297+
298+
commitmentExist Store Bytes32 Packet Bool
299+
commitmentExist store expectedCommitment packet =
300+
getCommitment store (commit packet) exceptedCommitment
301+
302+
batchSend Store [Packet] Store
303+
batchSend store packets = do
304+
assert (all (commitmentExist store commitmentMagic) packets)
305+
setCommitment store (packetKey channelId (commit packets)) commitmentMagic
306+
307+
batchAcks Store [Packet] [Acknowledgement] Store
308+
batchAcks store packets acks = do
309+
assert (
310+
all
311+
(\(ack, packet) -> commitmentExist store (mergeAck (keccak256 ack)) packet)
312+
(zip acks packet)
313+
)
314+
setCommitment
315+
store
316+
(packetReceiptKey channelId (commit packets))
317+
(mergeAck (commit acks))
318+
```
319+
320+
`batchSend` is used to commit a batch of previously sent packets. It allows the relayer to provide a single membership proof for the whole batch at destination (recv).
321+
322+
`batchAcks` is used to commit a batch of previously written acknowledgements. It allows the relayer to provide a single membership proof for the whole batch at destination (ack).
323+
324+
This functions can be used to trade execution gas on the source chain for the destination and vice versa. Committing a batch of sent packets will require an extra transaction on the source but will lower the execution gas on destination (single proof). Similarly, the batching of acknowledgements trade execution gas on the destination for the source. This further allow relayers and market makers to efficiently handle asset transfers based on gas price.

evm/contracts/core/04-channel/IBCPacket.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ library IBCPacketLib {
3939
function commitAck(
4040
bytes calldata ack
4141
) internal pure returns (bytes32) {
42-
return mergeAck(keccak256(abi.encodePacked(ack)));
42+
return mergeAck(keccak256(ack));
4343
}
4444

4545
function commitAckMemory(
4646
bytes memory ack
4747
) internal pure returns (bytes32) {
48-
return mergeAck(keccak256(abi.encodePacked(ack)));
48+
return mergeAck(keccak256(ack));
4949
}
5050

5151
function commitPacketsMemory(

evm/contracts/core/24-host/IBCCommitment.sol

-14
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,6 @@ library IBCCommitment {
3636
return abi.encode(CHANNELS, channelId);
3737
}
3838

39-
function packetCommitmentPath(
40-
uint32 channelId,
41-
uint64 sequence
42-
) internal pure returns (bytes memory) {
43-
return abi.encode(PACKETS, channelId, sequence);
44-
}
45-
4639
function batchPacketsCommitmentPath(
4740
uint32 channelId,
4841
bytes32 batchHash
@@ -102,13 +95,6 @@ library IBCCommitment {
10295
return keccak256(channelPath(channelId));
10396
}
10497

105-
function packetCommitmentKey(
106-
uint32 channelId,
107-
uint64 sequence
108-
) internal pure returns (bytes32) {
109-
return keccak256(packetCommitmentPath(channelId, sequence));
110-
}
111-
11298
function batchPacketsCommitmentKey(
11399
uint32 channelId,
114100
bytes32 batchHash

0 commit comments

Comments
 (0)