diff --git a/package-lock.json b/package-lock.json index 89f845c4..f86ed810 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "paillier-bigint": "^3.4.3", "scrypt-ord": "^1.0.16", - "scrypt-ts": "^1.3.25", + "scrypt-ts": "^1.3.28", "scrypt-ts-lib": "^0.1.27" }, "devDependencies": { @@ -4734,16 +4734,16 @@ } }, "node_modules/scrypt-ts": { - "version": "1.3.25", - "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-1.3.25.tgz", - "integrity": "sha512-Z7bEr28LBmx2ikhCEcFeaHVqPfV9lIunBTzhLb9D5p5NRKJMN7aABso7u1qKk+aoPqNN+lPtnKJhNykdGMN+pA==", + "version": "1.3.28", + "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-1.3.28.tgz", + "integrity": "sha512-joNGvZfUgXZPO1eYbSWD+pgU/jmtoOGEQACqZsuh25x9uegvEXXcQ6FcPLygHU/aItxgqITnFUhxd9GoA9t3jA==", "dependencies": { "deep-equal": "^2.2.0", "fast-diff": "^1.2.0", "lodash": "^4.17.21", "object-hash": "^3.0.0", "reflect-metadata": "^0.1.13", - "scryptlib": "^2.1.38", + "scryptlib": "^2.1.41", "socket.io-client": "^4.6.1", "superagent": "^8.0.9" }, @@ -4778,14 +4778,15 @@ } }, "node_modules/scryptlib": { - "version": "2.1.38", - "resolved": "https://registry.npmjs.org/scryptlib/-/scryptlib-2.1.38.tgz", - "integrity": "sha512-oM9gJ8juJ20hKEplOzQAZ+zHdhC8mxQpFFiWDgjeFHedrBlIlKRdxWCoVSVU8PhYOq31/CfUtEQqDXV60t2p7A==", + "version": "2.1.41", + "resolved": "https://registry.npmjs.org/scryptlib/-/scryptlib-2.1.41.tgz", + "integrity": "sha512-z3WLdqiPWrhUejT4KtiDjPG6jMXv2boanIyTf/8AkK59O6ZDenJB7D585oh3ZrF7aYf74x1a12ZJ5vLmbwuF1Q==", "hasInstallScript": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.7", "@jridgewell/sourcemap-codec": "^1.4.15", "bsv": "^1.5.6", + "chalk": "2.4.2", "compare-versions": "^3.6.0", "find-node-modules": "^2.1.3", "get-proxy-settings": "^0.1.13", @@ -4804,6 +4805,30 @@ "node": ">=14.0.0" } }, + "node_modules/scryptlib/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/scryptlib/node_modules/chalk/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/scryptlib/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4817,11 +4842,32 @@ "node": ">=12" } }, + "node_modules/scryptlib/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/scryptlib/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, "node_modules/scryptlib/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/scryptlib/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/scryptlib/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4841,6 +4887,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/scryptlib/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, "node_modules/scryptlib/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -4862,6 +4916,17 @@ "node": ">=8" } }, + "node_modules/scryptlib/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/scryptlib/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -9291,16 +9356,16 @@ } }, "scrypt-ts": { - "version": "1.3.25", - "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-1.3.25.tgz", - "integrity": "sha512-Z7bEr28LBmx2ikhCEcFeaHVqPfV9lIunBTzhLb9D5p5NRKJMN7aABso7u1qKk+aoPqNN+lPtnKJhNykdGMN+pA==", + "version": "1.3.28", + "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-1.3.28.tgz", + "integrity": "sha512-joNGvZfUgXZPO1eYbSWD+pgU/jmtoOGEQACqZsuh25x9uegvEXXcQ6FcPLygHU/aItxgqITnFUhxd9GoA9t3jA==", "requires": { "deep-equal": "^2.2.0", "fast-diff": "^1.2.0", "lodash": "^4.17.21", "object-hash": "^3.0.0", "reflect-metadata": "^0.1.13", - "scryptlib": "^2.1.38", + "scryptlib": "^2.1.41", "socket.io-client": "^4.6.1", "superagent": "^8.0.9" } @@ -9328,13 +9393,14 @@ } }, "scryptlib": { - "version": "2.1.38", - "resolved": "https://registry.npmjs.org/scryptlib/-/scryptlib-2.1.38.tgz", - "integrity": "sha512-oM9gJ8juJ20hKEplOzQAZ+zHdhC8mxQpFFiWDgjeFHedrBlIlKRdxWCoVSVU8PhYOq31/CfUtEQqDXV60t2p7A==", + "version": "2.1.41", + "resolved": "https://registry.npmjs.org/scryptlib/-/scryptlib-2.1.41.tgz", + "integrity": "sha512-z3WLdqiPWrhUejT4KtiDjPG6jMXv2boanIyTf/8AkK59O6ZDenJB7D585oh3ZrF7aYf74x1a12ZJ5vLmbwuF1Q==", "requires": { "@discoveryjs/json-ext": "^0.5.7", "@jridgewell/sourcemap-codec": "^1.4.15", "bsv": "^1.5.6", + "chalk": "2.4.2", "compare-versions": "^3.6.0", "find-node-modules": "^2.1.3", "get-proxy-settings": "^0.1.13", @@ -9347,6 +9413,26 @@ "yargs": "^17.6.2" }, "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + } + } + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -9357,11 +9443,29 @@ "wrap-ansi": "^7.0.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -9375,6 +9479,11 @@ "path-is-absolute": "^1.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -9390,6 +9499,14 @@ "strip-ansi": "^6.0.1" } }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 55a8b049..ebd5cfab 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "dependencies": { "paillier-bigint": "^3.4.3", "scrypt-ord": "^1.0.16", - "scrypt-ts": "^1.3.25", + "scrypt-ts": "^1.3.28", "scrypt-ts-lib": "^0.1.27" }, "devDependencies": { diff --git a/src/contracts/priceBet.ts b/src/contracts/priceBet.ts index 917b7b93..74d5dafa 100644 --- a/src/contracts/priceBet.ts +++ b/src/contracts/priceBet.ts @@ -11,26 +11,29 @@ import { } from 'scrypt-ts' import { RabinSig, RabinPubKey, WitnessOnChainVerifier } from 'scrypt-ts-lib' -export type ExchangeRate = { - timestamp: bigint - price: bigint - symbol: ByteString +export type Message = { + marker: bigint // 1 byte + timestamp: bigint // 4 bytes LE + price: bigint // 8 bytes LE + decimal: bigint // 1 byte + tradingPair: ByteString } /* * A betting contract that lets Alice and Bob bet on the price of the BSV-USDC pair * in the future. The price is obtained from a trusted oracle. - * Read our Medium article for more information about using oracles in Bitcoin: - * https://medium.com/coinmonks/access-external-data-from-bitcoin-smart-contracts-2ecdc7448c43 + * https://api.witnessonchain.com/#/v1/V1Controller_getPrice */ export class PriceBet extends SmartContract { // Price target that needs to be reached. @prop() targetPrice: bigint + @prop() + decimal: bigint - // Symbol of the pair, e.g. "BSV_USDC" + // Trading pair, e.g. "BSV-USDC" @prop() - symbol: ByteString + tradingPair: ByteString // Timestamp window in which the price target needs to be reached. @prop() @@ -50,7 +53,8 @@ export class PriceBet extends SmartContract { constructor( targetPrice: bigint, - symbol: ByteString, + decimal: bigint, + tradingPair: ByteString, timestampFrom: bigint, timestampTo: bigint, oraclePubKey: RabinPubKey, @@ -59,7 +63,8 @@ export class PriceBet extends SmartContract { ) { super(...arguments) this.targetPrice = targetPrice - this.symbol = symbol + this.decimal = decimal + this.tradingPair = tradingPair this.timestampFrom = timestampFrom this.timestampTo = timestampTo this.oraclePubKey = oraclePubKey @@ -69,12 +74,13 @@ export class PriceBet extends SmartContract { // Parses signed message from the oracle. @method() - static parseExchangeRate(msg: ByteString): ExchangeRate { - // 4 bytes timestamp (LE) + 8 bytes rate (LE) + 1 byte decimal + 16 bytes symbol + static parseMessage(msg: ByteString): Message { return { - timestamp: Utils.fromLEUnsigned(slice(msg, 0n, 4n)), - price: Utils.fromLEUnsigned(slice(msg, 4n, 12n)), - symbol: slice(msg, 13n, 29n), + marker: Utils.fromLEUnsigned(slice(msg, 0n, 1n)), + timestamp: Utils.fromLEUnsigned(slice(msg, 1n, 5n)), + price: Utils.fromLEUnsigned(slice(msg, 5n, 13n)), + decimal: Utils.fromLEUnsigned(slice(msg, 13n, 14n)), + tradingPair: slice(msg, 14n), } } @@ -87,22 +93,21 @@ export class PriceBet extends SmartContract { ) // Decode data. - const exchangeRate = PriceBet.parseExchangeRate(msg) + const message = PriceBet.parseMessage(msg) // Validate data. + assert(message.marker == 2n, 'incorrect oracle message type.') + assert(message.decimal == this.decimal, 'incorrect decimal.') + assert(message.timestamp >= this.timestampFrom, 'Timestamp too early.') + assert(message.timestamp <= this.timestampTo, 'Timestamp too late.') assert( - exchangeRate.timestamp >= this.timestampFrom, - 'Timestamp too early.' - ) - assert( - exchangeRate.timestamp <= this.timestampTo, - 'Timestamp too late.' + message.tradingPair == this.tradingPair, + 'incorrect trading pair.' ) - assert(exchangeRate.symbol == this.symbol, 'Wrong symbol.') // Decide winner and check their signature. const winner = - exchangeRate.price >= this.targetPrice + message.price >= this.targetPrice ? this.alicePubKey : this.bobPubKey assert(this.checkSig(winnerSig, winner), 'Winner checkSig failed.') diff --git a/src/contracts/priceBet2.ts b/src/contracts/priceBet2.ts deleted file mode 100644 index 6e18bb84..00000000 --- a/src/contracts/priceBet2.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { - SmartContract, - prop, - ByteString, - method, - assert, - Utils, - Sig, - PubKey, - slice, - byteString2Int, -} from 'scrypt-ts' -import { RabinSig, RabinPubKey, RabinVerifier } from 'scrypt-ts-lib' - -export type Message = { - marker: bigint // 1 byte - timestamp: bigint // 4 bytes LE - price: bigint // 8 bytes LE - decimal: bigint // 1 byte - tradingPair: ByteString -} - -/* - * A betting contract that lets Alice and Bob bet on the price of the BSV-USDC pair - * in the future. The price is obtained from a trusted oracle. - * https://oracle-demo.vercel.app/docs#/v1/V1Controller_getPrice - */ -export class PriceBet2 extends SmartContract { - // Price target that needs to be reached. - @prop() - targetPrice: bigint - @prop() - decimal: bigint - - // Trading pair, e.g. "BSV-USDC" - @prop() - tradingPair: ByteString - - // Timestamp window in which the price target needs to be reached. - @prop() - timestampFrom: bigint - @prop() - timestampTo: bigint - - // Oracles Rabin public key. - @prop() - oraclePubKey: RabinPubKey - - // Public keys of both players. - @prop() - alicePubKey: PubKey - @prop() - bobPubKey: PubKey - - constructor( - targetPrice: bigint, - decimal: bigint, - tradingPair: ByteString, - timestampFrom: bigint, - timestampTo: bigint, - oraclePubKey: RabinPubKey, - alicePubKey: PubKey, - bobPubKey: PubKey - ) { - super(...arguments) - this.targetPrice = targetPrice - this.decimal = decimal - this.tradingPair = tradingPair - this.timestampFrom = timestampFrom - this.timestampTo = timestampTo - this.oraclePubKey = oraclePubKey - this.alicePubKey = alicePubKey - this.bobPubKey = bobPubKey - } - - // Parses signed message from the oracle. - @method() - static parseMessage(msg: ByteString): Message { - return { - marker: Utils.fromLEUnsigned(slice(msg, 0n, 1n)), - timestamp: Utils.fromLEUnsigned(slice(msg, 1n, 5n)), - price: Utils.fromLEUnsigned(slice(msg, 5n, 13n)), - decimal: Utils.fromLEUnsigned(slice(msg, 13n, 14n)), - tradingPair: slice(msg, 14n), - } - } - - @method() - public unlock(msg: ByteString, sig: RabinSig, winnerSig: Sig) { - // Verify oracle signature. - assert( - RabinVerifier.verifySig(msg, sig, this.oraclePubKey), - 'Oracle sig verify failed.' - ) - - // Decode data. - const message = PriceBet2.parseMessage(msg) - - // Validate data. - assert(message.marker == 2n, 'incorrect oracle message type.') - assert(message.decimal == this.decimal, 'incorrect decimal.') - assert(message.timestamp >= this.timestampFrom, 'Timestamp too early.') - assert(message.timestamp <= this.timestampTo, 'Timestamp too late.') - assert( - message.tradingPair == this.tradingPair, - 'incorrect trading pair.' - ) - - // Decide winner and check their signature. - const winner = - message.price >= this.targetPrice - ? this.alicePubKey - : this.bobPubKey - assert(this.checkSig(winnerSig, winner), 'Winner checkSig failed.') - } - - static parseSig(sig: { s: ByteString; padding: ByteString }): RabinSig { - return { - s: byteString2Int(sig.s + '00'), - padding: sig.padding, - } - } -} diff --git a/tests/priceBet.test.ts b/tests/priceBet.test.ts index d859cc89..0b412337 100644 --- a/tests/priceBet.test.ts +++ b/tests/priceBet.test.ts @@ -1,67 +1,68 @@ import { PriceBet } from '../src/contracts/priceBet' import { - ByteString, bsv, PubKey, - byteString2Int, toByteString, MethodCallOptions, findSig, + ByteString, } from 'scrypt-ts' -import { RabinPubKey, RabinSig } from 'scrypt-ts-lib' +import { RabinPubKey, RabinSig, WitnessOnChainVerifier } from 'scrypt-ts-lib' import { expect, use } from 'chai' import { getDefaultSigner } from './utils/helper' import chaiAsPromised from 'chai-as-promised' use(chaiAsPromised) -// All data was pre-fetched from the WitnessOnChain oracle service. -// See https://witnessonchain.com/ - -const RESP_0 = { - digest: 'fa922e641c9d050000000000044253565f555344430000000000000000', - rate: 36.79, - signatures: { - rabin: { - padding: '', - public_key: - 'ad7e1e8d6d2960129c9fe6b636ef4041037f599c807ecd5adf491ce45835344b18fd4e7c92fd63bb822b221344fe21c0522ab81e9f8e848206875370cae4d908ac2656192ad6910ebb685036573b442ec1cff490c1638b7f5a181ae6d6bc9a04a305720559c893611f836321c2beb69dbf3694b9305a988c77e0a451c38674e84ce95a912833d2cf4ca9d48cc76d8250d0130740145ca19e20b1513bb93ca7665c1f110493d1b5aa344702109df5feca790f988eaa02f92e019721ae0e8bfaa9fdcd3401ffb4433fbe6e575ed9f704a6dc60872f0d23b2f43bfe5e64ce0fbc71283e6dedee79e20ad878917fa4a8257f879527c58f89a8670be591fc2815f7e7a8d74a9830788404f66170058dd7a08f47c4954324088dbed2f330015ccc36d29efd392a3cd5bf9835871f6b4b203c228af16f5b461676ce8e51003afd3137978117cf41147f2bb615a7c338bebdca5f81a43fe9b51480ae52ce04cf2f2b1714599fe09ae8401e0e155b4caa89fb37b00c604517fc36961f84901a73a343bb40', - signature: - '3d90715373a2564bc76ecfd8d4bf1a15411713f39b2f21fa301de763974d0844f64b6724eceb6d2622058e8d730690a4bdaca3f5823c1586eea1c533a6edbbf97d04fd03cb3cbfccc7698deaa8a20c33e5e6f22081c50046cb18abb0c3418b6c228fdaa68e96ddffc9594642274378119d60713e80ae65340c4e8374c45dc3ac821ef5241b3f28668ba6907471ef7f1433c5dfe7d0e48a2fbc64dee09a80492126847e80fe90b0efaa8adf90a2960d475c53c3781897f0328ca4237b317e4c25f055ef5d7a2f68388341f88222e100621184e3b6a06c6801582c8d20a2b4e29546409a6b8b059c5d523f4c993219dfb45fca3ad640fb88ba569ce487f428727c53f34d7a5c5b51dbda97933db53f5ca01a76a1f749ce869ff48da17bb1afaa03775879b956b5b1bc3e6a0b47ef75ab1ec9398df0e21d6946b01fa97c708ea724437a7dde06bbe87137c068e77bfb1e91bb332161e525b2079bc64853685f5cb5a5449aa51d84fcae1722c49f3222ddaca2441c4b21c1d72bf7ad81ca4df71f3e', - }, +// All data was pre-fetched from https://api.witnessonchain.com/ + +// https://api.witnessonchain.com/#/info/AppController_getInfo +const PUBKEY = { + publicKey: + 'ad7e1e8d6d2960129c9fe6b636ef4041037f599c807ecd5adf491ce45835344b18fd4e7c92fd63bb822b221344fe21c0522ab81e9f8e848206875370cae4d908ac2656192ad6910ebb685036573b442ec1cff490c1638b7f5a181ae6d6bc9a04a305720559c893611f836321c2beb69dbf3694b9305a988c77e0a451c38674e84ce95a912833d2cf4ca9d48cc76d8250d0130740145ca19e20b1513bb93ca7665c1f110493d1b5aa344702109df5feca790f988eaa02f92e019721ae0e8bfaa9fdcd3401ffb4433fbe6e575ed9f704a6dc60872f0d23b2f43bfe5e64ce0fbc71283e6dedee79e20ad878917fa4a8257f879527c58f89a8670be591fc2815f7e7a8d74a9830788404f66170058dd7a08f47c4954324088dbed2f330015ccc36d29efd392a3cd5bf9835871f6b4b203c228af16f5b461676ce8e51003afd3137978117cf41147f2bb615a7c338bebdca5f81a43fe9b51480ae52ce04cf2f2b1714599fe09ae8401e0e155b4caa89fb37b00c604517fc36961f84901a73a343bb40', +} +// https://api.witnessonchain.com/#/v1/V1Controller_getPrice +const RESP = { + timestamp: 1708923144, + tradingPair: 'BSV-USDC', + price: 748600, + decimal: 4, + data: '020819dc65386c0b0000000000044253562d55534443', + signature: { + s: 'e66925a8225eeba1c44cc670dc01bbcaeeb51d09f3945c8d15fcabde01856824db813b862a67c9dc00242bddfe2fcb402ccdfa97ebaeac45ca279932f143b3ae4c00d49aea6aff5f737cfb642662055ddffaf9f8f399d160516fb8e735039baa6f38b0de5acc88d01706bd4135bea8da2bb61257bfb71a0d551622d38693b9126837d28d7c36dd26e358ac24a7feb7edfc965ef9a761a96e801128f34e07a5a00aa2a5dc2905ed0067330ba41c7708d19e1892ff6d66fad004037b327206f152427b50137d03b4436fcfe51cf75fd216c2ed3831f315bc3b71874a0330d5aa9719a7dca4c6028fa3a26826a25c71951c6ed4d9c27cd4e59f88f4c609e4dc9cdce793b72a92f6a0673dd533856db80091ca68889f0ead8c4916c9b1d672fdeb3f724b98c9d00fda13d6b36d4d6ae7968efd4b74ea6e9eae16b1827d93fc674c4928363cf628f2d30640e979d81a17718281b8c8174d353bd72001f96e13342a5c1c1c03063e1836d8389c2da2c63eb59a71dd8a324730ddb0d6ee99eb20bc2b10', + padding: '0000', }, - symbol: 'BSV_USDC', - timestamp: 1680773882, } describe('Test SmartContract `PriceBet`', () => { - const rabinPubKey: bigint = byteString2Int( - RESP_0.signatures.rabin.public_key + '00' - ) - let alicePrivKey: bsv.PrivateKey let bobPrivKey: bsv.PrivateKey let priceBet: PriceBet + const decimal = 4 + const currentPrice = Math.round(RESP.price * 10 ** decimal) + before(() => { // Prepare inital data. alicePrivKey = bsv.PrivateKey.fromRandom(bsv.Networks.testnet) bobPrivKey = bsv.PrivateKey.fromRandom(bsv.Networks.testnet) + const rabinPubKey: RabinPubKey = + WitnessOnChainVerifier.parsePubKey(PUBKEY) - const decimal = 4 - const targetPriceFloat = 36.3 // USDT + const targetPriceFloat = 36.3 // USDC const targetPrice = Math.round(targetPriceFloat * 10 ** decimal) - const timestampFrom = 1680652800n // Thu, 05 Apr 2023 00:00:00 GMT - const timestampTo = 1680998400n // Thu, 09 Apr 2023 00:00:00 GMT 🥚 - const symbol = toByteString(RESP_0.symbol, true) + '0000000000000000' + const timestampFrom = 1708905600n // Mon Feb 26 2024 00:00:00 GMT + const timestampTo = 1708992000n // Tue Feb 27 2024 00:00:00 GMT + const tradingPair = toByteString('BSV-USDC', true) PriceBet.loadArtifact() priceBet = new PriceBet( BigInt(targetPrice), - symbol, + BigInt(decimal), + tradingPair, timestampFrom, timestampTo, - rabinPubKey as RabinPubKey, + rabinPubKey, PubKey(alicePrivKey.publicKey.toByteString()), PubKey(bobPrivKey.publicKey.toByteString()) ) @@ -69,30 +70,20 @@ describe('Test SmartContract `PriceBet`', () => { it('should pass w correct sig and data.', async () => { // Pick winner. - const decimal = 4 - const currentPrice = Math.round(RESP_0.rate * 10 ** decimal) - let winner = alicePrivKey - if (currentPrice < priceBet.targetPrice) { - winner = bobPrivKey - } + const winner = + currentPrice >= priceBet.targetPrice ? alicePrivKey : bobPrivKey const winnerPubKey = winner.publicKey // Connect signer. await priceBet.connect(getDefaultSigner(winner)) await priceBet.deploy(1) - const oracleSigS = byteString2Int( - RESP_0.signatures.rabin.signature + '00' - ) - const oracleSigPadding: ByteString = RESP_0.signatures.rabin.padding - const oracleSig: RabinSig = { - s: oracleSigS, - padding: oracleSigPadding, - } + const oracleMsg: ByteString = WitnessOnChainVerifier.parseMsg(RESP) + const oracleSig: RabinSig = WitnessOnChainVerifier.parseSig(RESP) const callContract = async () => priceBet.methods.unlock( - RESP_0.digest as ByteString, + oracleMsg, oracleSig, (sigResps) => findSig(sigResps, winnerPubKey), // Method call options: @@ -104,36 +95,26 @@ describe('Test SmartContract `PriceBet`', () => { }) it('should fail paying wrong player.', async () => { - // Pick winner. - const decimal = 4 - const currentPrice = Math.round(RESP_0.rate * 10 ** decimal) - let looser = alicePrivKey - if (currentPrice >= priceBet.targetPrice) { - looser = bobPrivKey - } - const looserPubKey = looser.publicKey + // Pick loser. + const loser = + currentPrice >= priceBet.targetPrice ? bobPrivKey : alicePrivKey + const loserPubKey = loser.publicKey // Connect signer. - await priceBet.connect(getDefaultSigner(looser)) + await priceBet.connect(getDefaultSigner(loser)) await priceBet.deploy(1) - const oracleSigS = byteString2Int( - RESP_0.signatures.rabin.signature + '00' - ) - const oracleSigPadding: ByteString = RESP_0.signatures.rabin.padding - const oracleSig: RabinSig = { - s: oracleSigS, - padding: oracleSigPadding, - } + const oracleMsg: ByteString = WitnessOnChainVerifier.parseMsg(RESP) + const oracleSig: RabinSig = WitnessOnChainVerifier.parseSig(RESP) const callContract = async () => priceBet.methods.unlock( - RESP_0.digest as ByteString, + oracleMsg, oracleSig, - (sigResps) => findSig(sigResps, looserPubKey), + (sigResps) => findSig(sigResps, loserPubKey), // Method call options: { - pubKeyOrAddrToSign: looserPubKey, + pubKeyOrAddrToSign: loserPubKey, } as MethodCallOptions ) diff --git a/tests/priceBet2.test.ts b/tests/priceBet2.test.ts deleted file mode 100644 index 2530c856..00000000 --- a/tests/priceBet2.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { PriceBet2 } from '../src/contracts/priceBet2' -import { - ByteString, - bsv, - PubKey, - byteString2Int, - toByteString, - MethodCallOptions, - findSig, -} from 'scrypt-ts' -import { RabinPubKey, RabinSig } from 'scrypt-ts-lib' -import { expect, use } from 'chai' -import { getDefaultSigner } from './utils/helper' -import chaiAsPromised from 'chai-as-promised' -use(chaiAsPromised) - -// All data was pre-fetched from https://oracle-demo.vercel.app/docs - -// https://oracle-demo.vercel.app/info -const PUBKEY = - '496297dbe0759a886eca725ade26baff74ceb23bf6113ce9a2857252ea25afc04fd66bece618c8e96c637f401ce4c21cc2129f62f323f92873ac3fc3f1f15bb921c4bef5c3d30d974208a9b0544cbfb82e73d1639f7329cd81a05c983f62e91673dc3d8e4a567a65f24fb9279ff88b84cf3d52fc2e8a107685dc2382e43a413b066f2bc20a42c15e8f43de1f2b4a6bfc441e6233fe87df8b45bb61238163875684fe0a088822b8597ca2c2e95ad09685206955b8f456bdf18c5f91d38404488571557ab045d03ce551eed96fcef40c0af2e681dfe4583a9121134379fc05f77b32718ac075911ce1fb258720362ada19ff6d92b0d1b8bc95db42076d03fdbac66933b00966cf9c1b5300999647134aaba7460521c3362596083646197d667c1d849f74ae1e0fc8160ff9812e8e12eef886a2662485798c5701f0721408d1c877a0165d1c1e754fcb4e502da16de648348584726bf3831fa4d7129ffe5bc34da51e3c1f1de22e0a8b86b4bfa0709dbea9b30307714a4cb8a28c270c7724f8d25e' -const RESP = { - timestamp: 1700428685, - tradingPair: 'BSV-USDC', - price: 478400, - decimal: 4, - data: '028d7b5a65c04c070000000000044253562d55534443', - signature: { - s: '763e7453f6156fbcf7a063f42f1a73fd21758f0e31d4fee74ae040da73e0f3402abab3bff02ba662132283da5c5c1b7924cd2981d3da72af231a3ef9e9eb971bf3045e419f5f67b0814d1c77a98350b786c80ee6e94000b0e852268e9536e01651562dfee8a1c7988b0e0392fbc48bb64fcce9fa22fee6992fb4938637d063f7406db5b50df8608b696a17fe97fc06bfd2f812c92d4f1cca246d8d61a535f900aa72e7d91ce38c6b0f2746a1816fd8606c15ed0a1f9ced404c19ff4c4b96c5a7b6b1fa7e5e8ba1a82fb7fa1d06f3ed8cfaae3a1f7f55103e95281ba1a45eba7041d6e1fad5960605f8f7481208b5efbd168c9a38f3fdc5d7856a1778b37ad6859118dec36272ba801ac17f9e6ca84e7c30c05e81777d632e046173e629ca5276385f669169fac682d267b21214c8479536f6f8e62c9826e1785f23a0ce976183e289a0b25a4772a93369b3d5828c87b92a6d56e7720693e979dd903f7df749b71538c5703f2fb330887b532e72b1d005a191edc0142b336f1c958c487ef4a152', - padding: '0000000000', - }, -} - -describe('Test SmartContract `PriceBet2`', () => { - let alicePrivKey: bsv.PrivateKey - let bobPrivKey: bsv.PrivateKey - - let priceBet: PriceBet2 - - const decimal = 4 - const currentPrice = Math.round(RESP.price * 10 ** decimal) - - before(() => { - // Prepare inital data. - alicePrivKey = bsv.PrivateKey.fromRandom(bsv.Networks.testnet) - bobPrivKey = bsv.PrivateKey.fromRandom(bsv.Networks.testnet) - const rabinPubKey: bigint = byteString2Int(PUBKEY + '00') - - const targetPriceFloat = 36.3 // USDC - const targetPrice = Math.round(targetPriceFloat * 10 ** decimal) - const timestampFrom = 1700352000n // Sun Nov 19 2023 00:00:00 GMT - const timestampTo = 1700438400n // Mon Nov 20 2023 00:00:00 GMT - const tradingPair = toByteString('BSV-USDC', true) - - PriceBet2.loadArtifact() - priceBet = new PriceBet2( - BigInt(targetPrice), - BigInt(decimal), - tradingPair, - timestampFrom, - timestampTo, - rabinPubKey as RabinPubKey, - PubKey(alicePrivKey.publicKey.toByteString()), - PubKey(bobPrivKey.publicKey.toByteString()) - ) - }) - - it('should pass w correct sig and data.', async () => { - // Pick winner. - const winner = - currentPrice >= priceBet.targetPrice ? alicePrivKey : bobPrivKey - const winnerPubKey = winner.publicKey - - // Connect signer. - await priceBet.connect(getDefaultSigner(winner)) - await priceBet.deploy(1) - - const oracleSig: RabinSig = PriceBet2.parseSig(RESP.signature) - - const callContract = async () => - priceBet.methods.unlock( - RESP.data as ByteString, - oracleSig, - (sigResps) => findSig(sigResps, winnerPubKey), - // Method call options: - { - pubKeyOrAddrToSign: winnerPubKey, - } as MethodCallOptions - ) - return expect(callContract()).not.rejected - }) - - it('should fail paying wrong player.', async () => { - // Pick loser. - const loser = - currentPrice >= priceBet.targetPrice ? bobPrivKey : alicePrivKey - const loserPubKey = loser.publicKey - - // Connect signer. - await priceBet.connect(getDefaultSigner(loser)) - await priceBet.deploy(1) - - const oracleSig: RabinSig = PriceBet2.parseSig(RESP.signature) - - const callContract = async () => - priceBet.methods.unlock( - RESP.data as ByteString, - oracleSig, - (sigResps) => findSig(sigResps, loserPubKey), - // Method call options: - { - pubKeyOrAddrToSign: loserPubKey, - } as MethodCallOptions - ) - - return expect(callContract()).to.be.rejectedWith( - /signature check failed/ - ) - }) -}) diff --git a/tests/turingMachine.test.ts b/tests/turingMachine.test.ts index 299cc269..722c772c 100644 --- a/tests/turingMachine.test.ts +++ b/tests/turingMachine.test.ts @@ -1,7 +1,9 @@ -import { expect } from 'chai' +import { expect, use } from 'chai' import { TuringMachine, StateStruct } from '../src/contracts/turingMachine' import { getDefaultSigner } from './utils/helper' import { MethodCallOptions } from 'scrypt-ts' +import chaiAsPromised from 'chai-as-promised' +use(chaiAsPromised) const allStates: StateStruct[] = [ {