Skip to content

Commit 28e0520

Browse files
Merge pull request #105 from IntersectMBO/feat/add-redeemer-index
feat: add redeemer index
2 parents 2e12239 + 98b59fa commit 28e0520

File tree

20 files changed

+1192
-101
lines changed

20 files changed

+1192
-101
lines changed

.changeset/rude-toes-relax.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
"@evolution-sdk/devnet": patch
3+
"@evolution-sdk/evolution": patch
4+
---
5+
6+
Add deferred redeemer construction for dynamic index resolution
7+
8+
**RedeemerBuilder module** (`RedeemerBuilder.ts`):
9+
- `IndexedInput` type: `{ index: number, utxo: UTxO }` - provides the final sorted index and original UTxO after coin selection
10+
- Three modes for redeemer construction:
11+
- `Static`: Direct Data value when index not needed
12+
- `Self`: Per-input function `(input: IndexedInput) => Data` for single UTxO index
13+
- `Batch`: Multi-input function `(inputs: IndexedInput[]) => Data` for stake validator coordinator pattern
14+
- Type guards: `isSelfFn`, `isBatchBuilder`, `isStaticData`
15+
- Internal types: `DeferredRedeemer`, `toDeferredRedeemer`
16+
17+
**Evaluation phase updates**:
18+
- Add `resolveDeferredRedeemers` to convert deferred redeemers after coin selection
19+
- Build `refToIndex` and `refToUtxo` mappings from sorted inputs
20+
- Invoke Self/Batch callbacks with resolved `IndexedInput` objects
21+
22+
**Operations updates**:
23+
- `collectFrom` and `mintTokens` now accept `RedeemerArg` (Data | SelfRedeemerFn | BatchRedeemerBuilder)
24+
- Store deferred redeemers in `state.deferredRedeemers` for later resolution
25+
26+
**Test coverage** (`TxBuilder.RedeemerBuilder.test.ts`):
27+
- Tests for all three modes with mint_multi_validator.ak spec
28+
29+
**Architecture docs** (`redeemer-indexing.mdx`):
30+
- Document the circular dependency problem and deferred construction solution
31+
- Explain stake validator coordinator pattern with O(1) index lookup

docs/content/docs/architecture/index.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ Architectural patterns provide consistency:
169169
<Card title="Deferred Execution" href="/docs/architecture/deferred-execution">
170170
Immutable builder pattern, program-as-value semantics, and composition
171171
</Card>
172+
<Card title="Coin Selection" href="/docs/architecture/coin-selection">
173+
UTxO selection algorithms for covering transaction requirements
174+
</Card>
175+
<Card title="Redeemer Indexing" href="/docs/architecture/redeemer-indexing">
176+
Deferred redeemer construction for Plutus script index optimization
177+
</Card>
178+
<Card title="Script Evaluation" href="/docs/architecture/script-evaluation">
179+
Plutus validator execution and ExUnits calculation
180+
</Card>
172181
<Card title="Provider Layer" href="/docs/architecture/provider-layer">
173182
Blockchain access abstraction across Blockfrost, Kupmios, Maestro, Koios
174183
</Card>

docs/content/docs/architecture/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"index",
55
"transaction-flow",
66
"coin-selection",
7+
"redeemer-indexing",
78
"script-evaluation",
89
"unfrack-optimization",
910
"devnet",
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
title: Redeemer Indexing
3+
description: Deferred redeemer construction for efficient validator input lookup
4+
---
5+
6+
import { Mermaid } from "@/components/mdx/mermaid"
7+
8+
## Abstract
9+
10+
Plutus validators can receive input indices via redeemers for O(1) array lookup instead of O(n) traversal. On-chain traversal is expensive in execution units, so passing indices allows validators to directly access only the inputs they care about.
11+
12+
The challenge: Cardano currently enforces canonical sorting of inputs, and indices depend on this final sorted order. When coin selection or other operations add inputs to the transaction, they insert into the sorted order and shift existing indices.
13+
14+
The solution: **defer redeemer construction** until all inputs are known and sorted.
15+
16+
> **Note**: [CIP-128](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0128) proposes removing the canonical ordering requirement, allowing user-controlled input order. Once implemented, you could place contract inputs at known positions (e.g., indices 0, 1, 2) and append wallet inputs after, making indices predictable without deferred construction.
17+
18+
## Why Index-Based Lookup Matters
19+
20+
Cardano transactions sort inputs lexicographically by `(txHash, outputIndex)`. A validator receives all transaction inputs in this canonical order. Without indices, finding relevant inputs requires traversing the entire list.
21+
22+
<Mermaid chart={`
23+
graph LR
24+
subgraph Traversal["Without Indices: O(n)"]
25+
T1[For each input] --> T2[Check if relevant] --> T3[Process if match]
26+
end
27+
28+
subgraph Direct["With Indices: O(1)"]
29+
D1[Redeemer contains indices] --> D2["Access inputs[i] directly"] --> D3[Process immediately]
30+
end
31+
`} />
32+
33+
Consider a transaction with 15 inputs: 5 contract UTxOs and 10 wallet UTxOs for fees. A naive validator would check all 15 inputs to find the 5 it cares about. With indices in the redeemer, it directly accesses `inputs[3]`, `inputs[7]`, `inputs[12]`, etc.
34+
35+
## The Stake Validator Pattern
36+
37+
A common pattern uses a stake validator as the main coordinator. Spend validators are "dumb" and only verify that the stake validator ran. The stake validator receives all relevant input indices and performs the actual business logic.
38+
39+
<Mermaid chart={`
40+
graph TD
41+
subgraph Transaction
42+
SW[Stake Validator Withdrawal]
43+
S1[Spend Input 1]
44+
S2[Spend Input 2]
45+
S3[Spend Input 3]
46+
W1[Wallet Input]
47+
W2[Wallet Input]
48+
end
49+
50+
subgraph Validation
51+
SW -->|"redeemer: [0, 2, 4]"| Logic[Business Logic]
52+
Logic -->|"inputs[0]"| S1
53+
Logic -->|"inputs[2]"| S2
54+
Logic -->|"inputs[4]"| S3
55+
S1 -->|check withdrawal exists| SW
56+
S2 -->|check withdrawal exists| SW
57+
S3 -->|check withdrawal exists| SW
58+
end
59+
`} />
60+
61+
The stake validator:
62+
1. Receives indices of contract inputs in its redeemer
63+
2. Directly accesses those inputs via index
64+
3. Validates business logic for all contract inputs in one execution
65+
66+
The spend validators:
67+
1. Simply verify the stake validator ran (withdrawal exists in transaction)
68+
2. Delegate all logic to the stake validator
69+
70+
This pattern reduces total execution cost since the stake validator runs once instead of N spend validators running independently.
71+
72+
## The Circular Dependency
73+
74+
The problem: input indices depend on the final sorted order of **all** inputs. But coin selection adds wallet UTxOs after the user specifies script inputs. The indices change.
75+
76+
<Mermaid chart={`
77+
graph LR
78+
P1["User specifies<br/>script inputs"] --> P2["Needs indices<br/>for redeemer"]
79+
P2 -.->|"depends on"| P4
80+
P3["Transaction built"] --> P4["Coin selection<br/>adds wallet UTxOs"]
81+
P4 --> P5["Inputs sorted<br/>indices known"]
82+
P5 --> P6["Too late!<br/>Redeemer built"]
83+
`} />
84+
85+
## Deferred Construction
86+
87+
Instead of building the redeemer immediately, store a **builder function** that will be invoked after coin selection:
88+
89+
<Mermaid chart={`
90+
sequenceDiagram
91+
participant User
92+
participant Builder
93+
participant CoinSelection
94+
participant Resolver
95+
96+
User->>Builder: Specify inputs with redeemer function
97+
Note over Builder: Store function, don't execute
98+
99+
User->>Builder: build()
100+
Builder->>CoinSelection: Run selection
101+
CoinSelection-->>Builder: Wallet UTxOs added
102+
103+
Builder->>Resolver: All inputs now known
104+
Resolver->>Resolver: Sort inputs canonically
105+
Resolver->>Resolver: Compute indices
106+
Resolver->>Resolver: Invoke redeemer function with indices
107+
Resolver-->>Builder: Concrete redeemer returned
108+
109+
Builder-->>User: Transaction ready
110+
`} />
111+
112+
## Three Modes
113+
114+
### Batch Mode
115+
116+
For validators that need indices of multiple inputs. The function receives all indexed inputs and returns a single redeemer.
117+
118+
<Mermaid chart={`
119+
graph TD
120+
B1[Specify inputs to track] --> B2[Function called once after sorting] --> B3["Receives all { index, utxo } pairs"] --> B4[Returns redeemer with indices list]
121+
`} />
122+
123+
Primary use case: stake validator coordinator receiving list of contract input indices.
124+
125+
### Self Mode
126+
127+
For spend validators that need their own index. The function is called once per script UTxO.
128+
129+
<Mermaid chart={`
130+
graph TD
131+
SE1[Each script UTxO] --> SE2[Function called per UTxO] --> SE3["Receives own { index, utxo }"] --> SE4[Returns redeemer with own index]
132+
`} />
133+
134+
Use case: spend validator that looks up its own input by index for validation.
135+
136+
### Static Mode
137+
138+
For redeemers that don't need indices. The data is used directly.
139+
140+
<Mermaid chart={`
141+
graph TD
142+
S1[Concrete redeemer data] --> S2[No computation needed] --> S3[Used directly]
143+
`} />
144+
145+
## Indexed Input
146+
147+
Both Batch and Self modes receive indexed inputs containing:
148+
149+
- **Index**: Final 0-based position in canonically sorted transaction inputs
150+
- **UTxO**: The original UTxO data
151+
152+
The UTxO is included because redeemers may need output reference data, not just the index.
153+
154+
## When to Use Each Mode
155+
156+
| Mode | Use Case |
157+
|------|----------|
158+
| **Batch** | Stake validator coordinator with list of contract input indices |
159+
| **Self** | Spend validator needing its own index |
160+
| **Static** | Redeemer doesn't depend on indices |
161+
162+
## Related Topics
163+
164+
- [Deferred Execution](/docs/architecture/deferred-execution) - Program storage and fresh state model
165+
- [Transaction Flow](/docs/architecture/transaction-flow) - Build phases and state machine

docs/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import "./.next/types/routes.d.ts";
3+
import "./out/dev/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

0 commit comments

Comments
 (0)