Cairo开发流程示例

概述

学习Cairo开发主要有2个资料,一个是官方提供的学习文档,另一个则是官方提供的playground。 本文将主要概念和开发流程简要介绍,同时附上案例

基本概念

  1. StarkNet - 一个无需许可的去中心化 ZK-Rollup,作为以太坊上的 L2 网络运行,任何 dApp 都可以在不影响以太坊的可组合性和安全性的情况下实现无限规模的计算。

  2. Cairo - 一种编程语言用于可被证明程序的编写,使得其他验证者确信该程序被正确执行。StarkNet的基础设施及其合约都是使用Cairo编写的。

  3. 内存模型 - 支持只读的非确定性内存,这意味着每个内存单元的值由证明者选择,但它不能随时间改变(在 Cairo 程序执行期间)。我们使用语法 [x] 来表示地址 x处的内存值。例如,如果我们在程序开始时断言 [0] = 7,那么在整个运行期间 [0] 的值将是 7

  4. Assert - 断言声明,assert <expr0> = <expr1>,在Cairo中有2个用处:一是验证左右2边的值是否相同;二是给内存单元设置值,如“内存模型”中的例子。

  5. 递归,而非循环 - 由于内存模型的不可变性,需要使用递归实现循环的功能。

  6. 类型felt - field element,整型,范围在-P/2 < x < P/2,其中P的取值(StarkNet实现时)是质数2^251 + 17*2^192 + 1

  7. 除法 - 由于不支持浮点计算,因此有余数的情况下,是采用逆运算来记录结果的。例如,x = 7 / 3 ,将记录为一个felt满足3 * x = 7,原理是当3 * x的结果超出felt取值范围后,会自动将为7。因此,下面y的结果还是7,而不是6

x = 7 / 3
y = x * 3
  1. 内存分配 - 使用标准库函数alloc()来分配一个新的内存段。实际上,分配内存的确切位置只有在程序终止时才会确定,这样可以避免指定分配的大小。

  2. 提示(hint) - 提示是一段Python代码,其中包含只有证明者才能看到和执行的指令。从验证者的角度来看,这些提示是不存在的。

func bar() -> (res):
    alloc_locals
    local x
    %{ ids.x = 5 %}  # Note this line.
    assert x * x = 25
    return (res=x)
end

%{ %}括起来的部分提示,其含义就是将5赋值给ids.x,也就是在上面1行语句local x申请的x。 * 作用: 提示有助于处理程序输入的方式——证明者必须知道要处理哪个输入,但验证者并不关心(验证者只关心初始状态以及解决方案是否有效)。 换句话来说,提示是不需要被用于生成执行轨迹和定义约束的,因此,如何充分利用提示及其他方法来缩短执行轨迹、减小内存模型来优化Cairo程序显得非常重要,这关系着系统的执行时间和需支付的gas费用。

开发流程

Cairo目前,有3种开发模式:本地开发测试,面向以太坊的部署开发和面向StarkNet的部署开发。其中本地模式,可以使用提示,无需部署合约。而其他2种则需要部署在相应网络上,只能使用官方指定的包含了提示的基本函数库。

  1. 本地开发测试模式

    1. 创建Cairo文件(例如puzzle.cairo),编写合约
    2. 执行编译命令
    	cairo-compile puzzle.cairo --output puzzle_compiled.json
    
    1. 执行执行命令,具体参数可以查看帮助文档
    	cairo-run --program=puzzle_compiled.json \
    		--print_output --layout=small \
    		--program_input=puzzle_input.json
    
  2. 面向StarkNet的部署开发

    1. 创建Cairo文件(例如puzzle.cairo),编写合约
    2. 执行编译命令,确保没有bug
    	starknet-compile puzzle.cairo --output puzzle-compiled.json --abi puzzle-abi.json
    
    1. 创建py文件,编写部署、测试代码
    @pytest.mark.asyncio
    async def test_arithmetic():
        # 创建用于StarkNet系统的模拟器
        starknet = await Starknet.empty()
    
        # 部署合约,puzzle.cairo
        contract = await starknet.deploy(
            source=CONTRACT_FILE,
        )
    
        # 调用puzzle中的对外暴露的方法
        x = 3; y = 14;
        response = await contract.recursive_calc_pow(x, y).call()
        assert response.result[0] == (x ** y) % P
    
    1. 执行python文件

此外,还可以参考命令行模式下部署合约、调用合约的方式。

注意事项

  1. 内建指令: %builtins。 须在程序开始导入,例如,output

  2. 隐式参数及调用:隐式参数可以显示调用或者隐式调用。 main函数隐式调用;func_abc函数显示调用

%builtins output

func main{output_ptr : felt*}(){output_ptr : felt*}:
	res = func_abc{output_ptr = output_ptr}()
...
  1. 使用提示,需在提示的外部,即验证者能执行的部分,对提示修改的数据进行校验。

  2. return () 的有() ,其中可以放返回值。

有用信息

StarkNet Alpha on Mainnet

StarkNet Alpha version 4 on Goerli