Solidity错误Stack Too Deep

本文详细探讨了在Solidity中遇到的'Stack Too Deep'错误,该错误源于以太坊虚拟机(EVM)的限制。通过分析智能合约的字节码和EVM操作,揭示了错误出现的原因和解决策略,特别是与事件日志相关的触发因素。文章通过实例展示了如何通过调整参数顺序和理解操作码参数的堆栈管理来避免此类错误。
摘要由CSDN通过智能技术生成

当一个人开始在Solidity编写智能合约时,他/她迟早会遇到一个非常烦人的障碍。“Stack Too Deep”错误。很容易陷入这个陷阱,当发生这种情况时,通常很难找到出路。公平地说,根本原因不在于Solidity本身,而在于以太坊虚拟机(EVM),因此可能会影响编译成EVM的其他语言(即LLL,Serpent,Viper),但这是一个微妙的区别在编写智能合约的日常工作中。

令人惊讶的是,考虑到这可能导致的烦恼程度,很难找到如何处理它的好资源,所以我决定写这篇文章试图对它有所启发,为了我自己的利益和其他任何人谁可能对它绝望。

一般而言,当代码需要访问堆栈中比第16个元素更深的slot(从顶部向下计数)时,似乎会生成此错误。但是,我们如何实现这一目标可以通过多种方式完成。这篇文章的目的不是提供关于如何产生这种错误的完整理论:根据我的经验,有太多的方法可以做到这一点。但它将为一个共同的触发器提供一个很好的理由,并希望让读者更多地了解EVM如何管理它的堆栈。甚至可以将相同的逻辑扩展到发生错误的其他情况,并寻找避免它的方法。

在Solidity中,大多数类型(例如基本类型,例如数字,地址和布尔值,但不是数组,结构和映射)都通过值传递给函数:当调用函数时,堆栈的一部分(即stock frame)被分配用于保存程序在函数返回时返回的地址和函数值类型输入和输出参数的副本。每个参数通常在堆栈中保存一个slot,其中每个slot为256位。

这提供了命中Stack Too Deep错误的最基本方法:总共有超过16个输入和输出参数。但实际上,如果我们希望该函数做一些有用的事情,我们必须非常小心,并且可能必须减少参数的数量。

为了测试这个,我在Remix中创建了一个小合约,如下所示:

pragma solidity ^0.4.24;
contract TestStackError {
  event LogValue(uint);
  function logArg(uint a1) public {
    emit LogValue(a1);
  }
}

Remix非常适合这样的调查,因为我们可以快速编写合约并对其进行查询,但基本上是因为Remix提供了一个功能强大的调试器,其中包含操作码反汇编以及堆栈,内存和存储的完整列表。代码中前后移动也很容易,这是我用任何语言提供的最佳调试体验之一。

这个合约非常简单:它没有状态变量,只有一个函数,它也非常简单。此函数只接受一个参数并记录它。

我将此合约复制到Remix中的新文件,编译并部署它。应该没有错误和警告,因此我转到Run选项卡,然后点击Deploy

然后,我扩展SimpleFunction合约的列表,并在logArg前面的框中输入单个值。我按下按钮并检查控制台中的输出:

如你所见,我输入了值7,并将其作为日志中的唯一元素返回。 虽然日志值得另外发布,但我在这里应该提到一些事情。

这是此调用的JSON格式的日志对象:

logs [
{
  "from": "0xef55bfac4228981e850936aaf042951f7b146e41",
  "topic": "0xfcf771399d75a67a6d0e730ae98d34c40b6bfe6ebf8053b98ddf4da8c2706250",
  "event": "LogValue",
  "args": {
    "0": "7",
    "length": 1
  }
}
  • 日志由emit关键字在solidity中创建,这会引发一个solidity event并对应于LOGn操作码。
  • 日志可以通过离线运行的客户端应用程序进行过滤。过滤器是日志中可用主题的条件。
  • 日志始终具有topic0,这是事件event签名的编码。
  • 可以通过索引参数来创建更多主题。最多可以有3个索引参数。其余的被视为事件数据。

在这个简单的例子中,我们可以很容易地确定只有一个主题0xfcf771399d75a67a6d0e730ae98d34c40b6bfe6ebf8053b98ddf4da2c2706250并且数据显示为日志对象的args成员的一部分。我们还可以验证代码是否按预期工作。

现在让我们测试这个合约的限制,并改变函数以接受最大数量的参数。

pragma solidity ^0.4.24;
contract TestStackError {
  event LogValue(uint);
  function logArg(uint a1, uint a2, uint a3, uint a4,
	uint a5, uint a6, uint a7, uint a8,
	uint a9, uint a10, uint a11, uint a12,
	uint a13, uint a14, uint a15, uint a16
  ) public {
    emit LogValue(a16);
  }
}

我有16个输入变量,没有输出变量,因此我只需要使用16个堆栈槽。 我调用传递值1到1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值