有状态合约

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

状态装饰器

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

contract Counter {
    @state
    int counter;

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

}

传播状态

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

keep state

当准备好将新状态传递到当前花费UTXO的交易中的输出时,只需调用内置函数 this.getStateScript(), 即可获取包含最新状态的锁定脚本。编译器为每个有状态合约自动生成改函数,有状态合约至少有一个带有 @state 修饰的属性。

最后,使用 OP_PUSH_TX 确保当前交易的输出包含新的状态。下面是一个示例合约,它记录了 increment() 被调用的次数。

contract Counter {
    @state
    int counter;

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

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

        // mutate state
        this.counter++;

        // 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;
    }
}