BSV 智能合约
Bitcoin 脚本的能力通常被认为是很有限的,无法实现复杂的智能合约。一个经常被诟病的点就是其无法实现有状态的智能合约。以太坊出现的一个主要原因,就是为了克服这个问题。
有些合约的确是有状态的,因为这些合约需要参与者在合约的多个阶段与之交互,并依赖随时间变化的状态,比如链上投票或游戏。接下来我们将展示一种在 BSV 智能合约中管理状态的通用机制。我们还将用 sCrypt 语言来实现一个有状态的合约。
预备知识:OP_PUSH_TX
在研究如何在 BSV 智能合约中管理状态之前,我们再来回顾一种强大的技术 OP_PUSH_TX。你可以把它看成一个伪操作码,这个伪操作码把当前 transaction 放入栈里,这样就可以在运行时使用。更准确地说,能让我们查看在 BIP143 中定义的签名验证中使用的哈希前数据(preimage)。preimage 的格式如下:
实现有状态合约
一旦我们可以通过 OP_PUSH_TX 技术访问到合约当前交易的上下文,我们就可以对它的 inputs 和 outputs 设置任意的约束。
在合约中实现状态的一种方法是把锁定脚本分成两部分:数据和代码。数据部分就是状态。代码部分则包含了状态转换规则,也就是合约的商业逻辑。数据部分可以通过OP_RETURN <data>
或者OP_PUSHDATA <data> OP_DROP
的方式附加到代码后面。虽然数据部分不会被运行,但它前面的代码部分会使用它,所以数据部分还是会对合约产生影响。
用 OP_PUSH_TX,我们从第5条可以获得被花费的 output 的锁定脚本内容,从第8条可以获得新的 output 内容。为了管理状态,我们要求锁定脚本的代码部分不能变(即合约规则不能变),数据(状态)部分的变化则必须符合代码部分规定的状态转换规则。
这类似于面向对象编程中的对象的概念,代码部分是对象的方法,数据部分是对象的成员变量。对象方法是不可变的。成员变量被封装起来,只能通过对象方法来改变它们。对象方法通过解锁脚本来调用,解锁脚本选择调用哪个方法并传入对应方法的参数。
一个示例合约:计数器
我们来看一个有状态合约的简单实例:计数器合约,记录合约的 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 的脚本末尾处。如下图所示:
总结
本文的目的是展示 BSV 智能合约能做什么以及如何实现。许多所谓的脚本限制是因为没有意识到它的潜力。随着我们进一步解释和演示脚本可以实现什么时,人们将会发现它具有极强的可扩展性、通用性和面向未来的特性。我们将展示取消人为限制的 BSV 网络可以运行任何能在其他区块链运行的智能合约,同时具备无限扩容的能力。再加上一些经济激励,可以让许多跨行业的应用更加高效和安全。