Skip to content
This repository was archived by the owner on May 4, 2026. It is now read-only.

Commit 10bf5c4

Browse files
authored
Merge pull request #580 from ProvableHQ/mohammadfawaz/dyn_record
Clarify `dyn record`s as inputs and outputs of dynamic calls
2 parents 8264bf0 + 22936cc commit 10bf5c4

1 file changed

Lines changed: 78 additions & 0 deletions

File tree

documentation/language/programs_in_practice/interfaces.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,81 @@ fn get_memo(rec: dyn record) -> u64 {
202202
```
203203

204204
`dyn record` complements dynamic calls: while dynamic calls allow a program to route logic to any compliant callee, `dyn record` allows that same program to accept, inspect, and forward records from programs it has never seen at compile time, without losing the safety guarantees of the type system.
205+
206+
### Dynamic Records and Dynamic Calls
207+
208+
Regardless of what the interface signature says, dynamic calls always take dynamic records as inputs and return dynamic records as outputs.
209+
210+
When making a dynamic call, all record arguments are treated as `dyn record` under the hood, and all record return values come back as `dyn record` — even when the interface uses a concrete static record type. There are four cases depending on what the interface declares and what the caller provides:
211+
212+
**Case A — Interface expects `dyn record`, caller has `dyn record`**
213+
214+
Pass the dynamic record directly with no conversion needed.
215+
216+
```leo
217+
interface ARC20 {
218+
fn transfer_private(token: dyn record, to: address) -> dyn record;
219+
}
220+
221+
program caller.aleo {
222+
fn main(target: identifier, token: dyn record, to: address) -> dyn record {
223+
return ARC20@(target)::transfer_private(token, to); // direct pass-through
224+
}
225+
}
226+
```
227+
228+
**Case B — Interface expects `dyn record`, caller has a static record**
229+
230+
Convert the static record explicitly to `dyn record` using `as` before passing it.
231+
232+
```leo
233+
interface ARC20 {
234+
fn transfer_private(token: dyn record, to: address) -> dyn record;
235+
}
236+
237+
program my_token.aleo : ARC20 {
238+
record Token { owner: address, amount: u64 }
239+
240+
fn do_transfer(target: identifier, token: Token, to: address) -> dyn record {
241+
return ARC20@(target)::transfer_private(token as dyn record, to); // explicit cast
242+
}
243+
}
244+
```
245+
246+
**Case C — Interface expects a static record, caller has a static record**
247+
248+
Leo implicitly converts the static record to `dyn record` at the call site. Nothing extra is required from the caller, though an implicit unsafe step occurs under the hood.
249+
250+
```leo
251+
interface ARC20 {
252+
record Token;
253+
fn transfer_private(token: Token, to: address) -> Token;
254+
}
255+
256+
program caller.aleo {
257+
record Token { owner: address, amount: u64 }
258+
259+
fn main(target: identifier, token: Token, to: address) -> dyn record {
260+
return ARC20@(target)::transfer_private(token, to); // implicit conversion under the hood
261+
}
262+
}
263+
```
264+
265+
**Case D — Interface expects a static record, caller has a `dyn record`**
266+
267+
Leo implicitly casts the dynamic record to the expected static type at the call site. The return value is still `dyn record`.
268+
269+
```leo
270+
interface ARC20 {
271+
record Token;
272+
fn transfer_private(token: Token, to: address) -> Token;
273+
}
274+
275+
program caller.aleo {
276+
fn main(target: identifier, token: dyn record, to: address) -> dyn record {
277+
return ARC20@(target)::transfer_private(token, to); // implicit cast, returns dyn record
278+
}
279+
}
280+
```
281+
282+
In all four cases, the return type of a dynamic call that involves records is always `dyn record`, regardless of what the interface declares.

0 commit comments

Comments
 (0)