01_概念

  • 本章课程共3小节,学习时长因人而异

概念

1.1_开始 学习相关的概念 - 课程学习时长因人而异

我们今天将学到什么?

大家好☀,今天我们非常兴奋地为Metaschool的开发人员带来有史以来第一个关于Sui区块链的详细课程。那么,我们学到了什么?我们将开发对Sui区块链及其工作原理的理解。本课程非常重要,因为它将帮助您获得必要的基础知识,从而开始您在Sui上构建的激动人心的旅程。虽然从理论和概念开始可能会感觉很慢,但从这个角度学习Sui不仅重要而且有趣,因为Sui为区块链引入了一种新的开发范式。所以让我们从基础开始吧!那么,什么是隋呢?Sui是一个layer-1区块链,具有创新的面向对象数据模型,以资产为中心,对开发人员友好,允许熟悉但强大的开发人员体验。Sui使用Move编程语言来开发智能合约。Move是专门为区块链资产的安全管理而构建的。有300多个项目建立在Sui上,包括顶级游戏和DeFi项目,如Arcade Champion, Ethos 8192, VMeta, Cetus,Scallop,Turbos等。

课程概述

在我们开始内容之前,让我给你一个概述我们将涵盖的内容:

介绍Sui区块链

了解可以在Sui上构建什么

了解Sui的架构

探索Sui的关键功能

到底谁应该上这门课?

本课程适用于希望了解Sui区块链的初学者web3开发人员。如果你对Move on Sui没有经验,不要担心,我们会支持你的。无论如何,有一个基本的编码知识是很好的。

你将从这门课中学到什么?

作为一名开发人员,通过完成本课程,您可以期望获得以下结果:

了解Sui的独特之处

学习Sui体系结构的价值主张

通过SuixMetaschool Discord House探索Sui生态系统中的机会

赚取高达1000 xp

完成证明

好吧,很高兴在这里说出来。在你和我一起完成这门课程后,你将获得xp和一个特殊的NFT,这将在Metaschool平台上解锁更多的机会。下面是NFT的预览图。

img-1

在我们继续之前,让我们先制定一些规矩。请正确地完成你们的快速作业。加入我们的Discord服务器并在那里询问所有相关问题。我们是一个免费的开源平台,如果你在X和LinkedIn上关注我们,这将是一个很大的支持。保持快乐积极!好了,朋友们,不用再等了,让我们直接进入我们精彩的Sui区块链课程吧!我们开始吧!🙌

什么是Sui,它将如何重新定义Web3的可能性

大家好,正如我之前提到的,我们将从在Sui中构建最佳应用程序所需的概念开始。在我们开始在Sui上构建一些很棒的项目之前,我会尽我所能帮助大家理解这些关键概念。拿起你最喜欢的饮料,让我们开始吧!

什么是Sui

Sui是第1层区块链网络,这意味着它是完全自力更生的,处理自己的共识,执行和安全的网络。Sui的网络架构使其能够以闪电般的速度完成交易,并具有一致的、可预测的成本——这是应用程序构建者的基本要素。我们不会深入探讨网络设计的细节,但高层次的,需要知道的信息是:Sui使用委托权益证明来抵抗Sybil以及新的共识和排序机制-分别是Narwhal 和 Bullshark。与以对象为中心的设计相结合,Sui的网络架构的这些元素为开发人员和最终用户提供了速度和成本方面的好处。这使得Sui非常适合于延迟敏感(低延迟要求)的应用程序,例如游戏和快速零售支付。

提示:延迟敏感”是指不能等待或需要快速发生的事情。如果你在玩电子游戏,你按下一个按钮跳跃,你希望游戏能立即做出反应。在一个高速互联网和即时信息的世界里,我们都喜欢事情快速发生,这就是“延迟敏感”。

Sui抛弃了Solidity和以太坊虚拟机(EVM)的组合,选择了Move编程语言和相应的Move VM。Move编程语言实际上是由Facebook的Libra/Diem团队开发的。领导Move语言开发的 Sam Blackshear 实际上是Mysten Labs的联合创始人,该公司是支持和开发Sui技术的研发公司。从高层次上讲,Move的开发是为了创建一种更安全、以资产为中心的编程语言,专为区块链使用而构建。我们稍后将深入探讨细节,但就目前而言,这是学习Sui的一个良好开端。

以下几点总结了我们之前讨论的内容:

Sui具有新颖的共识和排序机制——Narwhal 和 Bullshark。

Sui以对象为中心的数据模型是独特和有能力的开发者体验的基础。

Move编程语言用于编写Sui中的智能合约。

Sui的历史

经过2022年的多次测试网迭代,Sui的主网于2023年5月3日启动。你可能还记得Libra/Diem项目,Meta(即Facebook)是在2020年左右建立的。如果不记得,这里有一个快速入门:Libra/Diem是Meta内部构建的区块链,旨在为用户提供一种非常快速的支付方式,并具有为支付优化的区块链的所有好处。这个项目伴随着一件非常重要的事情,那就是开发了一种新的编程语言Move。不管是好是坏,Meta面临着强大的监管阻力,并决定停止该项目。Libra/Diem项目的五位领导者决定继续他们的工作,扩大他们在Meta创造的可能性,并离开Meta创建了一家新公司Mysten Labs。在Libra/Diem项目的五位梦想家——evan Cheng、Adeniyi Abiodun、Sam Blackshear、George Danezis和Kostas chalkias的带领下,mysten实验室建造了Sui。随着主网发布的临近,Sui基金会成立了,其目标是推广Sui,并帮助建立一个繁荣的Sui社区。在2022年进行了多次测试网迭代之后,Sui的主网于2023年5月启动。从那时起,Mysten实验室和开发Sui应用程序的开发人员社区继续推进Sui的发展。

img-1

Sui有何不同

我们学到了很多关于Sui到底是什么,对吧? 现在,是什么造就了它的不同?让我们看看Sui解决了什么问题,它与其他区块链有什么不同

1、可靠的可用性,一致的费用,易于上手以支持大规模使用

问题:区块链的广泛使用受到限制,因为有许多问题阻碍了大规模使用。网络有停机时间,费用通常是不可预测的,并且很难将新用户引入web3。现有的区块链都在努力解决这些问题,但不幸的是,通常在发现问题后才去解决。一个有效的开发者平台不可能只关注这些问题,解决方案需要尽可能多地预先规划。

解决方法:Sui是通过学习和观察其他区块链生态系统中而从头开始构建的。Sui的网络架构被设计为在需求强烈的时候具有弹性,收费结构被设计为为用户保持一致,以避免意外的变化,Sui背后的开发团队已经为开发人员提供了工具,以应对为大众构建时的共同挑战。Sui正在以一种全新的方式为未来的需求而建设。在构建一个强大的应用程序时,您希望确保底层技术不仅支持您当前的需求,还支持您未来的需求。通过以全新的视角构建高性能的第一层,Sui为下一代区块链应用程序提供了一个引人注目的开发环境。

2、真实资产所有权

问题:通常认为,区块链上与自我托管账户相关的任何东西都是由账户运营商单方面拥有的。事实并非如此。例如,在以太坊中,任何通过智能合约(即ERC-20代币)铸造的资产都不会真正“离开”智能合约。用户并不真正拥有资产,他们只是被分配了与资产交互的权限。虽然这似乎是一个不重要的细节,但它实际上对用户有影响,我们将在深入研究Sui中的对象和所有权时讨论。

解决方法:现在这个问题无法通过任何区块链来解决,因为大多数l1和L2s都有类似于以太坊的架构。但在Sui的案例中,像令牌这样的资产被表示为单独的“对象”,其所有权直接定义在对象上,而不仅仅是智能合约中的条目。这意味着每个资产(例如,代币或NFT)独立于原始智能合约而存在。用户可以直接控制对象,不需要信任智能合约的所有权映射。

结束

综上所述,我们了解了Sui的基础知识,它的起源故事,以及它与其他区块链的区别。如果你在这里学到的东西听起来很有趣,不要担心,我们才刚刚开始触及表面。在我们了解更多有关Sui的内容后,我们将开始创建一些非常酷的东西。所以,保持兴奋吧!在接下来的课程中,我们将学习Sui如何使安全性和可扩展性成为可能。请继续关注!

quiz

Sui 与其他区块链不同的关键特征是什么?它如何解决更广泛的区块链使用所面临的挑战?

Sui上的产品

我为你们所有人感到骄傲,你们学习了所有关于Sui的知识,领先了一步,并在课程中学习了许多知识。在本课中,我们将探讨以下内容:

1、开发人员在Sui上构建什么?

2、在Sui上部署了什么dapp ?

3、为什么应该在Sui上发布很酷的东西?

我们开始吧

你能在Sui上建立什么?

img-1

作为一名开发人员,你可以在Sui上构建一些很棒产品。

让我们仔细看看在Sui区块链上可以构建和发布产品。

商业:Sui的架构和设计允许闪电般的交易,甚至可以跳过耗时的共识步骤。这对于商业领域的应用程序非常有用,就像没有人想要等待他们的咖啡达成共识

DeFi:想要在Sui中构建DeFi应用程序的开发人员可以轻松使用许多独特的工具——可编程交易块、赞助交易、深度账本等——所有这些都具有Sui提供的速度、成本效益和吞吐量

游戏:如前所述,游戏是延迟敏感的,Sui可以支持可以并行处理的极低延迟事务。不仅如此,Sui的架构还可以处理大量的吞吐量需求——这对于火爆的游戏来说是非常重要的。

在Sui上建立很酷的产品

现在,让我们来看看一些在Sui上已经建成的和正在建造的令人惊叹的产品

游戏:让我们看看一些与Sui合作并在Sui区块链上发布游戏的游戏工作室。

DAPPs:现在,让我们探索一些基于Sui构建的dapp。

为什么要在Sui上面发布很酷的产品

既然您已经了解了Sui是如何在链上构建各种事物的优秀区块链,那么现在是时候深入了解Sui区块链的生态系统了。任何人都可以在Sui上构建高质量的dapp,即使这个dapp在其他平台上构建非常具有挑战性。如果您决定在Sui上构建自己的产品,您可以提交赠款提案,以获得Sui基金会的资金和技术支持。即使您没有提交资助申请,Mysten实验室、Sui基金会和Sui社区的团队也可以帮助您解决任何问题!你可以使用这个链接加入Sui Discord社区。

有趣的事实:Sui提供各种资助,包括开发者资助、教育资助和学术研究资助。开发者资助计划为合格的链上开发者提供1万至10万美元的资助。你可以在这里找到更多的细节:Sui Grants Hub

别担心!我们将在下一课中详细讨论Sui是如何工作的。

结束语

了解了Sui为像你这样的开发人员所做的所有令人惊叹的事情,这是一项伟大的工作!你是否对更多地钻研Sui区块链感到兴奋?在下一节中,我们将详细探讨Sui的工作原理。

quiz

开发者可以在 Sui 区块链上构建哪三类应用程序?

1.2_进一步了解Sui - 课程学习时长因人而异

Sui VS 以太坊

欢迎回来,学习者!在上一节中,我们对Sui区块链有了一个基本的了解和概述。在本节中,我们将了解Sui区块链的实际工作原理。但在直接进入之前,让我们首先了解Sui中的某些实现与以太坊等其他传统区块链的不同之处。

Sui VS 以太坊

让我给你大致描述一下Sui区块链有多快。

img-1

那么我们能从中得出什么结论呢?让我用现实世界的例子一个接一个地介绍每一点,让你更容易理解。

交易处理速度

Sui比以太坊和Solana等其他主流区块链具有更高的可扩展性。根据测试网压力测试,Sui能够处理每秒最大事务数(TPS)为297,000。为什么这很重要?嗯,如果你已经在以太坊呆了一段时间,你就会看到有限的交易吞吐量会发生什么——网络费用急剧增加,因为每个人都在竞相出价,试图推动他们的交易打包。

想象一下,你在一家只有一个厨师的餐馆里,这基本上意味着烹饪的时间会更长。食物的烹饪时间可能很慢,特别是在用餐高峰期的时候,可能会有很长的等待时间。因此,他们在单位时间内交付的订单要少得多。这就类似以太坊这样的传统区块链的工作方式。

现在想象一下,每个人的订单被分配给不同的厨师,他们彼此独立工作。这将大大加快订单处理时间,从而在单位时间内交付更多订单。这就是为什么在Sui中TPS更高的原因之一,验证器可以处理不同的事务。Sui的架构是为更高的TPS而构建的,未来的升级将允许验证者添加额外的工作机器,以快速满足激增的需求。这就像有额外的下班厨师,你可以在需要的时候打电话给他们。

Move语言

正如我之前提到的,Sui使用了一种名为Move的以对象为中心的智能合约语言,这是一种更具表现力的语言,这意味着它提供了一种灵活而详细的方式来定义资产和智能合约如何相互交互,从而减少了安全隐患,定义了良好的对象所有权,减少了代码中的错误。

这是一个非常重要的特性,因为web3已经见证了由于这类漏洞造成的几次攻击。如果我必须举一个例子,我会选择著名的DAO黑客,导致6000万美元的ETH被盗,并导致以太坊分为两条链——以太坊和以太坊经典,后者识别原始的以太坊区块链。

Sui上的MOVE已经预先构建,以防止诸如重入攻击,下流,溢出等漏洞。我们将在下一节课中详细讨论Move。现在让我们转到执行部分!

执行模型

Sui的执行模型帮助它在任何给定时间处理多个事务,就像一条多车道高速公路一样。在下一节中,我们将更详细地了解它是如何使用并行事务实现这一目标的。但是让我们以餐厅为例来做一个基本的概述。

为了理解这一点,让我们回顾一下我们在解释TPS概念时使用的餐厅示例:

在只有一名厨师的餐厅(比如以太坊),顾客们来的很慢,每点一餐之间都有足够的时间让厨师来处理每一餐。尽管准备每个订单可能需要更长的时间,但由于流量低,这不会对业务产生重大影响。但随着客流量的增加,排队的人越来越多,餐厅很难处理订单。

另一方面,Sui的经营方式就像一家拥有多名厨师的餐厅。即使有一些重叠的订单,每个厨师都可以独立准备订单,而不必等待前一个完成。这种并行处理使Sui与众不同,并使其能够实现高TPS。

结束语

简而言之,以太坊和Sui都是第1层区块链,但它们具有不同的智能合约语言,交易速度和执行风格。Sui的并行执行使其成为高吞吐量和可扩展应用程序的首选。我们相信,你可能已经想知道他们是如何做到这一点的,我们继续深入学习!让我们直接进入下一节。

quiz

将 Sui 与以太坊和 Solana 等其他主流区块链的交易处理速度进行比较,并解释为什么 Sui 的更高可扩展性很重要。

Sui是如何工作的

现在,您已经了解了Sui与以太坊区块链的不同之处,让我们了解Sui区块链的基本架构,并了解Sui如何实现如此高的性能。

Sui是如何工作的

有几个因素在形成Sui的基础和决定它如何运作方面起着至关重要的作用。让我们深入研究一下这些因素是什么。

Sui对象

对象所有权

交易周期

现在让我们详细探讨它们。

Sui中的对象(MOVE体系结构)

既然我们已经了解了使用Move的意义,那么我们需要了解Sui最基本的组成部分——对象。

什么是对象

对象就像任何其他编程语言中的结构体一样。每个对象都可以存储任何类型的数据,无论是整数、布尔值还是地址。把对象想象成一个盒子,里面存储着不同大小的不同物品。现在每个盒子都有一个主人,对吧?每个Move对象都有一个直接在对象本身上定义的“所有者”。这意味着对象完全包含所有权信息,不需要像我们在其他区块链中看到的那样将所有权映射与资产配对。还有更多的对象所有权,所以让我们深入挖掘!

不同类型的对象

在Sui中,最常见的两种所有权类型是单一所有者对象和共享对象。

单个所有者对象:在单个所有者对象中,只有单个地址能够传输或使用该对象。好吧,但为什么这很重要呢?Sui上的单一所有者对象可以绕过冗长的共识过程,在不到一秒的时间内完成!现在我们已经完成了技术解释,让我用一个现实世界的例子来解释这个概念。想象一下:一家著名银行的高度安全的私人金库。这个金库只属于你,只有通过识别你指纹的生物识别系统才能进入。在里面,你可以存放你最珍贵的贵重物品——珠宝、重要文件,甚至是稀有的硬币。就像Sui上的单一拥有者一样,这个保险库由您单独控制。您可以决定何时访问它,以及存储或删除什么,而无需达成任何共识。您对保险库的操作是即时和自主的,反映了区块链数字领域中单一所有者对象的效率和安全性。

img-1

共享对象:共享对象没有单一的所有者,但是对所有用户开放,可以与之交互,也可以是一个定义的子集。让我们举个例子来理解这一点。现在,想象一个共同的家庭传家宝,比如说,一个精致的古董钟,在一个家庭庄园里。这个钟是一个共享的物体,就像隋朝上的共享物体一样。每个家庭成员都有权访问,使用,甚至在特殊场合临时拆除时钟。然而,重大的决定,如出售或改变时钟,需要家庭共识,只有在集体同意后才会采取行动。这种共享管理反映了区块链中的共享对象,其中多方都有访问权和一定程度的控制权,但重大变更需要双方同意。这是关于平衡个人欲望与共同所有权的责任和特权。

资产所有权

区块链很酷,因为它们让你有能力拥有和管理你的数字资产,比如nft(独特的数字项目),而不需要任何中间人。让我们来看看Sui的资产所有权有何不同。

其他区块链(如以太坊)的所有权:

把以太坊想象成你的nft的安全数字金库。这些nft被保存在一个叫做“智能合约”的东西里,有点像一个自动保险库。在以太坊中,账户是存储的基本单位——有你想到的典型账户(由人类拥有)和那些容纳智能合约的账户。你“拥有”的nft实际上是在它们起源的智能合约账户中持有的。现在,合约开发人员可以为该合约设置规则,就像指定您的保险库应该如何工作一样,但这里有一个问题:作为NFT持有者,您不能直接控制其中的NFT。要对你的NFT做任何事情,比如贷款或交易它们,你需要参与智能合约,并假设合约授予了你对NFT的所有权。把智能合约想象成一个中介,它会帮助你,但它也会让事情变得更复杂。例如,假设你想把一辆nft借给朋友。你不能像交实物一样交出来。你需要使用智能合约来促进这个过程。这对于以太坊上的任何其他令牌都是一样的,因为所有令牌都是通过智能合约部署的,甚至包括稳定币。

sui上的对象所有权

现在,Sui采取了一种不同的方法。就像在家里有自己的私人画廊一样。请记住,Sui上的所有东西都是一个具有定义的所有者的对象,这意味着用户实际上对该对象拥有直接的所有权-无需信任该对象源自的智能合约。这些物品代表了你的nft,最酷的部分是它们直接存储在你的个人账户中,就像你的艺术品挂在你自己安全的家里的墙上一样。你可以直接控制。这种直接所有权意味着您可以轻松地将您的NFT借给朋友或与另一个用户进行交易,而无需作为中介处理智能合约。Sui简化了流程,将资产所有权交到您手中。由于对象(如nft)通常只有一个所有者,因此我们可以利用拥有对象事务的好处,这是闪电般的速度。现在进一步探讨事务是如何工作的,以了解拥有对象事务是如何如此快速地完成的。

sui上的交易

Sui以不同的方式处理自有对象事务和共享对象事务。

自有对象交易

对于拥有的对象交易,比如转移代币,Sui在完成交易之前不需要网络达成共识。这是因为对象的所有权是直接在对象本身中定义的;只要定义的所有者是发起交易的人,交易将在不等待共识的情况下执行。Sui没有依靠典型的共识相关协议在网络上传播交易,而是使用拜占庭一致广播(BCB)协议进行拥有对象交易。与要求验证者就事务有效性达成共识的传统方式不同,拥有对象事务可以在没有共识的情况下完成,这使得它们非常快速。让我为你总结一下整个过程,发送者向网络提交一笔交易。Sui验证者通过权益证明机制投票决定交易是否符合其要求。发送者收集选票,创建一个未签名的证书,作为拜占庭容错的证明。发送方与网络共享该证书,所有验证器节点验证其有效性。对于拥有对象事务,如果证书有效,则事务将最终确定并执行。用户返回签名的证书,该证书表示事务的结束。拥有对象事务也不需要在结束之前进行排序,这意味着不相关的事务可以并行执行。例如,假设安娜给苏珊送了1个NFT,雷给汤姆送了10个代币。这些事务彼此之间没有任何关系。使用Sui,它们可以同时快速地处理,而不需要每个人都同意订单。

共享对象事务

涉及共享对象(如DEX智能合约)的交易需要排序和共识,因为多个用户可能试图同时与同一个对象进行交互。因此,必须有某种事务顺序感。虽然共享对象交易必须经过共识,但Sui的新共识机制仍然能够快速完成交易。在Sui中,排序和共识有两个单独的协议,分别称为arwhal 和 Bullshark。除了在完成之前增加了排序和共识之外,该过程与拥有对象事务非常相似。让我为你总结一下这个过程:发送者将交易发送到网络。如果交易符合其要求,则通过权益证明机制进行投票。发送方收集投票,创建一个未签名的证书,作为拜占庭容错的证明。发送方与网络共享此未签名证书。然后,Sui验证者使用独角鲸和牛鲨就交易输出的顺序和有效性达成共识。用户返回签名的证书,该证书表示事务的结束。简而言之,在签署证书之前,该过程与拥有对象事务非常相似。共享对象事务要求Sui验证者在签署证书之前就事务的顺序和结果达成共识。对于我们的传家宝的例子,想象一个家庭成员想要出售古董,但是在这个行动发生之前,其他家庭成员必须同意。

结束语

恭喜!虽然我们仍在研究理论,但要知道,在为自己的应用程序做出设计决策时,它对您的帮助是极其重要的。接下来,我们将通过Sui区块链的特性来了解更多。下一节再见!

quiz

Sui 区块链基本架构中的三个关键因素是什么?Sui 中对象的所有权模型与以太坊等其他区块链有何不同?

让我们回顾一下Sui的主要特点

很好地学习了Sui的工作原理!我相信,如果你的朋友问你关于Sui的事情,你现在可以很容易地回答。在接下来的30分钟里,让我通过Sui的主要特征来总结我们到目前为止学到的东西。我们需要回忆一下Sui的独特之处,以及它与其他区块链的不同之处。让我们回忆一下。

sui的关键特征

让我们来讨论一下Sui区块链的一些关键特性。

1、将数据存储在对象中开启了新的可能性

Sui区块链将数据存储在对象中,而不是以账户的形式存储。回想一下,对象就像存储不同大小的不同项目的盒子,每个盒子都有一个唯一的ID。对象帮助开发人员轻松地定义、创建和管理数据。

2、无忧赞助交易

在Sui中,一个地址可以为另一个地址进行的交易支付交易费用。特别是如果你是新手,理解汽油费用和处理交易过程可能会有点困惑。这有助于应用程序开发人员为其用户支付初始gas费用,并使他们的入门过程更加容易。这些类型的交易称为自发起交易。

3、一致的网络费用

波动的费用结构对用户来说并不直观。想象一下,你早上买了一杯咖啡,下午又买了一杯,发现咖啡的价格几乎和咖啡本身一样高。Sui的收费结构旨在保持美元金额的稳定,并且在24小时内不会波动。

4、安全的开发环境

Sui对Move编程语言的实现及其以对象为中心的设计为开发人员提供了一个功能强大且极其安全的开发环境。不仅需要移动证明器的正式验证才能部署应用程序,而且还有许多常见的智能合约错误在Sui上甚至不可能出现!

5、事务并行运行

Sui独特的数据模型允许并行事务执行,为许多事务创建更快的完成时间,并允许网络基础设施在高需求时保持弹性。当您想要使用Sui解决web2问题时,这是非常有用的,因为大多数web2应用程序都需要快速处理系统。

img-1

这些并不是Sui提供的所有很酷的功能,但现在,这些已经足够让你知道了。现在,让我们进一步探讨Sui与其他区块链的不同之处。

Sui VS 其他区块链

以下是使Sui从其他区块链中脱颖而出的一些独特功能。

1、高性能

与其他区块链相比,Sui提供了出色的性能。它执行并行交易的特性使其比其他区块链更健壮。

2、安全性

隋朝独特的建筑即使在恶劣的环境下也能提供极大的安全保障。它平滑地处理附加验证器或系统中发生死锁的情况。Sui向用户提供了很大的保证,即使网络受到攻击,他们的数据也会保持安全,这是其他区块链很难提供的。不仅如此,Sui生态系统不断监控其社区的安全威胁,并立即调查和修复发现的威胁,以确保用户安全。不同的机构和人员对Sui的不同特性进行了多次安全审计,以确保极大的安全性,例如Halborn对Sui core和Sui的智能合约进行了安全审计。你可以在Sui的官方网站上找到更多相关信息。

3、易于上手

Sui允许开发人员使用Move编程语言和以对象为中心的方法创建伟大、安全、可靠的nft和dapp。

结束语

当我们探索Sui时,您可能已经意识到他们对区块链技术的许多元素采取了新的方法。无论是数据模型,本地入门工具还是代币经济学,Sui都以全新的眼光看待每一个元素,创造了一个令人兴奋的生态系统。记住,社区是来帮忙的——Meta学校、Sui社区、Sui基金会。如果你有兴趣深入了解任何方面,大胆的交流!

quiz

Sui区块链的三个关键特征是什么?Sui在性能、安全性和开发者体验方面与其他区块链有何不同?

01_概念

  • 本章课程共3小节,学习时长因人而异

概念

结束

让我们结束这门课吧

好了,让我们结束这段不可思议的旅程吧!🎉是时候花点时间反思一下你在本课程中学到的一切。我为你们所有人感到超级兴奋,因为你们都决定从Sui开始,现在我们一起完成了我们的第一门课。

对Sui区块链的理解

您获得了Sui区块链的基本知识。你了解了Sui使用了什么共识机制,解决了什么问题,以及Sui的详细历史。让我们回忆一下我们对Sui的了解。Sui是一个无权限的Layer-1区块链。与现有的以太坊和比特币等第一层区块链不同,Sui提供闪电般的交易,可预测的费用和动态数字资产的完全所有权。

Sui上的产品

你探讨了开发人员在Sui区块链上构建了什么,已经在Sui上构建了什么应用程序,以及你还可以构建什么。您通过深入研究Sui区块链的世界,探索了Sui开发者如何获得赠款的支持。

Sui体系结构的认识

你们深入了解了Sui是如何工作的,它使用了什么共识,以及Sui的基本属性是什么。

⚒️一个小而重要的请求

这是一个100%的开源项目,就像我们平台上的其他项目一样。您可以在[这里](https://github.com/0xmetaschool/Learning-Projects/tree/main/Learn Everything About Sui%2C its Concepts and Protocols)找到教程标记文件。如果您在课程中发现任何问题,请麻烦解决一下。我们,在Metaschool,热爱我们社区的贡献,也感谢我们Discord 和GitHub上的贡献者。

当您做出贡献时:不要忘记在⭐️上标记我们的存储库。我们将非常感激!❤️我们是一个完全免费的平台,我们的目标是保持不变,所以请考虑在 XLinkedIn 上关注我们。

🎊祝贺

您已经完成了教程,现在已经具备了深入的知识,可以开始在令人兴奋的Sui区块链世界中进行构建。我们祝你好运!✌🏻🔮

02_基础

  • 本章课程共11小节,学习时长因人而异

基础

2.1_开始 学习基础知识 - 课程学习时长因人而异

我们今天做什么?

大家☀️好,今天我们非常高兴能为Metaschool上的开发人员带来关于Sui区块链的详细系列的第二门课程。让我们从祝贺你完成第一门Sui的课程并开始第二门课程。呜呼!

在第一门课程中,我们带您了解了Sui区块链的基础知识及其工作原理。在本课程中,我们将更深入地学习 Sui上的 MOVE 语言,这是 Sui 的原生编程语言。简而言之,在本课程中,您还将使用Sui上的MOVE语言在 Sui 上进行构建,我们马上开始学习。

课程概述

我们将要:

1、设置我们的开发环境

2、运行我们的第一份 Sui 合约

3、了解Sui上的MOVE语言的包架构

4、学习Sui上的MOVE语言的基础知识

5、通过多重挑战测试您的知识

究竟谁应该参加这门课程?

本课程适用于希望了解 Sui 区块链并已完成我们关于 Sui 的第一门入门课程的初学者 web3 开发人员。您应该具备基本的编程知识,因为它将帮助您快速开始本课程。

你会从这门课程中得到什么?

作为开发人员,当您完成本课程时,您将获得以下结果:

1、在 Sui 基础知识上学习 Sui上的MOVE语言

2、在 Sui 上构建 dApp

3、通过 SuixMetaschool Discord House 探索 Sui 生态系统中的机会

4、赚取高达 1000 经验值

5、在课程完成后领取您的 NFT

完成证明

如果您与我一起完成这门课程,您将获得 XP 和一个特殊的 NFT,这将为您在 Metaschool 平台上解锁更多机会。这是 NFT 的样子。

img

现在,在我们继续前进之前,让我们先设定一些学习规则

1、请正确地完成您的快速作业

2、加入我们的 discord 服务器并在那里提出所有相关问题。

3、我们是一个免费的开源平台,如果您在 XLinkedIn 上关注我们,那将是一个巨大的支持

4、保持快乐和积极!

好了,伙计们,不用再等了。但在开始了解 Sui 上的MOVE语言之前,让我们看看 Sui上的MOVE语言 到底是什么以及它在下一节中带来的好处。到时候见!

为什么创建Sui上的MOVE语言

让我们从了解 Sui 开始, Sui上的MOVE语言 和 Move语言 之间的区别,以及为什么 Sui上的MOVE语言 是区块链中的游戏规则改变者

让我们回想一下,Sui区块链

Sui 是一个去中心化的第 1 层区块链,它改变了我们在区块链中定义资产所有权的方式以及交易的处理方式。

sui-icon.jpeg

Sui 在 Move on Sui 中引入了可组合对象。这些对象可以是单一拥有的,也可以是共享拥有的。对于单一所有者的简单交易,如转移代币或 NFT,Sui 放弃了共识。来自不同用户的这些交易是相互独立的,因此 Sui 以并行方式实现这些交易,称为并行交易。这意味着,每笔交易都可以在给定时间同时处理。这成倍地提高了 Sui 的可扩展性。好吧,我们将在课程后面讨论这个问题。

对于共享所有者对象,Sui 实现了一种称为 Delegated Proof of Stake 的共识机制。简单来说,这意味着当提交交易时,验证者投票决定是否批准交易。

Move到底是什么?

您一定已经学习过“Sui Chain简介”课程中的Move on Sui。但是,让我们退后一步。

在 Move on Sui 构建之前,就已经存在 Move 编程语言,我们通常称之为 Core Move。Core Move是一种开源编程语言,主要用于构建和操作智能合约。The Move 支持跨不同区块链的通用库、工具和开发者社区。Move 也具有适应性,这意味着我们可以对其进行多项增强以执行不同的操作。而这正是Move on Sui正在做的事情。它使用核心 Move 并对其进行增强,以制作与 Sui 兼容的 Move。

Sui上的MOVE

Move on Sui 与其他区块链上的核心 Move 或 Move 有一些重大区别。Move on Sui 受益于 Move 的灵活性和安全性,并在一定程度上增强了它,提高了吞吐量、减少了延迟,并使 Move 编程语言更平易近人、更适合使用。让我们来看看 Move on Sui 的一些独特功能。

move的主要区别

以下是Sui MOVE的主要区别:

1、Sui 有其以对象为中心的存储

2、Sui 中的地址表示对象 ID

3、Sui 中的每个对象在全局范围内都有唯一的 ID

4、Sui 有一个模块初始值设定项 (init)

5、Sui 具有通过引用将对象作为输入的入口点

让我们简要讨论一下这些差异。

以对象为中心的存储

Sui 链有自己的 Sui 存储,而不是全局存储。核心 Move 中的全局存储会导致可扩展性问题,Sui 链通过保留 Sui 链上的地址所拥有的对象和模块来解决这个问题。Sui 链中进行的所有交易都通过唯一标识符表示,以允许链并行运行交易。

Sui 中的地址表示对象 ID

好吧,在 move 中,Sui 使用 32 字节的地址标识符来表示帐户地址。每笔交易都由账户地址(发送者)签名,以访问交易的所有信息。在 Move on Sui 中,账户地址存储在 id: UID 中,即对象中的必填字段。

具有关键能力的对象,全局唯一 ID

在 Sui 中,使用键能力对于将简单的结构体转换为 Sui 对象至关重要。具有键能力的对象必须具有 id: UID 字段,该字段存储对象的唯一地址。

模块初始值设定项

模块初始值设定项的工作方式类似于 Move on Sui 中的构造函数。正如我们所讨论的,如果您在 Sui 存储中发布模块,初始值设定项有助于初始化对象字段的初始值。初始值设定项在运行时执行,这意味着当您发布模块时,初始值设定项只会执行一次。

入口点通过引用将对象作为输入

在 Move on Sui 中,您可以将对象作为输入传递给公共函数,这些函数可由 Sui 事务调用。有多种方法可以做到这一点。您可以通过引用、可变引用或值传递它们。在这里,按值传递的对象可以被删除或转移给另一个所有者,而您可以更改通过可变引用传递的对象的数据,而无需更改所有权或创建任何新实例。

在 move 中,您还可以调用入口函数,即使它们是私有的,只要其他非入口函数没有使用它们的输入

不用担心!我们将在接下来的课程中详细讨论上述所有主题。

总结

move在许多方面都是不同和独特的。它提供了许多独特的功能,帮助我们扩展应用程序。因此,与 Move on Sui 合作将帮助您部署可扩展的独特智能合约。我知道这似乎很多!但是我已经找到了你,我将在即将到来的课程中详细讨论所有新概念。现在,让我们继续前进,学习如何设置开发环境来运行 Move on Sui 程序,并学习如何在 Sui 上部署您的第一个程序。

quiz

为什么要创建 Move on Sui,以及它与其他区块链上的 Core Move 或 Move 区分开来的一些关键功能是什么?

2.2_hello world 部署你的第一个合约 - 课程学习时长因人而异

设置开发环境

欢迎回来!我希望您喜欢学习 Sui 和 Move 编程语言。在本课中,我将指导您设置开发环境以在系统上运行 move 程序。在我们深入研究之前,确保您已在系统上安装了所有必要的先决条件至关重要。不用担心!我将为您提供特定系统的安装说明。因此,请随意滚动浏览课程以找到相关的安装指南。

在MacOS 上安装的准备

让我们完成在MacOS上安装Move on Sui的必要步骤。打开终端并运行以下命令。

使用以下命令进行安装 brew

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

使用以下命令进行安装 cURL

brew install curl

使用以下代码进行安装 CMake

brew install cmake

安装 Git

brew install git

在Linux上安装的准备

现在,让我们来看看在 Linux 系统上安装 move 时需要准备什么

使用以下命令进行更新 apt-get

sudo apt-get update

使用以下命令进行安装 cURL

sudo apt install curl

安装 Git CLI

sudo apt-get install git-all

使用以下命令进行安装 cMake

sudo apt-get install cmake

安装 GCC

sudo apt-get install gcc

安装 libssl-dev

sudo apt-get install libssl-dev

安装 pkg-config .此命令是可选的。如果您的系统中有 OpenSSL,请安装。

sudo apt-get install pkg-config

安装 libclang-dev

sudo apt-get install libclang-dev

安装 libpq-dev

sudo apt-get install libpq-dev

安装 build-essential

sudo apt-get install build-essential

在Windows安装的准备

让我们来看看在 Windows 上安装 move 需要准备什么

1、Windows 11 已经安装了 Microsoft 版本 cURL 的 .

2、使用以下链接下载并安装 Git CLI:https://git-scm.com/download/

3、从这里下载并安装 cMake :https://cmake.org/download/

4、下载并安装协议缓冲区。请将它们添加到您的 Windows PATH 环境变量中。

5、下载并安装C++构建工具:https://visualstudio.microsoft.com/downloads/

注意:该 sui console 命令在 PowerShell 中不起作用。

安装 Cargo 和 Rust

现在您已经完成了系统中的所有必备安装,无论是 MacOS、Linux 还是 Windows,您都需要安装 Cargo 和 Rust。运行以下命令来执行此操作

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

您还可以访问 Rust 官方页面,了解安装 Rust 的其他方法。

安装 Move on Sui

现在一切都完成了,让我们终于安装 Sui。运行以下命令以从 devnet 安装 Sui 二进制文件。

cargo install --locked --git https://github.com/MystenLabs/sui.git --branch devnet sui

检查您的 Sui 安装版本

sui --version

注意:如果您在安装 Sui 时遇到错误,请确保所有先决条件都已更新

总结

成功设置开发环境后,让我们继续运行和部署“Hello World”程序。这将使您了解如何在 Sui 区块链上部署您的智能合约。

quiz

分享 sui —version 命令的屏幕截图

部署你的第一个Sui合约

你在为 Move on Sui 设置开发环境方面做得非常出色。在本课中,我们将学习如何在 Sui 区块链上运行和部署“Hello World”智能合约,并在 Explorer 中进行探索。

部署您的第一个 Sui 合约

打开您最喜欢的终端,因为我们将运行一些命令来实现我们的目标。

第一步是初始化工作空间环境。这将包含用于运行任何 Move 文件的基本文件。您可以使用以下命令创建工作区;我给我的 hello_world 起了个名字:

sui move new hello_world

该命令将生成一个名为的 hello_world 文件夹,它将包含一个文件 Move.toml 和一个文件夹 sources

deploy-1.png

P.S.:我正在使用Visual Studio IDE,因为它更好地可视化了我的工作区的结构。

导航到目录 sources/ 。创建新的移动文件。我正在命名它 Hello.move

deploy-2.png

将以下代码粘贴到您刚刚创建的代码中 Hello.move

#![allow(unused)]
fn main() {
// Copyright (c) 2022, Sui Foundation
// SPDX-License-Identifier: Apache-2.0

/// A basic Hello World example for move, part of the move intro course:
/// https://github.com/sui-foundation/sui-move-intro-course
/// 
module hello_world::hello_world {

    use std::string;
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    /// An object that contains an arbitrary string
    struct HelloWorldObject has key, store {
        id: UID,
        /// A string contained in the object
        text: string::String
    }

    public entry fun mint(ctx: &mut TxContext) {
        let object = HelloWorldObject {
            id: object::new(ctx),
            text: string::utf8(b"Hello World!")
        };
        transfer::public_transfer(object, tx_context::sender(ctx));
    }

}

}

注意:本课仅侧重于在 Sui 上运行我们的“Hello World”程序。我们将在即将到来的课程中讨论文件详细信息和解释。所以别担心,我们已经为您准备好了

在 Sui 上构建和发布

现在,让我们使用以下命令构建 Move 文件:

sui move build

这将生成如下所示的输出:

deploy-3.png

运行该命令后,项目目录将包含一个 build 文件夹和一个 Move.lock 文件。

deploy-4.png

创建您的 Sui 帐户

现在,让我们运行以下命令来创建 Sui 帐户

sui client new-address ed25519

它将生成如下输出:

deploy-5.png

重要提示:请保存恢复短语,我们将在下一课中用到它来设置我们的钱包。

在下面的命令中替换为 [YOUR_ADDRESS] 您的地址并运行它。

sui client switch --address [YOUR_ADDRESS]

您还可以使用以下 sui client active-address 命令查看当前活动地址

deploy-6.png

最后,前往 Sui Devnet faucet discord channel并粘贴“!faucet [YOUR_ADDRESS]”以接收 10 个 Sui 代币。

设置 dev 环境

要在 Sui Devnet 上部署,请执行以下命令。

sui client new-env --alias devnet --rpc https://fullnode.devnet.sui.io:443
sui client switch --env devnet

发布合约

首先,复制 Hello.move 文件的绝对路径:

deploy-7.png

替换为 [YOUR_PATH] 文件的绝对路径,然后运行以下命令:

sui client publish --gas-budget 10000000 [YOUR_PATH]

我们将有一个很长的输出,但滚动到输出的开头并复制交易摘要:

deploy-8.png

前往 https://suiexplorer.com/?network=devnet。将 Transaction Digest 粘贴到搜索栏中,以在 Sui Explorer 上查找您的交易

deploy-9.png

喔!你做得很好!我们已经在 Sui 🎉 上部署了我们的第一个“Hello World”程序

总结

总之,这节课介绍了在 Sui 上运行智能合约的过程。我们学习了如何在 Sui devnet 上创建 Move 工作区、构建和发布 Move 文件、创建 Sui 帐户以及部署合约。恭喜您完成了这节课!

quiz

分享一下Sui Explorer部署并搜索“Hello World”智能合约后的截图。

设置 Sui 钱包

欢迎回来!至此,您完成了对 Sui 和 Move 编程语言的学习。那么在这节课中,您将学习如何设置您的 Sui 钱包。准备好迎接新一轮令人振奋的学习了吗?让我们开始吧!

设置 Sui 钱包

让我们安装 Sui 钱包,以便您可以部署您的合约。

您可以使用浏览器扩展轻松添加它。对于 Chrome,请转到此链接 ,然后将其添加到 Chrome。

sui-1-1.png

将 Sui 钱包扩展程序添加到 Chrome 后,您将被重定向到新页面。使用您的 Gmail 注册。然后,您可以看到“添加”选项,单击它。

Frame 3560370 (2).jpg

之后,您将看到一个选项“导入助记词”。点击它。

Frame 3560370 (3).jpg

请记住,我要求您保存在上一课中创建的钱包的恢复短语。现在是时候在这里使用它了。粘贴您的恢复阶段,然后单击“添加帐户”

Frame 3560370 (4).jpg

设置您的密码并接受条款和条件。点击“创建钱包”。

Frame 3560370 (6).jpg

创建钱包后,您可以查看您的帐户。

Frame 3560370 (5).jpg

目前,它设置为主网,您可以将鼠标悬停在右上角的设置图标上。转到网络并更改为所需的网络。当我们将合约部署到 Devnet 网络时,我们正在更改为 Devnet 网络。

Screen Recording 2023-11-03 at 11.01.49 AM.gif

获取一些测试代币

您只需单击“请求 Devnet Sui 令牌”按钮即可请求 Sui 令牌。

Frame 3560370 (7).jpg

注意:如果您需要 Testnet 等其他网络的 Sui 代币,只需更改网络并请求代币即可

总结

在本课程中,您学习了如何设置 Sui 钱包以及如何在网络上获取一些代币。接下来,我们将深入了解我们在本节中创建的 Move on Sui 包的架构。我们还将在下一节中探讨 Move on Sui 中的基本智能合约结构。所以请保持你的动力。

quiz

分享您装满假代币的 Sui 钱包的屏幕截图

2.3_move的包结构 工欲善其事必先利其器 我们一起了解一下MOVE包结构 - 课程学习时长因人而异

了解 Move.toml 和 Move.lock 文件

现在您已经知道如何使用 Move on Sui 创建 Move 包了。在本课中,我们将详细探讨 Move 包的重要组件。我们将探讨 Move.tomlMove.lock 文件的工作原理以及它们的用例。

Move.toml 文件

当您使用 sui move new 命令创建 Move 包时,它会使用它创建一个 Move.toml 文件。默认情况下,它包含一些内容,但随着时间的推移,随着你的成长,你会发现你需要更改文件中的内容并根据你的需要进行修改。因此,最终,我们需要详细了解该文件,以便将来轻松使用它。

您可能已经注意到 Move.toml 文件具有三个主要组成部分。让我们来探索一下它们。

  1. [package][package] 部分包含 Move 包的名称和版本。它代表您的包的元数据。
  2. [dependencies][dependencies] 代表我们的 Move 包所依赖的所有包。默认情况下,此处提到的唯一包是 Sui 框架,但随着您的进步,您可以在此处添加更多包。
  3. [addresses] :这代表命名地址列表。该字段帮助我们为要在智能合约代码中使用的地址提供别名。

现在,让我们通过示例详细讨论这些组件。

[package] 部分

[package] 部分具有以下三个重要字段。

  1. namename 字段帮助识别您在运行 sui move new 时创建的 Move 包名称,例如 sui move new hello_world
  2. versionversion 有助于识别软件包的版本。默认情况下,Sui 编译器在创建包时为您的包提供第一个版本,例如 0.0.1
  3. published-at :默认情况下,Move 编译器不会创建此字段。此字段有助于识别我们包裹的地址。例如,Sui 框架发布在 0x2 地址,因此其发布位置将为 "0x2" 。我们在发布包后包含此字段,并帮助其他包使用 published-at 地址访问我们的包。

现在,让我们看看默认情况下 [package] 部分在 Move.toml 文件中的样子。

#![allow(unused)]
fn main() {
[package]
name = "hello_world"
version = "0.0.1"
}

添加 published-at 字段后 [package] 部分如下所示。请记住,这只是一个示例,地址可能会有所不同。

#![allow(unused)]
fn main() {
[package]
name = "hello_world"
version = "0.0.1"
published-at = "0x0"
}

[dependencies] 部分

默认情况下,Sui 编译器会在 [dependencies] 部分添加一个包。它使用 GitHub 链接添加了 Sui 框架。默认情况下是这样的:

#![allow(unused)]
fn main() {
[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
}

您还可以从本地路径导入任何包。为此,您可以像这样添加包。

#![allow(unused)]
fn main() {
Sui = { local = "../crates/sui-framework" }
}

[addresses] 部分

默认情况下,Sui 编译器在 [addresses] 部分添加两个地址:一个是包的地址,另一个是 Sui 网络的地址。它看起来是这样的。

#![allow(unused)]
fn main() {
[addresses]
hello_world = "0x0"
sui =  "0x2"
}

在这里,别名,例如 sui ,可以帮助我们轻松地从智能合约中的 sui 导入包,如下所示:

#![allow(unused)]
fn main() {
use sui::transfer
}

如果没有别名,我们就需要编写地址,这可能会给我们带来混乱。

Move.lock 文件

当您使用 sui move build 命令构建 Move 包时,它会在包的根目录中创建 Move.lock 文件。 Move.lock 文件包含有关您的包及其需要导入的依赖项的信息。这有助于验证您的源代码及其正在导入的代码。由于 Move.toml 文件是可编辑的,因此 Move.lock 文件不可编辑。 Move.lock 帮助编译器识别运行 Move 包所需的基本内容,这就是为什么我们无法更改或将 Move.lock 文件移动到任何其他位置。这是我们的 hello_world 模块的 Move.lock 的样子。

#![allow(unused)]
fn main() {
@generated by Move, please check in and do not edit manually.

[move]
version = 0
manifest_digest = "1EA2B7F6C986E247256A7F281C4A866CE325DE9724C9D8BCBE2B0BA4CB7E18C5"
deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082"

dependencies = [
  { name = "Sui" },
]

[[move.package]]
name = "MoveStdlib"
source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/testnet", subdir = "crates/sui-framework/packages/move-stdlib" }

[[move.package]]
name = "Sui"
source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/testnet", subdir = "crates/sui-framework/packages/sui-framework" }

dependencies = [
  { name = "MoveStdlib" },
]
}

总结

Move.tomlMove.lock 文件在 Move 包中起着至关重要的作用,这就是为什么了解它们的工作原理以及我们甚至需要它们的原因。接下来,我们将探讨在 Move on Sui 中智能合约是如何声明的。我们将探讨 moduleuse 关键字在 Move on Sui 中的用法。

quiz

Move.lock 文件是如何生成的以及它在 Move 包中的用途是什么?

MOVE ON Sui 的基本结构模块-1

学习如何设置 Move on Sui 包以及 Move on Sui 包的重要组件如何工作是一项伟大的工作。现在,在本课中,我们将探讨 move 中智能合约的结构。 move 中完整的智能合约由哪些不同组件组成?

module 关键字

在 Move on Sui 中,我们使用 module 关键字声明智能合约,就像我们在 Solidity 中使用 contract 关键字声明智能合约一样。以下是声明智能合约的完整语法。

module package_name::module_name {
		// module code goes here
}

让我们了解一下 package_namemodule_name 这里代表什么。

  • package_name 是您在创建包时为包指定的名称,例如,在 sui move new hello_world 命令中,包名称为 hello_world
  • module_name 是您在包中创建的模块的名称。一个包中可以有多个模块,这就是为什么显式提及模块名称很重要。

让我们看看它如何看待我们的“Hello World”示例。

module hello_world::hello_world {
		// module code goes here
}

合约的所有内容都在 Move on Sui 中的 module 内。甚至是 import 语句,与 Solidity 编程语言不同。

use 关键字

在 Move on Sui 中, use 用于导入模块内的任何模块。这是 use 的完整结构。

use <Address/Alias>::<ModuleName>;

让我们了解一下 <Address/Alias><ModuleName> 这里代表什么。

  • <Address/Alias> 代表我们要在模块中使用的模块的地址。为了简单起见,我们还可以在此处使用别名而不是地址,并在 Move.toml 文件中提及别名,正如我们在上一课中讨论的那样。
  • <ModuleName> 仅表示模块名称。例如,如果 sui 包中有一个我们想要在我们的模块中使用的 transfer 模块,那么我们将执行如下操作:
use sui::transfer

一些重要的 Move on Sui 模块

让我们探讨一下我们在大多数智能合约中使用的一些重要的 Move on Sui 模块。

  • use std::string;
    
    • 这个导入语句帮助我们在模块中使用字符串。
  • use sui::transfer;
    
    • 该语句帮助我们编写将对象从一个帐户地址转移到另一个帐户地址的功能。
  • use sui::object;
    
    • 此行有助于从 sui 框架导入 object 模块。该模块有助于在我们的模块中创建一个对象。
  • use sui::tx_context;
    
    • 这有助于从 sui 框架导入 tx_contexttx_context 帮助我们识别交易的基本信息,例如发送者地址、签名者地址、 tx 的纪元号等。

Move on Sui 中还有多个其他模块用于不同的目的。目前来说,学习这些就足够了。

小结

我们讨论了帮助我们在 Move 中创建模块的两个基本重要关键字。其中 module 关键字在 move 中创建模块, use 关键字在导入任何模块中发挥作用。接下来,我们将探讨有助于在 move 中创建模块的更重要术语。

quiz

在 Move on Sui 中,如何使用 module 关键字声明智能合约,声明中的 package_name 和 module_name 有何意义?

Move on Sui 的基本模块结构-2

现在您已经了解了 Sui 模块结构的主要两个组件,让我们更深入地了解哪些其他东西可以构成完整且安全的 Sui 模块。

move对象

在本课程中,我们将向您快速介绍在 Sui 上MOVE对象,并且我们将在接下来的课程中进一步探讨它们。在 Move on Sui 中,对象携带有关模块的重要信息,例如您的帐户地址、钱包中的当前余额或您想要转账的金额。

您可以使用 struct 关键字创建对象,但情况有所不同。与 Go 等其他编程语言不同,您只需使用 struct 来声明一个 Move 对象,而在 Move on Sui 中,您不能简单地使用 struct 关键字来创建 Move 对象。必须将其与 key 配对才能使其成为 move 对象。这是结构的样子。不用担心!我们将在接下来的课程中详细讨论 key 的含义。

#![allow(unused)]
fn main() {
struct StructName has key {
		// struct contents goes here
}
}

对于 Move on Sui 中的每个 modulestruct - 拥有一个有助于识别创建该对象的帐户地址的 ID 非常重要。以下是您可以如何做到这一点。

#![allow(unused)]
fn main() {
use sui::object::UID;

struct StructName has key {
    id: UID,
    // other struct contents go here
}
}

不用担心!在介绍基础知识后,我们将在接下来的课程中详细讨论对象。现在我们只关注一个简单的 module 结构。

Sui 上的move 构造函数(模块初始化器)

在 Move on Sui 中,构造函数在模块发布时被调用。这意味着构造函数只被调用一次。因此,构造函数帮助我们做一些我们只想做一次的事情,例如,定义安全性并确保只有模块的创建者可以更改或拥有对象。构造函数还有助于初始化对象并确保它们拥有正确的帐户地址。它具有以下属性。

  1. 函数名称必须是 init
  2. 它必须具有以下类型的最后一个参数之一。
    1. &TxContext
    2. &mut TxContext
  3. 它不能有任何返回值。
  4. 它可以具有可选参数,例如任何对象或任何类型的变量。

我们来看看它的基本编码结构。

#![allow(unused)]
fn main() {
fun init(optional_parameter: anyDataType, ctx: &mut TxContext) {	
    // init code goes here
}
}

让我们看一个示例代码以更好地理解 init

module examples::init {
    use sui::transfer;
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};

    // Defining the ExampleObject
    public struct ExampleObject has key {
        id: UID
    }

    // init will be only called once at the time of publishing the module
    // Here, we are restricting the object so that only the owner can own 
    // the object
    fun init(ctx: &mut TxContext) {	
        transfer::transfer(ExampleObject {
            id: object::new(ctx),
        }, tx_context::sender(ctx))
    }
}

这里, init 采用一个参数 ( ctx: &mut TxContext ) 并使用 transfer::transfer() 将对象的所有权转移到发送者地址。不用担心,如果您还不太理解,我们将在接下来的课程中详细研究传输操作和对象所有权。

注意: initmodule 中不是必需的,当您需要返回任何内容时,您也可以将函数定义为构造函数的形式。

完整Move on Sui合约的基本结构

因此,我们已经介绍了 Move on Sui 合约的基本组成部分。这些并不是有助于在 Move on Sui 中构建合约的全部内容,但它是理解我们构建合约所需的基本概念的良好开端。完整的结构如下所示。

module package_name::module_name {
		use sui::object::UID;

		public struct StructName has key {
		    id: UID,
		    // struct contents goes here
		}

		fun init() {
		     // constructor code goes here
		}

		// module code goes here

}

模块创建完整合约所需的其他基本内容

与任何其他编程语言一样,我们需要学习如何编写、创建和使用以下不同的编码方面:

  1. 变量
  2. 控制流程
  3. 表达式和运算
  4. 功能
  5. 高级对象概念
  6. 和许多其他

在接下来的课程中,我们将详细介绍所有这些概念。

小结

现在,您知道 Move on Sui 中合约的基本结构是什么样的了。接下来,我们将介绍 Move on Sui 的基础知识,从变量、数据类型、控制流以及我们可以在 Move on Sui 中执行的不同操作开始

quiz

在 Move on Sui 中,如何创建对象,这个过程中 struct 关键字与 key 关键字配对的意义是什么?

2.4_MOVE基础知识 - 课程学习时长因人而异

如何声明变量和常量?

在上一节中,您了解了 move 包的架构,并了解了 move中智能合约的结构基础知识。现在,我们将探索 move的更多基础知识。

let 关键字

在 Move on Sui 中,我们使用 let 关键字来声明和初始化变量。声明变量后,您以后可以轻松访问和修改它。使用 let 关键字声明和初始化变量的方法有多种。

#![allow(unused)]
fn main() {
let <Variable> : <Type>
let <Variable> = <Expression>
let <Variable> : <Type> = <Expression>
}

现在让我们看一些例子。无需担心类型,我们将在几分钟内讨论它们。请记住,在 move 中必须添加分号。

#![allow(unused)]
fn main() {
let a;
let b : u8;
let c = true;
let d : u8 = 10;
}

很简单,不是吗?请记住,我们使用 struct 关键字声明了一个对象,但要初始化该对象,我们需要使用 let 关键字。这是一个例子:

module hello_world::hello_world {
	// Importing object module
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// Declaring HelloWorldObject
	public struct HelloWorldObject has key {
		id: UID,
	}

	// Initializing the constructor
	fun init(ctx: &mut TxContext) {
		// Initializing the HelloWorldObject using let keyword
		let object = HelloWorldObject {
			id: object::new(ctx),
		}
	}
}
  • 注意:这些示例不可执行,它们是为了帮助您理解不同概念的用法。

您是否注意到我们如何使用 let 关键字来初始化构造函数内部的对象?我们使用 object::new(ctx) 使用 object 模块来初始化对象。

const 关键字

在Move on Sui中, const 关键字用于声明和初始化常量。我们可以在模块级别定义常量,这意味着它们可以在函数外部初始化,并且可以在模块中的任何位置使用。以下是初始化常量的方法。

#![allow(unused)]
fn main() {
const <Variable> : <Type> = <Expression>
}

可以使用任何数据类型定义常量。以下是了解常量的一些重要提示。

  1. 常量一旦定义就不能更改。
  2. 无法从其模块中访问它们。
  3. 它们主要用于定义模块级常量值,该值无法在模块中的任何位置更改和访问。

这是一个例子。

#![allow(unused)]
fn main() {
module examples::example {
		
	const MAX : u64 = 100;

	public fun is_max(num: u64): bool {
		num == MAX
	}

}
}

小结

变量和常量在 Move on Sui 中发挥着重要作用。它们用于定义对象并存储关键信息。接下来,我们将探索可在 move 中使用的不同数据类型。

quiz

在 Move on Sui 中,如何使用 let 关键字声明和初始化变量?提供示例说明声明变量的不同方法。

数据类型

现在您已经了解了如何在 Move 中初始化变量和常量,接下来我们来介绍一下可以使用什么类型的数据类型来定义这些变量和常量。让我们深入研究它们。

无符号整数类型

以下是您可以在 Move 中使用的以下无符号整数类型。

  1. u8 :该类型可以存储 0 到 2^8 - 1 之间的数字,等于 0 - 255。
  2. u16 :该类型可以存储0到2^16 - 1之间的数字。
  3. u32 :该类型可以存储从 0 到 2^32 - 1 的数字。
  4. u64 :该类型可以存储0到2^64 - 1之间的数字。
  5. u128 :该类型可以存储从 0 到 2^128 - 1 的数字。
  6. u256 :该类型可以存储从 0 到 2^256 - 1 的数字。

现在,让我们看一个示例来了解如何在代码中使用它们。

#![allow(unused)]
fn main() {
let a : u8 = 100;
let b : u64;
b = 300
}

以下是如何在 Move 中使用数据类型。

module examples::integers {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// Declaring the ExampleObject
	public struct ExampleObject has key {
		id: UID,
		a: u8,
		b: u16
	}

	// Initializing the constructor
	fun init(a: u8, b: u16, ctx: &mut TxContext) {
		let object = ExampleObject {
			id: object::new(ctx),
			a: a,
			b: b,
		}
	}
}

在此示例中,我们在对象 ExampleObjectb 以及 u8u16 数据类型> ,然后在 init 构造函数中初始化它们。

布尔值

在 move 中,布尔值的工作方式与其他编程语言中相同。它有两个值: truefalse 。让我们看看如何在代码中使用它们。

#![allow(unused)]
fn main() {
let a = true;
		 
let b : bool; b = false;

let c : bool = true;
}

以下是如何在 Move on Sui 中使用它们。

module examples::boolean {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// Declaring the ExampleObject
    public struct ExampleObject has key {
		id: UID,
		a: u8,
		flag: bool,
	}

	// Initializing the constructor
	fun init(a: u8, flag: bool, ctx: &mut TxContext) {
		let object = ExampleObject {
		id: object::new(ctx),
			a: a,
			flag: flag,
		}
	}
}

在此示例中,我们在对象中声明了 au8 以及 bbool 数据类型, ExampleObject ,然后在 init 构造函数中初始化它们。

向量

Vector是一种数据结构,其工作原理类似于move中的数组。要使用向量,我们导入一个模块 std::vector ,并使用 let 来声明它。向量的格式如下: vector<T> ,其中 T 是任何数据类型。以下是如何将空向量声明为 Move。

#![allow(unused)]
fn main() {
module examples::boolean {
	use std::vector;

	let v = vector::empty<u8>();
}
}

在 Move 中, vector 最常见的用法是使用 vector<u8> ,它作为字节类型字符串来定义或使用字符类型变量。

字符串

在 move 中,您需要一个外部模块 std::string 来处理字符串并将字符串类型数据类型定义为变量。让我们看看如何与他们合作。

module examples::strings {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// std::string import
	use std::string::{Self, String};

	// Declaring the ExampleObject
	public struct ExampleObject has key {
		id: UID,
		name: String,
	}

	// Initializing the constructor
	fun init(name_bytes: vector<u8>, ctx: &mut TxContext) {
		let object = ExampleObject {
			id: object::new(ctx),
			name: string::utf8(name_bytes)
		}
	}
}

在这里,我们进行了类型转换以将字符串存储在 name 中。我们使用 vector<u8> 通过使用 string::utf8() 方法进行类型转换来填充 name 字符串。

地址

在 move 中,您可以使用 @ 符号用地址初始化变量。以下是您可以如何做到这一点。

#![allow(unused)]
fn main() {
let addr = @0x2;
}

这里, 0x2 是一个地址, @ 帮助识别它是一个地址。现在,让我们看看可以使用的不同形式的地址书写格式。

  1. @0x1 :这是 0x00000000000000000000000000000001 的缩写形式。
  2. @0x42 :这是 0x00000000000000000000000000000042 的缩写形式。
  3. @0xDEADBEEF :这是 0x000000000000000000000000DEADBEEF 的缩写形式。
  4. @0x0000000000000000000000000000000A :这是一个完整的地址形式。
  5. @std :这将指向 std 的地址。

要将地址传递给函数或在对象中定义地址,您需要在 Move 中使用名为 address 的关键字。以下是您可以如何做到这一点。

module examples::address {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// std::string import
	use std::string::{Self, String};

	// Declaring the ExampleObject
	public struct ExampleObject has key {
		id: UID,
		addr: address,
	}

	// Initializing the constructor
	fun init(add: address, ctx: &mut TxContext) {
		let object = ExampleObject {
			id: object::new(ctx),
			addr: add
		}
	}
}

在此示例中,我们在对象 ExampleObject 中声明了 addraddress ,然后在 init 构造函数中初始化它们。

小结

在本课中,我们介绍了各种示例。您想尝试在您的系统上运行它们吗?您可以执行以下操作:创建一个名为 examples 的新模块,然后创建不同的包,例如 integersstringsvector ,和 address es。最后,使用 sui move build 命令运行它们,看看会发生什么。如果您遇到任何错误,也请尝试修复它们。

整数、布尔值、字符串、向量和地址是使用 move 时经常使用的主要数据类型。这些数据类型将帮助您构建基础并开始编写您的第一个应用程序。接下来,我们将深入研究控制流并学习如何在 move中使用这些数据类型。

move 中的控制流

让我们继续学习如何在 move中使用不同的条件。在本课程中,我们将探讨 if-else 条件以及如何像其他编程语言一样使用它们。此外,我们将回顾如何在move中使用变量,这将帮助您理解 if-else 条件背后的逻辑。

值得注意的是,Move on Sui 避免使用 for-while 循环,因为它们可能导致非终止或无限执行,这违背了该语言确定性和可预测性的目标。相反,迭代逻辑是使用递归和模式匹配来实现的。让我们仔细看看。

if 表达式

if 表达式允许您在指定的某些条件成立的情况下运行代码块。如果条件为假,它将移动到另一个代码块。 if 是一个表达式,因此它应该以分号结尾。其语法如下:

#![allow(unused)]
fn main() {
if (<bool_expression>) {
	<expression> 
} else {
	<expression>
};
}

让我们看一个使用 if 表达式的实际示例:

module examples::if {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// Declaring the ExampleObject
	public struct ExampleObject has key {
		id: UID,
		num: u8,
	}

	// Initializing the constructor
	fun main(ctx: &mut TxContext) {

		// Try switching to false
		let a = true;
		let b = if (a) {
			10
		} else {
			20
		};
		
		let obj = ExampleObject{
			id: object::new(ctx),
			num: b,
		}

	}
}

在上面的示例中,如果 atrue ,则变量 b 设置为 10 并且如果 afalse ,变量 b 设置为 20 。这是使用 if 表达式实现的。最后,我们将 ExampleObject 字段的 num 设置为 b 的值。

注意:if-else中的返回类型必须相同。否则,它将导致错误,并且变量 b 将可以选择为其他类型或未定义。

您还可以单独使用 if - 不带 else 条件的 if 。以下是如何在代码中使用它。

module examples::if {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// Declaring the ExampleObject
	public struct ExampleObject has key {
		id: UID,
		num: u8,
	}

	// Initializing the main function
	fun main(ctx: &mut TxContext) {

		// Try switching to false
		let a = true;
		let b = if (a) {
			10
		};

		let obj = ExampleObject{
			id: object::new(ctx),
			num: b,
		}

    }
}

请注意,使用单独的 if 条件将 false 值分配给 a 可能会导致 b 未定义,这可能会导致代码出现问题。因此,建议不要使用单独的 if 条件。

使用内置的 assert 函数

assert 功能在 Move on Sui 中非常方便。当我们想要在不满足某个条件时中止程序时使用它。这是编写 assert 函数的基本结构。

#![allow(unused)]
fn main() {
assert!(condition, 0);
}

以下是如何在代码中使用它。

#![allow(unused)]
fn main() {
module examples::assert {
	fun main(a: bool) {

		assert!(a == true, 0);
		// code here will be executed if (a == true)
    }
}
}

程序将首先检查条件 a == true 是否满足,如果为 true,则其余条件将满足,如果不满足,程序将中止。

Move on Sui 中的递归

因此,正如我们之前告诉过您的那样,Move on Sui 不使用 for-while 循环,而是使用递归函数。递归函数不允许您无限次运行程序。它必然满足一定的条件并结束程序。让我们看看如何在 Move on Sui 中计算数字的阶乘。

module examples::assert {
	fun factorial(n: u64): u64 {
		if (n == 0) {
			return 1;
		} else {
			return n * factorial(n - 1);
		}
	}
}

该函数使用 if-else 语句来检查基本情况 (n == 0) 和递归情况 ( n * factorial(n - 1) )。这确保了该函数始终会终止并返回一个值。

小结

了解在 move中处理不同的条件使您有机会满足编写智能合约所需的不同条件。它还允许您编写开发游戏等所需的多个数学条件。例如,在 move 上构建您自己的随机数生成器。接下来,我们将探索您可以在 Move on Sui 中执行的某些表达式和运算符。

quiz

提供编码示例,说明 if 表达式、单独的 if 条件和内置断言函数的使用。

MOVE表达式、范围和运算符

我们已经介绍了 Move on Sui 的许多概念。 Move on Sui也提供了多种表达方式。让我们深入了解它们以及范围在 Move on Sui 中的工作原理。

Sui表达式

返回某些内容或以分号结尾的语句的代码单元是表达式。该内容可以是整数、字符串、布尔值或任何有效的数据类型。我们可以有把握地说函数是表达式,因为它们返回一个值。

Move on Sui有3种表达方式:

  • 文字

让我们通过示例来探讨这些表达式:

空表达式

我们可能永远不会直接使用这个表达式,但知道我们可以选择空表达式总是好的。

只需在函数体中添加一个空括号即可表示空表达式。 VM 会自动忽略这些内容,因此定义这些内容无需额外成本。

#![allow(unused)]
fn main() {
fun function_name() {
	() 
}
}

文字表达式

我们学会了定义变量和常量,对吗?

对您来说,好消息是定义它们并以分号结束它们使其成为一个表达式。

以下是我们在变量和常量课程中使用的示例。所有这些都是文字表达。唷!是不是很简单😃

#![allow(unused)]
fn main() {
let a;
let b : u8;
let c = true;
let d : u8 = 10;
}
  • 最简单的表达式可以是 10 + 5; 甚至 2; 。我们实际上使用它们来定义变量或常量。

块表达式

{} 标记的块是一个表达式。块中可以包含表达式或其他块。它可能会也可能不会返回值。如果是块的返回值,则不必以分号结尾。

#![allow(unused)]
fn main() {
module examples::example {
	fun function_name() {
		// this is a block
		{ }; // this too
		{ 3; // a block
			{true;} // another expression in a block
		}
	}
}
}

MOVE语言的范围

既然我们讨论了块表达式,我们还需要了解其中的 Scope 的含义。正如定义范围一样,范围是指某个变量居住或存在的区域。这意味着每当我们使用 {} 定义块时,我们就定义了一个范围。因此,我们将在该范围内定义的所有变量都可以访问,但在该范围之外,我们无法调用它们。

我知道这可能会让您感到困惑,所以让我用一个例子来解释它,让您更容易理解:

#![allow(unused)]
fn main() {
module examples::example {
	fun function_name() {
		// This is the scope of a function - Block A
		let a = 0;

		{
			let b = 0; 
		}; // this is a scope within a function- Block B

		{ // Block C
			let c = 0;
			{let flag = true;} // this is a nested scope - Block D
		}
	}
}
}

考虑到上面的例子,我们可以注意到以下几点:

  • 块 A 中定义的任何内容都可以被块 B、C 和 D 访问。这意味着我们可以在 function_name 函数中的任何位置访问 a
  • 块 B 中定义的任何内容都无法被块 A、C 和 D 访问。这意味着我们无法在其他任何地方访问 b
  • 块 C 中定义的任何内容对于块 A 和 B 来说都无法访问,但对于块 D 来说是可以访问的,因为它嵌套在块 C 中。这意味着除了块 C 和 D 之外,我们无法访问 c 任何其他地方。
  • 块 D 中定义的任何内容都无法被块 A、B 和 C 访问。这意味着我们无法访问除块 D 之外的任何其他地方的 flag ,甚至不能访问块 C。

让我将所有这些解释总结为一行。变量只能存在于其定义的范围或块内。

因为我们已经完成了表达式和范围。现在让我们快速看看 Move on Sui 中有哪些整数运算符。

MOVE语言操作

Move on Sui 有各种运算符,包括以下 uint 运算符: u8u64u128

Move 有多种运算符来修改整数值。这是一个列表:

操作员操作员名称
+
-
/分区
*穆尔
%模组
<<左移
>>右移
&
^异或

_ 是独一无二的

move 不允许你声明一个你不使用的变量,它会抛出一个错误。如此高效,不是吗?

但如果我们出于某种原因必须定义一个值,我们确实有一个作弊门😏。

考虑以下代码:

module examples::example {

	fun add() {
        let sum = 1;
    }
}

这将引发错误,因为我们没有在其他任何地方使用 sum 。但是,如果将 sum 替换为 _ ,如下所示,我们将不会收到错误。酷!!

module examples::example {

	fun add() {
        let _ = 1;
    }
}

小结

在本课中,我们介绍了 Move 中的表达式,包括空表达式、文字表达式和块表达式。我们还了解了范围以及它们如何定义变量可访问性。了解表达式和范围对于在 Move on Sui 中编写有效的代码至关重要。最后,我们了解了 Move on Sui 中可用的整数运算符,并探索了 Move on Sui 的独特之处。通过理解这些运算符,我们就可以开始编写基本代码了。

在继续您的编程之旅时,不断练习和探索 Move on Sui!

quiz

解释 Move on Sui 中范围的概念以及它如何影响变量的可访问性。

2.5_move函数 - 课程学习时长因人而异

函数基础知识

现在您已经了解了 move 的所有基础知识,让我们直接学习如何使用 move 中的函数。

函数定义

在 move 中,函数是执行发生的地方。您可以使用 fun 关键字定义函数。您可能已经注意到,构造函数也是使用 fun 关键字定义的。让我们看看如何在 move 中定义函数。

#![allow(unused)]
fn main() {
fun function_name() {
	// Function code goes here
}
}

这是编写函数的最简单方法,其中没有提到参数或返回类型。

模块中的函数

如您所知,我们将所有内容编写在 move 的模块内。现在,让我们通过一个示例来看看模块内部的结构。

#![allow(unused)]
fn main() {
module examples::example {

	fun add(num_1: u64, num_2: u64) {
		let sum = num_1 + num_2;
	}
}
}

在这里,我们没有返回,只是传递了参数并添加了它们。

函数参数

正如您所看到的,与所有其他编程语言一样,您可以将参数传递给函数。传递参数的格式为 variable_name: data_type 。您可以不向函数传递任何参数,也可以向函数传递多个参数。让我们看一下编码示例。

#![allow(unused)]
fn main() {
module examples::example {
	fun function_name(argument_1: u8, argument_2: u8) {
		// Function code goes here
	}
}
}

函数参数是在函数范围内定义的。当函数被销毁时,参数也会被销毁。以下是传递函数参数的基本语法指针。

  1. 多个参数使用逗号分隔。
  2. 所有参数都在括号内定义。

函数返回

由于我们都喜欢从函数返回值,move 也可以做到这一点。为了定义函数的返回类型,我们在括号外提及数据类型: fun fun_name(): data_type {} 。让我们看一下编码示例。

#![allow(unused)]
fn main() {
module examples::example {
	fun function_name(argument_1: u8, argument_2: u8): u8 {
		// Function code goes here
	}
}
}

现在,我们如何从函数返回一个值?为此,我们不需要 move 中的 return 关键字。我们只需编写一个表达式、一个变量名和一个返回任意值的函数名,函数末尾无需任何其他关键字。让我们看一下编码示例。

#![allow(unused)]
fn main() {
module math::math {
	fun sum(a: u8, b: u8): u8 {
		a + b
	}
}
}

这里, sum 函数返回 a + b 的值。让我们看另一个例子。

module examples::strings {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// std::string import
	use std::string::{Self, String};
		
	// Declaring the Name
	public struct Name has key {
		id: UID,
		name: String,
	}

	// Initializing the constructor
	fun fill_name(name_bytes: vector<u8>, ctx: &mut TxContext): Name {
		Name {
			id: object::new(ctx),
			name: string::utf8(name_bytes)
		}
	}
}

在本例中, fill_name 函数将返回一个对象 Name

小结

函数在任何编程语言中都发挥着重要作用。因此,了解每种编程语言如何使用函数非常重要。接下来,我们将探讨函数的可见性说明符,以涵盖在 move 中使用函数的高级主题

quiz

解释模块内函数的结构,并提供示例说明模块内函数的结构

有哪些不同的可见性说明符?

现在您已经了解了函数的基础知识,让我们进一步深入探讨 move 中函数的不同方面。

功能可见性

可见性意味着如何从定义函数的模块中访问函数。例如,任何其他模块是否可以访问该函数。因此,在某些情况下,开发人员可能需要其特定模块之外的功能,而在某些情况下则不需要。因此,为了处理这个问题,我们有两个最流行的可见性说明符:Private 和 Public。

私有函数

默认情况下,函数可见性设置为私有,这意味着该函数只能由同一模块访问,但不能在模块外部访问。让我们看下面的例子。

module examples::math {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// std::string import
	use std::string::{Self, String};

	// Declaring the Name
	public struct Numbers has key {
		id: UID,
		a: u8,
		b: u8,
	}

	// Initializing the constructor
	fun init(a: u8, b: u8, ctx: &mut TxContext) {
		let numbers = Numbers {
			id: object::new(ctx),
			name: string::utf8(name_bytes)
		}
	}

	fun add(n: Numbers) {
		let sum = n.a + n.b;
	}
}

这里,默认情况下, add 是私有函数。

公有函数

您可以将 public 关键字与 move 函数一起使用以将其公开。公共函数可以在模块外部访问,这意味着其他模块(也称为它们的函数)也可以访问公共函数。让我们在最后一个示例中添加一个公共函数。

module examples::math {
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// std::string import
	use std::string::{Self, String};

	// Declaring the Name
	public struct Numbers has key {
		id: UID,
			a: u8,
			b: u8,
	}

	// Initializing the constructor
	fun new(num_1: u8, num_2: u8, ctx: &mut TxContext) {
		let numbers = Numbers {
			id: object::new(ctx),
				a: num_1,
				b: num_2,
		}
	}

	fun add(n: Numbers) {
		let sum = n.a + n.b;
	}

	public fun sub(n: Numbers){
		let result = n.a - n.b;
	}
}

现在,通过将 public 关键字放在 fun 关键字之前,我们使 sub 函数成为可以在模块外部访问的公共函数。

输入函数

在move中,基本操作之一是代表各个用户的地址之间的gas对象传输。当您进行交易时, entry 函数是直接调用的。要初始化 entry 函数,我们需要确保以下三件事。

  1. 该函数应由 entry 关键字标记。
  2. 它不应该有返回值。
  3. 入口函数的最后一个参数必须是对该类型实例的 TxContext 可变引用。

让我们以我们在课程开始时讨论的“Hello World”程序为例。

#![allow(unused)]
fn main() {
public entry fun mint(ctx: &mut TxContext) {
    let object = HelloWorldObject {
        id: object::new(ctx),
        text: string::utf8(b"Hello World!")
    };
    transfer::public_transfer(object, tx_context::sender(ctx));
}
}

现在,该函数将在交易时执行,并且 &mut TxContext 是一个可变引用,有助于识别想要铸造合约的发送者的地址。函数末尾的 transfer 模块不会返回任何值,但有助于将对象存储在 Sui 全局存储中,您可以在部署模块后全局访问该对象。别担心,我们将在课程结束时详细讨论“Hello World”程序。

小结

功能可见性和入口功能在move中起着至关重要的作用。通过了解它们,您已经很厉害了。现在,我们需要更深入地研究 move 对象。让我们深入探讨一下。

quiz

提供一个编码示例,演示 move 模块中公共函数的使用。

2.6_能力类型 - 课程学习时长因人而异

自定义类型和功能

恭喜您完成了一半的课程。到目前为止你做得很好。在本课中,我们将探讨这些能力——这些能力实际上有助于赋予任何类型执行不同功能的能力。让我们详细探讨一下它们。

能力到底是什么?

在 Move 编程语言中,能力就像特殊规则,决定您可以如何处理某些类型的信息或值。将其视为设置您可以执行的操作的权限,例如共享文档或对其进行编辑。

该系统非常详细,允许您非常具体地了解这些值的行为方式。例如,它可以管理某个值是否可以复制或永久保存在某个地方。

为了实现这一点,Move 使用了一种检查点系统。该语言中的一些基本命令(我们称之为“字节码指令”)需要一个值在使用之前具有正确的“许可单”(或能力)。然而,并不是所有的命令都是严格的。有些不需要这种特殊许可。

因此,简单来说,Move 中的功能有助于严格检查每条数据的功能和去向,确保一切都以正确的方式使用。

能力的语法

要将功能添加到类型,请将功能添加到 struct 并需要使用以下语法。

#![allow(unused)]
fn main() {
 struct StructName has ABILITY [, AnyOtherAbility] { [FIELDS] }
}

示例代码如下所示。

#![allow(unused)]
fn main() {
struct Color has key, store {
	id: UID,
	red: u8
}
}

不同类型的能力

能力有四种类型。

  1. copy :允许复制值。
  2. drop :允许丢弃或自动析构。
  3. store :此功能允许值存在于全局存储的结构内部。
  4. key :此功能允许类型充当全局存储的密钥。

我们来一一讨论。

key能力

key 能力允许类型充当全局存储操作的键。这意味着要对全局存储执行某些操作,我们必须向该类型添加 key 功能。如果结构具有 key 能力,则意味着其所有字段都具有 store 能力。以下是我们如何在 Move on Sui 的代码中使用它。

module examples:: copy {
	// Importing object module
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// Declaring ExampleObject
	public struct ExampleObject has key {
		id: UID,
	}

	// Initializing the constructor
	public entry fun new(ctx: &mut TxContext): ExampleObject  {
		// Initializing the ExampleObject using let keyword
		let object = ExampleObject {
			id: object::new(ctx),
		}
	}

}

Store能力

store 能力是《Move on Sui》中的一个重要能力。在 Move on Sui 中,在 key 旁边添加 store 是一个很好的做法。存储能力允许结构及其字段存在于全局存储中。这是唯一不需要执行操作的功能,但与 key 功能一起使用时,它会自动控制全局存储中类型的存在。以下是我们如何在 Move on Sui 的代码中使用它。

module examples:: copy {
	// Importing object module
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// Declaring ExampleObject
	public struct ExampleObject has key, store {
		id: UID,
	}

	// Initializing the constructor
	public entry fun new(ctx: &mut TxContext): ExampleObject  {
		// Initializing the ExampleObject using let keyword
		let object = ExampleObject {
			id: object::new(ctx),
		}
	}

}

在《随水而行》中, key 能力是强制使用的,但 store 能力是可选使用的。

Copy能力

复制能力赋予对象被复制的能力。这意味着它使对象能够使用 copy 运算符进行自身复制。让我们看看如何在代码中使用它。

module examples:: copy {
	// Importing object module
	use sui::object::UID;
	use sui::tx_context::{Self, TxContext};

	// Declaring ExampleObject
	public struct ExampleObject has key, copy {
		id: UID,
	}

	// Initializing the constructor
	public entry fun new(ctx: &mut TxContext): ExampleObject  {
		// Initializing the ExampleObject using let keyword
		let object = ExampleObject {
			id: object::new(ctx),
		}
		// Copying the `object` using `copy` operator 
		let copy_object = copy object;
		copy_object;
	}

}

Drop能力

drop 能力允许我们删除类型。这意味着当 Move 程序执行时,类型会被破坏。具有删除能力的结构意味着该结构的所有实例都将被删除。但是 Move on Sui,对象有一个 UID,我们不能简单地使用 drop 运算符删除 UID。

小结

了解这些能力至关重要,因为它们在《Move on Sui》中被广泛使用。您一定也注意到在前面的示例中,我们经常使用密钥和存储功能。现在您明白我们为什么使用它们了。接下来,我们将详细了解对象,我们将看到更多能力的用例。

quiz

解释 Move 中存储功能的重要性,强调其在允许结构及其字段存在于全局存储中的作用

2.7_对象 - 课程学习时长因人而异

Move on Sui 中的对象基础知识

我为您走到这一步并完成 Move on Sui 的所有基础知识感到非常自豪。由于您已经了解了这些对象以及如何声明和初始化它们,现在我们将从头开始详细学习它们,并进一步深入以更好地理解它们。

我们回想一下,Move on Sui 上的对象是什么?

简单来说,Move on Sui 中的对象就像一个保存数据的容器。

正如我们之前讨论的,要在 Move on Sui 中创建对象,您需要使用称为 struct 的东西来定义其结构。 struct 就像一个蓝图,描述了对象将保存哪些信息。例如, struct Sum 具有 number_1number_2 值的字段。

#![allow(unused)]
fn main() {
struct Sum {
    number_1: u8,
    number_2: u8,
}
}

你发现这里的错误了吗?你是对的。这不是 Move on Sui 对象。为了使其成为 Move on Sui 对象,我们需要添加能力 key 和 ID,使用 sui::object::UID 为每个对象提供唯一的 ID,以帮助识别 Sui 网络上的对象。现在,让我们的 Sum 结构成为 Move on Sui 对象。

#![allow(unused)]
fn main() {
use sui::object::UID;

struct SumObject has key {
    id: UID,
    number_1: u8,
    number_2: u8,
}
}

还记得吗,我们需要在 Move on Sui 中将所有代码写在 module 内?您的任务是编写 module 内的所有代码并查看其外观。

在 Move on Sui 中创建对象

现在您知道如何在 Move on Sui 中定义对象了,但是如何初始化它呢?我们在之前的课程中也已经介绍过这一点,但让我们回想一下,因为记住有关这些物体的所有信息很重要,就好像我们的生活依赖于它们一样。

因此,要创建一个对象,我们必须使用我们在对象内部定义的值填充所有字段。为此,首先,我们需要为 id 分配一个值。由于 UID 定义了 id 的数据类型,因此我们需要用唯一的 ID 填充 id 。这就是 sui::objectsui::tx_context 模块派上用场的地方。 sui::object 帮助我们调用 new() 函数,该函数使用 sui::tx_context 给我们的值实例化新的 id。我们先看一个例子,然后我们会进一步理解这一点。

module sum::sum {
		use sui::object;
		use sui::tx_context::TxContext;

		// Defining the SumObject
		public struct SumObject has key {
		    id: UID,
		    number_1: u8,
		    number_2: u8,
		}
		// Constructor function used to iniatlize the SumObject
		fun new(num_1: u8, num_2: u8, ctx: &mut TxContext): SumObject {
				SumObject {
		        id: object::new(ctx),
		        number_1: num_1,
				    number_2: num_2,
		    }
		}
}

您是否注意到 TxContext 在此示例中如何发挥作用?是的,您在前面的示例中也看到过这种语法,但是让我们再看一遍,因为这就像蛋糕中的面粉一样。没有面粉,我们就无法制作蛋糕。我们认为这个例子不好,但你的想法对吗?

因此, TxContext 为我们提供了交易信息, &mut 使其可变,然后我们将 ctx 传递给 object::new() 函数创建 UID 的新实例。

将对象存储在 Move on Sui 中

那么,您已经初始化了该对象,但是它存储在哪里呢?当您在构造函数内部初始化对象时,它会存储在本地,这意味着它可以从当前函数返回,作为参数传递给另一个函数,或存储在另一个结构内部。但是,如果我们想从另一个模块访问该对象或在全局级别(例如在资源管理器上)查看它,该怎么办?由于Sui中没有打印系统,我们需要找到一个解决方法。

因此,为此我们使用 transfer 模块。 transfer 模块帮助将对象添加到持久存储中。它具有有助于做到这一点的所有功能。以下是使用 transfer 模块的格式。

#![allow(unused)]
fn main() {
transfer::function_call(object, recipient)
}

这是使用 transfer 模块的最简单表示。我们在函数内部使用这一行,并将对象添加到全局存储中,其中接收者是对象的所有者,所有者可以是地址、另一个对象或共享对象。不用担心!我们将在接下来的课程中详细讨论对象所有权。

该模块的常见用途是将对象的所有权转移给调用合约的人,这里我们称之为当前交易的发送者或签名者,例如铸造一个你拥有的 NFT。那么,我们怎样才能获取发件人的信息呢?在 Move on Sui 中,获取信息的唯一方法是使用入口函数,该函数的最后一个参数是事务上下文 ctx: &mut TxContext ,它为我们提供了当前事务的所有信息。

因此,为了获取当前的交易签名者,我们可以这样调用: tx_context::sender(ctx) 。让我们通过以下示例来了解传输模块的完整工作原理。

 module sum::sum {
		use sui::object;
		use sui::tx_context::TxContext;

		// Defining the SumObject
		public struct SumObject has key {
		    id: UID,
		    number_1: u8,
		    number_2: u8,
		}
		// Constructor function used to iniatlize the SumObject
		fun new(num_1: u8, num_2: u8, ctx: &mut TxContext): SumObject {
				SumObject {
		        id: object::new(ctx),
		        number_1: num_1,
				    number_2: num_2,
		    }
		}

		// create function is used to call the constrcutor function and 
		// transfer the ownership of the object and make it public using
		// `transfer` module and function.
		public entry fun create(red: u8, green: u8, blue: u8, ctx: &mut TxContext){
			let Color = new(red, gree, blue, ctx);
			transfer::transfer(color_object, tx_context::sender(ctx))
		}
}

在这里,我们使用 the transfer::transfer(color_object, tx_context::sender(ctx)) 行将 SumObject 公开,并将其所有权分配并转移到 sender 地址。

您知道为什么我们在“Hello World”程序中使用这一行吗?

小结

您在本课程中已经介绍了对象的基础知识。现在,您已经非常了解如何在 Move 中声明和使用对象。

完成挑战将帮助您更快地掌握所学知识。请务必完成挑战,并在完成挑战后与我们分享代码的屏幕截图。在下一课中,我们将探索使用 Move on Sui 对象的更高级概念,因此首先练习基础知识非常重要。

quiz

所以,这是给你的一个小挑战。编写一个 animal 合约,其中包含 AnimeObject 以及 nameno_of_legsfavorite_food 属性。创建构造函数,然后将 AnimeObject 对象的所有权转移到 sender 地址。

创建合约后,尝试将其部署在 Sui 区块链上并探索会发生什么。

在MOVE中使用对象

最后,我们介绍了如何在 Move 中定义、创建 Sui 对象并获取其所有权。现在,我们需要学习如何使用这些对象。我们来详细了解一下。

在Move on Sui中,你只能调用你拥有的对象。要使用这些对象,您需要将它们作为参数传递给 entry 函数。现在,我们首先看一下将对象作为参数传递的两种不同方法:通过引用和通过值。

通过引用传递对象

有多种方法可以通过引用传递对象。

  1. &T :只读引用
  2. &mut T :可变引用

这里, T 代表我们通过引用传递的任何内容, & 符号用于告诉对象是通过引用传递的。让我们看看它们是如何工作的。

只读引用

只读引用仅允许您读取对象的数据。它不允许您更改或更新对象的数据。让我们举一个在上一课中使用的示例来阐明如何在代码中使用它。

module sum::sum {
		use sui::object;
		use sui::tx_context::TxContext;

		// Defining the SumObject
		public struct SumObject has key {
		    id: UID,
		    number_1: u8,
		    number_2: u8,
		}

		// Constructor function used to initialize the SumObject
		fun new(num_1: u8, num_2: u8, ctx: &mut TxContext): SumObject {
				SumObject {
		        id: object::new(ctx),
		        number_1: num_1,
				    number_2: num_2,
		    }
		}

		// make_copy function that uses the values data of obj to create a new object
		public entry fun make_copy(obj: &SumObject, ctx: &mut TxContext) {
				let sum_obj = new(obj.num_1, obj.num_2, ctx);
				transfer::transfer(sum_obj, tx_context::sender(ctx))
		}
}

在这里,在 make_copy 函数中,我们以只读形式使用 obj 对象,并使用它来创建和更新新对象 sum_obj

可变引用

因此,可变引用与只读引用相反。可变引用允许您改变对象中的数据,并更改对象字段的数据。让我们看一个示例代码来了解可变引用的工作原理。

module sum::sum {	
		use sui::object;
		use sui::tx_context::TxContext;

		// Defining the SumObject
		public struct SumObject has key {
		    id: UID,
		    number_1: u8,
		    number_2: u8,
		}

		// copy_into function uses the properties data of obj_1 (non-mutable)
		// to update the data of obj_2 (mutable)
		public entry fun copy_into(obj_1: &SumObject, obj_2: &mut SumObject, ctx: &mut TxContext) {
				obj_2.num_1 = obj_1.num_1;
				obj_2.num_2 = obj_1.num_2;
				transfer::transfer(obj_2, tx_context::sender(ctx))
		}
}

这里, obj_1 是一个只读对象, obj_2 是一个可变引用。我们正在使用 obj_1 的属性更新 obj_2 的数据属性。更新 obj_2 属性后,我们将使用 the transfer 模块更改 obj_2 的所有权。使用条目修饰符声明函数使得该函数可被事务调用。但我们必须确保交易的发送者必须同时拥有 obj_1obj_2

按值传递对象

使用对象的第二种方法是将对象按值传递给 Move entry 函数。这使得对象不会存储在 Sui 存储中,代码可以决定对象应该存储在哪里。让我们看看 Move on Sui 中如何按值传递对象。

module sum::sum {	
		use sui::object;
		use sui::tx_context::TxContext;

		// Defining the SumObject
		public struct SumObject has key {
		    id: UID,
		    number_1: u8,
		    number_2: u8,
		}

		// Passing the object by value
		public entry fun update_num_1(num_1: u8, object: SumObject): SumObject {
			let obj = object;
			obj.number_1 = num_1;
			obj
		}
}

在这里,我们按值传递 object ,创建一个新对象 obj ,更新 number_1 值,然后返回新对象。

按值传递对象的问题是,当我们传递对象时,它会在内存中创建该对象的一个新实例,并且在我们不自行删除它之前不会被删除。

正如您所知,每个 Move on Sui 对象都应该具有 UIDUID 结构不具有 drop 功能,这使得 Move on Sui 通常不使用任何地方的 drop 能力。那么,如果我们想释放内存存储空间,该怎么办呢?为了处理按值传递对象,我们有两种方法。

  1. 删除对象
  2. 转移对象

让我们一一回顾一下。

删除对象

因此,如果您决定删除该对象,我们需要进行解包。您不能直接删除该对象。您可以做的就是解压对象的字段,将所有字段存储到新变量中,然后为它们分配垃圾值。让我们看看如何进行拆包。

#![allow(unused)]
fn main() {
let SumObject { id, number_1: _, number_2: _ } = object;
}

在这里,我们解压了该对象,并使用 _ 使 number_1number_2 值不含任何数据。由于我们无法释放 id 字段,因此我们需要一些额外的帮助。我们使用 object::delete 函数删除 UID 并释放空间。请记住,您只能删除定义该对象的模块内部的对象。现在让我们看一下完整的编码示例。

module sum::sum {	
		use sui::object;
		use sui::tx_context::TxContext;

		// Defining the SumObject
		public struct SumObject has key {
		    id: UID,
		    number_1: u8,
		    number_2: u8,
		}

		// Deleting the object
		public entry fun delete(object: SumObject) {
				let ColorObject { id, red: _, green: _, blue: _ } = object;
        object::delete(id);
		}
}

注意:我们只删除按值传递的对象,而不删除按引用传递的对象,因为如果删除引用的对象,它将从全局存储中删除,这是我们无法承受的。但是,按值传递的对象是原始对象的新实例。因此,在我们按值使用对象后清除内存非常重要。

转移对象

处理该对象的另一种方法是将其转让给另一个所有者。因此,要传输对象,我们需要定义 transfer 函数。让我们看看如何在代码中定义它。

#![allow(unused)]
fn main() {
public entry fun transfer(object: SumObject, recipient: address) {
    transfer::transfer(object, recipient)
}
}

这里, recipient 代表我们想要将对象传输到的地址。请记住,您不能在 entry 函数之外调用 transfer::transfer()

小结

我为您能走到这一步并学习 Move on Sui 的所有基础知识而感到自豪。接下来,我们将进一步深入了解对象所有权的工作原理以及如何更改它。

quiz

解释为什么删除过程仅限于定义对象的模块以及它与通过引用传递的对象有何不同

对象所有权

到目前为止,您已经探索了对象的所有基础知识以及如何使用它们。我们为您保持动力而感到自豪。现在,在本课程中,我们将探索不同的对象所有权方法,这将帮助您定义对象的安全性。

对象所有权

对象所有权定义了在进行交易时如何使用对象。对象所有权有多种类型。这是这些。

  1. 地址拥有
  2. 动态字段
  3. 不可变的
  4. 共享
  5. 嵌套

让我们一一详细探讨它们。

地址拥有

地址拥有的对象是由32字节地址拥有的对象,可以是帐户地址或对象ID。所有者是唯一有权访问它的人。您可以通过以下两种方式创建地址拥有的对象。

#![allow(unused)]
fn main() {
sui::transfer::transfer(obj: T, recipient: address)
sui::transfer::public_transfer(obj: T, recipient: address)
}
  1. 如果您要为对象定义自定义传输策略,请使用 sui::transfer::transfer 函数,这意味着它仅具有 key 能力,没有 store 能力。
  2. 如果对象具有 store 能力,则使用 sui::transfer::public_transfer 创建地址拥有的函数。

当您只想拥有该对象的一个所有者时,可以使用地址拥有的对象。

不可变对象

不可变对象是不可变的,这意味着它没有所有者,不能转让给任何人,也不能删除。以下是如何使对象不可变。

#![allow(unused)]
fn main() {
public native fun public_freeze_object(obj: T)
}

对象成为不可变对象后,就无法使其可变。但是,您可以使用对 entry 函数的不可变引用将其作为只读对象作为参数传递给函数。

共享对象

共享对象是每个人都可以访问的对象。与地址拥有的对象相比,共享对象可以被网络上的任何人访问。以下是创建共享对象的方法。

#![allow(unused)]
fn main() {
transfer::share_object(obj: T)
}

嵌套

与许多编程语言一样,您可以在另一个 struct 内部使用 struct 类型实例。以下是您可以如何做到这一点。

#![allow(unused)]
fn main() {
struct Foo has key {
    id: UID,
    bar: Bar,
}

struct Bar has store {
    value: u64,
}
}

这里,具有 key 能力的 Foo Sui 对象有一个具有 Bar 数据类型的实例 bar ,它是一个 struct 。这里, Bar 不是Sui对象,因为它没有 key 能力。您可以将其设为 Sui 对象并在 Foo 中使用它。

我们将在接下来的课程中了解有关对象包装的更多信息。

动态对象字段

有多种方法可以使用对象字段来存储不同类型的数据。但它有几个缺点:

  1. 发布模块时,对象具有一组固定的字段,由 struct 声明中的键标识。
  2. 一个对象包裹多个其他对象可能会增加 Gas 费,并且有大小限制。
  3. 使用 Move vector 类型不可能存储不同类型的对象集合,因为它需要单一类型 <T>

这就是动态字段发挥作用的地方。

总结

我们为您详细了解这些物体而感到自豪。接下来,我们将详细解释“Hello World”程序,然后我们将评估如何在 Move on Sui 上编写您自己的智能合约

quiz

在 Move on Sui 中定义了对象所有权的概念,并概述了五种类型的对象所有权方法:地址拥有、动态字段、不可变、共享和包装。

2.8_回到 hello world - 课程学习时长因人而异

了解 Hello World 程序

我们已经运行了 Hello World 程序并与它进行了交互。现在我们已经完成了基础知识,我们可以深入了解它的完整代码。

完整代码

Hello World程序的完整代码为:

// Copyright (c) 2022, Sui Foundation
// SPDX-License-Identifier: Apache-2.0

/// A basic Hello World example for move, part of the move intro course:
/// https://github.com/sui-foundation/sui-move-intro-course
/// 
module hello_world::hello_world {

    use std::string;
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    /// An object that contains an arbitrary string
   public struct HelloWorldObject has key, store {
        id: UID,
        /// A string contained in the object
        text: string::String
    }

    public entry fun mint(ctx: &mut TxContext) {
        let object = HelloWorldObject {
            id: object::new(ctx),
            text: string::utf8(b"Hello World!")
        };
        transfer::public_transfer(object, tx_context::sender(ctx));
    }

}

解释

让我们深入了解代码的逐行解释:

#![allow(unused)]
fn main() {
// Copyright (c) 2022, Sui Foundation
// SPDX-License-Identifier: Apache-2.0
/// A basic Hello World example for move, part of the move intro course:
/// https://github.com/sui-foundation/sui-move-intro-course
/// 
}
  • 代码开头的注释表明其版权(Sui Foundation)和 SPDX-License-Identifier(指定 Apache-2.0 许可证)。
  • module hello_world::hello_world { :代码在 hello_world 命名空间内定义了一个名为 hello_world 的模块。
#![allow(unused)]
fn main() {
    use std::string;
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};
}
  • 该代码使用 use 关键字导入各种模块,包括:
    • std::string :用于使用标准库中的字符串数据类型。
    • sui::object::{Self, UID} :这会从 sui::object 模块导入“Self”和“UID”类型。
    • sui::transfer :这会导入“transfer”模块,用于传输对象。
    • sui::tx_context::{Self, TxContext} :从 sui::tx_context 模块导入“Self”和“TxContext”类型,用于处理事务上下文。
#![allow(unused)]
fn main() {
    struct HelloWorldObject has key, store {
        id: UID,
        /// A string contained in the object
        text: string::String
    }
}
  • 在模块内,定义了一个名为 HelloWorldObject 的结构。该结构代表一个具有以下属性的对象:
    • id :对象的唯一标识符(UID)。
    • text :包含一些任意文本的字符串。
#![allow(unused)]
fn main() {
public entry fun mint(ctx: &mut TxContext) {
        let object = HelloWorldObject {
            id: object::new(ctx),
            text: string::utf8(b"Hello World!")
        };
        transfer::public_transfer(object, tx_context::sender(ctx));
    }
}
  • 定义了一个名为 mint 的公共入口函数。该函数采用对 TxContext 的可变引用作为参数。
  • mint 函数内部:
    • 它创建一个新的 HelloWorldObject 对象。
    • 使用 object::new 函数初始化对象的 id 字段,生成新的唯一标识符。
    • 对象的 text 字段使用文本“Hello World!”进行初始化。使用 string::utf8(b"Hello World!")
    • 最后,它使用 transfer::public_transfer 函数将 HelloWorldObject 传输到发送者。这表明 mint 函数负责创建和传输该对象。

你一直做得很棒!我确信现在您可以轻松打印您想要的任何文本。那么为什么不尝试打印你的名字呢?构建和学习!

小结

综上所述,我们分析了 move 中的“Hello World”程序,包括模块导入、结构体创建和函数定义。现在,您已准备好修改代码并进行实验。请记住,实践是学习的关键。快乐编码!

quiz

讨论 HelloWorldObject 结构中文本字段的初始化。字符串“Hello World!”是怎样的?表示,为什么使用 string::utf8 函数?

2.9_先进概念 - 课程学习时长因人而异

Sui上的事件

嘿,学习者,我想向您鞠躬,因为相信我,如果您到目前为止已经仔细学习了所有课程,那么您已经掌握了大量关于如何使用 Move on Sui 进行编码的知识,这太棒了!现在,在本课程中,我们将了解一些可以使用 Move on Sui 执行的高级操作,所以让我们开始吧。

为什么需要在Sui事件?

好的,所以每当您与 Sui 区块链上的合约进行交互时,都会有大量的数据流入和流出,并且由于 Move on Sui 是一种以对象为中心的语言,因此 Sui 对象充当与 Sui 通信的媒介区块链,但是如果你想监控数据的流入和流出怎么办?这就是事件发挥作用的地方,您可以根据智能合约发出事件所需满足的某些条件从智能合约发出事件。

例如,您有一个 NFT 合约,其中包含以下函数:

  • 总供应量是多少?
  • NFT 的代币 ID 是什么?
  • 现在,如果您想在有人铸造 NFT 时收到通知该怎么办?

为了解决这个问题,您可以使用事件来发出某些参数,例如铸造 NFT 的代币 ID、带来 NFT 的用户的地址等。让我快速引导您了解如何在 Sui 智能合约中实现事件

注意:Sui 上的事件也表示为 Sui 对象。

我们如何使用 Move on Sui 发出事件?

在开始编写代码部分之前,我先带您了解一下 Sui 中事件对象中的不同属性:

  • id :包含交易摘要 ID 和事件序列的 JSON 对象。
  • packageId :发出事件的包的对象 ID。
  • transactionModule :执行事务的模块。
  • sender :触发事件的 Sui 网络地址。
  • type :发出的事件类型。
  • parsedJson :描述事件的 JSON 对象。
  • timestampMs :Unix 纪元时间戳(以毫秒为单位)。

我认为理论部分已经足够了,让我们继续使用代码片段来理解这个概念

因此,要使用 Move on Sui 创建事件,第一步是导入。

#![allow(unused)]
fn main() {
use sui::event;
}

让我用代码来解释事件,首先,让我们创建我们的 Hero 对象。

module example::events {
	use sui::object::{UID};

 //*Object representing our hero*
	public struct Hero has key{
		id: UID,
		power: u8,
		}
}

现在让我们为我们的英雄创造一把剑。

module example::events {
	use sui::object::{UID};
 
	//*Object representing our hero*
	struct Hero has key, store{
		id: UID,
		power: u8,
	}
	 
	//*Object representing our hero's sword*
	struct Sword has key {
	id: UID
	}

}

现在我们就来创造一个买剑的乐趣吧。目前,为了保持示例简单,我不会使用英雄。

module example::events {
	use sui::object::{UID};
	use sui::tx_context::{Self, TxContext};
	use sui::transfer;
 
	//*Object representing our hero*
	struct Hero has key, store{
		id: UID,
		power: u8,
	}
	 
	//*Object representing our hero's sword*
	struct Sword has key {
	id: UID
	}

	//function to buy a sword, if the user owns a hero
	pub fun buy_sword (hero: Hero, ctx: &mut TxContext){
		let sword = Sword {
		id: object::new(ctx),
		};
		
	//Transfer the sword to the caller of this function
		transfer::transfer(sword, tx_context::sender(ctx));

	} 

}

现在,如果我希望智能合约在有人购买剑时通知我怎么办?为此,我们将使用事件,让我引导您完成它们。

module example::events {
	use sui::object::{UID};
	use sui::tx_context::{Self, TxContext};
	use sui::transfer;
 
	// Object representing our hero
	public struct Hero has key, store{
		id: UID,
		power: u8,
	}
	 
	// Object representing our hero's sword
	public struct Sword has key {
		id: UID
	}
	
	// Object representing an event
	public struct buySwordEvent has copy, drop {
	  owner: address,
	}

	// Function to buy a sword, if the user owns a hero
	pub fun buy_sword (hero: Hero, ctx: &mut TxContext){
		let sword = Sword {
		id: object::new(ctx),
		};
		
	// Emitting an event with the address of the user who brought the sword
	event::emit(buySwordEvent {
      owner: tx_context::sender(ctx),
   });

	//Transfer the sword to the caller of this function
		transfer::transfer(sword, tx_context::sender(ctx));

	} 

}

现在,每当携带一把剑时,就会发出一个事件,通知成功购买剑。

小结

在本课程中,我们了解了 Sui 上的事件,并了解了如何使用 Move on Sui 来实现事件。希望这对您来说很有趣,下一课对您来说会更加令人兴奋,敬请期待!

quiz

在move编程中,在智能合约中使用事件的目的是什么?

对象包装

我为你在课程中取得这么大的进步感到非常自豪。到目前为止,您已经了解了所有基础知识。不仅如此,您还了解了“Hello World”程序的工作原理。现在是时候进一步讨论一些高级主题了。在本课程中,我们将介绍对象包装,当您深入在 Sui 上构建 dApp 时,它将为您提供极大帮助。

对象包装

在 move 编程语言中,您可以通过一种称为包装的方法将一个数据结构放入另一个数据结构中来组织数据结构。想象一下,这就像一个工具箱,里面装有不同的物品。在 move 中,您可以通过将 struct 类型的字段放在另一个字段中来组织数据结构,如下所示:

#![allow(unused)]
fn main() {
struct Foo has key {
    id: UID,
    bar: Bar,
}

struct Bar has key, store {
    id: UID,
    value: u64,
}
}

对于要成为 Sui 对象的结构,它应该具有 key 能力。现在,如果将 Bar 类型的 Sui 对象放入 Foo 类型的 Sui 对象中,则对象类型 Foo 会包装对象类型 Bar .对象类型 Foo 是包装器或包装对象。

在 move 代码中,您可以将 Sui 对象作为非 Sui 对象结构类型的字段。

  • 想象一下,您有一个名为“Foo”的结构,它不是 Sui 对象,还有另一个名为“Bar”的结构,它是具有“key”和“store”功能的 Sui 对象。您可以将“Bar”放入“Foo”中,但这只是暂时的,不会持久保留在区块链上。
  • 当你包装一个对象时,它就成为包装对象数据的一部分。这意味着您无法通过其唯一 ID 查找它,也无法将包装对象作为区块链交易中的参数传递。访问它的唯一方法是通过包装它的对象。
  • 例如,您不能创建这样的情况:对象 A 包装对象 B、B 包装 C、C 也包装 A。这会导致代码复杂性和问题。
  • 您可以“解开”包裹的对象。当你解开它时,它又变成了一个独立的对象,可以直接在区块链上访问。该对象保留与包装之前相同的唯一 ID。

有几种常见方法可以将 Sui 对象包装到另一个 Sui 对象中。让我们看看下面的内容:

直接包装

如果将一个 Sui 对象类型直接作为另一个 Sui 对象类型中的字段,则称为直接包装。在这种类型的包装中,除非包装对象被销毁,否则无法解开包装对象。它可用于锁定访问权限有限的对象。

让我们看一个例子。假设您在 Sui 中有一种名为 Object 的 NFT 样式对象,它具有 scarcitystyle 等属性。为了确保公平交易,你需要确保稀缺性相同,只有款式不同。这是因为 NFT 的稀缺性越高,其价值就越高。以下是实现它的方法:

#![allow(unused)]
fn main() {
public struct Object has key, store {
    id: UID,
    scarcity: u8,
    style: u8,
}

public entry fun create_object(scarcity: u8, style: u8, ctx: &mut TxContext) {
    let object = Object {
        id: object::new(ctx),
        scarcity,
        style,
    };
    transfer::transfer(object, tx_context::sender(ctx));
}
}

在上面的代码中,您可以使用 create_object 函数创建具有指定稀缺性和样式的对象。任何人都可以调用它,创建的对象将发送给交易发送者。

现在,您可能还记得前面的部分,只有对象所有者才能发送交易来改变对象。因此,一个人无法发送一笔将自己的对象与他人的对象交换的交易。因此,任何想要交换其对象的人都需要将其对象发送到第三方(例如交换站点)。为了确保您保留对您的对象(例如硬币和 NFT)的保管权并且不将全部保管权交给第三方,您需要使用直接包装,如下所示:

#![allow(unused)]
fn main() {
public struct ObjectWrapper has key {
    id: UID,
    original_owner: address,
    to_swap: Object,
    fee: Balance<Sui>,
}
}

ObjectWrapper 包装要交换 to_swap 的对象并跟踪其原始所有者。您可能还需要支付交换费用。

要请求交换,您需要定义一个入口函数:

#![allow(unused)]
fn main() {
public entry fun request_swap(object: Object, fee: Coin<Sui>, service_address: address, ctx: &mut TxContext) {
    assert!(coin::value(&fee) >= MIN_FEE, 0);
    let wrapper = ObjectWrapper {
        id: object::new(ctx),
        original_owner: tx_context::sender(ctx),
        to_swap: object,
        fee: coin::into_balance(fee),
    };
    transfer::transfer(wrapper, service_address);
}
}

在此函数中,要交换的对象使用 ObjectWrapper 包装,并指定费用。然后将包装器发送到服务地址。

这里的关键是,即使服务运营商拥有 ObjectWrapper,他们也无法访问或窃取其中包装的“对象”。这是因为 Sui 不允许在没有特定的解包函数的情况下访问包装对象,而该函数未在此模块中定义。

通过 Option包装

在上面的直接包装示例中,您必须销毁一个对象才能将其内部的对象取出。但有时,您可能需要更大的灵活性,其中包装类型并不总是在其中包含包装对象,并且您可以在需要时将包装对象替换为其他对象。

让我们举一个拥有剑和盾牌的战士的例子。他应该可以选择拥有或不拥有它们。他应该能够添加新的剑和盾牌或替换当前的剑和盾牌。

在下面的代码中,我们定义了一个带有可选 swordshield 字段的 SimpleWarrior 类型。 swordshield 是其他 Sui 对象类型。

#![allow(unused)]
fn main() {
public struct SimpleWarrior has key {
    id: UID,
    sword: Option<Sword>,
    shield: Option<Shield>,
}

public struct Sword has key, store {
    id: UID,
    strength: u8,
}

public struct Shield has key, store {
    id: UID,
    armor: u8,
}
}

当你创建一个新的战士时,你一开始没有任何装备:

#![allow(unused)]
fn main() {
public entry fun create_warrior(ctx: &mut TxContext) {
    let warrior = SimpleWarrior {
        id: object::new(ctx),
        sword: option::none(),
        shield: option::none(),
    };
    transfer::transfer(warrior, tx_context::sender(ctx))
}
}

此函数 create_warrior 创建一个没有剑或盾的新 SimpleWarrior

要装备新的剑或盾,您可以定义如下函数:

#![allow(unused)]
fn main() {
public entry fun equip_sword(warrior: &mut SimpleWarrior, sword: Sword, ctx: &mut TxContext) {
    if (option::is_some(&warrior.sword)) {
        let old_sword = option::extract(&mut warrior.sword);
        transfer::transfer(old_sword, tx_context::sender(ctx));
    };
    option::fill(&mut warrior.sword, sword);
}
}

在上面的函数 equip_sword 中,您传递了战士作为可变引用和一把包裹到战士中的剑。如果战士已经拥有一把剑,它会首先移除旧剑并将其发送回发送者。然后,它将新剑包裹在战士体内。这样,战士在装备新东西时就可以更换他们的装备。

通过vector包装

在 Sui 中,您可以将对象包装在另一个 Sui 对象的向量场中,这类似于使用 Option 类型包装对象。这种方法允许一个对象包含零个、一个或多个相同类型的包装对象。

让我们考虑一个涉及宠物和农场的例子:

#![allow(unused)]
fn main() {
public struct Pet has key, store {
    id: UID,
    cuteness: u64,
}

public struct Farm has key {
    id: UID,
    pets: vector<Pet>,
}
}

在此代码中,我们有两种对象类型:

  1. Pet :代表个体宠物,具有唯一的ID和“可爱度”值,这只是宠物特征的占位符。
  2. Farm :代表一个农场,也有一个唯一的ID。这里的关键特征是 pets 字段,它是 Pet 对象的向量。这意味着一个农场可以饲养多只宠物。

通过此设置,您可以将多个宠物添加到农场。每个宠物都被包裹在一个 Pet 对象中,这些对象被收集并存储在farm对象的 pets 字段中。这允许您通过农场对象管理和访问与特定农场关联的所有宠物。

小结

在本课程中,您已经完全掌握了对象包装。您非常清楚它是什么以及如何在 Move on Sui 中使用它。在下一课中,我们将继续学习 Move on Sui 中的更多高级主题和学习活动。

quiz

“直接包装”和“通过选项包装”之间的主要区别是什么?

2.10_挑战 - 课程学习时长因人而异

挑战:使用 Move on Sui 创建计算器合约

现在您已经了解了 move 的所有基础知识,是时候挑战自己并进行一些编码了。

所以我将解释挑战的要求是什么。你可以构建它。那么为什么还要等呢,让我们开始吧!

构建一个计算器

因此,任务是使用运算符、函数以及您迄今为止学到的所有 move 基础知识构建一个 u64 整数计算器。

  • 根据您的需要创建函数和对象。
  • 创建一个名为 add 的函数,该函数采用两个输入参数。它将两个整数的值相加并返回结果。
  • 创建一个名为 sub 的函数,该函数采用两个输入参数。它从第一个整数值中减去第二个整数值并返回结果。
  • 创建一个名为 mul 的函数,该函数采用两个输入参数。它将两个整数相乘并返回结果。
  • 创建一个名为 div 的函数,该函数采用两个输入参数。它将第一个整数除以第二个整数并返回结果。

部署计算器合约并与之交互

  • 完成计算器的代码后,将其部署到Sui区块链上。
  • 现在转到 Sui Explorer 并与合约交互。

小结

总之,本课程让您通过创建和部署计算器 (+、-、x、%) 合约来实际应用您的 move 和区块链知识。

在下一课中,我们将重点学习该作业的解决方案。

quiz

通过 Sui Explorer 分享已部署合约的屏幕截图。

解决方案:使用 Move on Sui 创建计算器合约

所以我希望您已经完成了该解决方案,但让我们讨论该解决方案并将其也部署在 Sui 上。

完整代码

计算器的完整代码是:

module calculator::calculator{
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::transfer;

    public struct Output has key, store{
        id: UID,
        result: u64,

    }

    public entry fun start (ctx: &mut TxContext){
       let output = Output{
            id: object::new(ctx),
            result: 0,
        }; 

        transfer::public_transfer(output, tx_context::sender(ctx));  

    }

    public entry fun add (a:u64, b:u64, ctx: &mut TxContext){
        let output = Output{
            id: object::new(ctx),
            result: a + b,
        };
        
        transfer::public_transfer(output, tx_context::sender(ctx));  
    }
    
    public entry fun sub (a:u64, b:u64, ctx: &mut TxContext){
        let output = Output{
            id: object::new(ctx),
            result: a - b,
        };
        
        transfer::public_transfer(output, tx_context::sender(ctx)); 
    }

    public entry fun mul (a:u64, b:u64, ctx: &mut TxContext){
        let output = Output{
            id: object::new(ctx),
            result: a * b,
        };
        
        transfer::public_transfer(output, tx_context::sender(ctx));
    }

    public entry fun div (a:u64, b:u64, ctx: &mut TxContext){
        let output = Output{
            id: object::new(ctx),
            result: a / b,
        };
        
        transfer::public_transfer(output, tx_context::sender(ctx));
    }

}

解释

让我们看一下代码:

#![allow(unused)]
fn main() {
module calculator::calculator{
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::transfer;
}

导入模块:

  • 我们首先导入为我们的程序提供工具和功能的必要模块。
  • use sui::object::{Self, UID}; :这一行导入了与对象相关的模块,包括Self和UID类型,这有助于我们管理对象的唯一标识符。
  • use sui::tx_context::{Self, TxContext}; :在这里,我们导入用于事务上下文处理的模块,包括 Self 和 TxContext 类型。
  • use sui::transfer; :这会导入传输模块,使我们能够传输对象。
#![allow(unused)]
fn main() {
   public struct Output has key, store{
        id: UID,
        result: u64,

    }
}

定义结构体:

  • 我们使用 struct 关键字定义一个名为 Output 的结构体。
  • struct 有两个字段:
    • id :它是 Output 对象的每个实例的唯一标识符 (UID)。
    • result :该字段存储表示计算结果的64位无符号整数(u64)。
#![allow(unused)]
fn main() {
    public entry fun start (ctx: &mut TxContext){
       let output = Output{
            id: object::new(ctx),
            result: 0,
        }; 

        transfer::public_transfer(output, tx_context::sender(ctx));  

    }
}

定义 start 函数:

  • 我们创建一个名为 start 的公共入口函数,它将对 TxContext 的可变引用作为参数。
  • start 函数内部:
    • 我们创建一个名为 output using 的新 Output 对象 let output = Output { id: object::new(ctx), result: 0 };`
    • 我们将 output 对象的 id 字段设置为使用 object::new(ctx) 生成的唯一标识符。
    • 我们将 result 字段初始化为 0,就像我们从计算器设置为零开始一样。
    • 最后,我们使用 transfer::public_transferoutput 对象传输给某人,并将发送者指定为 tx_context::sender(ctx)
#![allow(unused)]
fn main() {
    public entry fun add (a:u64, b:u64, ctx: &mut TxContext){
        let output = Output{
            id: object::new(ctx),
            result: a + b,
        };
        
        transfer::public_transfer(output, tx_context::sender(ctx));  
    }
}

定义 add 函数:

  • 这是一个名为 add 的公共入口函数。
  • 它需要两个 64 位无符号整数( ab )作为输入以及对 TxContextctx )的可变引用处理事务上下文。
  • 用途:此函数旨在将两个数字相加并将结果存储在 Output 对象中。

函数内部:

  • #![allow(unused)]
    fn main() {
    let output = Output { id: object::new(ctx), result: a + b };
    }
    • 我们创建一个名为 output 的新 Output 对象。
    • 我们使用 object::new(ctx)output 对象分配一个唯一标识符 (UID)。
    • 我们计算 ab 的总和并将其存储在 output 对象的 result 字段中。
  • transfer::public_transfer(output, tx_context::sender(ctx));
    
    • 我们将“输出”对象传输给某人,并将发送者指定为 tx_context::sender(ctx)
#![allow(unused)]
fn main() {
    public entry fun sub (a:u64, b:u64, ctx: &mut TxContext){
        let output = Output{
            id: object::new(ctx),
            result: a - b,
        };
        
        transfer::public_transfer(output, tx_context::sender(ctx)); 
    }
}

定义 sub 函数:

  • 这是一个名为 sub 的公共入口函数。
  • 它需要两个 64 位无符号整数( ab )作为输入以及对 TxContextctx )的可变引用处理事务上下文。
  • 用途:此函数旨在将两个数字相减并将结果存储在 Output 对象中。

函数内部:

  • let output = Output { id: object::new(ctx), result: a + b };
    
    • 我们创建一个名为 output 的新 Output 对象。
    • 我们使用 object::new(ctx)output 对象分配唯一标识符 (UID)。
    • 我们通过从 a 中减去 b 并将其存储在 output 对象的 result 字段中来计算减法。
  • 最后,我们将 output 对象传输给某人,并使用 tx_context::sender(ctx) 指定发送者。

#![allow(unused)]
fn main() {
    public entry fun mul (a:u64, b:u64, ctx: &mut TxContext){
        let output = Output{
            id: object::new(ctx),
            result: a * b,
        };
        
        transfer::public_transfer(output, tx_context::sender(ctx));
    }
}

定义 mul 函数:

  • 这是 mul 函数,它处理乘法。
  • 它需要两个 64 位无符号整数 ab 以及事务上下文 ctx

函数内部:

  • 我们创建一个新的 Output 对象。
  • 我们通过 ab 相乘来计算结果。
  • output 对象存储此结果。
  • 我们传输 output 对象,并使用 tx_context::sender(ctx) 指定发送者。
#![allow(unused)]
fn main() {
    public entry fun div (a:u64, b:u64, ctx: &mut TxContext){
        let output = Output{
            id: object::new(ctx),
            result: a / b,
        };
        
        transfer::public_transfer(output, tx_context::sender(ctx));
    }
}

定义 div 函数:

  • 该函数 div 处理除法。
  • 它需要两个 64 位无符号整数 ab 以及事务上下文 ctx

函数内部:

  • 我们创建一个新的 Output 对象。
  • 我们通过将 a 除以 b 来计算结果。
  • output 对象存储此结果。
  • 我们传输 output 对象,并使用 tx_context::sender(ctx) 指定发送者。

该代码定义了一个计算器程序,其结构体 Output 用来存储结果和各种算术运算(加法、减法、乘法和除法)作为公共入口函数。每个函数都会使用结果创建一个 Output 对象,并将其传输给使用 transfer 的人员。

部署合约

将以下命令中的 [YOUR_ADDRESS] 替换为您的帐户地址并运行:

#![allow(unused)]
fn main() {
sui client switch --address [YOUR_ADDRESS]
}
  • 确保有足够的汽油。如果您没有,请前往 Sui Devnet faucet Discord 频道并粘贴“!faucet [YOUR_ADDRESS]”以接收 10 个 Sui 代币。

运行以下命令,以便我们可以在 Sui devnet 上部署:

#![allow(unused)]
fn main() {
sui client switch --env devnet
}

要发布合同,请复制 Hello.move 文件的绝对路径。将 [YOUR_PATH] 替换为文件的绝对路径并运行以下命令:

#![allow(unused)]
fn main() {
sui client publish --gas-budget 10000000 [YOUR_PATH]
}

我们将有一个很长的输出,但滚动到输出的开头并复制交易摘要。前往 https://suiexplorer.com/?network=devnet。将交易摘要粘贴到搜索栏中,即可在 Sui Explorer 上查找您的交易。

现在点击计算器模块的包地址:

Frame 3560370.jpg

您将看到如下所示的所有功能:

Frame 3560370 (1).jpg

现在就开始与功能互动并享受吧~

小结

在本课程中,我们学习了如何使用 Move 创建计算器合约。我们讨论了代码结构和每个函数的用途。我们还提供了有关在 Sui Devnet 上部署合约的说明。随意探索合约的功能并与之互动。快乐编码!

quiz

分享一下Sui Explorer的功能截图。

2.11_总结 - 本章学习内容回顾

总结

好吧,让我们总结一下并庆祝您不可思议的旅程!现在是时候花点时间反思一下您在本课程中学到的所有内容了。让我们总结一下您所学到的知识以及它如何为您在区块链开发领域提供帮助。

入门

首先,我们向您介绍了 Move on Sui 的世界,并解释了它的创建原因。您已经设置了 Sui SDK、创建了帐户并部署了第一个 Hello World 合约。这些初始步骤对于奠定您的旅程基础至关重要。

Move on Sui 中的变量和常量

您已经了解了如何声明变量和常量,探索了 Move 中的各种数据类型和运算符。这些知识对于构建复杂的智能合约和应用程序至关重要。

Move on Sui 中的功能

函数是任何程序的核心,您已经学习了如何使用不同的可见性说明符创建它们。您还掌握了功能设计的基础知识,这对于制定有效且高效的智能合约至关重要。

move的对象

对象是 move 的基础,您现在知道如何在合约中声明和使用它们。对象可组合性的概念使您能够创建复杂的模块化应用程序。

Move on Sui 中的控制流程

控制流对于管理合约逻辑至关重要。您已经学会了如何声明循环,并接受了快速挑战来增强您的技能。

Move on Sui 的先进概念

通过深入研究事件日志记录和对象包装等更高级的主题,您可以深入了解如何使您的合约更加通用和信息丰富。

Sui练习

为了测试您的知识,您面临着开发计算器合约的挑战。这一挑战的解决方案使您能够实际了解如何在现实场景中应用您的技能。

通过本课程,您已经释放了在 Sui 区块链上构建去中心化应用程序和智能合约的潜力。

⚒️一个小而重要的请求

这是一个100%的开源项目,就像我们平台上的其他项目一样。您可以在[这里](https://github.com/0xmetaschool/Learning-Projects/tree/main/Learn Everything About Sui%2C its Concepts and Protocols)找到教程标记文件。如果您在课程中发现任何问题,请麻烦解决一下。我们,在Metaschool,热爱我们社区的贡献,也感谢我们Discord 和GitHub上的贡献者。

当您做出贡献时:不要忘记在⭐️上标记我们的存储库。我们将非常感激!❤️我们是一个完全免费的平台,我们的目标是保持不变,所以请考虑在 XLinkedIn 上关注我们。

🎊 恭喜

您已完成本教程,现在已具备了深入的知识,可以开始在令人兴奋的 Sui 区块链世界中进行构建。我们祝你好运! ✌🏻🔮

03_Token

  • 本章课程共4小节,学习时长因人而异

Token

3.1_开始 学习token相关知识 - 课程学习时长因人而异

我们今天学习什么

大家好,今天我们非常高兴为 Metaschool 的开发者带来有关 Sui 区块链的详细系列的第三门课程。让我们从第三门课程开始,祝贺您完成了 Sui 的前两门课程。呜呼!

那么我们今天要建造什么?我们将在 Move on Sui 中构建我们自己的代币,然后将其部署在 Sui 区块链上。这个项目很重要,因为它为您提供了在 Sui 上进行构建所需的知识。

让我们看看在课程结束时您将如何与令牌进行交互。您将铸造您的代币并在钱包中查看它。多么激动人心!!

newly_minted_token_in_wallet.gif

课程大纲

我们将:

  1. 设置 Sui Wallet 和开发环境(如果尚未设置)
  2. 在 Move on Sui 中创建我们自己的代币智能合约
  3. 在 Sui 区块链上部署智能合约

谁应该参加这门课程?

本课程面向希望在 Sui 区块链上进行构建并已完成我们的前两门 Sui 课程的 web3 开发人员和构建者。您应该具备基本的编程知识,因为它将帮助您快速开始本课程。

您将从本课程中获得什么?

作为开发人员,完成本课程后,您将获得以下成果:

  1. 在 Sui 上构建和部署代币智能合约
  2. 通过 SuixMetaschool Discord House 探索 Sui 生态系统中的机会
  3. 赚取高达 1000 XP
  4. 完成课程后领取您的 NFT

完成证明

如果您和我一起完成本课程,您将获得 XP 和特殊的 NFT,这将为您在 Metaschool 平台上解锁更多机会。这是 NFT 的样子。

Untitled

现在,在我们继续之前,让我们先制定一些内部规则。

  1. 请正确完成你的快速作业。
  2. 加入我们的不和谐服务器并在那里询问所有相关问题。
  3. 我们是一个免费的开源平台,如果您在 X 和 LinkedIn 上关注我们,这将是一个巨大的支持。 🫣
  4. 保持快乐和积极!

好了各位,不用再等了。下一课见!

走进 move的世界

大家好,我们很高兴您完成了 Sui 学习路径的前两门课程。在本课中,我们将对 Sui 和 Move 进行一些修改。我们知道您想立即开始编码,但是修改一些概念将帮助您轻松地编写代码。

我们来复习一下,Sui中对象的重要性

对象就像任何其他编程语言中的结构一样。每个对象都可以存储任何数据,无论是整数、布尔值还是地址。它们在有效存储大量数据方面发挥着重要作用。每个对象都有所有权,标识谁拥有该对象。所有权告诉我们对象的交易将如何进行。在 Sui 中,有两种类型的对象:

  1. 单一所有者对象
  2. 共享所有者对象

以下是在 move 中定义对象的方法。

#![allow(unused)]
fn main() {
struct ExampleObject has key {
		 id: UID,
		 data_1: u8,
		 data_2: u8,
}
}

注意:对象定义不定义对象的所有权。我们在初始化和转移所有权过程中定义它。

回想一下,move中如何写合约

我们在上一课程中详细介绍了 Move 合约的编写。让我们回顾一下“Hello World”示例代码。

// Copyright (c) 2022, Sui Foundation
// SPDX-License-Identifier: Apache-2.0

/// A basic Hello World example for move, part of the move intro course:
/// https://github.com/sui-foundation/sui-move-intro-course
/// 
module hello_world::hello_world {

    use std::string;
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    /// An object that contains an arbitrary string
    public struct HelloWorldObject has key, store {
        id: UID,
        /// A string contained in the object
        text: string::String
    }

    public entry fun mint(ctx: &mut TxContext) {
        let object = HelloWorldObject {
            id: object::new(ctx),
            text: string::utf8(b"Hello World!")
        };
        transfer::public_transfer(object, tx_context::sender(ctx));
    }

}

让我们花点时间回顾一下这份合约所完成的工作。为了清晰起见,我们将总结要点。

模块定义

您可以使用 module 关键字定义合约。以下是您在上面的程序中定义它的方式。

#![allow(unused)]
fn main() {
module hello_world::hello_world
}

导入库

定义模块后,我们导入在 Move on Sui 中创建合约所需的重要库,就像我们在 hello_world 合约中所做的那样。

#![allow(unused)]
fn main() {
use std::string;
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
}

定义一个对象

没有对象,Sui的合约是不完整的。它在 Sui 中定义了具有 key 能力和 id 的对象。其他能力的出现可以使物体更好地工作。以下是我们在 hello_world 合约中定义它的方式。

#![allow(unused)]
fn main() {
/// An object that contains an arbitrary string
public struct HelloWorldObject has key, store {
    id: UID,
    /// A string contained in the object
    text: string::String
}
}

定义 entry 函数

如果您想创建对象并将其转移给所有者,那么使用 entry 关键字定义函数非常重要。这是 hello_world 合约中的 mint 函数的样子。

#![allow(unused)]
fn main() {
public entry fun mint(ctx: &mut TxContext) {
    let object = HelloWorldObject {
        id: object::new(ctx),
        text: string::utf8(b"Hello World!")
    };
    transfer::public_transfer(object, tx_context::sender(ctx));
}
}

这里的 mint 函数调用 transfer::public_transfer() 方法并将 HelloWorldObject 铸造到 Sui 全局存储中。

小结

好了,伙计们,Sui 和 Move 就到此结束了!您还记得如何在 Move on Sui 中定义简单合约。在编写代币合约时,您将复制您的知识并在 Move on Sui 中创建您自己的代币。

在下一课中,您将设置编写自己的合同的环境。在继续之前不要忘记完成任务!

quiz

Move on Sui 中如何定义合约?

3.2_环境设置 - 课程学习时长因人而异

设置环境

欢迎回来!这样你就完成了《Move on Sui》的复习。好吧,在本课程中,您将学习如何设置运行代码的环境。准备好开始新一轮令人兴奋的学习了吗?让我们开始吧!

注意:我们假设您已经在 Sui 课程路径的最后一门课程中在系统中安装了 Sui 和 Rust。

检查您的版本

打开您最喜欢的终端。让我们检查一下您的 Sui 安装版本。

#![allow(unused)]
fn main() {
sui --version
}

注意:如果您在安装 Sui 时遇到错误,请确保遵循部署您的第一个 Sui 合约课程中的所有步骤。

创建您的 Sui 文件夹

第一步是初始化工作空间环境。这将包含运行任何 Move 文件的基本文件。您可以使用以下命令创建工作区;我已将我的命名为 metaschool

#![allow(unused)]
fn main() {
sui move new metaschool
}

导航到 sources/ 目录。创建一个新的移动文件。我将其命名为 pepe.move 。您可以使用您想要的任何货币名称来命名 Move 文件。可能是芒果、路飞,甚至是皮卡丘。 😃

这就是我的目录现在的样子:

Frame 3560370 (13).jpg

检查你的钱包

您可以使用以下命令查看当前的活动地址:

#![allow(unused)]
fn main() {
sui client active-address
}

如果您没有有效地址,请按照以下步骤操作:

  1. 运行以下命令来创建您的 Sui 帐户:

    #![allow(unused)]
    fn main() {
    sui client new-address ed25519
    }

    它将生成如下输出:

    deploy-5.png

  • 重要提示:保存恢复短语,使用它来导入您的钱包非常重要。
  1. 将以下命令中的 [YOUR_ADDRESS] 替换为运行最后一个命令后收到的地址并运行它。

    #![allow(unused)]
    fn main() {
    sui client switch --address [YOUR_ADDRESS]
    }
  2. 前往 Sui Devnet faucet discord channel 频道并粘贴“!faucet [YOUR_ADDRESS]”即可接收 10 个 Sui 代币。

小结

在本课程中,您将设置您的环境。现在,我们准备编写代币的代码

quiz

分享您在本课程中创建的 Move on Sui 项目的屏幕截图。

3.3_合约 编写并部署代币合约 - 课程学习时长因人而异

使用 Move 编写您的代币智能合约

欢迎回来!这样您就完成了环境设置。你做得真棒!好吧,做好准备,因为在本课中,您将学习如何编写token。你兴奋吗?让我们开始吧!

编写代码

导航到 sources/pepe.move 。让我们逐行查看您将要编写的代码:

首先, metaschool 是包名称。它应该与我们使用命令 sui move new metaschool 初始化 Sui 工作区的文件夹名称相同。因此,如果您使用了任何其他名称,请务必在此处替换它。此外, pepe 是模块名称。因此,如果您将其命名为 pepe 之外的其他名称,请务必在此处更新它。

#![allow(unused)]
fn main() {
module metaschool::pepe {
}
  • 此行定义了一个名为 metaschool::pepe 的模块,它将包含 Move 令牌实现。模块是一种组织代码并将相关功能分组在一起的方法。
#![allow(unused)]
fn main() {
    use std::option;
    use sui::coin;
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};
}
  • 导入包含要在代码中使用的预构建函数和类型的模块( std::optionsui::coinsui::transfersui::tx_context ) 。
#![allow(unused)]
fn main() {
    struct PEPE has drop {}
}
  • 使用 has drop 属性定义一个名为 PEPE 的新结构。
#![allow(unused)]
fn main() {
    fun init(witness: PEPE, ctx: &mut TxContext) {
        let (treasury, metadata) = coin::create_currency(witness, 9, b"PE", b"PEPE", b"", option::some(url::new_unsafe_from_bytes(b"https://silver-blushing-woodpecker-143.mypinata.cloud/ipfs/Qmed2qynTAszs9SiZZpf58QeXcNcYgPnu6XzkD4oeLacU4")), ctx);
        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury, tx_context::sender(ctx))
    }
}
  • 定义一个名为 init 的函数,它是模块初始值设定项。
  • init 函数采用以下内容:
    • Move 使用见证设计模式,规定传递的类型只能启动一次。需要立即消耗或丢弃见证资源,以防止创建给定对象的多个实例。
    • 一次性见证人(OTW)是 Move on Sui 中的一种特殊类型,始终只有一个实例。它用于任何我们希望某些操作发生一次的地方。例如,一次性创造一枚硬币。您可以在这里探索有关 OTW 的更多信息:https://docs.sui.io/concepts/sui-move-concepts/one-time-witness
    • 所以我们传递了具有放置功能的 PEPE 结构。
    • 我们还传递了对 TxContext 的可变引用。
  • 在函数内部,调用 coin::create_currency 函数来创建新的 PEPE 货币:
    • PEPE 见证人作为第一个参数传递。
    • 所需的小数位数作为第二个参数。例如,对于 9 作为十进制值;假设我们要铸造100个代币,那么我们需要传递100*10^9。
    • 我们的代币符号是 PE
    • 令牌名称是 PEPE ,这是第四个参数。
    • 接下来,添加一个空图标 URL,以及一个可选的元数据。
  • coin::create_currency 函数返回一个包含 treasurymetadata 的元组,它们分别是 TreasuryCapCoinMetadata 对象。
    • TreasuryCap 就像一个管理器,控制对 mintburn 方法的访问。
  • 调用 transfer::public_freeze_object 冻结元数据,这样硬币元数据一旦发布就没有人可以更改
  • 最后,使用 transfer::public_transfertreasury 传输给交易的发送者。
#![allow(unused)]
fn main() {
    public entry fun mint(
        treasury: &mut coin::TreasuryCap<PEPE>, amount: u64, recipient: address, ctx: &mut TxContext
    ) {
        coin::mint_and_transfer(treasury, amount, recipient, ctx)
    }
}
}
  • 定义一个名为 mint 的公共入口函数。
  • mint 函数采用对 coin::TreasuryCap<PEPE> 的可变引用,类型为 u64amount ,类型为 recipient address ,以及对 TxContext 作为参数的可变引用。
  • 在函数内部,调用 coin::mint_and_transfer 函数来铸造新代币并将其转移到指定的 recipient

完整代码

完整的代码如下,您可以将其粘贴到在 Move Studio 中创建的文件中。

module metaschool::pepe{
    use std::option;
    use sui::coin;
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};
    use sui::url::{Self, Url};

    // Name matches the module name but in UPPERCASE
    public struct PEPE has drop {}

    // Module initializer is called once on module publish.
    // A treasury cap is sent to the publisher, who then controls minting and burning.
    fun init(witness: PEPE, ctx: &mut TxContext) {
        let (treasury, metadata) = coin::create_currency(witness, 9, b"PE", b"PEPE", b"", option::some(url::new_unsafe_from_bytes(b"https://silver-blushing-woodpecker-143.mypinata.cloud/ipfs/Qmed2qynTAszs9SiZZpf58QeXcNcYgPnu6XzkD4oeLacU4")), ctx);
        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury, tx_context::sender(ctx))
    }

    public entry fun mint(
        treasury: &mut coin::TreasuryCap<PEPE>, amount: u64, recipient: address, ctx: &mut TxContext
    ) {
        coin::mint_and_transfer(treasury, amount, recipient, ctx)
    }
}

小结

唷!在理解和创建 Move on Sui 上的第一个代币方面做得非常出色。您可以在此处探索在 Sui 中创建硬币:https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/sui-framework/coin.md

在下一课中,您最终将在 Sui 区块链上部署代币。

quiz

与我们分享代码的屏幕截图

在 Sui 上部署您的第一个代币合约

欢迎回来!这样您就完成了可替代代币的编写。你做得很好!在本课程中,您将深入了解将代币部署到 Sui 区块链。你兴奋吗?让我们开始吧!

代码

这是我们在上一课中编写的合约的完整代码,以防您错过:

#![allow(unused)]
fn main() {
module metaschool::pepe {
    use std::option;
    use sui::coin;
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    // Name matches the module name, but in UPPERCASE
    public struct PEPE has drop {}

    // Module initializer is called once on module publish.
    // A treasury cap is sent to the publisher, who then controls minting and burning.
    fun init(witness: PEPE, ctx: &mut TxContext) {
        let (treasury, metadata) = coin::create_currency(witness, 9, b"PE", b"PEPE", b"", option::none(), ctx);
        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury, tx_context::sender(ctx))
    }

    public entry fun mint(
        treasury: &mut coin::TreasuryCap<PEPE>, amount: u64, recipient: address, ctx: &mut TxContext
    ) {
        coin::mint_and_transfer(treasury, amount, recipient, ctx)
    }
}
}

部署您的合约

步骤1

首先,我们必须进入 metaschool 目录,因为它包含我们的合约。

#![allow(unused)]
fn main() {
cd metaschool
}

第2步

现在,让我们使用以下命令构建移动文件。这将创建一个新的 build 文件夹和一个 Move.toml 文件。

#![allow(unused)]
fn main() {
sui move build
}

步骤3

现在,您需要在 Sui 中设置开发环境,以便使用以下命令部署在 Sui 测试网上:

#![allow(unused)]
fn main() {
sui client new-env --alias devnet --rpc https://fullnode.devnet.sui.io:443
sui client switch --env devnet
}

步骤4

现在是公开您的合同的时候了。为此,请右键单击 pepe.move 的绝对路径,然后选择 VS 代码上的 copy path 选项。获得路径后,您现在可以使用以下命令进行发布,方法是将 [YOUR_PATH] 替换为您刚刚复制的路径:

注意:如果您使用 VS code 以外的 IDE,请尝试查找 pepe.move 文件的确切完整路径。

#![allow(unused)]
fn main() {
sui client publish --gas-budget 100000000 [YOUR_PATH]
}

步骤5

现在您已经发布了合约,您将获得一个很长的输出。滚动并复制交易摘要。您可以使用此交易摘要通过以下链接使用 Sui Explorer 搜索您的交易:

https://suiexplorer.com/?network=devnet

sui-explorer-gif.gif

小结

在本课程中,您在 Sui 区块链上部署了代币。祝您未来部署顺利!

接下来,我们就来总结一下本次课程吧。

quiz

与我们分享成功铸造交易的屏幕截图

3.4_总结 - 本章学习内容回顾

总结

让我们结束教程并回顾一下我们在本课程中学到的内容。

Sui 区块链和 Sui 修订版

您修改了对 Sui Blockchain 和 Move on Sui 的理解。您了解这些有什么好处以及我们如何从中受益。

编写和部署智能合约

您已经获得了使用 Move on Sui 在 Sui 上编写和部署自己的简单智能合约的知识。

智能合约开发的坚实基础

通过了解 Sui 和 Move on Sui 的基础知识,您将为继续探索和开发 Sui 区块链上的智能合约奠定坚实的基础。

⚒️一个小而重要的请求

这是一个100%的开源项目,就像我们平台上的其他项目一样。您可以在[这里](https://github.com/0xmetaschool/Learning-Projects/tree/main/Learn Everything About Sui%2C its Concepts and Protocols)找到教程标记文件。如果您在课程中发现任何问题,请麻烦解决一下。我们,在Metaschool,热爱我们社区的贡献,也感谢我们Discord 和GitHub上的贡献者。

当您做出贡献时:不要忘记在⭐️上标记我们的存储库。我们将非常感激!❤️我们是一个完全免费的平台,我们的目标是保持不变,所以请考虑在 XLinkedIn 上关注我们。

🎊 恭喜

您已完成本教程,现在已具备基础知识,可以开始探索 Sui 区块链上令人兴奋的区块链和智能合约开发世界。

✌🏻🔮

04_dapp

  • 本章课程共6小节,学习时长因人而异

Dapp

4.1_开始 学习dapp相关知识 - 课程学习时长因人而异

我们今天学习什么?

大家好☀️,今天我们非常高兴为 Metaschool 的开发者带来有关 Sui 区块链的详细系列的第四门课程。让我们从第四门课程开始,祝贺您完成了 Sui 的前三门课程。呜呼!

那么,我们今天要建造什么?我们将使用 move 构建一个代币交换 Defi 应用程序,这将帮助用户在 dex 中实现交换功能。该 dApp 将帮助您将以太坊代币与 USDC 代币进行交换。该项目将帮助您学习使用 Move on Sui 构建端到端 defi 应用程序。

让我们看看您的 Dex dApp 会是什么样子。

frontend-interact-1.png

课程大纲

我们将:

  1. 设置 Sui 钱包和开发环境(如果尚未设置)
  2. 设置移动项目
  3. 在 Move on Sui 中编写不同的智能合约
  4. 从头到尾构建 Swap dApp
  5. 为 Move 智能合约编写测试
  6. 在 Sui 区块链上部署 dApp
  7. 将 dApp 与前端连接
  8. 运行前端并与前端交互

谁应该参加这门课程?

本课程面向希望在 Sui 区块链上进行构建并已完成我们的前三门 Sui 课程的 web3 开发人员和构建者。您应该具备基本的编程知识,因为它将帮助您快速开始本课程。

您将从本课程中获得什么?

作为开发人员,完成本课程后,您将获得以下成果:

  1. 在 Sui 上构建和部署代币智能合约
  2. 通过 SuixMetaschool Discord House 探索 Sui 生态系统中的机会
  3. 赚取高达 1000 XP
  4. 完成课程后领取您的 NFT

完成证明

如果您和我一起完成本课程,您将获得 XP 和特殊的 NFT,这将为您在 Metaschool 平台上解锁更多机会。这是 NFT 的样子。

Untitled

现在,在我们继续之前,让我们先制定一些内部规则。

  1. 请正确完成你的快速作业。
  2. 加入我们的不和谐服务器并在那里询问所有相关问题。
  3. 保持快乐和积极!

好了各位,不用再等了。下一课见!

DeFi 和 Dex 简介

我们为您决定参加这门课程感到非常自豪。您现在已经完成了 Sui 学习路径中的三门课程,这将是您的第四门课程。 🎊 您现在已经具备了 Sui 区块链的所有基础知识。在本课中,我们将探讨什么是 DeFi,Sui 如何帮助构建 DeFi 应用程序,以及我们的 DeFi 应用程序将具有哪些功能。

介绍 DeFi 应用

所以,想象一下,如果你的钱不需要传统银行或金融巨头。这就是 DeFi 所做的。它可以让像你我这样的普通人直接相互处理财务事宜,而不是依赖通常的嫌疑人。没有中间人,只有点对点的力量。

DeFi 是如何运作的?

以下是有关 DeFi 运作方式的独家新闻:

  1. DeFi 基于区块链技术运行,通过数字区块确保交易安全且不可变。
  2. DeFi 绕过银行等传统中介机构,通过促进直接点对点交易提供更低的费用和更高的利率。
  3. 用户友好的应用程序促进借贷和交易等 DeFi 活动,使所有人都能进行金融交互。
  4. 利用区块链,DeFi 超越地理障碍,实现全球范围内的无缝金融互动。

DeFi 的目标

  • 可访问性:任何有互联网连接的人都可以与 DeFi 协议进行交互。
  • 安全性和透明度:一切都在区块链上,因此超级安全,并且交易对所有人开放,而无需透露您是谁。
  • 自治:DeFi 不需要大银行。它就像金融领域的反叛领袖,无需中央控制即可完成自己的任务。

请记住,虽然 DeFi 并不能让你完全匿名,但它的目的是让金融游戏变得更好。

Sui 上的 DeFi

让我们讨论一下 Sui 在 DeFi 应用中扮演什么角色,以及为什么它们是 DeFi 创建的可靠平台。

  • 利用新颖的共识机制实现高效稳定的交易,满足简单和复杂的 DeFi 操作。
  • 实现并行执行,同时执行交易,消除延迟并确保 DeFi 交互顺畅。
  • 引入存储基金代币经济学,彻底改变区块链存储成本并保持公平的费用,从而为 DeFi 创建一个组织良好的数字生态系统

什么是去中心化交易所?

去中心化交易所(通常缩写为 DEX)是一种无需中央机构或中介机构即可运行的加密货币交易所。 DEX 不依赖中心化平台来促进交易并持有用户资金,而是使用智能合约或其他去中心化协议来直接在用户之间进行点对点交易。

我们都会在某个时候使用 Metamask,并且您会持有一些代币。现在,如果您想将您的代币交换为另一个代币,您将需要一个可以做到这一点的交易所。有两种方法可以实现:中心化交易和去中心化交易。

传统或中心化交易所就像银行:它持有您的代币并管理交易。但正如银行可能成为窃贼的目标一样,中心化交易所也可能容易受到黑客攻击或滥用。但通过去中心化交易,您可以在没有任何中心化实体的情况下交换您的代币,而是通过智能合约管理的规则集进行交换。

去中心化交易所的优势

  1. 安全性:用户保留对其私钥和资金的控制权,从而降低中心化交易所遭到黑客攻击或盗窃的风险。
  2. 隐私:用户可以直接相互交易,无需中介。
  3. 审查阻力:去中心化交易所通常更能抵抗审查,因为它们不依赖中心化服务器或权威机构。
  4. 互操作性:一些去中心化交易所可以促进不同区块链平台或资产之间的交易。

小结

简而言之,Move 和 move 打造了终极 DeFi 总部。 Move 的资产友好型设计满足了 Sui 的可扩展性和低成本存储的要求,为 DeFi 领域的实时、低延迟交易提供了最佳选择。接下来,我们将开始构建和设置我们的开发环境

quiz

用你自己的话解释一下 DeFi 和 Dex

4.2_设置环境 - 课程学习时长因人而异

设置环境

欢迎回来!在本课程中,您将学习如何设置环境和 Move 项目来运行代码。

注意:我们假设您已经在 Sui 课程路径的第一门课程中在系统中安装了 Sui 和 Rust。如果没有,您可以访问此链接并安装它。

检查您的版本

打开您最喜欢的终端。让我们检查一下您的 Sui 安装版本。

#![allow(unused)]
fn main() {
sui --version
}

注意:如果您在安装 Sui 时遇到错误,请确保遵循部署您的第一个 Sui 合约课程中的所有步骤。

新建MOVE项目

打开您最喜欢的终端,因为我们将运行一些命令来实现我们的目标。

第一步是初始化工作空间环境。这将包含运行任何 Move 文件的基本文件。您可以使用以下命令创建工作区;我将我的命名为 dex

#![allow(unused)]
fn main() {
sui move new dex
}

该命令将生成一个名为 dex 的文件夹,其中包含一个文件 Move.toml 和一个文件夹 sources

  • P.S.:我使用 Visual Studio IDE,因为它可以更好地可视化我的工作区结构。

是时候创建用于编码的文件了,因此请按照以下步骤操作:

  • 现在在 sources 文件夹中创建 3 个新文件,名为:

    • eth.move
    • usdc.move
    • dex.move

    我们将在这些文件中编写代码。

  • 接下来,在 sources 中创建一个 test 文件夹,并在其中创建一个名为 test_dex.move 的文件。

您的项目结构如下所示:

Screen Shot 2024-01-29 at 4.26.19 PM.png

检查你的钱包

您可以使用以下命令查看当前的活动地址:

#![allow(unused)]
fn main() {
sui client active-address
}

如果您没有有效地址,请按照以下步骤操作:

  1. 运行以下命令来创建您的 Sui 帐户:

    #![allow(unused)]
    fn main() {
    sui client new-address ed25519
    }

    它将生成如下输出:

    deploy-5.png

  • 重要提示:保存恢复短语,使用它来导入您的钱包非常重要。
  1. 将以下命令中的 [YOUR_ADDRESS] 替换为运行最后一个命令后收到的地址并运行它。

    #![allow(unused)]
    fn main() {
    sui client switch --address [YOUR_ADDRESS]
    }
  2. 前往 Sui Testnet faucet discord 频道并粘贴“!faucet [YOUR_ADDRESS]”以接收 10 个 Sui 代币。

小结

在本课程中,我们学习了如何设置项目以在 Move on Sui 中编写代码。按照提供的步骤,我们现在准备开始为 Move 项目编写代码。快乐编码!

quiz

分享一下你的 Sui 客户端地址截图。

4.3_搭建代币交换DAPP - 课程学习时长因人而异

编写交易池智能合约

欢迎回来!您已准备好开始构建 dApp。那么为什么还要等呢,让我们直接进入编码。

交易池智能合约

导航到 sources/eth.move

第一个任务是为我们的水龙头创建一种货币,我将其命名为 ETH ,您可以随意命名。

#![allow(unused)]
fn main() {
use std::option;
use sui::url;
use sui::transfer;
use sui::coin;
use sui::tx_context::{Self, TxContext};

public struct ETH has drop {}
}
  • 该代码首先导入必要的库( std::optionsui::urlsui::transfersui::coinsui::tx_context::{Self, TxContext} )。
  • 接下来,使用放置功能定义名为 ETH 的结构。该结构将用作某些操作的见证。
#![allow(unused)]
fn main() {
fun init(witness: ETH, ctx: &mut TxContext) {
  let (treasury_cap, metadata) = coin::create_currency<ETH>(
    witness,
    9,
    b"ETH",
    b"ETH Coin",
    b"Ethereum Native Coin",
    option::some(url::new_unsafe_from_bytes(b"https://s2.coinmarketcap.com/static/img/coins/64x64/1027.png")),
    ctx
  );

  transfer::public_transfer(treasury_cap, tx_context::sender(ctx));

  transfer::public_share_object(metadata);
}
}
  • 定义了模块初始化函数 ( init ),它采用 ETH 类型的见证和对 TxContext 的可变引用。
  • init 函数内部:
    • coin::create_currency 被调用以创建具有特定参数的新货币(ETH Coin),例如见证人、小数位、符号、名称、图标 URL 和可选元数据。
    • 然后,使用从 create_currency 获取的财务能力和元数据将财务能力传输给部署者 ( transfer::public_transfer ) 并公开共享元数据 ( transfer::public_share_object )。
#![allow(unused)]
fn main() {
#[test_only]
public fun init_for_testing(ctx: &mut TxContext) {
	  init(ETH {}, ctx);
}
}
  • 最后,定义测试函数。在本例中,有一个仅测试的初始化函数 ( init_for_testing ),用 #[test_only] 注释。此函数使用虚拟 ETH 见证函数调用主初始化函数 ( init ) 以进行测试。

完整代码

eth.move 的完整代码是:

module dex::eth {
  use std::option;

  use sui::url;
  use sui::transfer;
  use sui::coin;
  use sui::tx_context::{Self, TxContext};

  public struct ETH has drop {}

  fun init(witness: ETH, ctx: &mut TxContext) {
      let (treasury_cap, metadata) = coin::create_currency<ETH>(
            witness, 
            9, 
            b"ETH",
            b"ETH Coin", 
            b"Ethereum Native Coin", 
            option::some(url::new_unsafe_from_bytes(b"https://s2.coinmarketcap.com/static/img/coins/64x64/1027.png")), 
            ctx
        );

      transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
      transfer::public_share_object(metadata);
  }

  #[test_only]
  public fun init_for_testing(ctx: &mut TxContext) {
    init(ETH {}, ctx);
  }
}

小结

在本课中,我们编写了用于创建新货币的代码。在下一课中,我们将为 USDC 编写合约。我们走吧!

quiz

解释一下transfer::public_transfer 的用途。

编写 Dex 智能合约

欢迎回来!到目前为止你做得很好!我们已经完成了货币的创建。因此,让我们开始写一些深刻而实际的内容吧!

Dex 智能合约

导航到 sources/dex.move

该合约实现了去中心化交易(DEX)系统,允许用户在 Sui 区块链上交易以太坊(ETH)和 USDC(稳定币)。

DEX 使用 DeepBook 协议,这是一个用于管理买卖订单的中央限价订单簿(CLOB)系统。现在将 DeepBook 想象为一个中间实体,为您管理买卖订单。

例如,如果买家提交以 1,800 美元买入 1 ETH 的订单,并且有相应的以 1,800 美元或更低价格卖出 1 ETH 的订单,则 DeepBook 协议将匹配这些订单并执行交易。买方将收到 1 ETH,卖方将收到指定金额的 USDC(在本例中为 1,800 美元)。

现在让我们深入研究代码!

#![allow(unused)]
fn main() {
module dex::dex {

  // Import necessary modules and types
  use std::option;
  use std::type_name::{get, TypeName};
  use sui::transfer;
  use sui::sui::Sui;
  use sui::clock::{Clock};
  use sui::balance::{Self, Supply};
  use sui::object::{Self, UID};
  use sui::table::{Self, Table};
  use sui::dynamic_field as df;
  use sui::tx_context::{Self, TxContext};
  use sui::coin::{Self, TreasuryCap, Coin};
  use deepbook::clob_v2::{Self as clob, Pool};
  use deepbook::custodian_v2::AccountCap;
  use dex::eth::ETH;
  use dex::usdc::USDC;
}
  • 该代码首先定义一个名为 dex::dex 的 Move 模块。
  • 导入各种模块和类型,包括来自 Sui 和 DeepBook 库的模块和类型。
#![allow(unused)]
fn main() {
 // Constants
  const CLIENT_ID: u64 = 122227;
  const MAX_U64: u64 = 18446744073709551615;
  const NO_RESTRICTION: u8 = 0;
  const FLOAT_SCALING: u64 = 1_000_000_000;
  const EAlreadyMintedThisEpoch: u64 = 0;
}

接下来,我们有常数,让我为你分解一下,

  • CLIENT_ID:u64 = 122227;这是用于在 DeepBook 协议中下订单的初始客户端 ID。合约下的每个订单都会有一个唯一的客户 ID。 MAX_U64:u64 = 18446744073709551615;这是无符号 64 位整数的最大值。在下达无限期限价单时,它用作“无限”时间戳的占位符。
  • 无限制:u8 = 0;该常数表示下限价单时没有任何限制。它用作没有任何特定限制的订单参数的占位符。
  • 浮动缩放:u64 = 1_000_000_000; // 1e9 该常量表示浮点运算的缩放因子。它用于在合约中的十进制值和整数表示之间进行转换。
  • EAlreadyMintedThisEpoch: u64 = 0;这是 mint_coin 函数中使用的错误代码。用于表示用户在当前纪元已经铸造了一枚币。
#![allow(unused)]
fn main() {
  //One-time witness to create the DEX coin
  struct DEX has drop {}
}

现在我们声明一个具有 drop 能力的 struct DEX,这种模式称为一次性见证模式(OTW)。 Move编程语言中的一次性见证(OTW)是一种特殊的结构体,用于控制某些资源的初始化或创建,确保此类操作只能执行一次。

#![allow(unused)]
fn main() {
 public struct Data<phantom CoinType> has store {
    cap: TreasuryCap<CoinType>,
    faucet_lock: Table<address, u64>,
}
}
  • 定义了一个通用的 Data 结构,它存储与特定硬币类型相关的数据。
  • 它包含一个 TreasuryCap 和一个表( faucet_lock )来存储用户地址及其最后的铸造纪元。
#![allow(unused)]
fn main() {
public struct Storage has key {
    id: UID,
    dex_supply: Supply<DEX>,
    swaps: Table<address, u64>,
    account_cap: AccountCap,
    client_id: u64,
}
}
  • Storage 结构体是用关键能力和 UID 定义的。
  • 它有以下字段:
    • id :表示 UID(唯一标识符),它是该结构体实例的唯一标识符。
    • dex_supply :代表 DEX 代币的供应量,类型为 Supply<DEX>
    • swaps :表示一个名为 swaps 的表,其中包含地址键和 u64 值。该表用于跟踪每个地址执行的交换次数。
    • account_cap :代表 AccountCap ,与合约上下文中的账户能力相关。
    • client_id :表示名为 client_idu64 字段,用于跟踪合约中的客户端 ID。
#![allow(unused)]
fn main() {
#[allow(unused_function)]
  fun init(witness: DEX, ctx: &mut TxContext) {
    let (treasury_cap, metadata) = coin::create_currency<DEX>(
      witness,
      9,
      b"DEX",
      b"DEX Coin",
      b"Coin of Sui DEX",
      option::none(),
      ctx
    );

    transfer::public_freeze_object(metadata);

    transfer::share_object(Storage {
      id: object::new(ctx),
      dex_supply: coin::treasury_into_supply(treasury_cap),
      swaps: table::new(ctx),
      account_cap: clob::create_account(ctx),
      client_id: CLIENT_ID,
    });
  }
}
  • 该代码定义了一个名为 init 的函数,该函数具有 #[allow(unused_function)] 属性,表明它有意允许未使用的函数。
  • init 函数采用两个参数: DEX 类型的 witness 和对名为 ctxTxContext 的可变引用> .
  • 函数内部:
  • 它使用 coin::create_currency 函数创建 DEX 货币。该函数负责使用特定参数(例如符号、名称和元数据)初始化 DEX 代币。
  • coin::create_currency 的结果是一个包含 treasury_cap (DEX 的 TreasuryCap)和 metadata 的元组。
  • 它与 Sui 网络共享 metadata 并使用 transfer::public_freeze_object 使其不可变。
  • 它与 Sui Network 共享 Storage 对象。 Treasury Cap 转化为供应量以铸造 DEX 代币。
    • 它创建一个具有以下字段的新 Storage 对象:
      • id :使用 object::new(ctx) 创建,表示新的唯一标识符。
      • dex_supply :使用 coin::treasury_into_supplytreasury_cap 转换而来。
      • swaps :使用 table::new(ctx) 创建新表。
      • account_cap :使用 clob::create_account(ctx) 创建。
      • client_id :用常量 CLIENT_ID 初始化。
    • Storage 对象使用 transfer::share_object 与 Sui Network 共享。

注意:这里,DEX 货币是我们 DEX 的原生代币,您在 DEX 上每成功兑换 2 次即可获得该代币。在下一门课程中,我对这个令牌有一个有趣的想法。敬请关注!

#![allow(unused)]
fn main() {
public fun user_last_mint_epoch<CoinType>(self: &Storage, user: address): u64 {
    let data = df::borrow<TypeName, Data<CoinType>>(&self.id, get<CoinType>());

    if (table::contains(&data.faucet_lock, user)) return *table::borrow(&data.faucet_lock, user);

    0
  }
}
  • 该代码在 Storage 结构的上下文中定义了一个名为 user_last_mint_epoch 的公共函数。
  • 它是一个视图函数,用于检索用户的最后铸造纪元。它从存储中加载硬币数据并检查用户是否使用了水龙头。
  • 该功能是通用的 CoinType ,表明它可以使用不同类型的硬币。
  • 它需要两个参数:
    • self :对 Storage 结构体的引用,指示该函数对该结构体的实例进行操作。
    • useraddress 表示函数检索其最后铸造纪元的用户。
  • 函数内部:
  • 它使用 df::borrow 使用 self.idStorage 对象的动态字段访问与指定 CoinType 关联的数据。
  • 它通过使用 table::contains 验证其地址是否存在于关联 Datafaucet_lock 表中来检查 user 是否曾经使用过水龙头。 。
  • 如果用户使用了水龙头,该函数将使用 table::borrow 返回为该用户存储的最后一个纪元。
  • 如果用户没有使用过水龙头,函数返回 0 作为默认值。

注意:这里, df 是动态字段。 Sui 中的动态字段是一种在对象中存储数据的方法,不受结构声明中预定义字段的限制。它们允许您在运行时动态添加和删除字段,与常规对象字段相比具有更大的灵活性。

在我们的代码中,通过使用动态字段,Storage 对象可以存储与不同货币类型(ETH 和 USDC)相关的数据,而无需在 Storage 结构中为每种类型定义单独的字段。这允许更大的灵活性和可扩展性,因为可以通过简单地创建具有相应类型名称的新动态字段来添加新的硬币类型。

#![allow(unused)]
fn main() {
  public fun user_swap_count(self: &Storage, user: address): u64 {
    if (table::contains(&self.swaps, user)) return *table::borrow(&self.swaps, user);

    0
  }
}
  • 该代码在 Storage 结构的上下文中定义了一个名为 user_swap_count 的公共函数。
  • 它是一个检索用户交换计数的视图函数。它检查用户是否曾经交换过并返回计数。
  • 该函数有两个参数:
    • self :对 Storage 结构体的引用,指示该函数对该结构体的实例进行操作。
    • useraddress 表示函数检索其交换计数的用户。
  • 函数内部:
  • 它通过使用 table::contains 验证其地址是否存在于 Storage 对象的 swaps 表中来检查指定的 user 是否曾经执行过交换。
  • 如果用户执行了交换,该函数将使用 table::borrow 返回为该用户存储的交换计数。
  • 如果用户未执行任何交换,该函数将返回 0 作为默认值。
#![allow(unused)]
fn main() {
  public fun entry_place_market_order(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>,
    account_cap: &AccountCap,
    quantity: u64,
    is_bid: bool,
    base_coin: Coin<ETH>,
    quote_coin: Coin<USDC>,
    c: &Clock,
    ctx: &mut TxContext,
  ) {
    let (eth, usdc, coin_dex) = place_market_order(self, pool, account_cap, quantity, is_bid, base_coin, quote_coin, c, ctx);
    let sender = tx_context::sender(ctx);
    transfer_coin(eth, sender);
    transfer_coin(usdc, sender);
    transfer_coin(coin_dex, sender);
  }
}

  • 该代码在 Storage 结构的上下文中定义了一个名为 entry_place_market_order 的公共函数。
  • 可以调用该函数在DEX(去中心化交易所)下市价单。
  • 它需要几个参数:
    • self :对 Storage 结构的可变引用,指示该函数可以修改实例。
    • pool :对 ETH 和 USDC 的 Pool 的可变引用,在其中下达市价订单。
    • account_cap :对与用户帐户关联的 AccountCap 的引用。
    • quantityu64 代表市价单的数量。
    • is_bidbool 指示订单是否为出价(买入)。
    • base_coin :代表基础货币(ETH)的 Coin 对象。
    • quote_coin :代表报价货币(USDC)的 Coin 对象。
    • c :对 Clock 对象的引用,可能表示区块链上的时间戳。
    • ctx :对 TxContext 的可变引用,提供事务上下文信息。

函数内部:

  • 它调用另一个名为 place_market_order 的函数来执行市价订单,获得三个结果代币: ethusdccoin_dex
  • 它使用 tx_context::sender(ctx) 检索发件人的地址。
  • 它调用 transfer_coin 函数三次,将生成的硬币( ethusdccoin_dex )转移到发送者的地址。
#![allow(unused)]
fn main() {
public fun place_market_order(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>,
    account_cap: &AccountCap,
    quantity: u64,
    is_bid: bool,
    base_coin: Coin<ETH>,
    quote_coin: Coin<USDC>,
    c: &Clock,
    ctx: &mut TxContext,    
  ): (Coin<ETH>, Coin<USDC>, Coin<DEX>) {
  let sender = tx_context::sender(ctx);  

  let client_order_id = 0;
  let dex_coin = coin::zero(ctx);

  if (table::contains(&self.swaps, sender)) {
    let total_swaps = table::borrow_mut(&mut self.swaps, sender);
    let new_total_swap = *total_swaps + 1;
    *total_swaps = new_total_swap;
    client_order_id = new_total_swap;

    if ((new_total_swap % 2) == 0) {
      coin::join(&mut dex_coin, coin::from_balance(balance::increase_supply(&mut self.dex_supply, FLOAT_SCALING), ctx));
    };
  } else {
    table::add(&mut self.swaps, sender, 1);
  };
  
  let (eth_coin, usdc_coin) = clob::place_market_order<ETH, USDC>(
    pool, 
    account_cap, 
    client_order_id, 
    quantity,
    is_bid,
    base_coin,
    quote_coin,
    c,
    ctx
    );

    (eth_coin, usdc_coin, dex_coin)
  }
}
  • 该代码在 Storage 结构的上下文中定义了一个名为 place_market_order 的公共函数。
  • 该功能负责在 DEX(去中心化交易所)下市价单。
  • 它需要几个参数:
    • self :对 Storage 结构的可变引用,指示该函数可以修改实例。
    • pool :对 ETH 和 USDC 的 Pool 的可变引用,在其中下达市价订单。
    • account_cap :对与用户帐户关联的 AccountCap 的引用。
    • quantityu64 代表市价单的数量。
    • is_bidbool 指示订单是否为出价(买入)。
    • base_coin :代表基础货币(ETH)的 Coin 对象。
    • quote_coin :代表报价货币(USDC)的 Coin 对象。
    • c :对 Clock 对象的引用,可能表示区块链上的时间戳。
    • ctx :对 TxContext 的可变引用,提供事务上下文信息。

函数内部:

  • 它使用 tx_context::sender(ctx) 检索发件人的地址。
  • 它将 client_order_id 初始化为 0 并使用 coin::zero(ctx) 创建一个 dex_coin 作为零值硬币。
  • 它通过使用 table::contains 验证发送者的地址是否存在于 Storage 对象的 swaps 表中来检查发送者是否曾经执行过交换。
    • 如果发送方之前执行过交换,则会增加总交换计数器并更新 client_order_id
    • 如果总交换次数为偶数,则为用户铸造 1 个 DEX 代币。这涉及增加 DEX 供应,将其转化为代币,并将其与零价值的 DEX 代币结合起来。
    • 如果发送者之前没有执行过任何交换,它会通过向 swaps 表添加一个条目来注册帐户。
  • 它调用 clob::place_market_order 函数将市价单放入 DEX 池中,获得 eth_coinusdc_coin
  • 它返回一个包含生成的硬币的元组: (eth_coin, usdc_coin, dex_coin)
#![allow(unused)]
fn main() {
  public fun create_pool(fee: Coin<Sui>, ctx: &mut TxContext) {
    clob::create_pool<ETH, USDC>(1 * FLOAT_SCALING, 1, fee, ctx);
  }
}
  • 该代码在合约上下文中定义了一个名为 create_pool 的公共函数。
  • 该函数负责使用 clob::create_pool 函数在Deep Book中创建一个池。
  • 它需要两个参数:
    • feeCoin<Sui> 代表创建池所需支付的费用。
    • ctx :对 TxContext 的可变引用,提供事务上下文信息。

函数内部:

  • 它调用 clob::create_pool 函数在Deep Book中创建ETH-USDC池。
  • 创建的池将与 Sui Network 共享。
  • 池的刻度大小设置为 1 USDC - 1e9
  • 没有为池指定最小批量。
  • 创建池的费用指定为 fee 参数,即 Coin<Sui>
  • 1 * FLOAT_SCALING 参数可能代表池中 ETH 和 USDC 的初始流动性或数量。
  • 该功能有助于创建池,允许用户在 Deep Book 平台上交易资产。
#![allow(unused)]
fn main() {
  public fun fill_pool(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>,
    c: &Clock,
    ctx: &mut TxContext
  ) {
    create_ask_orders(self, pool, c, ctx);
    create_bid_orders(self, pool, c, ctx);
  }
}
  • 该代码在合约上下文中定义了一个名为 fill_pool 的公共函数。
  • 仅当指定池中不存在现有订单时才调用该函数。
  • 它需要几个参数:
    • self :对 Storage 结构的可变引用,指示该函数可以修改实例。
    • pool :对 ETH 和 USDC 的 Pool 的可变引用,代表连续限价订单簿 (CLOB) 池。
    • c :对 Clock 对象的引用,可能表示区块链上的时间戳。
    • ctx :对 TxContext 的可变引用,提供事务上下文信息。

函数内部:

  • 它包含一条注释,指示仅当池中没有现有订单时才应调用该函数。
  • 它调用两个函数: create_ask_orderscreate_bid_orders
  • create_ask_orders 使用参数 selfpoolcctx 进行调用。
    • 该功能负责在 DeepBook 中存入资金、下限价卖单以及允许其他用户购买代币。
  • create_bid_orders 使用相同的参数调用。
    • 该功能负责在 DeepBook 中存入资金、下限价买单以及允许其他用户出售代币。

此功能的目的是用初始订单填充池,提供流动性并为其他用户提供交易。

#![allow(unused)]
fn main() {
  public fun create_state(
    self: &mut Storage, 
    eth_cap: TreasuryCap<ETH>, 
    usdc_cap: TreasuryCap<USDC>, 
    ctx: &mut TxContext
  ) {

    df::add(&mut self.id, get<ETH>(), Data { cap: eth_cap, faucet_lock: table::new(ctx) });
    df::add(&mut self.id, get<USDC>(), Data { cap: usdc_cap, faucet_lock: table::new(ctx) });
  }
}
  • 该代码在合约上下文中定义了一个名为 create_state 的公共函数。
  • 该函数旨在在部署期间调用,并且只应调用一次,因为它会初始化 ETH 和 USDC 与 TreasuryCaps 的合约状态。
  • 它需要三个参数:
    • self :对 Storage 结构的可变引用,指示该函数可以修改实例。
    • eth_capTreasuryCap<ETH> 代表 ETH 货币的 TreasuryCap。
    • usdc_capTreasuryCap<USDC> 代表 USDC 货币的 TreasuryCap。
    • ctx :对 TxContext 的可变引用,提供事务上下文信息。

函数内部:

  • 它包含一条注释,指示该函数只能在部署期间调用。
  • 它使用 df::add 函数将大写字母保存在具有动态对象字段的 Storage 对象内。
  • df::add 函数向 Storage 对象添加一个动态字段,该字段具有基于 get<ETH>()get<USDC>() 类型的唯一标识符。
  • 对于以太币:
  • 它添加了一个带有键 get<ETH>() 的动态字段和一个包含 eth_capData 结构以及一个新的空水龙头锁表。
  • 对于 USDC:
  • 它添加了一个带有键 get<USDC>() 的动态字段和一个包含 usdc_capData 结构以及一个新的空水龙头锁表。

该函数的目的是在部署期间初始化 ETH 和 USDC 与 TreasuryCaps 的合约状态。

#![allow(unused)]
fn main() {
  public fun mint_coin<CoinType>(self: &mut Storage, ctx: &mut TxContext): Coin<CoinType> {
    let sender = tx_context::sender(ctx);
    let current_epoch = tx_context::epoch(ctx);
    let type = get<CoinType>();
    let data = df::borrow_mut<TypeName, Data<CoinType>>(&mut self.id, type);

    if (table::contains(&data.faucet_lock, sender)){
      let last_mint_epoch = table::borrow(&data.faucet_lock, tx_context::sender(ctx));
      assert!(current_epoch > *last_mint_epoch, EAlreadyMintedThisEpoch);
    } else {
      table::add(&mut data.faucet_lock, sender, 0);
    };

    let last_mint_epoch = table::borrow_mut(&mut data.faucet_lock, sender);
    *last_mint_epoch = tx_context::epoch(ctx);
    coin::mint(&mut data.cap, if (type == get<USDC>()) 100 * FLOAT_SCALING else 1 * FLOAT_SCALING, ctx)
  }
}
  • mint_coin 函数设计为使用 ETH 或 USDC 类型进行调用。
  • 如果类型为 USDC,则每个纪元铸造 100 USDC;如果类型为 ETH,则每个纪元铸造 1 ETH。
  • 该函数有两个参数:
    • self :对 Storage 结构的可变引用,指示该函数可以修改实例。
    • ctx :对 TxContext 的可变引用,提供事务上下文信息。

函数内部:

  • 它从交易上下文中检索发送者的地址和当前纪元。
  • 它使用 get<CoinType>() 函数根据通用参数 ( CoinType ) 确定硬币类型。
  • 它使用 df::borrow_mut 加载与指定 CoinType 关联的 Data 结构。
  • 它检查发送者在水龙头锁定表中是否有记录。如果是,它会检查发送者是否有资格在当前时期进行铸造。
    • 如果符合条件,则继续进行;否则,它会引发错误( EAlreadyMintedThisEpoch )。
  • 如果发送方在表中没有记录,则会添加一条默认纪元为 0 的新记录。
  • 它借用了对最后铸造纪元的可变引用并将其更新为当前纪元。
  • 它使用 coin::mint 函数铸造一枚硬币(100 USDC 或 1 ETH)。
#![allow(unused)]
fn main() {
fun create_ask_orders(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>, 
    c: &Clock,
    ctx: &mut TxContext
  ) {

    let eth_data = df::borrow_mut<TypeName, Data<ETH>>(&mut self.id, get<ETH>());

    clob::deposit_base<ETH, USDC>(pool, coin::mint(&mut eth_data.cap, 60000000000000, ctx), &self.account_cap);

    clob::place_limit_order(
      pool,
      self.client_id,
     120 * FLOAT_SCALING, 
     60000000000000,
      NO_RESTRICTION,
      false,
      MAX_U64,
      NO_RESTRICTION,
      c,
      &self.account_cap,
      ctx
    );

    self.client_id = self.client_id + 1;
  }
}
  • create_ask_orders 函数负责在交易池中创建卖价订单。
  • 卖价订单通常由想要以报价代币 (USDC) 形式以指定价格出售一定数量基础代币 (ETH) 的卖家下达。
  • 该函数需要几个参数:
    • self :对 Storage 结构的可变引用,指示该函数可以修改实例。
    • pool :对交易池的可变引用 ( Pool<ETH, USDC> )。
    • c :时钟对象( Clock ),用于确定链上的时间戳。
    • ctx :对事务上下文 ( TxContext ) 的可变引用。

函数内部:

  • 它使用动态字段访问 ( df::borrow_mut<TypeName, Data<ETH>> ) 从存储中检索与 ETH 硬币类型关联的可变数据。
  • 它使用 clob::deposit_base<ETH, USDC> 函数将 60,000 ETH 存入交易池。该金额是使用 ETH 数据中的 coin::mint 铸造的。
  • 然后,它在池中放置限价卖单:
    • 该订单是以每 ETH 120 USDC 的价格出售 60,000 ETH。
    • 每个订单的订单 ID ( client_id ) 都会递增。
    • 订单没有到期时间戳( MAX_U64 表示没有到期)。
    • 使用 clob::place_limit_order 下订单。
  • 最后,它增加 client_id 为下一个订单做准备。
#![allow(unused)]
fn main() {
fun create_bid_orders(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>, // The CLOB pool
    c: &Clock,
    ctx: &mut TxContext
  ) {
    let usdc_data = df::borrow_mut<TypeName, Data<USDC>>(&mut self.id, get<USDC>());

    clob::deposit_quote<ETH, USDC>(pool, coin::mint(&mut usdc_data.cap, 6000000000000000, ctx), &self.account_cap);

    clob::place_limit_order(
      pool,
      self.client_id, 
      100 * FLOAT_SCALING, 
      60000000000000,
      NO_RESTRICTION,
      true,
      MAX_U64, 
      NO_RESTRICTION,
      c,
      &self.account_cap,
      ctx
    );
    self.client_id = self.client_id + 1;
  }
}
  • create_bid_orders 函数负责在交易池中创建买单。
  • 竞价订单通常由想要以报价代币 (USDC) 形式以指定价格购买一定数量基础代币 (ETH) 的买家下达。
  • 该函数需要几个参数:
    • self :对 Storage 结构的可变引用,指示该函数可以修改实例。
    • pool :对交易池的可变引用 ( Pool<ETH, USDC> )。
    • c :时钟对象( Clock ),用于确定链上的时间戳。
    • ctx :对事务上下文 ( TxContext ) 的可变引用。

函数内部:

  • 它使用动态字段访问 ( df::borrow_mut<TypeName, Data<USDC>> ) 从存储中检索与 USDC 硬币类型关联的可变数据。
  • 它使用 clob::deposit_quote<ETH, USDC> 功能将6,000,000 USDC存入交易池。该金额是使用 USDC 数据中的 coin::mint 铸造的。
  • 然后,它在池中下达限价买单:
    • 该订单是以每 ETH 100 USDC 或更高的价格购买 60,000 ETH。
    • 每个订单的订单 ID ( client_id ) 都会递增。
    • 订单没有到期时间戳( MAX_U64 表示没有到期)。
    • 使用 clob::place_limit_order 下订单。
  • 最后,它增加 client_id 为下一个订单做准备。
#![allow(unused)]
fn main() {
fun transfer_coin<CoinType>(c: Coin<CoinType>, sender: address) {
    if (coin::value(&c) == 0) {
      coin::destroy_zero(c);
    } else {
    transfer::public_transfer(c, sender);
    }; 
  }
}
  • transfer_coin 函数负责将特定类型的硬币( CoinType )转移到指定的接收者( sender 地址)。
  • 它需要两个参数:
    • cCoin<CoinType> 代表要转移的硬币。
    • senderaddress 表示硬币的接收者。

函数内部:

  • 它使用 coin::value(&c) == 0 检查硬币是否具有任何价值。
  • 如果硬币的价值为零,它会使用 coin::destroy_zero(c) 销毁硬币。
  • 如果硬币具有非零值,它将使用 transfer::public_transfer(c, sender) 将硬币转移到指定的发送者。
#![allow(unused)]
fn main() {
#[test_only]
  public fun init_for_testing(ctx: &mut TxContext) {
    init( DEX {}, ctx);
  }
}
}
  • init_for_testing 函数是一个仅供测试的函数,旨在用于测试目的。
  • 它采用对 TxContext ( ctx ) 的可变引用作为参数。
  • 在函数内部,它使用 DEX 类型的见证( DEX 结构的空实例)和提供的 TxContext 函数> .
  • 该测试函数可能用于初始化 DEX 模块以进行测试场景,而不影响主部署。

完整代码

dex.move 的完整代码是:

module dex::dex {
  use std::option;
  use std::type_name::{get, TypeName};

  use sui::transfer;
  use sui::sui::Sui;
  use sui::clock::{Clock};
  use sui::balance::{Self, Supply};
  use sui::object::{Self, UID};
  use sui::table::{Self, Table};
  use sui::dynamic_field as df;
  use sui::tx_context::{Self, TxContext};
  use sui::coin::{Self, TreasuryCap, Coin};

  use deepbook::clob_v2::{Self as clob, Pool};
  use deepbook::custodian_v2::AccountCap;

  use dex::eth::ETH;
  use dex::usdc::USDC;

  const CLIENT_ID: u64 = 122227;
  const MAX_U64: u64 = 18446744073709551615;
  const NO_RESTRICTION: u8 = 0;
  const FLOAT_SCALING: u64 = 1_000_000_000; 

  const EAlreadyMintedThisEpoch: u64 = 0;

  public struct DEX has drop {}

  public struct Data<phantom CoinType> has store {
    cap: TreasuryCap<CoinType>,
    faucet_lock: Table<address, u64>
  }

 public struct Storage has key {
    id: UID,
    dex_supply: Supply<DEX>,
    swaps: Table<address, u64>,
    account_cap: AccountCap,
    client_id: u64
  }

  #[allow(unused_function)]
  fun init(witness: DEX, ctx: &mut TxContext) { 

  let (treasury_cap, metadata) = coin::create_currency<DEX>(
            witness, 
            9, 
            b"DEX",
            b"DEX Coin", 
            b"Coin of Sui DEX", 
            option::none(), 
            ctx
        );
    
    transfer::public_freeze_object(metadata);    

		transfer::share_object(Storage { 
      id: object::new(ctx), 
      dex_supply: coin::treasury_into_supply(treasury_cap), 
      swaps: table::new(ctx),
      account_cap: clob::create_account(ctx),
      client_id: CLIENT_ID
    });
  }

  public fun user_last_mint_epoch<CoinType>(self: &Storage, user: address): u64 {
    let data = df::borrow<TypeName, Data<CoinType>>(&self.id, get<CoinType>());

    if (table::contains(&data.faucet_lock, user)) return *table::borrow(&data.faucet_lock, user);

    0 
  }

  public fun user_swap_count(self: &Storage, user: address): u64 {
    if (table::contains(&self.swaps, user)) return *table::borrow(&self.swaps, user);

    0
  }

  public fun entry_place_market_order(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>,
    account_cap: &AccountCap,
    quantity: u64,
    is_bid: bool,
    base_coin: Coin<ETH>,
    quote_coin: Coin<USDC>,
    c: &Clock,
    ctx: &mut TxContext,   
  ) {
    let (eth, usdc, coin_dex) = place_market_order(self, pool, account_cap, quantity, is_bid, base_coin, quote_coin, c, ctx);
    let sender = tx_context::sender(ctx);

    transfer_coin(eth, sender);
    transfer_coin(usdc, sender);
    transfer_coin(coin_dex, sender);
  }

  public fun place_market_order(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>,
    account_cap: &AccountCap,
    quantity: u64,
    is_bid: bool,
    base_coin: Coin<ETH>,
    quote_coin: Coin<USDC>,
    c: &Clock,
    ctx: &mut TxContext,    
  ): (Coin<ETH>, Coin<USDC>, Coin<DEX>) {
  let sender = tx_context::sender(ctx);  

  let client_order_id = 0;
  let dex_coin = coin::zero(ctx);

  if (table::contains(&self.swaps, sender)) {
    let total_swaps = table::borrow_mut(&mut self.swaps, sender);
    let new_total_swap = *total_swaps + 1;
    *total_swaps = new_total_swap;
    client_order_id = new_total_swap;

    if ((new_total_swap % 2) == 0) {
      coin::join(&mut dex_coin, coin::from_balance(balance::increase_supply(&mut self.dex_supply, FLOAT_SCALING), ctx));
    };
  } else {
    table::add(&mut self.swaps, sender, 1);
  };
  
  let (eth_coin, usdc_coin) = clob::place_market_order<ETH, USDC>(
    pool, 
    account_cap, 
    client_order_id, 
    quantity,
    is_bid,
    base_coin,
    quote_coin,
    c,
    ctx
    );

    (eth_coin, usdc_coin, dex_coin)
  }
  
  public fun create_pool(fee: Coin<Sui>, ctx: &mut TxContext) {

    clob::create_pool<ETH, USDC>(1 * FLOAT_SCALING, 1, fee, ctx);
  }

  public fun fill_pool(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>, 
    c: &Clock, 
    ctx: &mut TxContext
  ) {
    
    create_ask_orders(self, pool, c, ctx);
    create_bid_orders(self, pool, c, ctx);
  }

  public fun create_state(
    self: &mut Storage, 
    eth_cap: TreasuryCap<ETH>, 
    usdc_cap: TreasuryCap<USDC>, 
    ctx: &mut TxContext
  ) {

    df::add(&mut self.id, get<ETH>(), Data { cap: eth_cap, faucet_lock: table::new(ctx) });
    df::add(&mut self.id, get<USDC>(), Data { cap: usdc_cap, faucet_lock: table::new(ctx) });
  }

  public fun mint_coin<CoinType>(self: &mut Storage, ctx: &mut TxContext): Coin<CoinType> {
    let sender = tx_context::sender(ctx);
    let current_epoch = tx_context::epoch(ctx);
    let type = get<CoinType>();
    let data = df::borrow_mut<TypeName, Data<CoinType>>(&mut self.id, type);

    if (table::contains(&data.faucet_lock, sender)){

      let last_mint_epoch = table::borrow(&data.faucet_lock, tx_context::sender(ctx));

      assert!(current_epoch > *last_mint_epoch, EAlreadyMintedThisEpoch);
    } else {

      table::add(&mut data.faucet_lock, sender, 0);
    };

    let last_mint_epoch = table::borrow_mut(&mut data.faucet_lock, sender);
    *last_mint_epoch = tx_context::epoch(ctx);
    coin::mint(&mut data.cap, if (type == get<USDC>()) 100 * FLOAT_SCALING else 1 * FLOAT_SCALING, ctx)
  }

  fun create_ask_orders(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>, 
    c: &Clock, 
    ctx: &mut TxContext
  ) {

    let eth_data = df::borrow_mut<TypeName, Data<ETH>>(&mut self.id, get<ETH>());

    clob::deposit_base<ETH, USDC>(pool, coin::mint(&mut eth_data.cap, 60000000000000, ctx), &self.account_cap);

    clob::place_limit_order(
      pool,
      self.client_id,
     120 * FLOAT_SCALING, 
     60000000000000,
      NO_RESTRICTION,
      false,
      MAX_U64,
      NO_RESTRICTION,
      c,
      &self.account_cap,
      ctx
    );

    self.client_id = self.client_id + 1;
  }

  fun create_bid_orders(
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>,
    c: &Clock,
    ctx: &mut TxContext
  ) {

    let usdc_data = df::borrow_mut<TypeName, Data<USDC>>(&mut self.id, get<USDC>());

    clob::deposit_quote<ETH, USDC>(pool, coin::mint(&mut usdc_data.cap, 6000000000000000, ctx), &self.account_cap);

    clob::place_limit_order(
      pool,
      self.client_id, 
      100 * FLOAT_SCALING, 
      60000000000000,
      NO_RESTRICTION,
      true,
      MAX_U64,
      NO_RESTRICTION,
      c,
      &self.account_cap,
      ctx
    );
    self.client_id = self.client_id + 1;
  }

  fun transfer_coin<CoinType>(c: Coin<CoinType>, sender: address) {
    
    if (coin::value(&c) == 0) {
      coin::destroy_zero(c);
    } else {
    
    transfer::public_transfer(c, sender);
    }; 
  }

  #[test_only]
  public fun init_for_testing(ctx: &mut TxContext) {
    init( DEX {}, ctx);
  }
}

您还可以在这里找到完整的 dex 应用程序代码:https://github.com/0xmetaschool/sui-dex-dapp

小结

在本课中,我们学习了Dex智能合约及其功能,例如填充交易池、初始化合约状态、创建卖价和买价订单、铸造硬币和转移硬币。总的来说,我们了解了 Dex 智能合约的关键功能。

quiz

解释一下entry_place_market_order函数的作用是什么

编写 USDC 智能合约

欢迎再次回来!让我们立即开始创建 USDC 智能合约。

USDC 智能合约

首先,导航到 sources/usdc.move 并开始编写以下代码:

首先,我们从定义合约并导入我们将在代码中使用的基本包开始。

#![allow(unused)]
fn main() {
module dex::usdc {
  use std::option;

  use sui::url;
  use sui::transfer;
  use sui::coin;
  use sui::tx_context::{Self, TxContext};
}

接下来,我们将定义名为 USDC 的结构。它是创造电流的一次性见证者。它具有掉落能力,因此无法转移或存储。

#![allow(unused)]
fn main() {
public struct USDC has drop {}
}

然后,我们创建一个 init 函数,该函数在创建模块时运行一次,并接受一次性 witness 作为第一个参数。

我们创建了一个具有 1 种能力的 witness 结构体, drop ,它保证整个网络中只有一个,因为你只能在 init 中获取它功能。

init 函数内,我们调用 create_currency 。创建货币需要一次性 witness 以确保它是唯一的硬币,并且只有 TreasuryCap 的持有者才被允许铸造和销毁该硬币。

#![allow(unused)]
fn main() {
fun init(witness: USDC, ctx: &mut TxContext) {
      let (treasury_cap, metadata) = coin::create_currency<USDC>(
            witness, // One time witness
            9, // Decimals of the coin
            b"USDC", // Symbol of the coin
            b"USDC Coin", // Name of the coin
            b"A stable coin issued by Circle", // Description of the coin
            option::some(url::new_unsafe_from_bytes(b"https://s3.coinmarketcap.com/static-gravity/image/5a8229787b5e4c809b5914eef709b59a.png")), // An image of the Coin
            ctx
        );

      // We send the treasury capability to the deployer
      transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
      // Objects defined in different modules need to use the public_ transfer functions
      transfer::public_share_object(metadata);
  }
}

最后,我们添加了将在接下来的课程中实施的测试。该测试将帮助我们验证代码并检查它是否正常工作

#![allow(unused)]
fn main() {
// ** Test Functions

  #[test_only]
  public fun init_for_testing(ctx: &mut TxContext) {
    init(USDC {}, ctx);
  }
}
}

完整代码

为了您的方便,这里是完整的代码。

module dex::usdc {
  use std::option;

  use sui::url;
  use sui::transfer;
  use sui::coin;
  use sui::tx_context::{Self, TxContext};

  // ** Structs

  // One Time Witness to create a Current in Sui
  // This struct has the drop ability so it cannot be transferred nor stored. 
  // It allows the Network to know it is a unique type

  public struct USDC has drop {}

  // The init function runs once on Module creation and accepts a one-time witness as the first argument
  // Witness is a struct with 1 ability, drop, it guarantees that there is only one in the entire network as you can only get it in the init function
  
fun init(witness: USDC, ctx: &mut TxContext) {
      // We call the create_currency
      // Creating a currency requires a one-time witness to ensure it is a unique coin
      // Only the holder of the TreasuryCap is allowed to mint and burn this coin
      // Metadata holds all the information about the coin, so other applications query it
      
			let (treasury_cap, metadata) = coin::create_currency<USDC>(
            witness, // One time witness
            9, // Decimals of the coin
            b"USDC", // Symbol of the coin
            b"USDC Coin", // Name of the coin
            b"A stable coin issued by Circle", // Description of the coin
            option::some(url::new_unsafe_from_bytes(b"https://s3.coinmarketcap.com/static-gravity/image/5a8229787b5e4c809b5914eef709b59a.png")), // An image of the Coin
            ctx
        );

      // We send the treasury capability to the deployer

      transfer::public_transfer(treasury_cap, tx_context::sender(ctx));

      // Objects defined in different modules need to use the public_ transfer functions
      
			transfer::public_share_object(metadata);
  }

  // ** Test Functions

  #[test_only]
  public fun init_for_testing(ctx: &mut TxContext) {
    init(USDC {}, ctx);
  }
}

小结

USDC 合约将帮助我们创建并用 USDC 代币填充我们的余额。这样我们就可以轻松地将USDC币兑换成ETH或者ETH兑换USDC币。接下来,我们将实现 Dex 智能合约,它将帮助我们交换 ETH 和 USDC 硬币并记录我们的代币余额。

quiz

解释一下transfer::public_share_object函数的用途。

4.4_部署 - 课程学习时长因人而异

部署 Dex DApp

在为 Move on Sui 设置开发环境方面做得非常出色。在本课程中,我们将学习在 Sui 区块链上运行和部署 dex Move 项目,并在资源管理器中对其进行探索。

更新 Move.toml 文件

要成功部署 Dex Sui 项目,需要更新 Move.toml 文件。需要此更新以确保可以轻松安装和使用某些依赖项。在合同的实施过程中,您可能已经注意到一些库的使用。要安装和使用这些库,请使用以下信息更新 Move.toml 文件。

注意:请记住使用与创建 Move on Sui 项目时使用的包名称相同的包名称。

#![allow(unused)]
fn main() {
[package]
name = "dex"
version = "0.0.1"

[dependencies]
MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "testnet-v1.14.0" }
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "testnet-v1.14.0" }
DeepBook = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/deepbook", rev = "testnet-v1.14.0" }

[addresses]
dex = "0x0"
std = "0x1"
sui = "0x2"
deepbook = "0xdee9"
}

在 Sui 上构建并发布

现在让我们使用以下命令构建 Move 文件。请记住使用 cd dex 命令移动到 dex 文件夹。

#![allow(unused)]
fn main() {
sui move build
}

这将生成如下输出:

deploy-1.png

注意:忽略所有警告并继续前进。

运行命令后,您的项目目录将包含一个 build 文件夹和一个 Move.lock 文件。

deploy-2.png

设置开发环境

要在 Sui Testnet 上部署,请执行以下命令。

#![allow(unused)]
fn main() {
sui client new-env --alias=testnet --rpc https://fullnode.testnet.sui.io:443
sui client switch --env testnet
}

发布合同

运行以下命令,最终将Move项目发布到测试网:

#![allow(unused)]
fn main() {
sui client publish --skip-dependency-verification --gas-budget 90000000
}

注意:当您在生产环境中部署时,我们建议不要使用 --skip-dependency-verification 标志

我们将有一个很长的输出,但滚动到输出的开头并复制交易摘要:

deploy-3.png

  • 注意:忽略所有警告并继续前进。但如果您遇到“资金不足”错误,那么您可以前往 Sui Testnet faucet discord 频道并粘贴“!faucet [YOUR_ADDRESS]”以接收 10 个 Sui 代币。

前往 https://suiexplorer.com/?network=testnet。将交易摘要粘贴到搜索栏中,以在 Sui Explorer 上查找您的交易:

deploy-4.png

呼!你做得很好,但等等!现在您需要创建池。池可以用代币填充你的 dex 应用程序。为此,我们需要运行 sources/dex.move 文件中的以下函数。

  1. create_pool
  2. create_state
  3. fill_pool

这是 sources/dex.move 文件中这些函数的屏幕截图。

pool-code.png

为了运行这些功能,我们的测试网钱包地址中需要 100 个 Sui 代币。即使是测试网帐户,也几乎不可能免费获得 100 个 Sui 代币。

因此,为了解决这个问题,我们已经在以下地址为您创建了一个池。这是我们 DEX 合约的 Package ID

注意:如果您希望在 Sui 上构建端到端产品,请联系 Metaschool 团队,我们将帮助您获得 100 个 Sui 代币。

0xa1dce324bcf781692a358adb27bd105844231d35863b5c99f94e54801d653788

下面是DEX存储ID,我们将在下一课中使用它。

0x6912d83e2c4868386511fe3f6f18aff9399b9ad5cae2d97943766e2ff160ab25

小结

总之,本课介绍了在 Sui 上运行智能合约。我们学习了如何创建 Move 工作区、构建和发布 Move 文件、创建 Sui 帐户以及在 Sui 测试网上部署合约。恭喜您完成本课!

quiz

分享您部署的 Sui Dex dApp 地址的屏幕截图

4.5_前端 - 课程学习时长因人而异

设置前端

荣誉!我们已经部署了 dApp。我们已准备好继续设置我们的前端。不再等待,出发吧!

设置 Dex 前端

dex 目录中打开一个新终端,或者继续使用之前打开的终端。

运行以下命令克隆前端目录:

#![allow(unused)]
fn main() {
git clone https://github.com/0xmetaschool/sui-dex-frontend
}

前端将具有以下文件结构。

frontend-1.1.png

让我们安装我们的依赖项:

#![allow(unused)]
fn main() {
yarn install
}

查找dex包ID

接下来,我们需要找到我们在编写合约时实现的 dex 模块的包 ID。请按照以下步骤执行此操作。

  1. 前往 Sui Explorer 并在搜索栏中输入您在上一课中获得的交易摘要。

    frontend-1.gif

  2. 您可以看到带有“摘要”标题的侧边栏。请滚动到侧边栏的末尾以找到“发布”字段。

    frontend-2.gif

  3. 现在,通过单击“Publish”字段中的“>”符号打开“Object”字段,然后单击“dex”模块。

    frontend-3.png

你会看到类似这样的页面:

frontend-4.png

  1. 复制您的包 ID,如以下屏幕截图中所述。这将是我们将在前端使用的 dex 包 ID。

    frontend-5.png

    设置环境变量

    为什么我们还需要 dex 包 ID?我们需要 ID 将 Move 项目与前端连接起来。请记住,我们在上一课中讨论过,要运行 dex 应用程序,我们需要一个池。因此,请转到克隆前端中的 .env 文件,并使用以下包 ID,因为此包 ID 已经用 100 Sui 填充了池,以方便您使用。

    #![allow(unused)]
    fn main() {
    DEX_PACKAGE_ID=0xa1dce324bcf781692a358adb27bd105844231d35863b5c99f94e54801d653788
    }

    另外,如果 .env 文件未更新,请记住粘贴以下 DEX_STORAGE_ID

    #![allow(unused)]
    fn main() {
    DEX_STORAGE_ID=0x6912d83e2c4868386511fe3f6f18aff9399b9ad5cae2d97943766e2ff160ab25
    }

    如果您已经创建了池,那么您可以使用您的 ID。我们需要填充池,因为如果不填充池,应用程序会给您错误。

    运行前端

    要运行前端,请在终端中运行以下命令。

    #![allow(unused)]
    fn main() {
    yarn dev
    }

    该命令将为您提供如下输出:

    frontend-6.png

    转到链接, local 字段将向您显示。您将在浏览器中看到类似的内容。

    frontend-7.png

    在这里,您可以看到有一个使用 Google 登录的选项。当您尝试单击“使用 Google 签名”按钮时,它将给出以下响应。

    frontend-8.png

    因此,我们需要 Google Cloud 凭据,以便我们能够使用 Google 登录并使用我们的应用程序。

    小结

    唷,我们为你自己建立前端而感到自豪。接下来,我们将设置 Google Cloud 凭据并启动并运行我们的前端。

quiz

分享运行应用程序后前端的屏幕截图。

设置 Google 凭据

我们为您走到这一步并为您的 DeFi 应用程序设置前端感到非常自豪。在本课中,我们将设置 Google 凭据。

设置 Google 凭据

按照以下步骤设置 Google Cloud 帐户并获取您要在前端应用程序中使用的客户端 ID。

  1. 前往 Google Cloud 仪表板并使用您的 Google 帐户登录。登录后您将看到如下页面。

    google-setup-1.png

  2. 在侧边栏,您可以看到“API 和服务”字段。转到它并单击“凭据”。

    google-setup-2.png

你会看到这样的页面:

google-setup-3.png

  1. 现在,单击“创建项目”,在字段中填写以下数据,然后单击“创建”按钮。
    1. 项目名称:Sui App登录
    2. 位置: 保留此字段不变

以下是我们如何填写这些字段。

google-setup-4.png

创建项目后,您将看到这样的页面。

google-setup-5.png

  1. 要创建凭据,请单击“+ CREATE CREDENTIALS”按钮,然后选择“OAuth client ID”。

    google-setup-6.png

    1. 接下来,如果您看到如下页面,请单击“配置同意屏幕”按钮。

      google-setup-7.png

    2. 选择“外部”选项,以便任何用户都可以使用您的应用程序,然后单击“创建”按钮。

      google-setup-8.png

    单击“CREATE”按钮后,您将看到如下页面。

    google-setup-9.png

    1. 您现在需要填写应用程序信息。填写以下字段,跳过任何其他字段,填写信息后单击“保存并继续”按钮。

      1. 应用名称:Sui应用登录
      2. 用户支持电子邮件:在这里,您可以填写您的电子邮件ID
      3. 授权域名:点击“+添加域名”按钮并输入以下链接 - sui.io
      4. 开发者联系信息:在此字段中填写您的电子邮件 ID
    2. 现在,您将看到如下所示的页面。移至页面末尾,单击“保存并继续”按钮,然后继续,无需在此处进行任何更改。

      google-setup-10.png

    3. 接下来,您将看到以下页面。再次单击“保存并继续”按钮,然后继续。

      google-setup-11.png

      1. 最后,您将看到以下页面。移至页面末尾并单击“返回仪表板”按钮。

        google-setup-12.png

      2. 将出现以下页面。单击“凭据”页面返回到凭据页面。

        google-setup-13.png

      3. 现在,再次选择“+ CREATE CREDENTIALS”,然后选择“OAuth client ID”。

        google-setup-15.png

      4. 从“应用程序类型”滚动条中选择“Web 应用程序”。

        google-setup-16.png

        1. 填写应用程序的“名称”、“授权重定向 URI”以及 http://localhost:3000/http://127.0.0.1:3000/ URI,然后单击“创建”按钮。

          google-setup-17.png

      5. 创建客户端 ID 后,将出现以下页面。复制客户端 ID。我们将此客户端 ID 粘贴到前端的 .env 文件中。

      google-setup-18.png

      或者您可以从此处复制客户端 ID。

      google-setup-19.png

  2. 最后,我们需要在 Google Cloud 上公开我们的应用程序。这样任何人,包括您,都可以不受任何限制地登录。为此,请从侧边栏转到“OAuth 同意屏幕”,然后单击“发布应用程序”按钮。

publish-app.png

将客户端 ID 粘贴到 .env 文件

返回前端并找到 .env 文件。如果不存在,请在根目录中创建该文件。将以下行粘贴到 .env 文件中,并将 YOUR_GOOGLE_CLIENT_ID 替换为您从 Google 云复制的客户端 ID。

NEXT_PUBLIC_CLIENT_ID_GOOGLE=YOUR_GOOGLE_CLIENT_ID

小结

设置 Google Cloud 客户端 ID 方面做得非常出色。在下一课中,我们将设置 ZK 登录并运行我们的前端

quiz

与我们分享您的 Google 客户端 ID。

与前端交互

您已成功设置前端的所有内容,但还剩下最后一步。在本课中,我们将配置 ZK 登录凭证、运行前端、探索其功能,并学习如何通过前端与 move 合约交互。

设置ZK登录

请导航到前端目录中的 .env 文件并添加以下行。为了您的方便,我们已经配置了 ZK Login,因此您只需复制并粘贴 ZK Prover URL,而无需花费数小时进行设置。

URL_ZK_PROVER=http://142.93.211.174:8001/v1

如果您想自行设置 ZK Login,请按照此链接中提供的 Readme.md 文件中给出的说明进行操作。

与前端交互

现在所有凭据都已设置完毕,让我们在位于前端目录的终端中输入以下命令来运行我们的应用程序。

yarn dev

这次,通过单击“使用 Google 签名”按钮再次尝试登录。验证您的 Google 帐户,然后登录。登录后等待几秒钟,让 ZK 与您的应用程序建立连接。请求完成后,你会看到这样的页面。

frontend-interact-1.png

让我们尝试一下应用程序的不同功能,看看它是如何工作的。

  1. 首先,点击“Mint Sui”按钮来增加您的 Sui 余额。
  2. 要填充您的 USDC 和 ETH 余额,请分别单击“铸造 ETH”和“铸造 USDC”按钮。
  3. 在兑换之前,需要增加余额,因为没有任何 ETH 或 USDC,您将无法兑换代币。
  4. 要交换代币,请填写余额并单击“交换”按钮。
  5. 如果您想将 ETH 兑换成 USDC 或将 USDC 兑换成 ETH,请点击箭头按钮切换位置。

小结

我们为您完成课程并建立您自己的 Dex 应用程序感到非常自豪。在下一课中,我们将总结本课程并概述您到目前为止所学到的内容

quiz

使用您的 Google 帐户登录并与我们分享前端的屏幕截图。

4.6_总结 -本章学习内容回顾

让我们总结一下

恭喜您阅读完本在 Sui 区块链上构建去中心化应用程序 (dApp) 的综合指南!在整个学习过程中,您获得了对 Sui 网络的宝贵见解,掌握了 Move 编程语言,并成功为 dex dApp 制作和部署了智能合约。让我们回顾一下您的成就并概述一下主要收获。

主要经验教训:

  1. Sui生态系统探索:
    • 您已经熟悉了 Sui 网络,了解其功能以及它如何在区块链领域脱颖而出。
  2. 移动先进概念:
  • 您从头到尾实施了 Move 项目,并实施了您在 Sui 学习路径的第二课程中学习的所有高级概念。
  1. 开发环境设置:
    • 设置 Sui 工具链并确保您的开发环境已准备好创建去中心化应用程序。
  2. 智能合约开发:
    • 逐步完成为 dex dApp 编写智能合约的过程。从数据结构设置到部署,您已经了解了 Move 如何为 Sui 生态系统中的开发人员提供支持。
  3. 前端集成:
  • 为您的 dApp 创建了一个前端,展示了智能合约和用户界面之间的无缝交互。
  1. 相互作用:
  • 了解如何与您的 dApp 交互,确保其按预期运行。

🛠 为这个项目做出贡献!

有趣的事实:我们所有的项目都是 100% 开源的。

这是给您的好消息!这是一个开源项目,您可以在此处找到教程 Markdown 文件。如果您在课程中发现任何问题,请随时解决。

在 Metaschool,我们热爱社区的贡献,我们也在我们的 Discord 和 GitHub 个人资料中向贡献者致谢。另外,别忘了给我们的存储库加注星标⭐️!我们将非常感激! ♥️

🎊 恭喜

感谢您加入去中心化应用程序世界这段激动人心的旅程。您对区块链领域学习和建设的承诺值得赞扬。祝您未来一切顺利,继续塑造去中心化技术的未来!! ✌🏻🔮


05_loyalty

  • 本章课程共6小节,学习时长因人而异

loyalty

5.1_开始 学习loyalty相关知识 - 课程学习时长因人而异

我们今天学习什么?

大家好☀️,今天我们非常高兴为 Metaschool 的开发者带来有关 Sui 区块链的详细系列的第五门课程。让我们从第五门课程开始,祝贺您完成了 Sui 的前四门课程。呜呼!

那么我们今天要学习什么?我们将使用 move 构建忠诚度智能合约,然后将其部署在 Sui 区块链上。

该忠诚度智能合约将使我们能够为我们的 dex dApp 实施忠诚度计划。忠诚度计划通过向 NFT 等用户提供独特的奖励,帮助您的 dex dApp 与其他应用程序区分开来。

让我们看看在课程结束时您将如何与合约交互。

interact-gif-3.gif

课程大纲

我们将:

  1. 设置 Sui Wallet 和开发环境(如果尚未设置)
  2. 在 Move on Sui 中创建我们的忠诚度智能合约
  3. 在 Sui 区块链上部署智能合约

谁应该参加这门课程?

本课程面向希望在 Sui 区块链上进行构建并已完成我们的前三门 Sui 课程的 web3 开发人员和构建者。您应该具备基本的编程知识,因为它将帮助您快速开始本课程。

您将从本课程中获得什么?

作为开发人员,完成本课程后,您将获得以下成果:

  1. 在 Sui 上构建并部署忠诚度智能合约
  2. 通过 SuixMetaschool Discord House 探索 Sui 生态系统中的机会
  3. 赚取高达 1000 XP
  4. 完成课程后领取您的 NFT

完成证明

如果您和我一起完成本课程,您将获得 XP 和特殊的 NFT,这将为您在 Metaschool 平台上解锁更多机会。这是 NFT 的样子。

course-gif

现在,在我们继续之前,让我们先制定一些内部规则。

  1. 请正确完成你的快速作业。
  2. 加入我们的不和谐服务器并在那里询问所有相关问题。
  3. 保持快乐和积极!

好了各位,不用再等了。下一课见

Dex 和忠诚度计划简介

大家好,我们很高兴您完成了隋学习路径的前四门课程。在本课中,我们将复习一下什么是 Dex。我们将探讨什么是忠诚度计划以及为什么我们需要为我们在上一门课程中创建的 dex dApp 创建忠诚度合同。

我们来复习一下,什么是去中心化交易所(dex)?

去中心化交易所(通常缩写为 DEX)是一种无需中央机构或中介机构即可运行的加密货币交易所。 DEX 不依赖中心化平台来促进交易并持有用户资金,而是使用智能合约或其他去中心化协议来直接在用户之间进行点对点交易。

我们都会在某个时候使用 Metamask,并且您会持有一些代币。现在,如果您想将您的代币交换为另一个代币,您将需要一个可以做到这一点的交易所。有两种方法可以实现:中心化交易和去中心化交易。

什么是忠诚度计划?

区块链生态系统中存在大量的 dex。那么,为什么人们应该使用你的 dex dApp?这就是忠诚度计划的用武之地。让我举个例子,忠诚度计划与信用卡积分非常相似,您在特定的使用条件下获得一定数量的积分,在我们的例子中,我们将赠送 1 个 DEX 硬币每 2 次交换,现在用户可以将 DEX 币质押在忠诚度 dApp 上,并在 dex dApp 上每进行一次交换即可赚取 1 积分。现在用户可以累积这些积分并每 5 点领取一个 NFT

这就是激励用户使用您的 dex dApp 并尽可能频繁地使用它只是为了获得您将介绍的奖励。

那么,我们的忠诚度 dApp 是做什么的

我们将在本课程中实施的忠诚合约将奖励用户 NFT。让我总结一下它将如何工作,

  • 在 dex 进行的每次交换中,用户都会获得 DEX 和 DEXC 余额。
  • 使用忠诚度计划,用户将抵押这些代币。
  • 当用户抵押其代币时,忠诚度 dApp 将开始向他们提供奖励。
  • 获得5次奖励后,用户就有资格领取奖励,即NFT。

小结

好了,伙计们,这就是 dex 和忠诚度 dApp 的总结!您还记得什么是 dex 以及忠诚度计划如何运作。

在下一课中,您将设置编写合同的环境。在继续之前不要忘记完成任务!

quiz

什么是忠诚度计划?

5.1_开始 学习loyalty相关知识 - 课程学习时长因人而异

设置环境

欢迎回来!这样你就完成了《Move on Sui》的复习。好吧,在本课程中,您将学习如何设置运行代码的环境。准备好开始新一轮令人兴奋的学习了吗?让我们开始吧!

注意:我们假设您已经在 Sui 课程路径的最后一门课程中在系统中安装了 Sui 和 Rust。

更新 Rust 和 Sui

在继续之前,请使用以下命令更新系统中的 Rust 和 Sui。

rustup update stable
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch testnet sui

检查您的版本

打开您最喜欢的终端。让我们检查一下您的 Sui 安装版本。

sui --version

注意:如果您在安装 Sui 时遇到错误,请确保遵循部署您的第一个 Sui 合约课程中的所有步骤。

创建您的 Sui 文件夹

第一步是初始化工作空间环境。这将包含运行任何 Move 文件的基本文件。您可以使用以下命令创建工作区;我已将我的命名为 metaschool

sui move new loyalty

该命令将生成一个名为 loyalty 的文件夹,其中包含一个文件 Move.toml 和一个文件夹 sources

  • P.S.:我使用 Visual Studio IDE,因为它可以更好地可视化我的工作区结构。

导航到 sources/ 目录。创建一个名为 loyalty.move 的新移动文件。其外观如下:

loyalty-structure.png

检查你的钱包

您可以使用以下命令查看当前的活动地址:

sui client active-address

如果您没有有效地址,请按照以下步骤操作:

  1. 运行以下命令来创建您的 Sui 帐户:

    sui client new-address ed25519
    

    它将生成如下输出:

deploy-5.png

  • 重要提示:保存恢复短语,使用它来导入您的钱包非常重要。
  1. 将以下命令中的 [YOUR_ADDRESS] 替换为运行最后一个命令后收到的地址并运行它。

    sui client switch --address [YOUR_ADDRESS]
    
  2. 前往 Sui Testnet faucet discord 频道并粘贴“!faucet [YOUR_ADDRESS]”以接收 10 个 Sui 代币。

小结

在本课程中,您将设置您的环境。现在,我们准备编写代币的代码。

quiz

分享一下你的sui客户端地址截图。

5.3_搭建忠诚度DAPP - 课程学习时长因人而异

编写忠诚度智能合约

现在您已经设置了开发环境,让我们开始编写智能合约。

现在让我们开始编写代码:

编写忠诚度合约

导航到 sources/loyalty.move 。让我们逐行查看您将要编写的代码:

首先, metaschool 是包名称。它应该与我们使用命令 sui move new metaschool 初始化 Sui 工作区的文件夹名称相同。因此,如果您使用了任何其他名称,请务必在此处替换它。此外, loyalty 是模块名称。因此,如果您将其命名为 loyalty 之外的其他名称,请务必在此处进行更新。

#![allow(unused)]
fn main() {
module loyalty::loyalty
}
  • 此行定义了一个名为 metaschool::loyalty 的模块,它将包含忠诚度合同的实现。模块是一种组织代码并将相关功能分组在一起的方法。
#![allow(unused)]
fn main() {
use sui::clock::Clock;
  use sui::object::{Self, UID};
  use sui::coin::{Self, Coin};
  use sui::tx_context::TxContext;
  use sui::balance::{Self, Balance};

  use deepbook::clob_v2::Pool;
  use deepbook::custodian_v2::AccountCap;
  
  use dex::eth::ETH;
  use dex::usdc::USDC;
  use dex::dex::{Self, DEX, Storage};
}
  • 在这里,我们导入包含要在代码中使用的预构建函数和类型的模块。
#![allow(unused)]
fn main() {
public struct LoyaltyAccount has key, store {
    id: UID,
    stake: Balance<DEX>,
    points: u64
  }

public struct NFT has key, store {
    id: UID
  }
}
  • 现在我们定义了一个对象,其中包含程序中质押的 DEX 币数量以及每次交换累积的积分数量的信息。

现在让我们创建一个函数 create_account ,它创建一个帐户对象来跟踪用户积分和赌注金额,如下所示:

#![allow(unused)]
fn main() {
public fun create_account(ctx: &mut TxContext): LoyaltyAccount {
    LoyaltyAccount {
      id: object::new(ctx),
      stake: balance::zero(),
      points: 0
    }
  }
}
  • 我们将创建另一个函数 loyalty_account_stake ,它允许模块读取 LoyaltyAccount 中质押的 DEX 代币数量。我们还将创建一个函数 loyalty_account_points 来读取 LoyaltyAccount 中的点数。
#![allow(unused)]
fn main() {
public fun loyalty_account_stake(account: &LoyaltyAccount): u64 {
    balance::value(&account.stake)
  }

  public fun loyalty_account_points(account: &LoyaltyAccount): u64 {
    account.points
  }
}
  • 接下来,我们将创建一个 get_reward 函数,该函数向用户铸造 NFT,以换取 5 个积分。我们还需要确保在 NFT 铸币之后,这 5 个积分会从用户的账户中扣除。
#![allow(unused)]
fn main() {
public fun get_reward(account: &mut LoyaltyAccount, ctx: &mut TxContext): NFT {
    assert!(account.points >= 5, ENeeds5Points);

    let points_ref = &mut account.points;
    *points_ref = *points_ref - 5;

    NFT {
      id: object::new(ctx)
    }
  }
}
  • 最后,我们正在创建一个用于质押 DEX 币并赚取奖励的功能,如下所示:
#![allow(unused)]
fn main() {
public fun stake(
    account: &mut LoyaltyAccount,
    stake: Coin<DEX>
  ) {
    balance::join(&mut account.stake, coin::into_balance(stake));
  }

  public fun unstake(
    account: &mut LoyaltyAccount,
    ctx: &mut TxContext
  ): Coin<DEX> {
    let value = loyalty_account_stake(account);

    coin::take(&mut account.stake, value, ctx)
  }

  public fun place_market_order(
    account: &mut LoyaltyAccount,
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>,
    account_cap: &AccountCap,
    quantity: u64,
    is_bid: bool,
    base_coin: Coin<ETH>,
    quote_coin: Coin<USDC>,
    c: &Clock,
    ctx: &mut TxContext,    
  ): (Coin<ETH>, Coin<USDC>, Coin<DEX>) {
    let (eth, usdc, coin_dex) = dex::place_market_order(self, pool, account_cap, quantity, is_bid, base_coin, quote_coin, c, ctx);

      if (loyalty_account_stake(account) != 0) {
        
          let points_ref = &mut account.points;
          *points_ref = *points_ref + 1;
      };

    (eth, usdc, coin_dex)
  }
}

我们已成功创建忠诚度合同。

完整代码

最终合同应如下所示:

module loyalty::loyalty {
  
  use sui::clock::Clock;
  use sui::object::{Self, UID};
  use sui::coin::{Self, Coin};
  use sui::tx_context::TxContext;
  use sui::balance::{Self, Balance};

  use deepbook::clob_v2::Pool;
  use deepbook::custodian_v2::AccountCap;
  
  use dex::eth::ETH;
  use dex::usdc::USDC;
  use dex::dex::{Self, DEX, Storage};

  const ENeeds5Points: u64 = 0;

  public struct LoyaltyAccount has key, store {
    id: UID,
    // Amount of DEX Coin staked in the program
    stake: Balance<DEX>,
    // Amount of points accumulated per swap
    points: u64
  }
  
  public struct NFT has key, store {
    id: UID
  }

  // @dev It creates an account object to keep track of a user points and stake amount
  public fun create_account(ctx: &mut TxContext): LoyaltyAccount {
    LoyaltyAccount {
      id: object::new(ctx),
      stake: balance::zero(),
      points: 0
    }
  }

  // @dev It allows a module to read the amount of DEX coins staked in an `LoyaltyAccount`
  public fun loyalty_account_stake(account: &LoyaltyAccount): u64 {
    balance::value(&account.stake)
  }

  // @dev It allows a module to read the number of points in a `LoyaltyAccount`
  public fun loyalty_account_points(account: &LoyaltyAccount): u64 {
    account.points
  }

  // @dev It mints an NFT to the user in exchange for 5 points
  public fun get_reward(account: &mut LoyaltyAccount, ctx: &mut TxContext): NFT {
    // Make sure he has at least 5 points
    assert!(account.points >= 5, ENeeds5Points);

    // Deduct 5 points
    let points_ref = &mut account.points;
    *points_ref = *points_ref - 5;

    // Mint the reward
    NFT {
      id: object::new(ctx)
    }
  }

  public fun stake(
    account: &mut LoyaltyAccount,
    stake: Coin<DEX>
  ) {
    // Deposit the coin in the contract
    balance::join(&mut account.stake, coin::into_balance(stake));
  }

  public fun unstake(
    account: &mut LoyaltyAccount,
    ctx: &mut TxContext
  ): Coin<DEX> {
    // Save the total balance amount in memory
    let value = loyalty_account_stake(account);

    // unstake the balance into a coin
    coin::take(&mut account.stake, value, ctx)
  }

  // @ User can swap via the program to earn points
  public fun place_market_order(
    account: &mut LoyaltyAccount,
    self: &mut Storage,
    pool: &mut Pool<ETH, USDC>,
    account_cap: &AccountCap,
    quantity: u64,
    is_bid: bool,
    base_coin: Coin<ETH>,
    quote_coin: Coin<USDC>,
    c: &Clock,
    ctx: &mut TxContext,    
  ): (Coin<ETH>, Coin<USDC>, Coin<DEX>) {
    let (eth, usdc, coin_dex) = dex::place_market_order(self, pool, account_cap, quantity, is_bid, base_coin, quote_coin, c, ctx);

          // If the user has 0 DEX tokens staked he earns no points
      if (loyalty_account_stake(account) != 0) {
        
          // Borrow mut
          let points_ref = &mut account.points;
          // Increment
          *points_ref = *points_ref + 1;
      };

    (eth, usdc, coin_dex)
  }

  // @dev It allows a test file to destroy the Loyalty Account object
  #[test_only]
  public fun destroy_account_for_testing(account: LoyaltyAccount) {
    // @dev Properties without the drop ability must be destroyed via their libraries
    let LoyaltyAccount { id, stake, points: _ } = account;
    balance::destroy_for_testing(stake);
    object::delete(id);
  }

  // @dev It allows a test file to destroy the NFT object
  #[test_only]
  public fun destroy_nft_for_testing(nft: NFT) {
    let NFT { id} = nft;
    object::delete(id);
  }
}

小结

恭喜您完成忠诚合约。从这里开始只会变得更有趣!接下来,我们将部署我们的合约。下一节见

quiz

get_reward函数的目的是什么?

5.4_部署 - 课程学习时长因人而异

部署忠诚度 DApp

在最后一节中编写忠诚度智能合约非常出色。在本节中,让我们部署我们的合约。

更新 loyalty 合约 Move.toml 文件

要成功部署 Dex Sui 项目,需要更新 Move.toml 文件。需要此更新以确保可以轻松安装和使用某些依赖项。在合同的实施过程中,您可能已经注意到一些库的使用。要安装和使用这些库,请使用以下信息更新 Move.toml 文件。

注意:请记住使用与创建 Move on Sui 项目时使用的包名称相同的包名称。

#![allow(unused)]
fn main() {
[package]
name = "loyalty"
version = "0.0.1"

[dependencies]
MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "testnet-v1.18.0" }
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "testnet-v1.18.0" }
Dex = { local = "../dex/contracts" }
DeepBook = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/deepbook", rev = "testnet-v1.18.0" }

[addresses]
loyalty = "0x0"
std = "0x1"
sui = "0x2"
deepbook = "0xdee9"
dex = "0xa1dce324bcf781692a358adb27bd105844231d35863b5c99f94e54801d653788"
}

在这里,我们使用 Dex dApp 并提供本地 Dex 应用程序路径。因此,您一定已经在本 Sui 学习路径的最后课程中开发了 Dex dApp。请在此处提供本地 Dex dApp 路径。确保预先部署 Dex 以在忠诚度应用程序中使用它(如果您有 100 个 Sui 测试网代币,否则您可以使用我们的合约)。在我们的例子中,路径如下所示。

Dex = { local = "/Users/username/Desktop/metaschool/Sui/dex-app/dex" }

[addresses] ,你一定注意到我们使用的是 dex 合约地址。使用此地址并且不要更改它,因为该合约已经创建了一个池,该池需要运行 dex 和忠诚度 dApp 所需的 100 个 Sui 代币。如果您与创建的池有 dex 合约,请随意在此处使用它。

使用现有的合约地址将有助于忠诚度 dApp 不再发布 dex 合约,而是使用现有的已发布地址。

注意:请使用更新版本的软件包以及 dex dApp 中使用的相同版本。否则,忠诚度 dApp 会报错。

更新 dex 合约 Move.toml 文件

正如您所注意到的,我们正在向忠诚度 dApp 添加本地 dex dApp 路径,我们需要更新 dex Move.toml 文件以添加 dex dApp 的适当发布地址。否则,你的忠诚就会给你带来错误。请使用以下 Move.toml 文件。

#![allow(unused)]
fn main() {
[package]
name = "Dex"
version = "0.0.1"
published-at = "0xa1dce324bcf781692a358adb27bd105844231d35863b5c99f94e54801d653788"

[dependencies]
MoveStdlib = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/move-stdlib", rev = "testnet-v1.18.0" }
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "testnet-v1.18.0" }
DeepBook = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/deepbook", rev = "testnet-v1.18.0" }

[addresses]
dex = "0xa1dce324bcf781692a358adb27bd105844231d35863b5c99f94e54801d653788"
std = "0x1"
sui = "0x2"
deepbook = "0xdee9"
}

我们在 [package] 字段下添加了 published-at 地址,在 [addresses] 字段下添加了 dex 地址。

在 Sui 上构建并发布忠诚度 dApp

按照以下步骤在区块链上构建并发布您的 Sui 项目。

步骤1

首先,我们必须进入 loyalty 目录,因为它包含我们的合约。

#![allow(unused)]
fn main() {
cd loyalty
}

第2步

现在,让我们使用以下命令构建移动文件。这将创建一个新的 build 文件夹和一个 Move.toml 文件。

#![allow(unused)]
fn main() {
sui move build
}

输出如下所示。

output-1.png

步骤3

您现在需要在 Sui 中设置开发环境,以便使用以下命令部署在 Sui 测试网上:

#![allow(unused)]
fn main() {
sui client new-env --alias=testnet --rpc https://fullnode.testnet.sui.io:443
sui client switch --env testnet
}

步骤4

运行以下命令,最终将Move项目发布到测试网:

#![allow(unused)]
fn main() {
sui client publish --skip-dependency-verification --gas-budget 90000000
}

我们将有一个很长的输出,但滚动到输出的开头并复制交易摘要:

output-2.png

  • 注意:如果您遇到“资金不足”错误,则可以前往 Sui Testnet faucet discord 频道并粘贴“!faucet [YOUR_ADDRESS]”以接收 10 个 Sui 代币。

步骤5

前往 https://suiexplorer.com/?network=testnet。将交易摘要粘贴到搜索栏中,以在 Sui Explorer 上查找您的交易:

output-3.png

小结

在本课程中,您在 Sui 区块链上部署了忠诚度智能合约。祝您未来部署顺利!

接下来,让我们将合约与前端集

quiz

搜索您的合约并与我们分享 Sui explorer 的屏幕截图。

5.5_前端 - 课程学习时长因人而异

设置前端

荣誉!我们已经部署了 dApp。我们已经准备好继续设置我们的前端。不再等待,出发吧!

设置忠诚度前端

loyalty 目录中打开一个新终端,或者继续使用之前打开的终端。

运行以下命令克隆前端目录:

#![allow(unused)]
fn main() {
git clone https://github.com/0xmetaschool/sui-loyalty-dapp-frontend.git
}

前端将具有以下文件结构。

frontend-1.png

让我们安装我们的依赖项:

#![allow(unused)]
fn main() {
yarn install
}

查找忠诚度套餐 ID

接下来,我们需要找到我们在编写合约时实现的 loyalty 模块的包 ID。请按照以下步骤执行此操作。

  1. 前往 Sui Explorer 并在搜索栏中输入您在上一课中获得的交易摘要。

    frontend-1.gif

  2. 您可以看到带有“摘要”标题的侧边栏。请滚动到侧边栏的末尾以找到“发布”字段。

    frontend-2.png

  3. 现在,通过单击“发布”字段中的“>”符号打开“对象”字段,然后单击“忠诚度”模块。

    frontend-gif-1.gif

你会看到类似这样的页面:

frontend-3.png

  1. 复制您的包 ID,如以下屏幕截图中所述。这将是我们将在前端使用的您的忠诚度套餐 ID。

    frontend-4.png

设置环境变量

为什么我们还需要忠诚度套餐 ID?我们需要 ID 将 Move 项目与前端连接起来。请记住,我们在上一课中讨论过,要运行忠诚度应用程序,我们需要一个池。因此,转到克隆前端中的 .env 文件,并填写您的忠诚度套餐 ID。

#![allow(unused)]
fn main() {
LOYALTY_PACKAGE_ID=YOUR_LOYALTY_PACKAGE_ID
}

此外,我们需要与 dex 应用程序使用的环境变量相同的环境变量。由于忠诚度应用程序只有在您不使用 Google 登录后才能运行,因此请将一些 ETH 代币兑换为 USDC 代币,将 USDC 代币兑换为 ETH 代币。因此,要么在此处使用相同的 .env 文件数据,要么复制粘贴以下变量。为了您的方便,我们还在 .env 文件中填充了这些变量。

#![allow(unused)]
fn main() {
DEX_STORAGE_ID=0x6912d83e2c4868386511fe3f6f18aff9399b9ad5cae2d97943766e2ff160ab25
DEX_PACKAGE_ID=0xa1dce324bcf781692a358adb27bd105844231d35863b5c99f94e54801d653788
NEXT_PUBLIC_CLIENT_ID_GOOGLE=YOUR_GOOGLE_CLIENT_ID
URL_ZK_PROVER=http://142.93.211.174:8001/v1
}

请务必将 YOUR_GOOGLE_CLIENT_ID 替换为您的 Google 客户端 ID,因为它不能被多个用户使用。

运行前端

要运行前端,请在终端中运行以下命令。

#![allow(unused)]
fn main() {
yarn dev
}

该命令将为您提供如下输出:

frontend-6.png

转到链接, local 字段将向您显示。您将在浏览器中看到类似的内容。

frontend-5.png

小结

唷,我们为您自己设置前端而感到自豪。接下来,我们将与前端进行交互,看看我们的 dex 和忠诚度 dApp 是如何协同工作的。

quiz

分享您系统中运行的前端的屏幕截图。

与前端交互

您已成功设置前端的所有内容。是时候同时与 dex 和忠诚度 dApp 进行交互,看看它是如何工作的了。

与前端交互

现在所有凭据都已设置完毕,让我们在位于前端目录的终端中输入以下命令来运行我们的应用程序。

yarn dev

这次,通过单击“使用 Google 签名”按钮再次尝试登录。验证您的 Google 帐户,然后登录。登录后等待几秒钟,让 ZK 与您的应用程序建立连接。请求完成后,你会看到这样的页面。

interact-1.png

最初,如果您之前没有与 dex dApp 进行过交互,您会在所有或部分字段中看到 0 余额。要使用忠诚度 dApp,我们必须填写“DEX 掉期”和“DEXC 余额”字段。为了填充这些字段,我们需要互换一些代币。

让我们尝试一下应用程序的不同功能,看看它是如何工作的。

  1. 首先,点击“Mint Sui”按钮来增加您的 Sui 余额。
  2. 要填充您的 USDC 和 ETH 余额,请分别单击“铸造 ETH”和“铸造 USDC”按钮。
  3. 在兑换之前,需要增加余额,因为没有任何 ETH 或 USDC,您将无法兑换代币。
  4. 要交换代币,请填写余额并单击“交换”按钮。
  5. 如果您想将 ETH 兑换成 USDC 或将 USDC 兑换成 ETH 代币,请点击箭头按钮切换位置。

将 ETH 兑换为 USDC 代币

以下是我们如何将 ETH 代币兑换成 USDC 代币。

interact-gif-1.gif

将 USDC 兑换为 ETH 代币

以下是我们如何将 USDC 代币兑换为 ETH 代币。

interact-gif-2.gif

交换代币后,您将看到余额字段的变化,如下所示。

interact-2.png

前往忠诚度 dApp

是时候质押您的 DEXC 代币来赚取使用 Dex 应用程序的奖励了。当您质押 DEXC 代币一次时,您将开始在每第二次代币交换中获得奖励。当您集齐5个奖励后,您将有资格获得奖励。

单击左上角的 Loyalty dApp 按钮前往忠诚度 dApp,然后质押或取消质押您的 DEXC 代币。

interact-gif-3.gif

获得5次奖励后,即可领取奖励。让我们看看它是如何工作的。在我们的例子中,奖励将是 NFT。

interact-gif-4.gif

小结

我们为您完成课程并建立您自己的链上忠诚度应用程序感到非常自豪。在下一课中,我们将总结本课程并概述您到目前为止所学到的内容

quiz

分享一下你的前端截图,弹出“NFT奖励成功领取”的提示

5.6_总结 -本章学习内容回顾

让我们总结一下

恭喜!在整个学习过程中,您获得了对 Sui 网络的宝贵见解,掌握了 Move 编程语言,并成功为忠诚度 dApp 制作和部署了智能合约。

让我们回顾一下您的成就并概述一下主要收获。

主要经验教训:

  1. Sui生态系统探索:
    • 您已经熟悉了 Sui 网络,了解其功能以及它如何在区块链领域脱颖而出。
  2. 移动先进概念:
  • 您从头到尾实施了 Move 项目,并实施了您在 Sui 学习路径的第二课程中学习的所有高级概念。
  1. 开发环境设置:
    • 设置 Sui 工具链并确保您的开发环境已准备好创建去中心化应用程序。
  2. 智能合约开发:
    • 逐步完成为忠诚度 dApp 编写智能合约的过程。从数据结构设置到部署,您已经了解了 Move 如何为 Sui 生态系统中的开发人员提供支持。
  3. 前端集成:
  • 为您的 dApp 创建前端,展示智能合约和用户界面之间的无缝交互。
  1. 相互作用:
  • 了解如何与您的 dApp 交互,确保其按预期运行。

🛠 为这个项目做出贡献!

有趣的事实:我们所有的项目都是 100% 开源的。

这是给您的好消息!这是一个开源项目,您可以在此处找到教程 Markdown 文件。如果您在课程中发现任何问题,请随时解决。

在 Metaschool,我们热爱社区的贡献,我们也在我们的 Discord 和 GitHub 个人资料中向贡献者致谢。另外,别忘了给我们的存储库加注星标⭐️!我们将非常感激! ♥️

🎊 恭喜

您已完成本教程,现在已具备开始探索 Sui 区块链上令人兴奋的区块链和智能合约开发世界的知识。