使用 sCrypt 实现有状态的 BSV 智能合约

BSV 智能合约

Bitcoin 脚本的能力通常被认为是很有限的,无法实现复杂的智能合约。一个经常被诟病的点就是其无法实现有状态的智能合约。以太坊出现的一个主要原因,就是为了克服这个问题。

有些合约的确是有状态的,因为这些合约需要参与者在合约的多个阶段与之交互,并依赖随时间变化的状态,比如链上投票或游戏。接下来我们将展示一种在 BSV 智能合约中管理状态的通用机制。我们还将用 sCrypt 语言来实现一个有状态的合约。

预备知识:OP_PUSH_TX

在研究如何在 BSV 智能合约中管理状态之前,我们再来回顾一种强大的技术 OP_PUSH_TX。你可以把它看成一个伪操作码,这个伪操作码把当前 transaction 放入栈里,这样就可以在运行时使用。更准确地说,能让我们查看在 BIP143 中定义的签名验证中使用的哈希前数据(preimage)。preimage 的格式如下:

sighashPreimage

实现有状态合约

一旦我们可以通过 OP_PUSH_TX 技术访问到合约当前交易的上下文,我们就可以对它的 inputs 和 outputs 设置任意的约束。

在合约中实现状态的一种方法是把锁定脚本分成两部分:数据和代码。数据部分就是状态。代码部分则包含了状态转换规则,也就是合约的商业逻辑。数据部分可以通过OP_RETURN <data>或者OP_PUSHDATA <data> OP_DROP的方式附加到代码后面。虽然数据部分不会被运行,但它前面的代码部分会使用它,所以数据部分还是会对合约产生影响。

用 OP_PUSH_TX,我们从第5条可以获得被花费的 output 的锁定脚本内容,从第8条可以获得新的 output 内容。为了管理状态,我们要求锁定脚本的代码部分不能变(即合约规则不能变),数据(状态)部分的变化则必须符合代码部分规定的状态转换规则。

code_and_data
这类似于面向对象编程中的对象的概念,代码部分是对象的方法,数据部分是对象的成员变量。对象方法是不可变的。成员变量被封装起来,只能通过对象方法来改变它们。对象方法通过解锁脚本来调用,解锁脚本选择调用哪个方法并传入对应方法的参数。

一个示例合约:计数器

我们来看一个有状态合约的简单实例:计数器合约,记录合约的 increment() 方法被调用了多少次。合约代码及注释如下:

import "util.scrypt";

contract Counter {
    public function increment(SigHashPreimage txPreimage, int amount) {
        require(Tx.checkPreimage(txPreimage));

        // deserialize state (i.e., counter value)
        bytes scriptCode = Util.scriptCode(txPreimage);
        int scriptLen = len(scriptCode);
        // counter is at the end
        int counter = unpack(scriptCode[scriptLen - Util.DataLen :]);

        // increment counter
        counter++;

        // serialize state
        bytes outputScript = scriptCode[: scriptLen - Util.DataLen] + num2bin(counter, Util.DataLen);
        
        bytes output = Util.buildOutput(outputScript, amount);
        // ensure output is expected: amount is same with specified
        // also output script is the same with scriptCode except counter incremented
        require(hash256(output) == Util.hashOutputs(txPreimage));
    }
}

首先,确保 txPreimage 确实为当前 Tx 的原像:

require(Tx.checkPreimage(txPreimage));

接着,我们得到了前一个锁定脚本内容,也就是前文提到的 preimage 第5条的 scriptCode

bytes scriptCode = Util.scriptCode(txPreimage);

然后,从 scriptCode 中提取出了前一个计数器的状态(也就是计数器的值):

int counter = unpack(scriptCode[scriptLen - Util.DataLen :]);

接下来,把计数器值加1并放在新的锁定脚本中。请注意,计数器值是锁定脚本中唯一更改的部分:

counter++;

bytes outputScript = scriptCode[: scriptLen - Util.DataLen] + num2bin(counter, Util.DataLen);

最后,确保当前 Tx 的 output 中包含了新的锁定脚本:

bytes output = Util.buildOutput(outputScript, amount);
require(hash256(output) == Util.hashOutputs(txPreimage));

这里有部署合约的代码。重复调用合约方法 increment(),计数器从0到9递增的合约实例可以在这里看到:0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9。请注意,计数器状态数据在每个 Tx 的第一个 output 的脚本末尾处。如下图所示:

counter

总结

本文的目的是展示 BSV 智能合约能做什么以及如何实现。许多所谓的脚本限制是因为没有意识到它的潜力。随着我们进一步解释和演示脚本可以实现什么时,人们将会发现它具有极强的可扩展性、通用性和面向未来的特性。我们将展示取消人为限制的 BSV 网络可以运行任何能在其他区块链运行的智能合约,同时具备无限扩容的能力。再加上一些经济激励,可以让许多跨行业的应用更加高效和安全。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sCrypt Web3应用开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值