Skip to content

Commit cd45762

Browse files
authored
Merge pull request #641 from konradkonrad/event_trigger_docs
Add spec document for event trigger defintions
2 parents 42f5625 + 55e1c52 commit cd45762

File tree

1 file changed

+376
-0
lines changed

1 file changed

+376
-0
lines changed

docs/event.md

Lines changed: 376 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,376 @@
1+
# EventTriggerDefinition Format Specification
2+
3+
This specification provides the format and encoding rules necessary to implement
4+
tooling that generates `EventTriggerDefinition` instances programmatically.
5+
These
6+
[`triggerDefinition`](https://github.com/shutter-network/contracts/blob/8b0051b13875a7450811c4634e72f2edad7f5016/src/shutter-service/ShutterEventTriggerRegistry.sol#L38)s
7+
are used to register event based decryption triggers. Keypers that take part in
8+
a keyper set that has these enabled, will watch the blockchain for events
9+
matching these definitions. The registration is done through a contract specific
10+
to the keyperset. An example
11+
[can be seen here](https://github.com/shutter-network/contracts/blob/8b0051b13875a7450811c4634e72f2edad7f5016/src/shutter-service/ShutterEventTriggerRegistry.sol).
12+
13+
## Elements / Types
14+
15+
### EventTriggerDefinition
16+
17+
An `EventTriggerDefinition` is a structured data object that combines:
18+
19+
- A reference to a specific smart contract (by address)
20+
- A set of log predicates that filter which events from that contract should
21+
trigger the release of the decryption key.
22+
23+
Reference:
24+
[EventTriggerDefinition Type](https://github.com/shutter-network/rolling-shutter/blob/42f562532acfc4f89f630d3de809fc4451636ab2/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L21-L25)
25+
26+
### LogPredicate
27+
28+
A `LogPredicate` pairs a reference to a specific value within an event log with
29+
a predicate that must be satisfied for the trigger to fire.
30+
31+
Reference:
32+
[LogPredicate Type](https://github.com/shutter-network/rolling-shutter/blob/42f562532acfc4f89f630d3de809fc4451636ab2/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L27-L32)
33+
34+
### LogValueRef
35+
36+
A `LogValueRef` identifies which value within an event log should be evaluated.
37+
It can reference:
38+
39+
- **Topics (Offsets 0-3)**: The indexed parameters of the event log. A topic is
40+
referenced by its index (0-3), where offset 0 is topic[0], etc.
41+
- **Data (Offsets 4+)**: The non-indexed data section of the log. Offset
42+
values >= 4 refer to 32-byte words in the log data, where offset 4 is the
43+
first word (bytes 0-31), offset 5 is the second word (bytes 32-63), etc. Note:
44+
data offset always starts at 4, even if there are fewer than 4 topics defined.
45+
46+
Reference:
47+
[LogValueRef Type and Documentation](https://github.com/shutter-network/rolling-shutter/blob/42f562532acfc4f89f630d3de809fc4451636ab2/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L34-L43)
48+
49+
### ValuePredicate
50+
51+
A `ValuePredicate` defines the condition that must be satisfied on a referenced
52+
log value. It consists of an operation (`Op`) and a set of arguments. The type
53+
and number of arguments required depend on the operation.
54+
55+
Reference:
56+
[ValuePredicate Type](https://github.com/shutter-network/rolling-shutter/blob/42f562532acfc4f89f630d3de809fc4451636ab2/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L45-L53)
57+
58+
## Encoding Format
59+
60+
### Binary Encoding
61+
62+
`EventTriggerDefinition` values are encoded using RLP (Recursive Length Prefix)
63+
encoding with a version byte prefix:
64+
65+
```
66+
EventTriggerDefinitionBytes := {
67+
version: uint8 // Current version is 0x01
68+
rlp_encoded_content: []byte // RLP-encoded EventTriggerDefinition
69+
}
70+
```
71+
72+
The version byte (`0x01`) allows for future format changes. After the version
73+
byte, the remaining bytes are RLP-encoded content representing the
74+
`EventTriggerDefinition` structure.
75+
76+
Reference:
77+
[MarshalBytes and UnmarshalBytes Implementation](https://github.com/shutter-network/rolling-shutter/blob/42f562532acfc4f89f630d3de809fc4451636ab2/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L58-L86)
78+
79+
### Versioning
80+
81+
The current version is **0x01** as defined by the `Version` constant.
82+
83+
### RLP Encoding Details
84+
85+
The RLP encoding format for the core structures are just nested RLP lists. The
86+
named object definitions below are for clarification purposes only:
87+
88+
```
89+
EventTriggerDefinition := {
90+
contract: Address,
91+
logPredicates: [LogPredicate, ...]
92+
}
93+
94+
LogPredicate := {
95+
logValueRef: LogValueRef,
96+
valuePredicate: ValuePredicate
97+
}
98+
99+
LogValueRef := Offset // if Length == 1
100+
LogValueRef := [Offset, Length] // if Length > 1
101+
102+
ValuePredicate := {
103+
op: uint64,
104+
intArgs: [BigInt, ...],
105+
byteArgs: [Bytes, ...]
106+
}
107+
108+
# actual dense format is more akin to this:
109+
[contract_address, [[offset, [op, [int_args], [byte_args]]], […]]]
110+
```
111+
112+
Reference:
113+
[RLP Encoding Implementation](https://github.com/shutter-network/rolling-shutter/blob/42f562532acfc4f89f630d3de809fc4451636ab2/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L211-L392)
114+
115+
## Operators
116+
117+
The `Op` type defines the operations that can be performed when evaluating a
118+
value predicate.
119+
120+
### Supported Operators
121+
122+
- **`Uint...` (0-4) Operators**: Unsigned integer (`uint256` / `big.Int`)
123+
comparisons
124+
- Argument: 1 unsigned integer
125+
- **`UintLt` (0)**
126+
- Returns true if `value < argument`
127+
- **`UintLte` (1)**
128+
- Returns true if `value <= argument`
129+
- **`UintEq` (2)**
130+
- Returns true if `value == argument`
131+
- **`UintGt` (3)**
132+
- Returns true if `value > argument`
133+
- **`UintGte` (4)**
134+
- Returns true if `value >= argument`
135+
- **`BytesEq` (5)**: Byte sequence equality comparison
136+
- Arguments: 1 byte sequence (exactly matching the value size)
137+
- Returns true if value == argument (byte-by-byte comparison)
138+
139+
Reference:
140+
[Operator Constants](https://github.com/shutter-network/rolling-shutter/blob/42f562532acfc4f89f630d3de809fc4451636ab2/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L298-L305)
141+
142+
Note, that different operators require different numbers and types of arguments:
143+
144+
- **Uint...-operators**: 1 integer argument, 0 byte arguments
145+
- **BytesEq**: 0 integer arguments, 1 byte argument
146+
147+
## Matching Logs Against Definitions
148+
149+
In the keyper implementation, the `Match` method is used to check if a given
150+
Ethereum log satisfies the `EventTriggerDefinition`:
151+
152+
1. The log's contract address must match the definition's contract address
153+
2. The log must satisfy **all** log predicates (logical `AND` of all conditions)
154+
155+
Reference:
156+
[Match Method](https://github.com/shutter-network/rolling-shutter/blob/42f562532acfc4f89f630d3de809fc4451636ab2/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L159-L176)
157+
158+
### LogValueRef.GetValue
159+
160+
To retrieve a value from a log:
161+
162+
- **Topics (Offset < 4)**: Returns the log's topic at index Offset as a 32-byte
163+
value
164+
- **Data (Offset >= 4)**: Extracts a slice of 32-byte words from the log's data,
165+
starting at word index (Offset - 4). If the slice extends beyond the log's
166+
data, it is zero-padded on the right to the expected length.
167+
168+
Reference:
169+
[GetValue Method](https://github.com/shutter-network/rolling-shutter/blob/main/rolling-shutter/keyperimpl/shutterservice/eventtrigger.go#L210-L228)
170+
171+
## Generating EventTriggerDefinition from EVM Contract ABI
172+
173+
To generate an `EventTriggerDefinition` from an EVM contract ABI and a set of
174+
predicates:
175+
176+
### Input Requirements
177+
178+
1. **Contract Address**: The Ethereum address where the contract is deployed
179+
2. **Contract ABI**: Standard Solidity JSON ABI format
180+
3. **Target Event Name**: The name of the event to trigger on
181+
4. **Predicate Specifications**: List of conditions, each specifying:
182+
- Parameter name (e.g., "to", "amount") or parameter index
183+
- Whether it's indexed or not (helps determine offset calculation)
184+
- Operation to apply (UintLt, UintEq, BytesEq, etc.)
185+
- Argument value(s)
186+
187+
### Algorithm Overview
188+
189+
```
190+
function generateEventTriggerDefinition(
191+
contractAddress: Address,
192+
abi: ContractABI,
193+
eventName: string,
194+
predicateSpecs: PredicateSpec[]
195+
): EventTriggerDefinition {
196+
197+
// 1. Find the event in the ABI
198+
event = findEventInABI(abi, eventName)
199+
if (!event) {
200+
throw EventNotFoundError(eventName)
201+
}
202+
203+
// 2. Build log predicates from predicate specifications
204+
logPredicates = []
205+
206+
for spec in predicateSpecs {
207+
// Find the parameter in the event
208+
param = findParameterInEvent(event, spec.parameterName)
209+
if (!param) {
210+
throw ParameterNotFoundError(spec.parameterName)
211+
}
212+
213+
// Calculate the LogValueRef offset
214+
if (param.indexed) {
215+
// Topics are at offsets 0-3
216+
offset = calculateTopicIndex(event, param)
217+
length = 1
218+
} else {
219+
// Data starts at offset 4
220+
offset = 4 + calculateDataWordOffset(event, param)
221+
length = calculateDataWordLength(param.type)
222+
}
223+
224+
logValueRef = LogValueRef{
225+
Offset: offset,
226+
Length: length
227+
}
228+
229+
// Create the ValuePredicate
230+
valuePredicate = createValuePredicate(
231+
spec.operation,
232+
spec.value,
233+
param.type
234+
)
235+
236+
logPredicates.append(LogPredicate{
237+
LogValueRef: logValueRef,
238+
ValuePredicate: valuePredicate
239+
})
240+
}
241+
242+
// 3. Assemble and return
243+
return EventTriggerDefinition{
244+
Contract: contractAddress,
245+
LogPredicates: logPredicates
246+
}
247+
}
248+
```
249+
250+
### Calculating Offsets
251+
252+
**For Indexed Parameters (Topics)**:
253+
254+
Topics are indexed parameters in Solidity events and are filtered by topic
255+
values in the EVM log. The offset corresponds to the topic index:
256+
257+
- Topic 0 is always the event signature hash and is implicit (offset 0 if
258+
included)
259+
- Additional topics (offset 1, 2, 3) correspond to indexed parameters
260+
261+
**For Non-Indexed Parameters (Data)**:
262+
263+
Non-indexed parameters are stored in the log's data field. They are packed as
264+
32-byte words:
265+
266+
- Word 0 (bytes 0-31) corresponds to offset 4
267+
- Word 1 (bytes 32-63) corresponds to offset 5
268+
- And so on...
269+
270+
For types smaller than 32 bytes, they are right-padded (for fixed-size types) or
271+
stored in a single word.
272+
273+
## Examples
274+
275+
### Example 1: ERC20 Transfer Trigger
276+
277+
Trigger when a specific address transfers tokens:
278+
279+
**Event ABI:**
280+
281+
```json
282+
{
283+
"name": "Transfer",
284+
"inputs": [
285+
{ "name": "from", "type": "address", "indexed": true },
286+
{ "name": "to", "type": "address", "indexed": true },
287+
{ "name": "value", "type": "uint256", "indexed": false }
288+
]
289+
}
290+
```
291+
292+
**Predicate Specification:**
293+
294+
- Parameter "from" equals 0x742d35Cc6634C0532925a3b844Bc9e7595f1bEb
295+
296+
**Generated EventTriggerDefinition (JSON):**
297+
298+
```json
299+
{
300+
"version": 1,
301+
"contract": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
302+
"logPredicates": [
303+
{
304+
"logValueRef": {
305+
"offset": 1,
306+
"length": 1
307+
},
308+
"valuePredicate": {
309+
"op": 5,
310+
"intArgs": [],
311+
"byteArgs": [
312+
"0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f1beb"
313+
]
314+
}
315+
}
316+
]
317+
}
318+
```
319+
320+
### Example 2: Multi-Condition Trigger
321+
322+
Trigger when a swap occurs with minimum token amount:
323+
324+
**Event ABI (Uniswap V3 SwapRouter):**
325+
326+
```json
327+
{
328+
"name": "Swap",
329+
"inputs": [
330+
{ "name": "sender", "type": "address", "indexed": true },
331+
{ "name": "recipient", "type": "address", "indexed": true },
332+
{ "name": "amount0Delta", "type": "int256", "indexed": false },
333+
{ "name": "amount1Delta", "type": "int256", "indexed": false }
334+
]
335+
}
336+
```
337+
338+
**Predicate Specifications:**
339+
340+
- Parameter "amount1Delta" >= 1000000000000000000 (1 token with 18 decimals)
341+
- Parameter "sender" equals 0x742d35Cc6634C0532925a3b844Bc9e7595f1bEb
342+
343+
**Generated EventTriggerDefinition (JSON):**
344+
345+
```json
346+
{
347+
"version": 1,
348+
"contract": "0x1111111254fb6d44bac0bed2854e76f90643097d",
349+
"logPredicates": [
350+
{
351+
"logValueRef": {
352+
"offset": 1,
353+
"length": 1
354+
},
355+
"valuePredicate": {
356+
"op": 5,
357+
"intArgs": [],
358+
"byteArgs": [
359+
"0x000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f1beb"
360+
]
361+
}
362+
},
363+
{
364+
"logValueRef": {
365+
"offset": 5,
366+
"length": 1
367+
},
368+
"valuePredicate": {
369+
"op": 4,
370+
"intArgs": ["1000000000000000000"],
371+
"byteArgs": []
372+
}
373+
}
374+
]
375+
}
376+
```

0 commit comments

Comments
 (0)