Tezos 和基于 SmartPy 的智能合约开发

区块链 2026-03-28

SmartPy和Tezos编程入门

本课程介绍了Tezos区块链及其智能合约语言SmartPy。我们编写、学习并测试了自己的第一个SmartPy合约。这只是一个开始,关于SmartPy和Tezos,还有很多需要学习和探索的东西。

区块链、Tezos和SmartPy介绍

1.1 区块链基础

在全面了解Tezos和SmartPy之前,我们必须先了解支撑所有这些技术的底层技术——区块链。区块链是由众多区块组成的链式结构,每个区块包含一个交易列表。区块链技术提供了一个去中心化的交易数据库,即“数字分类账”,对所有网络参与者可见。它的架构保证每笔交易都是唯一的,一旦记录在数据库中,便无法更改。

1.2 Tezos介绍

Tezos是一个典型的区块链平台,它与众多其他区块链平台(如比特币或以太坊)的区别在于其强调“自我修正”,允许协议在不进行硬分叉的情况下进行升级。这是一个重大优势,使协议具有适应性且不会过时。

Tezos还提供了一个智能合约平台。智能合约是一种自动执行的合约,买卖双方之间的协议直接通过代码编写。这种管理和验证数字协议的能力使Tezos在诸多领域具有潜在应用,包括金融服务、供应链、去中心化应用(DApp)等。

1.3 SmartPy:Tezos的智能合约语言

要在Tezos上创建智能合约,我们使用一种叫做SmartPy的语言。SmartPy是一个用于开发Tezos区块链智能合约的Python库。它是一种直观有效的语言,用于体现合约及相关的测试场景。

SmartPy最显著的特点是它与Python的整合,Python是世界上使用最广且发展最快的编程语言之一。如果你对Python已经有一定了解,那么学习SmartPy会相对容易。

用SmartPy创建你的第一份合约

访问SmartPy IDE

SmartPy包含一个功能齐全的集成开发环境(IDE),可通过浏览器访问。进入SmartPy IDE后,便可以开始编写你的第一个智能合约了。

270

编写智能合约

在SmartPy IDE中,找到编辑窗口,输入合约代码。我们首先来编写一个基本的合约。复制以下代码并粘贴到SmartPy编辑器中:

Python
import smartpy as sp
# A SmartPy module
@sp.module
def main():
    # A class of contracts
    class MyContract(sp.Contract):
        def __init__(self, myParameter1, myParameter2):
            self.data.myParameter1 = myParameter1
            self.data.myParameter2 = myParameter2
        # An entrypoint, i.e., a message receiver
        # (contracts react to messages)
        @sp.entrypoint
        def myEntryPoint(self, params):
            assert self.data.myParameter1 <= 123
            self.data.myParameter1 += params

合约解析

该合约代码包含以下几个重要部分:

import smartpy as sp- 此代码将导入SmartPy库,我们可以用它来编写合约。

@sp.module- 此装饰器告知SmartPy解释器,这个函数将包含一个SmartPy合约。

class MyContract(sp.Contract):- 在这里,我们为合约定义一个新类(用于创建新对象的蓝图)。它继承自sp.Contract超类,该超类提供了许多有用的功能,用于处理合约的状态并定义其行为。

self.data.myParameter1 = myParameter1和self.data.myParameter2 = myParameter2- 这里我们定义了合约的初始状态。这两个参数将在部署到区块链时传递给合约。

@sp.entrypoint- 此装饰器告知解释器,函数myEntryPoint是合约的入口点。入口点是我们部署合约后与合约交互的方式。

self.data.myParameter1 += params- 此行代码将myParameter1的值增加一定数量,此数量是传给myEntryPoint的量。

合约测试

编写合约的一个重要内容是对其进行彻底的测试。在SmartPy中,测试与开发过程相融合。使用以下代码将测试添加到合约中:

Python
# Tests
@sp.add_test(name="Welcome")
def test():
    # We define a test scenario, together with some outputs and checks
    # The scenario takes the module as a parameter
    scenario = sp.test_scenario(main)
    scenario.h1("Welcome")
    # We first define a contract and add it to the scenario
    c1 = main.MyContract(12, 123)
    scenario += c1
    # And call some of its entrypoints
    c1.myEntryPoint(12)
    c1.myEntryPoint(13)
    c1.myEntryPoint(14)
    c1.myEntryPoint(50)
    c1.myEntryPoint(50)
    c1.myEntryPoint(50).run(valid=False)  # this is expected to fail
    # Finally, we check its final storage
    scenario.verify(c1.data.myParameter1 == 151)
    # We can define another contract using the current state of c1
    c2 = main.MyContract(1, c1.data.myParameter1)
    scenario += c2
    scenario.verify(c2.data.myParameter2 == 151)

合约运行

编写好的完整合约代码应如下所示:

Python
import smartpy as sp
# This is the SmartPy editor.
# You can experiment with SmartPy by loading a template.
# (in the Commands menu above this editor)
#
# A typical SmartPy program has the following form:

# A SmartPy module
@sp.module
def main():
    # A class of contracts
    class MyContract(sp.Contract):
        def __init__(self, myParameter1, myParameter2):
            self.data.myParameter1 = myParameter1
            self.data.myParameter2 = myParameter2
        # An entrypoint, i.e., a message receiver
        # (contracts react to messages)
        @sp.entrypoint
        def myEntryPoint(self, params):
            assert self.data.myParameter1 <= 123
            self.data.myParameter1 += params

# Tests
@sp.add_test(name="Welcome")
def test():
    # We define a test scenario, together with some outputs and checks
    # The scenario takes the module as a parameter
    scenario = sp.test_scenario(main)
    scenario.h1("Welcome")
    # We first define a contract and add it to the scenario
    c1 = main.MyContract(12, 123)
    scenario += c1
    # And call some of its entrypoints
    c1.myEntryPoint(12)
    c1.myEntryPoint(13)
    c1.myEntryPoint(14)
    c1.myEntryPoint(50)
    c1.myEntryPoint(50)
    c1.myEntryPoint(50).run(valid=False)  # this is expected to fail
    # Finally, we check its final storage
    scenario.verify(c1.data.myParameter1 == 151)
    # We can define another contract using the current state of c1
    c2 = main.MyContract(1, c1.data.myParameter1)
    scenario += c2
    scenario.verify(c2.data.myParameter2 == 151)

编写好合约和测试代码后,可以直接在IDE中运行它们。单击IDE右上角的“Run”按钮。IDE将对合约进行编译,运行测试,并显示输出结果以及每个操作和状态变化的详细说明。

我们编写、学习并测试了自己的第一个SmartPy合约。这只是一个开始,关于SmartPy和Tezos,还有很多需要学习和探索的东西。不要走开,继续跟我们进行这场探索之旅吧!

合约存储

什么是存储?

在Tezos智能合约中,存储就像合约的内存,是保存与合约相关的所有数据的地方。从本质上说,它充当我们合约的状态,存储在多笔交易中持续存在的值,并使智能合约能够“记住”信息。正是这种能力使我们能够在Tezos区块链上构建复杂且有趣的去中心化应用。

深入了解存储

在分析本节课的代码之前,我们将进一步了解存储的概念。合约的存储是在函数调用之间持续存在的状态。如果你具有编程背景,可以将其视为合约的“全局状态”。它允许用户与合约进行持续的互动。

我们来看看本节课的合约代码:

Python
import smartpy as sp
@sp.module
def main():
    class StoreValue(sp.Contract):
        def __init__(self, value):
            self.data.storedValue = value
        @sp.entrypoint
        def replace(self, params):
            self.data.storedValue = params.value
        @sp.entrypoint
        def double(self):
            self.data.storedValue *= 2
        @sp.entrypoint
        def divide(self, params):
            assert params.divisor > 5
            self.data.storedValue /= params.divisor

if "templates" not in __name__:
    @sp.add_test(name="StoreValue")
    def test():
        c1 = main.StoreValue(12)
        scenario = sp.test_scenario(main)
        scenario.h1("Store Value")
        scenario += c1
        c1.replace(value=15)
        scenario.p("Some computation").show(c1.data.storedValue * 12)
        c1.replace(value=25)
        c1.double()
        c1.divide(divisor=2).run(
            valid=False, exception="WrongCondition: params.divisor > 5"
        )
        scenario.verify(c1.data.storedValue == 50)
        c1.divide(divisor=6)
        scenario.verify(c1.data.storedValue == 8)

代码解析与合约运行

在我们的存储合约中,有多个入口点——replace、double和divide。部署合约后,用户可以调用这些入口点与合约进行交互。

对于replace和divide入口点,用户必须在交易中提供参数。对于replace,需要参数value,对于divide,需要参数divisor。

在SmartPy IDE上运行此合约时,你可以在右侧看到合约运算和存储的可视化表示。你可以在这里尝试运行合约,具体步骤如下:

点击Deploy按钮,部署你的合约。

部署后,你将在Contracts下看到该合约。点击该合约。

现在可以看到所列合约的入口点。

要调用replace,请在字段中输入params.value的值,然后单击replace按钮。

要调用double,只需单击double按钮。

要调用divide,请在字段中输入params.divisor的值,然后单击divide按钮。

每一次运行都会创建一个新运算,可以在每次运算后看到更新的合约存储状态。

270

在这个合约中,我们通过self.data.storedValue = value命令行强调了存储的概念。其中,self.data指的是我们合约的存储。这是我们保存合约状态的地方:一个名为storedValue的单一参数。

本合约也包含多个入口点。入口点本质上是允许外部方与合约交互的公共函数。这里的入口点允许以各种方式修改storedValue。我们可以用一个新值替换它,或者将其翻倍,也可以用给定的除数除以它。

最后,我们可以试验一下如下场景:为storedValue创建一个初始值为12的合约实例,然后调用入口点以各种方式修改storedValue的值并验证结果。

存储的重要性

在构建Tezos智能合同时,存储和更新值的能力非常重要。它允许数据在与合约的不同交互中保持持久性。无论是维护代币合约中的余额,在去中心化应用中存储用户信息,还是在区块链上保存游戏状态,存储都是实现这些功能的核心所在。

智能合约中的存储可以包含简单的值,如整数、字符串和布尔值,也可以包含更复杂的数据结构,如列表、映射和自定义对象。这使我们能够在合约中构建复杂的逻辑和状态转换。

下面我们将继续探讨这些核心概念,在智能合约中引入更复杂的计算,并在我们的存储合约中使用更高级的数据类型。请继续关注。俗话说,熟能生巧。所以,不要犹豫,动手编写你的代码吧,尝试多多修改并观察结果,你一定能取得进步!

构建智能合约计算器

理论

Tezos上的智能合约可以有多个入口点,入口点可以看作是面向对象编程中的方法或函数。每个入口点都可以有自己的参数,并且可以与合约的存储进行交互。在我们的计算器合约中,每个数学运算都将是一个入口点。

需要注意的是,对存储数据的任何修改都将记录在区块链上。因此,我们执行的运算不像常规计算器中那样只能短暂存储。在Tezos区块链上,这些运算是不可篡改且可审计的。

此外,需要注意的是,由于Tezos区块链是去中心化的,所有计算都应该是确定性的。因此,像除法这样的运算可能与你所熟悉的稍有不同。在Tezos合约中,除法是整数除法,因此3除以2将得到1,而不是1.5。

实操

下面是该计算器合约的代码。Calculator合约将运算结果保存在其存储空间中。每个入口点都将接收一个参数,并使用存储结果和输入参数执行运算。

Python
import smartpy as sp

@sp.module
def main():
    class Calculator(sp.Contract):
        def __init__(self):
            self.data.result = 0
        @sp.entrypoint
        def multiply(self, x, y):
            self.data.result = x * y
        @sp.entrypoint
        def add(self, x, y):
            self.data.result = x + y
        @sp.entrypoint
        def square(self, x):
            self.data.result = x * x
        @sp.entrypoint
        def squareRoot(self, x):
            assert x >= 0
            y = x
            while y * y > x:
                y = (x / y + y) / 2
            assert y * y <= x and x < (y + 1) * (y + 1)
            self.data.result = y
        @sp.entrypoint
        def factorial(self, x):
            self.data.result = 1
            for y in range(1, x + 1):
                self.data.result *= y
        @sp.entrypoint
        def log2(self, x):
            self.data.result = 0
            y = x
            while y > 1:
                self.data.result += 1
                y /= 2

if "templates" not in __name__:
    @sp.add_test(name="Calculator")
    def test():
        c1 = main.Calculator()
        scenario = sp.test_scenario(main)
        scenario.h1("Calculator")
        scenario += c1
        c1.multiply(x=2, y=5)
        c1.add(x=2, y=5)
        c1.add(x=2, y=5)
        c1.square(12)
        c1.squareRoot(0)
        c1.squareRoot(1234)
        c1.factorial(100)
        c1.log2(c1.data.result)
        scenario.verify(c1.data.result == 524)

我们来实际操作一下如何实现这份合约!

第一步:将合约代码粘贴到SmartPy IDE中。

第二步:点击右上角的Run按钮来编译并模拟合约。

第三步:观察IDE右侧的模拟结果。你可以看到每次运算(如乘法、加法、平方根等)后合约存储的状态。

第4步:任意修改运算参数并观察合约存储的变化!

270

现在,你已经学会了构建和运行计算器智能合约,该合约能够执行基本的运算!我们将学习更高级的概念,如FIFO合约创建。最后,再次勉励大家,继续探索,开启愉快的编程之旅!

FIFO合约创建

理论

在FIFO的数据结构中,第一个加入队列的元素也将是第一个被移除的元素。也就是说,一旦添加了新元素,必须先移除之前添加的所有元素,才能移除新元素。

在智能合约中,实现FIFO队列可以用于许多场景,例如公平的排队系统,每个人都按照他们到达的顺序得到服务(或处理)。

实操

我们直接开始编写一个FIFO合约。合约的两个主要运算将是push(用于向队列添加元素)和pop(用于从队列中移除元素)。

合约将队列存储在其存储空间中的列表中,每次push运算都会将一个元素追加到列表的末尾,而每次pop运算都会从列表的开头移除一个元素。

合约代码如下:

Python
import smartpy as sp

@sp.module
def main():
    # The Fifo class defines a simple contract that handles push and pop instructions
    # on a first-in first-out basis.
    class SimpleFifo(sp.Contract):
        def __init__(self):
            self.data.first = 0
            self.data.last = -1
            self.data.saved = {}
        @sp.entrypoint
        def pop(self):
            assert self.data.first < self.data.last
            del self.data.saved[self.data.first]
            self.data.first += 1
        @sp.entrypoint
        def push(self, element):
            self.data.last += 1
            self.data.saved[self.data.last] = element
        @sp.onchain_view
        def head(self):
            return self.data.saved[self.data.first]

if "templates" not in __name__:
    @sp.add_test(name="Fifo")
    def test():
        scenario = sp.test_scenario(main)
        scenario.h1("Simple Fifo Contract")
        c1 = main.SimpleFifo()
        scenario += c1
        c1.push(4)
        c1.push(5)
        c1.push(6)
        c1.push(7)
        c1.pop()
        scenario.verify(sp.View(c1, "head")() == 5)

测试FIFO合约:

第一步:复制合约代码并粘贴至SmartPy IDE中。

第二步:点击右上角的Run按钮来编译和模拟合约。

第三步:查看IDE右侧的模拟结果。你将看到每次运算后合同存储的状态。

第四步:尝试更改运算的顺序或添加新运算。

递归视图与斐波那契数列

在计算机科学领域,递归指问题的解决方案取决于相同问题的较小实例。它是一个函数作为子程序调用自身的过程。这使得函数可以用较少的参数被调用,减少了函数需要维护的状态信息的数量,并提供了一种简洁的方式来表达解决某些类型问题的方法。对于初学者来说,递归通常被认为是一个具有挑战性的概念,但如果学习得当,它可以成为程序员的一个强大工具,有助于创建更简洁的代码,并且通常可以用来用简单的方案解决复杂问题。 在本课中,我们将把递归应用于斐波那契数列。斐波那契数列是一串数字,从第三个数字开始,每个数字都是前两个数字之和,如0、1、1、2、3、5、8、13、21、34,以此类推。该数列从0开始,第n个数字是第(n-1)个和第(n-2)个数字的和。

SmartPy中的递归视图

在SmartPy中,递归是通过允许函数在其自己的定义中调用自身来实现的。在处理可以分解为更小的、相同的子问题时,这种方法非常有用。视图(view)是一个SmartPy函数,它不会修改合约存储,但可以读取合约内容。我们所说的递归视图是指在执行过程中调用自身的视图函数。

斐波那契数列

斐波那契数列是一组数字,前两项为0和1,从第三项开始,每个数字都是前两个数字的和。这个数列虽然简单,但由于它是递归性的,可以很好地帮助我们理解递归。

斐波那契数列由递归关系定义:

SCSS

F(n) = F(n-1) + F(n-2)

该数列中,初始条件是F(0) = 0和F(1) = 1。要得到斐波那契数列中的第n个数字,我们只需将第(n-1)个和第(n-2)个数字相加。这种递归性正是斐波那契数列非常适合理解递归的原因。了解了递归及其在斐波那契数列中的应用之后,我们将深入研究实现递归视图以计算斐波那契数列的SmartPy代码。

代码解析

以下SmartPy代码定义了一个合约FibonacciView,它使用递归视图计算斐波那契数列。这个例子可以很好地帮助我们理解如何在SmartPy中创建和使用递归函数。

Python
import smartpy as sp

@sp.module
def main():
    class FibonacciView(sp.Contract):
        """Contract with a recursing view to compute the sum of fibonacci numbers."""
        @sp.onchain_view()
        def fibonacci(self, n):
            """Return the sum of fibonacci numbers until n.
            Args:
                n (sp.int): number of fibonacci numbers to sum.
            Return:
                (sp.int): the sum of fibonacci numbers
            """
            sp.cast(n, int)
            if n < 2:
                return n
            else:
                n1 = sp.view("fibonacci", sp.self_address(), n - 1, int).unwrap_some()
                n2 = sp.view("fibonacci", sp.self_address(), n - 2, int).unwrap_some()
                return n1 + n2

if "templates" not in __name__:
    @sp.add_test(name="FibonacciView basic scenario", is_default=True)
    def basic_scenario():
        sc = sp.test_scenario(main)
        sc.h1("Basic scenario.")
        sc.h2("Origination.")
        c1 = main.FibonacciView()
        sc += c1
        sc.verify(c1.fibonacci(8) == 21)

这个FibonacciView合约包含一个递归视图函数fibonacci,它返回第n个斐波那契数。

该函数用@sp.onchain_view()装饰器,表示这是对合约存储的只读操作。该函数将整数n作为参数,代表我们要检索的斐波那契数列中的位置。

在函数内部,为了安全起见,我们首先将n转换为整数。接下来是函数的递归部分。如果n小于2,我们只返回n,因为斐波那契数列的前两个数字是0和1。如果n大于或等于2,我们用递归的方式调用n-1和n-2的fibonacci函数,然后将结果相加来计算第n个斐波那契数。这符合定义斐波那契数列的递归关系。sp.view调用创建了对fibonacci函数本身的递归调用。

我们继续看看测试部分。

在测试函数basic_scenario中,创建一个新的FibonacciView合约实例,将其添加到本场景中,然后使用sc.verify检查第8个斐波那契数是否为21,若是,则斐波那契数列是正确的。

在SmartPy IDE中运行代码

运行代码:

打开SmartPy IDE。

将我们提供的代码复制并粘贴到编辑器中。

单击“Run”按钮,在IDE右侧会显示正在执行的测试场景。你可以看到正在执行的操作和验证的检查。

在这节课中,我们介绍了很多高级概念,包括递归的基础知识、它在编程中的使用、SmartPy中的递归视图,以及递归在斐波那契数列中的应用。我们还探讨了SmartPy中的一个工作代码示例,还学习了如何在SmartPy IDE中运行和验证此代码。

课程总结

课程回顾

SmartPy和Tezos编程入门:在课程开始,我们首先介绍了区块链技术,并重点关注Tezos。我们讨论了智能合约和SmartPy语言,学习了如何设置和使用SmartPy IDE。

合约存储:我们深入探讨了合约存储的概念,学习了如何创建一个存储合约。

 构建智能合约计算器:我们构建了一个基本的智能合约计算器,进一步了解了合约存储的工作原理,并介绍了入口点(合约中的一种函数)。

 FIFO合约创建:我们构建了先进先出(FIFO)合约,探讨了如何在合约中实现更复杂的逻辑。本节课演示了合约存储和入口点在创建有状态合约方面的强大功能。

递归视图与斐波那契数列:我们介绍了计算机科学中的一个基本概念——递归,用递归视图来计算斐波那契的数字。递归视图是可以调用自身的链上合约查询函数,为复杂的合约交互和计算提供了强大的工具。

学习建议

我们的编程之旅才刚刚开始!从本部分课程中学到的基础知识和技能将助力你进一步探究Tezos和SmartPy相关概念。Tezos上的智能合约编程是一个不断发展的领域,为我们带来了众多新兴话题。完成第一部分的学习后,你就迈出了成为经验丰富的Tezos和SmartPy开发人员的一大步。

免责声明:本网站、超链接、相关应用程序、论坛、博客等媒体账户以及其他平台和用户发布的所有内容均来源于第三方平台及平台用户。网站及其内容不作任何类型的保证,网站所有区块链相关数据以及其他内容资料仅供用户学习及研究之用,不构成任何投资、法律等其他领域的建议和依据。用户以及其他第三方平台在本网站发布的任何内容均由其个人负责,与本网无关。

相关文章