有状态合约

比特币/sCrypt 合约使用未花费交易输出 (UTXO) 模型:合约位于 UTXO 内,决定如何使用 UTXO 中的比特币。当一个 UTXO 被花费(即成功调用 sCrypt 合约公共函数)时,合约终止。对于保持状态并能够在携带可变状态的同时被多次调用的合约,必须遵循以下这些步骤。

状态装饰器

使用装饰器 @state 声明属于状态的任何属性。 状态属性可以像普通属性一样使用。

contract Counter {
    @state
    int counter;

    constructor(int counter) {
        this.counter = counter;
    }

}

更新状态

合约可以通过将状态存储在锁定脚本中来跨链式交易保持状态。 在以下示例中,合约从 state0state1,然后到 state2。 交易 1 tx1 中的输入是在 tx0 中花费 UTXO,而 tx2 花费 tx1

keep state

当你准备好将新状态传递到下一个 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;
    }
}