diff --git a/VERSIONLOG.md b/VERSIONLOG.md index 0cb12302..b10d9918 100644 --- a/VERSIONLOG.md +++ b/VERSIONLOG.md @@ -43,4 +43,9 @@ * remove `sources` field * remove `asm` field +## version = 10 + +* state hex start with push data + + The minimum version number currently supported: `8` \ No newline at end of file diff --git a/src/abi.ts b/src/abi.ts index 99994000..ad9ede08 100644 --- a/src/abi.ts +++ b/src/abi.ts @@ -315,8 +315,10 @@ export class ABICoder { switch (version) { case Int(0): + case Int(1): { - const [isGenesis, args] = Stateful.parseStateHex(contract, scriptHex); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [isGenesis, _, args] = Stateful.parseStateHex(contract, scriptHex); contract.statePropsArgs = args; contract.isGenesis = isGenesis; } diff --git a/src/compilerWrapper.ts b/src/compilerWrapper.ts index 8d91ba5c..35b03851 100644 --- a/src/compilerWrapper.ts +++ b/src/compilerWrapper.ts @@ -112,8 +112,18 @@ export class CompileResult { toArtifact(): ContractArtifact { + const compilerVersion = this.compilerVersion || '0.0.0'; + + const [major, minor, patch] = compilerVersion.split('.'); + + let version = CURRENT_CONTRACT_ARTIFACT_VERSION; + + if (parseInt(major) == 1 && parseInt(minor) < 20) { + version = 9; + } + const artifact: ContractArtifact = { - version: CURRENT_CONTRACT_ARTIFACT_VERSION, + version: version, compilerVersion: this.compilerVersion || '0.0.0', contract: this.contract || '', md5: this.md5 || '', diff --git a/src/contract.ts b/src/contract.ts index 9c14b7bd..79756160 100644 --- a/src/contract.ts +++ b/src/contract.ts @@ -38,7 +38,7 @@ export interface VerifyResult { success: boolean; error?: VerifyError; } -export const CURRENT_CONTRACT_ARTIFACT_VERSION = 9; +export const CURRENT_CONTRACT_ARTIFACT_VERSION = 10; export const SUPPORTED_MINIMUM_VERSION = 8; export interface ContractArtifact { @@ -153,6 +153,14 @@ export class AbstractContract { return artifact.version || 0; } + get stateVersion(): number { + const version = this.version; + if (version >= 10) { + return Stateful.CURRENT_STATE_VERSION; + } + return 0; + } + addFunctionCall(f: FunctionCall): void { this.calledPubFunctions.push(f); } @@ -239,7 +247,14 @@ export class AbstractContract { } }); - return this.codePart.add(bsv.Script.fromHex(Stateful.buildState(newState, false, this.resolver))); + switch (this.stateVersion) { + case 0: + return this.codePart.add(Stateful.buildStateV0(newState, false, this.resolver)); + case 1: + return this.codePart.add(Stateful.buildStateV1(newState, false, this.resolver)); + default: + throw new Error(`Unsupport state version: ${this.stateVersion}`); + } } run_verify(unlockingScript: bsv.Script | string | undefined, txContext?: TxContext): VerifyResult { @@ -402,8 +417,16 @@ export class AbstractContract { get dataPart(): Script | undefined { if (AbstractContract.isStateful(this)) { - const state = Stateful.buildState(this.statePropsArgs, this.isGenesis, this.resolver); - return bsv.Script.fromHex(state); + + + switch (this.stateVersion) { + case 0: + return Stateful.buildStateV0(this.statePropsArgs, this.isGenesis, this.resolver); + case 1: + return Stateful.buildStateV1(this.statePropsArgs, this.isGenesis, this.resolver); + default: + throw new Error(`Unsupport state version: ${this.stateVersion}`); + } } if (this._dataPartInHex) { @@ -448,9 +471,13 @@ export class AbstractContract { setDataPartInHex(hex: string): void { this._dataPartInHex = hex.trim(); if (AbstractContract.isStateful(this)) { - const [isGenesis, args] = Stateful.parseStateHex(this, this._dataPartInHex); + const [isGenesis, stateVersion, args] = Stateful.parseStateHex(this, this._dataPartInHex); this.statePropsArgs = args; this.isGenesis = isGenesis; + + if (stateVersion != this.stateVersion) { + throw new Error('the dataPart in hex can\'t match current artifact version'); + } } } diff --git a/src/stateful.ts b/src/stateful.ts index f72a826d..dfac0d31 100644 --- a/src/stateful.ts +++ b/src/stateful.ts @@ -6,7 +6,8 @@ import { Bool, Bytes, Int, isBytes, OpCodeType, PrivKey, PubKey, Ripemd160, Scry export default class Stateful { // state version - static readonly CURRENT_STATE_VERSION = Int(0); + // TODO: change to 1 if we upgrade scrypt compiler + static readonly CURRENT_STATE_VERSION = 1; static int2hex(n: Int): string { let asm = ''; @@ -95,14 +96,8 @@ export default class Stateful { } - /** - * only used for state contract - * @param args - * @param isGenesis - * @param finalTypeResolver - * @returns - */ - static buildState(args: Arguments, isGenesis: boolean, resolver: TypeResolver): string { + + static buildStateHex(args: Arguments, isGenesis: boolean, resolver: TypeResolver, version: Int): string { const args_ = args.map(arg => { return flatternArg(arg, resolver, { state: true, ignoreValue: false }); @@ -118,16 +113,34 @@ export default class Stateful { state_hex += args_.map(a => Stateful.toHex(a.value, a.type)).join(''); //append meta - if (state_hex) { - const state_len = state_hex.length / 2; - state_hex += num2bin(BigInt(state_len), 4) + num2bin(Stateful.CURRENT_STATE_VERSION, 1); - return state_hex; - } - + const state_len = state_hex.length / 2; + state_hex += num2bin(BigInt(state_len), 4) + num2bin(version, 1); return state_hex; } + /** + * only used for state contract + * @param args + * @param isGenesis + * @param resolver + * @returns + */ + static buildStateV0(args: Arguments, isGenesis: boolean, resolver: TypeResolver): bsv.Script { + return bsv.Script.fromHex(Stateful.buildStateHex(args, isGenesis, resolver, Int(0))); + } + + /** + * only used for state contract + * @param args + * @param isGenesis + * @param resolver + * @returns + */ + static buildStateV1(args: Arguments, isGenesis: boolean, resolver: TypeResolver): bsv.Script { + return bsv.Script.fromASM(Stateful.buildStateHex(args, isGenesis, resolver, Int(1))); + } + static buildDefaultStateArgs(contract: AbstractContract): Arguments { @@ -240,7 +253,13 @@ export default class Stateful { } - static parseStateHex(contract: AbstractContract, scriptHex: string): [boolean, Arguments] { + /** + * parsing state in hex format + * @param contract + * @param scriptHex + * @returns [isGenesis, version , states] + */ + static parseStateHex(contract: AbstractContract, scriptHex: string): [boolean, number, Arguments] { const metaScript = scriptHex.substr(scriptHex.length - 10, 10); const version = Number(bin2num(metaScript.substr(metaScript.length - 2, 2))); @@ -279,7 +298,7 @@ export default class Stateful { return deserializeArgfromHex(contract.resolver, arg, stateTemplateArgs, { state: true }); }); - return [isGenesis, args]; + return [isGenesis, version, args]; } } \ No newline at end of file diff --git a/test/state.test.ts b/test/state.test.ts index 9c5358ad..1424ad43 100644 --- a/test/state.test.ts +++ b/test/state.test.ts @@ -26,12 +26,18 @@ describe('state_test', () => { Sig("304402207b6ce0aaae3a379721a364ab11414abd658a9940c10d48cd0bc6b273e81d058902206f6c0671066aef4c0de58ab8c349fde38ef3ea996b9f2e79241ebad96049299541"), ); - expect(stateExample.dataPart?.toHex()).to.be.equal('01010001000101000100010001000100010001001400000000'); + expect(stateExample.dataPart?.toHex()).to.be.equal( + + stateExample.stateVersion >= 1 ? + '1901010001000101000100010001000100010001001400000001' + : '01010001000101000100010001000100010001001400000000'); stateExample.counter++; stateExample.state_bytes = Bytes('010101'); stateExample.state_bool = false; - expect(stateExample.dataPart?.toHex()).to.be.equal('000101030101010001000100010001000100010001001600000000'); + expect(stateExample.dataPart?.toHex()).to.be.equal(stateExample.stateVersion >= 1 ? + '1b000101030101010001000100010001000100010001001600000001' + : '000101030101010001000100010001000100010001001600000000'); }); @@ -49,11 +55,15 @@ describe('state_test', () => { ); - expect(stateExample.dataPart?.toHex()).to.be.eq('01010001000101000100010001000100010001001400000000'); + expect(stateExample.dataPart?.toHex()).to.be.eq(stateExample.stateVersion >= 1 ? + '1901010001000101000100010001000100010001001400000001' + : '01010001000101000100010001000100010001001400000000'); let newStateExample = StateExample.fromHex(stateExample.lockingScript.toHex()); - expect(newStateExample.dataPart?.toHex()).to.be.eq('01010001000101000100010001000100010001001400000000'); + expect(newStateExample.dataPart?.toHex()).to.be.eq(stateExample.stateVersion >= 1 ? + '1901010001000101000100010001000100010001001400000001' + : '01010001000101000100010001000100010001001400000000'); expect(newStateExample.counter == Int(0)).to.be.true; expect(newStateExample.state_bytes == Bytes('00')).to.be.true; @@ -81,7 +91,9 @@ describe('state_test', () => { ); stateExample.counter = 3n; - expect(stateExample.dataPart?.toHex()).to.be.eq('00010301000101000100010001000100010001001400000000'); + expect(stateExample.dataPart?.toHex()).to.be.eq(stateExample.stateVersion >= 1 ? + '1900010301000101000100010001000100010001001400000001' + : '00010301000101000100010001000100010001001400000000'); let newStateExample = StateExample.fromHex(stateExample.lockingScript.toHex());