Skip to content

Price Prediction Oracle

Oracle-settled price prediction payout.

txt
import std.schnorr

# A signed price report chooses the winner and directs the whole previous value to output 0.
# The report binds asset id, timestamp, and price before payout checks run.
Contract PricePrediction:

    Struct Script:
        SuffixData: string
        PartialHash: string
        Size: number

    Struct Output:
        Value: number
        LockingScript: Script

    Struct Input:
        Data: {txid: hex32, vout: hex4, sequence: hex4}

    Struct CurrentTX:
        Outputs: Output[3]

    Struct PreTX:
        VLIO: string
        Inputs: Input[3]
        UnlockingScriptHash: string
        Outputs: Output[3]

    def settle(msg: hex, R: hex, s: hex, ctx: CurrentTX, pretx: PreTX):
        # The current input tells us which previous output value is at stake.
        utxoData: {txid: hex32, vout: hex4, sequence: hex4}
        utxoData = Push(BVM.unlockingInput)
        vout = BinToNum(utxoData.vout)
        Delete(utxoData.sequence)
        Delete(utxoData.txid)

        # Save that previous value so output 0 can be checked later.
        pre_value = pretx.Outputs[vout].Value.Clone()
        SetAlt(pre_value)

        # Recompute the previous txid from the supplied transaction pieces.
        tx_data = Push(0)
        SetAlt(tx_data)
        for i in Range(2, -1, -1):
            size_copy = pretx.Outputs[i].LockingScript.Size.Clone()
            if size_copy != 0:
                tx_data_temp = PartialHash(pretx.Outputs[i].LockingScript.SuffixData, pretx.Outputs[i].LockingScript.PartialHash, pretx.Outputs[i].LockingScript.Size)
                tx_data_temp = Cat(pretx.Outputs[i].Value, tx_data_temp)
                SetMain(tx_data)
                tx_data = Cat(tx_data_temp, tx_data)
                SetAlt(tx_data)
                Keep(tx_data)
            else:
                Delete(pretx.Outputs[i].LockingScript.Size)
                Delete(pretx.Outputs[i].LockingScript.PartialHash)
                Delete(pretx.Outputs[i].LockingScript.SuffixData)
                Delete(pretx.Outputs[i].Value)

        SetMain(tx_data)
        tx_data = Sha256(tx_data)
        tx_data = Cat(pretx.UnlockingScriptHash, tx_data)
        SetAlt(tx_data)

        tx_input_data = Push(0)
        for i in Range(2, -1, -1):
            tx_input_data = Cat(pretx.Inputs[i].Data, tx_input_data)
        tx_input_hash = Sha256(tx_input_data)

        SetMain(tx_data)
        tx_data = Cat(tx_input_hash, tx_data)
        tx_data = Cat(pretx.VLIO, tx_data)
        txid = Hash256(tx_data)
        preTXID = BVM.unlockingInput.Slice(0, 32)
        EqualVerify(txid, preTXID)

        # Clone the report bytes before Schnorr verification consumes the original.
        msg_for_parse = msg.Clone()
        SetAlt(msg_for_parse)
        schnorrVerify(msg, R, s, self.oraclePubKey, self.generator, self.modulus)
        SetMain(msg_for_parse)

        # Parse the report as assetId(32) | timestamp(8) | price(8).
        {msg_assetId, r1}       = Split(msg_for_parse, 32)
        {msg_ts_b, msg_price_b} = Split(r1, 8)

        # The report must refer to the configured asset id.
        EqualVerify(msg_assetId, self.assetId)

        # Treat the timestamp as unsigned and require it to reach the settlement time.
        msg_ts_num   = BinToNum(Cat(msg_ts_b, 0x00))
        settle_clone = self.settleTime.Clone()
        settle_num   = BinToNum(Cat(settle_clone, 0x00))
        NumEqualVerify(GreaterOrEqual(msg_ts_num, settle_num), 1)

        # The threshold comparison selects Alice on success and Bob otherwise.
        price_num       = BinToNum(Cat(msg_price_b, 0x00))
        threshold_clone = self.threshold.Clone()
        threshold_num   = BinToNum(Cat(threshold_clone, 0x00))
        winner_flag     = GreaterOrEqual(price_num, threshold_num)

        # Output 0 must be P2PKH and pay the selected winner's public-key hash.
        out0_suffix = ctx.Outputs[0].LockingScript.SuffixData.Clone()
        { out0_prefix, out0_suffix } = Split(out0_suffix, 3)
        EqualVerify(out0_prefix, 0x76a914)
        { out0_pkh, out0_tail } = Split(out0_suffix, 20)
        EqualVerify(out0_tail, 0x88ac)
        if winner_flag == 1:
            EqualVerify(out0_pkh, self.alice)
        else:
            EqualVerify(out0_pkh, self.bob)

        # The winner receives the exact value that was previously locked.
        SetMain(pre_value)
        out0_value = ctx.Outputs[0].Value.Clone()
        EqualVerify(out0_value, pre_value)

        # Rebuild all declared outputs and bind them to the transaction output hash.
        outputs_data = Push(0)
        SetAlt(outputs_data)
        for i in Range(2, -1, -1):
            size = ctx.Outputs[i].LockingScript.Size.Clone()
            if size != 0:
                output_temp = PartialHash(ctx.Outputs[i].LockingScript.SuffixData, ctx.Outputs[i].LockingScript.PartialHash, ctx.Outputs[i].LockingScript.Size)
                output_temp = Cat(ctx.Outputs[i].Value, output_temp)
                SetMain(outputs_data)
                outputs_data = Cat(output_temp, outputs_data)
                SetAlt(outputs_data)
                Keep(outputs_data)
            else:
                Delete(ctx.Outputs[i].LockingScript.Size)
                Delete(ctx.Outputs[i].LockingScript.PartialHash)
                Delete(ctx.Outputs[i].LockingScript.SuffixData)
                Delete(ctx.Outputs[i].Value)
        SetMain(outputs_data)
        outputs_data = Sha256(outputs_data)
        EqualVerify(outputs_data, BVM.outputsHash)