Skip to content

Commit 415e4ae

Browse files
committed
Add spec document for event trigger defintions
Fixes #637
1 parent 42f5625 commit 415e4ae

File tree

1 file changed

+371
-0
lines changed

1 file changed

+371
-0
lines changed

docs/event.md

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

0 commit comments

Comments
 (0)