Solidity智能合约简介

各类教程 blockchain 3个月前 (03-27) 119次浏览 0个评论 扫描二维码
一个简单的智能合约

让我们从最基本的例子开始。如果你现在什么都不懂,那就好了,我们稍后会详细介绍。

存储

源代码是为 Solidity 版本 0.4.0 编写的,或者是任何不会破坏功能(最高但不包括 0.5.0 版本)的新版本。这是为了确保合约不会突然与新的编译器版本有所不同。关键字 pragma 的用法,通常,编译指示是编译器关于如何处理源代码的指令。

Solidity 合约是位于以太坊区块链的特定地址的代码(其 functions)和数据(其 state)的集合。该行声明一个称为类型的状态变量(无符号整数 256 位)。您可以将其视为数据库中的单个插槽,通过调用管理数据库的代码的函数可以查询和更改该插槽。要访问状态变量,不需要其他语言中常见的前缀 this。

注意

所有标识符(合约名称,函数名称和变量名称)都限制为 ASCII 字符集。可以在字符串变量中存储 UTF-8 编码的数据。

 

示例

以下合约将实施加密货币的最简单形式。可以凭空产生货币,但是只有创造合约的人才能做到这一点。此外,任何人都可以互相发送货币,而无需使用用户名和密码进行注册 – 所有你需要的是一个以太坊密钥对。

这份合约引入了一些新的概念,让我们一一浏览。

该行声明一个可公开访问的地址类型的状态变量。该类型是一个不允许任何算术运算的 160 bit 值。它适用于存储属于外部人员的合约或钥匙对的地址。关键字 public 自动生成一个函数,允许您访问状态变量的当前值。没有这个关键字,其他合约就无法访问这个变量。该函数看起来像这样:

当然,添加一个像这样的函数是行不通的,因为我们会有一个名字相同的函数和一个状态变量,但是希望你有这个想法 – 编译器会为你设计。

下一行也创建一个公共状态变量,但它是一个更复杂的数据类型。类型将地址映射为无符号整数。映射可以被看作 hash tables,它被虚拟初始化,使得每个可能的键都存在并被映射到其字节表示全部为零的值。但是,这种类比并不太过分,因为既不可能获得映射的所有键的列表,也不能获得所有值的列表。因此,最好记住(或是保留一个列表或使用更高级的数据类型)添加到映射的内容。在这种情况下,由关键字 public 创建的 getter function 有点复杂。它大致如下所示:

如您所见,您可以使用此功能轻松查询单个帐户的余额。

event Sent(address from, address to, uint amount);sendfromtoamount 该行声明了在 send 函数的最后一行中触发的所谓“事件” 。用户界面(当然也包括服务器应用程序)可以在没有太多成本的情况下监听在区块链上被触发的事件。一旦被触发,听众也将收到参数 from,to 和 amount,并且,这使得跟踪交易变得容易。为了听这个事件,你会使用

注意如何从用户界面调用自动生成的函数 balances。

特殊函数 Coin 是在创建合约期间运行的构造函数,不能在事后调用。它永久存储创建合约的人的地址:(msg 与 tx 和一起 block)是一个神奇的全局变量,其中包含一些允许访问区块链的属性。msg.sender 总是来自当前(外部)函数调用的地址。

最后,可以被用户和合约调用的功能是 mint 和 send。如果 mint 被除了创建合约的帐户之外的任何人调用,那么什么都不会发生。另一方面,send 任何人(已经有一些这些货币)可以使用货币给其他人。请注意,如果您使用此合约将货币发送到某个地址,那么当您在区块链浏览器中查看该地址时,您将看不到任何内容,因为您发送货币和已更改余额的事实仅存储在此数据存储中特定的货币合约。通过使用事件,创建追踪新货币交易和余额的“区块链浏览器”相对容易。

区块链基础

区块链作为一个概念对于程序员来说并不是很难理解。原因是大部分的复杂性(挖掘,哈希,椭圆曲线密码学,对等网络等等)只是为了提供一些特征和承诺。一旦你接受了这些功能,你就不必担心底层技术。

交易

区块链是全球共享的交易数据库。这意味着每个人都可以通过参与网络阅读数据库中的条目。如果你想改变数据库中的某些东西,你必须创建一个所谓的事务,这个事务必须被所有其他人接受。事务意味着你想做的改变(假设你想同时改变两个值)要么完全没有完成,要么完全完成。而且,当您的交易应用于数据库时,其他交易不能改变它。

举一个例子,设想一个以电子货币列出所有账户余额的表格。如果要求从一个账户转移到另一个账户,那么数据库的交易性质确保了如果从一个账户扣除金额,它总是被添加到另一个账户。如果由于任何原因,将金额添加到目标账户是不可能的,源账户也不被修改。

此外,交易总是由发件人(创建者)加密签名。这使得保护对数据库特定修改的访问变得非常简单。在电子货币的例子中,简单的检查确保只有持有账户钥匙的人可以从账户转账。

要克服的一个主要障碍是,在比特币方面,被称为“双重支出攻击”:如果网络中存在两个都想要清空账户的交易(即所谓的冲突)会发生什么?

这个抽象的答案是你不必在乎。将为您选择交易顺序,交易将被捆绑到所谓的“块”中,然后它们将在所有参与节点中执行和分配。如果两笔交易相互抵触,最后一笔交易将被拒绝,并不成为该块的一部分。

这些块在时间上形成了一个线性序列,这就是“区块链”这个词的来源。块以相当规则的间隔添加到链中 – 对于以太坊来说,这大概是每隔 17 秒。

作为“订单选择机制”(也就是所谓的“采矿”)的一部分,可能会发生这样的情况:块不时被还原,而只是在链的“顶端”。顶部添加的块越多,可能性越小。因此,您等待的时间越长,您的交易就越不可能被还原,甚至从区块链中移除。

以太坊虚拟机

概览

以太坊虚拟机或 EVM 是以太坊智能合约的运行环境。它不仅是沙盒,而且是完全隔离的,这意味着在 EVM 内运行的代码无法访问网络,文件系统或其他进程。智能合约甚至对其他智能合约的访问有限。

帐号

以太坊有两种共享相同地址空间的帐户:由公私密钥对(即人)控制的外部帐户和由与帐户一起存储的代码控制的合约帐户。

外部账户的地址是由公钥确定的,而合约的地址是在创建合约时确定的(它是从创建者地址和从该地址发送的交易数量中得到的,即所谓的“随机数”)。

无论帐户是否存储代码,这两种类型均由 EVM 平等对待。

每个帐户都有一个持久的键值存储,将 256 位的单词映射到 256 位的存储词。

此外,每个账户在以太网中都有一个余额,可以通过发送包含以 ether(以太币)的交易进行修改。

交易

交易是从一个帐户发送到另一个帐户(可能是相同的或特殊的零帐户,见下文)的消息。它可以包含二进制数据(有效载荷)和 ether(以太币)。

如果目标账户包含代码,则执行该代码,并将有效载荷作为输入数据提供。

如果目标账户是零账户(账户的地址是 0),则交易创建新的合约。如前所述,该合约的地址不是零地址,而是源自发件人的地址及其发送的交易数(“随机数”)。

天然气(Gas)

在创建之后,每笔交易都会收取一定数量的 gas,其目的是限制执行交易所需的工作量并支付执行费用。当 EVM 执行交易时,天然气按照特定的规则逐渐耗尽。

该 gas 价格是由交易的创建者设定的一个值,他必须在发送钱支付 gas_price * gas。如果执行后留下一些 gas,则以同样的方式退还。

如果 gas 在任何时候用完(即为负值),将触发一个 gas 外的异常,这将恢复对当前调用框架中的状态进行的所有修改。

Storage memory 和 Stack

每个帐户都有一个称为 storage 的永久存储区域。存储是将 256 位字映射到 256 位字的键值存储。从合约中列举存储是不可能的,而且读取更加昂贵,甚至更改存储。除了自己的合约外,合约不能读或写任何存储。

第二个存储区称为 memory,其中一个合约为每个消息调用获得一个新鲜清除的实例。存储器是线性的,可以在字节级别寻址,但读取限制在 256 位的宽度,写入可以是 8 位或 256 位宽。当访问(读取或写入)先前未触摸的内存字(即字中的任何偏移量)时,内存由一个字(256 位)扩展。在扩张的时候,必须支付 gas 的成本。内存越大,成本就越高(它按比例缩放)。

EVM 不是注册机器,而是堆栈机器,所以所有的计算都在一个称为 stack 的区域上执行。它最多具有 1024 个元素并包含 256 位的字。通过以下方式访问堆栈被限制在顶端:可以将最顶层的 16 个元素中的一个复制到堆栈顶部,或者将最顶层的元素与其下面的 16 个元素中的一个交换。所有其他操作从堆栈中取最上面的两个(或一个或多个,取决于操作)元素,并将结果推送到堆栈上。当然,可以将堆栈元素移动到存储器或内存中,但是不可能仅仅访问堆栈中更深处的任意元素,而不必先移除堆栈的顶部。

指令集

EVM 的指令集保持最小,以避免可能导致共识问题的错误实现。所有的指令都是基于 256 位字的基本数据类型。通常的算术,比特,逻辑和比较操作都存在。有条件的和无条件的跳转是可能的。此外,合约可以像访问其编号和时间戳一样访问当前块的相关属性。

消息胡椒

合约可以调用其他合约或通过消息调用的方式发送以太币到非合约账户。消息调用类似于交易,因为它们有一个源,一个目标,数据有效载荷,以太币,gas 和返回数据。实际上,每个交易都由一个顶级消息调用组成,这个消息调用又可以创建更多的消息调用。

合约可以决定剩余的 gas 应该发送多少内部的信息和多少要保留。如果在内部调用(或任何其他异常)中发生了一个不正常的异常,这将通过放置在堆栈上的错误值来指示。在这种情况下,只有与呼叫一起发送的 gas 用完。在 Solidity 中,在这种情况下,调用合约默认会导致手动异常,所以异常会“唤起”调用堆栈。

如前所述,被叫合约(可以与主叫方相同)将收到一个新鲜干净的内存实例,并可以访问呼叫有效载荷 – 将在一个称为 calldata 的单独区域中提供。在完成执行后,它可以返回将被存储在由调用者预先分配的调用者的存储器中的位置处的数据。

调用被限制在 1024 的深度,这意味着对于更复杂的操作,循环应该优于递归调用。

Delegatecall/Callcode 和 Libraries

消息调用存在一个特殊的变体,名为 delegatecall ,它与消息调用相同,除了目标地址上的代码是在调用合约的上下文中执行的,并且 msg.sender 和 msg.value 不会更改它们的值。

这意味着合约可以在运行时动态加载来自不同地址的代码。存储,当前地址和余额仍然是指呼叫合约,只有代码来自被叫地址。

这使得可以在 Solidity 中实现“库”功能:可重用的库代码,可以应用于合约的存储,例如为了实现一个复杂的数据结构。

日志

可以将数据存储在专门索引的数据结构中,该数据结构一直映射到块级别。这个被称为日志的特性被 Solidity 用来实现事件。合约创建后无法访问日志数据,但是可以从区块链之外高效访问它们。由于日志数据的某些部分存储在 bloom 过滤器中,因此可以以高效和密码安全的方式搜索这些数据,因此不下载整个区块链(“轻客户端”)的网络节点仍然可以找到这些日志。

创建

合约甚至可以使用特殊的操作码创建其他合约(即,他们不会简单地调用零地址)。这些创建调用和正常消息调用之间唯一的区别是有效负载数据被执行,结果作为代码存储,调用者/创建者在堆栈上接收新合约的地址。

自我销毁

从区块链中删除代码的唯一可能性是在该地址的合约执行 selfdestruct 操作时。存储在该地址的剩余 Ether 被发送到指定的目标,然后存储和代码从状态中移除。

警告

即使合约的代码不包含调用 selfdestruct,它仍然可以使用 delegatecall 或执行该操作 callcode。

注意

旧合约的修剪可能会或可能不会被以太坊客户实施。另外,归档节点可以选择无限期地保持合约存储和代码。

注意

目前外部帐户不能从状态中删除。


区块笔记版权所有丨转载请注明原文链接:Solidity 智能合约简介
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址