Skip to content

Latest commit

 

History

History
234 lines (164 loc) · 8.64 KB

File metadata and controls

234 lines (164 loc) · 8.64 KB

Task 2 - Leo 入门:学会这门语言

请将本文件复制到 learn/YourName/ 文件夹中,填写你的答案后提交 PR。

问题

Q1. Leo 中的 "Private by Default"(默认隐私)语义是什么?

A: 在 Leo 中,record 的字段以及 transition 函数的输入参数默认都是私有的(private)。除非显式标记为 public,否则这些数据在链上以加密形式存储,只有该 record 的所有者(owner)能够使用自己的 view key 解密查看。

constant:公开的常量,值不可变

public:公开字段,任何人可读

private(默认):私有字段,仅所有者可见

这一设计称为 “Private by Default”,保障了 Aleo 上默认的数据隐私安全。

Q2. Tuple 包含 array structs 的示例,以及如何访问 struct 中的元素。

A: Tuple 可以包含数组(array)和结构体(struct)。数组类型为 [Type; Length],tuple 类型为 (Type1, Type2, …)。访问方式:

tuple 元素:tuple.index(如 t.0、t.1)

数组元素:array[index](索引必须为编译期常量)

struct 字段:struct.field

嵌套访问:链式使用 . 和 []

示例:

leo // 定义一个 struct struct Point { x: u64, y: u64, }

// 创建一个包含两个 struct 的数组 let points: [Point; 2] = [ Point { x: 10u64, y: 20u64 }, Point { x: 30u64, y: 40u64 }, ];

// 创建包含一个数组和一个 struct 的 tuple let tup: ([u8; 3], Point) = ( [1u8, 2u8, 3u8], Point { x: 5u64, y: 6u64 }, );

// 访问示例 let first_x: u64 = points[0].x; // 访问数组中第一个 struct 的 x let tup_array: [u8; 3] = tup.0; // 获取 tuple 中的数组 let tup_point_x: u64 = tup.1.x; // 获取 tuple 中 struct 的 x


Q3. Aleo record 中 owner 字段的作用是什么?

A: 根据官方文档(https://docs.leo-lang.org/language/programs_in_practice/private_state),record 是 Aleo 上编码私有状态的方法。Record 数据结构必须包含一个名为 owner 且类型为 address 的组件。owner 字段的作用包括:

  1. 所有权标识owner 指定了该 record 的所有者(一个 Aleo 地址),只有 owner 才能使用(消费)这个 record 作为 transition 的输入。
  2. 隐私保护 — record 的数据在链上以加密形式存储,只有 owner 能用自己的 view key 解密查看 record 内容。
  3. UTXO 模型基础 — 类似比特币的 UTXO 模型,owner 机制实现了链上资产的所有权追踪。

此外,当传递 record 作为程序函数输入时,编译器会自动插入 _nonce: group_version: u8 组件,无需在 Leo 程序中显式声明。

record Token {
    owner: address,    // 必需的 owner 字段
    amount: u64,
}

Q4. 程序中的 final 是什么?

A: 根据官方文档(https://docs.leo-lang.org/language/programs_in_practice/public_state),final 是 Leo 中用于链上公开执行的代码块或函数类型。Leo 程序的执行分为两个阶段:

  1. transition 阶段 — 在链下执行,处理私有数据并生成零知识证明。
  2. final 阶段 — 在链上由验证节点公开执行,可以读写 mapping(公共链上存储)和 storage(存储变量)。

使用方式有两种:

// 方式一:在 transition 中使用 return final {} 块
fn dubble() -> Final {
    let addr: address = self.caller;
    return final {
        let current_value: u64 = balance.get_or_use(addr, 0u64);
        balance.set(addr, current_value + 1u64);
    };
}

// 方式二:定义 final fn(只在链上执行的独立函数)
final fn update_balance(addr: address, amount: u64) {
    balance.set(addr, amount);
}

重要提示:mapping 操作和 storage 变量操作只能final { } 块或 final fn 中使用。


Q5. 如何创建 helper functions(辅助函数)?

A: 根据官方文档(https://docs.leo-lang.org/language/programs_in_practice/functions),在Leo 中使用 fn 关键字(而非 transition)定义辅助函数(也叫 inline function / helper function)。辅助函数在编译时会被内联展开到调用它的 transition 中。

// 定义辅助函数
fn compute_fee(amount: u64, rate: u64) -> u64 {
    return amount * rate / 100u64;
}

// 在 transition 中调用辅助函数
transition process_payment(amount: u64) -> u64 {
    let fee: u64 = compute_fee(amount, 5u64);
    return amount - fee;
}

辅助函数的特点:

  • 使用 fn 关键字声明(不是 transition,不是 helper
  • 只能被 transition 或其他 fn 调用,不能直接被外部调用
  • 不能访问 self.caller 等上下文信息
  • 编译时内联展开,不会单独生成电路

Q6. helper functions 能否创建 records?

A: 不能。fn(辅助函数)不能创建或销毁 record。只有 transition 函数才有权限创建 record(作为输出返回)和销毁 record(作为输入消费)。这是 Leo 的安全设计——record 代表链上资产的所有权,其生命周期必须由 transition 管理,以确保能生成有效的零知识证明并正确更新链上状态。


Q7. constructor 的目的是什么?

A: 在 Leo 中,struct 和 record 的 constructor(构造器)用于创建该类型的新实例。语法是 TypeName { field1: value1, field2: value2 }。Constructor 确保了类型安全——必须为所有字段提供正确类型的值,不能遗漏或多余字段。

struct Point {
    x: u32,
    y: u32,
}

record Token {
    owner: address,
    amount: u64,
}

// 使用 constructor 创建 struct 实例
let p: Point = Point { x: 1u32, y: 2u32 };

// 使用 constructor 创建 record 实例(只能在 transition 中)
let t: Token = Token {
    owner: aleo1xxx...xxx,
    amount: 100u64,
};

Q8. 如何组合多个 interfaces(接口)?

A: 根据官方文档(https://docs.leo-lang.org/language/programs_in_practice/interfaces),在当前版本的 Leo 中,"interface" 的概念主要体现在程序间的交互和动态调度(Interfaces & Dynamic Dispatch)上。组合多个数据类型的方式是通过 struct 嵌套——一个 struct 的字段类型可以是另一个 struct,从而实现数据组合。

struct Coordinates {
    x: u64,
    y: u64,
}

struct Player {
    name: field,
    position: Coordinates,  // 嵌套组合
    score: u64,
}

// 访问嵌套字段使用链式 . 操作符
let p: Player = Player {
    name: 1field,
    position: Coordinates { x: 10u64, y: 20u64 },
    score: 100u64,
};
let x_pos: u64 = p.position.x;

对于程序间接口的组合,Leo 使用 import 导入外部程序并调用其 transition,通过动态调度(dynamic dispatch)使用 identifier 类型在运行时决定调用哪个程序。


Q9. record interface 中 .. 的含义是什么?

A: 在 Leo 中,.. 是 record/struct 更新时的展开语法(spread operator),用于在创建新实例时复制旧实例的未修改字段。这样可以避免逐一重写所有字段的冗余代码。

record Token {
    owner: address,
    amount: u64,
}

// 使用 .. 展开旧 record 的字段,只更新 amount
let updated: Token = Token {
    amount: new_amount,
    ..old_token  // 保留 old_token 的 owner 等其他字段
};

Q10. 何时使用 dyn record(动态 record)?

A: 根据官方文档(https://docs.leo-lang.org/language/programs_in_practice/interfaces),动态调度(dynamic dispatch)允许在运行时决定调用哪个程序的 transition。dyn record 用于在需要处理来自不同程序的 record 类型时,提供运行时类型灵活性。典型使用场景包括:

  1. 跨程序交互 — 当一个程序需要接收和处理来自多个不同程序的 record 输入,但在编译时无法确定具体类型时。
  2. 可扩展的协议设计 — 允许未来部署的新程序与现有程序交互,而无需修改现有代码。
  3. 动态调用 — 结合 identifier 类型,在运行时确定要调用的目标程序和要处理的 record 类型。

Q11. storage vector 支持的核心操作有哪些?

A: 根据官方文档(https://docs.leo-lang.org/language/programs_in_practice/public_state), storage vector 声明为 storage name: [type];,行为类似动态数组。支持的核心操作包括:

  • get(idx) — 查询指定索引处的元素,返回 type?(如果索引越界则返回 none
  • len() — 获取 vector 的当前长度
  • push(value) — 在末尾添加一个元素
  • pop() — 移除并返回最后一个元素(返回 type?
  • set(idx, value) — 设置指定索引处的值(索引必须在范围内)