有状态合约¶
比特币/sCrypt 合约使用未花费交易输出 (UTXO) 模型:合约位于 UTXO 内,决定如何使用 UTXO 中的比特币。当一个 UTXO 被花费(即成功调用 sCrypt 合约公共函数)时,合约终止。对于保持状态并能够在携带可变状态的同时被多次调用的合约,必须遵循以下这些步骤。
状态装饰器¶
使用装饰器 @state
声明属于状态的任何属性。 状态属性可以像普通属性一样使用。
contract Counter {
@state
int counter;
constructor(int counter) {
this.counter = counter;
}
}
更新状态¶
合约可以通过将状态存储在锁定脚本中来跨链式交易保持状态。 在以下示例中,合约从 state0
到 state1
,然后到 state2
。 交易 1 tx1
中的输入是在 tx0
中花费 UTXO,而 tx2
花费 tx1
。

当你准备好将新状态传递到下一个 UTXO 时,只需使用两个参数调用内置函数 this.updateState()
: - txPreimage:它是 preimage 当前支出交易。 它必须只有一个输出并且其中包含新状态。 - amount:单个输出中的 satoshis 数量。
该函数是为每个有状态合约自动生成的,即,一个合约至少有一个用 @state
修饰的属性。 如果你需要自定义的 sighash 类型,请使用 updateStateSigHashType
,不同于默认的 SigHash.ALL | SigHash.FORKID
。
下面是一个示例合约,它记录了 increment()
被调用的次数。
contract Counter {
@state
int counter;
public function increment(SigHashPreimage txPreimage, int amount) {
// mutate state
this.counter++;
require(this.updateState(txPreimage, amount));
// customed sighash type
// require(this.updateStateSigHashType(txPreimage, amount, SigHash.SINGLE | SigHash.FORKID));
}
}
高级¶
如果你需要对状态进行更细粒度的控制,例如,在支出交易中有多个输出,你可以调用另一个内置函数 this.getStateScript()
来获取包含最新状态属性的锁定脚本。
接下来,你使用 OP_PUSH_TX 来确保包含新状态的输出进入当前的支出交易。 它相当于上面使用 this.updateState()
的合约。
contract Counter {
@state
int counter;
public function increment(SigHashPreimage txPreimage, int amount) {
// mutate state
this.counter++;
require(Tx.checkPreimage(txPreimage));
// get the locking script containing the latest stateful properties
bytes outputScript = this.getStateScript();
// construct an output from its locking script and satoshi amount
bytes output = Utils.buildOutput(outputScript, amount);
// only 1 input here
require(hash256(output) == SigHash.hashOutputs(txPreimage));
}
}
限制¶
对于任何访问状态属性的公共函数,它必须包含一个通过 Tx.checkPreimage()
验证的 SighashPreimage
参数,即使用 OP_PUSH_TX。这不适用于任何非公共函数,包括构造函数。
contract Counter {
@state
int counter;
constructor(int counter) {
// OK: not a public function
this.counter = counter;
}
public function increment(SigHashPreimage txPreimage, int amount) {
// OK
this.counter++;
require(Tx.checkPreimage(txPreimage));
}
public function foo(SigHashPreimage txPreimage, int amount) {
require(Tx.checkPreimageOpt(txPreimage));
// OK
this.counter++;
require(true);
}
public function bar(SigHashPreimage txPreimage) {
// Not OK: missing Tx.checkPreimage*()
this.counter++;
require(true);
}
public function baz(int i) {
// Not OK: missing SigHashPreimage
this.counter++;
require(true);
}
function baz() : int {
// OK: not a public function
return this.counter;
}
}