在Solidity中创建无限制列表

2020-09-05 13:21 栏目:经验之谈 来源:网络整理 查看()

在大多数应用程序中,使用列表非常简单。大多数语言都提供了处理列表的库,所以我们不必担心细节。然而,智能合同不同于“大多数应用”,我们需要特别注意区块链施加的设计限制。

本文涉及的完整代码可以在github [5]中找到

列表的特征

让我们假设这个列表用于存储地址类型,但是事实上这个列表可以存储任何东西。我们可以将基本要求总结如下:

1.支持CRUD操作:创建、读取、更新和删除

2.无限制,可以容纳任意数量的元素

添加/删除列表元素

一些重要的考虑因素被添加到智能契约平台中,例如以太网。可运行多年的代码赋予了“无限”一词新的含义。

我们需要一个系统,在这个系统中,通过添加和删除元素所消耗的气体是相对恒定的,与列表中元素的数量无关,并且我们不希望所需的气体随着时间而增加。

因此,将列表存储在一个简单的数组中不是一个好的选择。简单数组的主要问题是,在开始删除元素时,有必要管理元素之间的“间隙”。添加/删除的元素越多,简单数组就变得越碎片化,并且需要一些压缩。我们可以很容易地使用一个函数进行压缩,这个函数的气体消耗取决于所列元素的数量。例如,移位操作取决于删除元素后的元素数量:

在Solidity中创建无限制列表

除了通过移动进行压缩,另一种方法是在创建新元素时填充空白。然而,这给如何记录“差距”带来了挑战。或者,我们可以通过将最后一个元素移动到被删除的位置来填充空白。然而,当成批读取长列表时,在移动元素时会有问题。

为了避免这样的问题,我们实现了一个双向链表。使用此解决方案,通过添加/删除元素消耗的气体量与列表大小无关。Add元素在列表的末尾追加一个新条目。要删除一个元素,您只需要更新被删除元素前后的元素指针。最重要的是,删除元素不会产生间隙。

列表状态变量的存储结构

让我们来看看这个[6]智能契约代码,尤其是用于存储的状态变量。每个列表元素由三部分信息组成,一部分指向前一个元素,另一部分指向下一个元素,以及元素数据本身。

struct ListElement {

uint256 prev

uint256 next

地址地址;

{}

列表相当于数字和列表元素结构(uint256=列表元素)私有项之间的映射关系;

列表元素结构中的上一个和下一个值通过存储前一个数字和下一个数字来构成字符串元素。

为了帮助您查看状态变量,合同还包括:uint256公共下一个项目;uint256公共总计;

NextItem存储下一个元素的编号,这可以确保编号的唯一性,并且删除的编号不再使用。

总计存储列表中元素的总数。使用此变量的原因也取决于应用程序。实际上,我们现在在这个合同中不需要它,我们可以删除它以节省汽油,但是我在这里使用它是为了防止在其他应用中需要它。

遍历列表以计算列表元素的数量将导致不同列表长度的不同气体消耗。

零元素无效

在我设计的列表中,请注意有一个特定于此应用程序的假设。这里我们有一个地址列表,所以数据保存在ListElement addr中。当然,您可以使用任何其他变量来代替。

重要的是默认地址值(零)的影响。我的代码包含一个非常方便的假设,即任何零地址都是无效的。我们可以解决这个限制。然而,在所有情况下,我们都需要某种方法来识别无效的(未初始化的)元素。

要理解这一点,请参考坚实度文档[7]映射:

映射可以被视为哈希表。他们在实际的初始化过程中创建每一个可能的键,并将其映射到字节形式的全零值:一种类型的默认值

因此,我们的映射可以理解为预先生成一系列addr为零的ListElement。将零值视为无效值有助于我们区分生成了哪些智能合同。如果我们想要一个值为零的地址有效,那么我们需要一些其他的标识符。和我相比,我们可以给prev多一点意义。

但是为了简单起见,我不会在这里做。请记住,使用映射可以帮助我们确定哪些元素是我们自己生成的。

预留零号

另一个需要注意的小细节是保持映射项的标识为零。因此,它永远不能通过合同界面创建/删除。

编号为零的元素存储指向第一个和最后一个列表元素的指针。第一个元素是项[0]。然后

最后一个元素是:项[0]。上一个

直接引用这两个值可以帮助我们阅读和添加元素。

函数签名

到目前为止,我们已经介绍了添加、删除和更新元素的所有相关细节。阅读无限列表也很有趣。但是在学习之前,定义合同接口:

外部函数add(地址addr)

外部功能移除(uint256 id)

外部功能更新(uint256 id,地址addr)

函数firstItem()公共视图返回(uint256)

函数lastItem()公共视图返回(uint256)

函数nextItem()公共视图返回(uint256)

函数totalItems()公共视图返回(uint256)

函数读取(uint256 start,uint256 toRead)外部视图

返回(地址[]内存地址列表,uint256 next)

除了读取元素,所有其他函数签名都应该非常直观。否则,在每个函数之前检查内联注释。

列表阅读

列表可能包含许多元素,因此阅读也有其自身的挑战。我们的“读取”功能是一种视图类型,因此它不消耗气体。然而,这并不意味着函数对其函数没有约束。内存消耗是最明显的限制。我们通过允许调用方批量读取项目来避免这个问题。

让我们看看函数签名函数读取(uint 256开始,uint 256读取)外部视图返回(地址[]内存地址列表,下一个uint 256)

参数

在Solidity中创建无限制列表

虽然这个解决方案使我们能够安全地读取一个长列表,但是将这个过程分成多个调用带来了另一个挑战。当我们成批读取列表时,如果有人删除元素会发生什么?问题如下所示。

假设调用者一次分批读取三个元素。以下是最原始的列表

在Solidity中创建无限制列表

请记住,start参数为零,这意味着从第一个元素读取,在本例中是id为1的元素。

调用方1 read (0,3)返回:([项目1,项目2,项目3],4)

呼叫者1读取(4,3)返回:([项目4,项目5,项目6],7)

呼叫者2移除(7)

在Solidity中创建无限制列表

调用方1读取(7,3)返回:失败: '无效读取位置'

此时,呼叫者1不知道下一个阅读位置。

呼叫者1可以使用堆栈来解决这个问题。对于每个成功的调用,调用方1将读取开始参数和返回的项数组推送到堆栈上。失败时,c调用者1从堆栈中弹出结果,并重复读取操作。让我们看一个例子:

调用方1read (span 0,3)返回:([项目1,项目2,项目3],4)被推送到堆栈上

([项目1,项目2,项目3],0)

调用方1 read (4,3)返回:([项目4,项目5,项目6],项目7)被推送到堆栈上

([项目4,项目5,项目6],4)

([项目1,项目2,项目3],0)

呼叫者2离开(7)

调用方1read (7,3)返回: failed : '无效读取位置'弹出堆栈:([项目4,项目5,项目6],4)

([项目1,项目2,项目3],0)

呼叫者1读取(4,3)返回:([项目4,项目5,项目6],项目8)推入堆栈:

([项目4,项目5,项目6],4)

([项目1,项目2,项目3],0)

调用方1read (8,3)返回:([项目8,项目9],0)推入堆栈:

([项目8,项目9],8)

([项目4,项目5,项目6],4)

([项目1,项目2,项目3],0)

最后一次返回0时,表示它已被读取。

通过查看函数页,了解如何使用列表进行分页读取

参考材料

[1]连锁翻译计划: https://github.com/lbc-team/Pioneer

[2]https://learnblockchain.cn/people/1234迪丰:

[3]learnblockchain。https://learnblockchain.cn/article/1425 :

[4]很小熊: https://learnblockchain.cn/people/15

[5]完整代码: https://github。com/kax xa123/blockchainethings/tree/master/UnboundedList

[6]这个: https://github。com/kax xa 23/blockchainethings/blob/master/Unboundedlist/contracts/list contract。溶胶

[7]坚固性文档: https://learnblockchain。cn/docs/solidity/type。html #映射类型

[8]02 _读取_堆栈。js : https://github。com/kaxxxa 123/blockchainethings/blob/master/Unboundedlist/test/02 _ read _ stack。js中的函数页

[9]亚历山大扎米特: http://www.blockchainthings.io/author.aspx?i=1

微信二维码
售前客服二维码

文章均源于网络收集编辑侵删

提示:仅接受技术开发咨询!

郑重申明:资讯文章为网络收集整理,官方公告以外的资讯内容与本站无关!
NFT开发,NFT交易所开发,DAPP开发 Keywords: NFT开发 NFT交易所开发 DAPP开发