From 70b61330a8d604c73958f0bdf3e603b48eafbd7c Mon Sep 17 00:00:00 2001 From: gyroflaw <83718263+gyroflaw@users.noreply.github.com> Date: Fri, 18 Aug 2023 17:47:19 +0900 Subject: [PATCH 1/5] feat: added CLContractHash & CLContractPackageHash --- src/lib/CLValue/ContractHash.test.ts | 56 +++++++++++++ src/lib/CLValue/ContractHash.ts | 80 ++++++++++++++++++ src/lib/CLValue/ContractPackageHash.test.ts | 65 +++++++++++++++ src/lib/CLValue/ContractPackageHash.ts | 89 +++++++++++++++++++++ src/lib/CLValue/constants.ts | 8 ++ src/lib/CLValue/index.ts | 2 + src/lib/CLValue/utils.ts | 12 ++- 7 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 src/lib/CLValue/ContractHash.test.ts create mode 100644 src/lib/CLValue/ContractHash.ts create mode 100644 src/lib/CLValue/ContractPackageHash.test.ts create mode 100644 src/lib/CLValue/ContractPackageHash.ts diff --git a/src/lib/CLValue/ContractHash.test.ts b/src/lib/CLValue/ContractHash.test.ts new file mode 100644 index 000000000..2d59b7674 --- /dev/null +++ b/src/lib/CLValue/ContractHash.test.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai'; + +import { CLValueParsers } from './Abstract'; +import { CLContractHash, CLContractHashType } from './ContractHash'; + +describe('CLAccountHash', () => { + it('Should be able to return proper value by calling .value()', () => { + const arr8 = Uint8Array.from(Array(32).fill(42)); + const myHash = new CLContractHash(arr8); + + expect(myHash.value()).to.be.deep.eq(arr8); + }); + + it('toBytes() / fromBytes() do proper bytes serialization', () => { + const expectedBytes = Uint8Array.from(Array(32).fill(42)); + const expectedHash = new CLContractHash(expectedBytes); + + const bytes = CLValueParsers.toBytes(expectedHash).unwrap(); + const hash = CLValueParsers.fromBytes( + bytes, + new CLContractHashType() + ).unwrap(); + + expect(bytes).to.deep.eq(expectedBytes); + expect(hash).to.deep.eq(expectedHash); + }); + + it('should throw error when invalid bytearray is provided', () => { + const arr8 = Uint8Array.from(Array(31).fill(42)); + const badFn = () => new CLContractHash(arr8); + + expect(badFn).throw('Invalid length'); + }); + + it('fromFormattedString / toFormattedString', () => { + const arr8 = Uint8Array.from(Array(32).fill(42)); + const myHash = new CLContractHash(arr8); + const formattedContractHash = myHash.toFormattedString(); + expect(formattedContractHash).to.eq( + 'contract-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a' + ); + + const myHashFromString = CLContractHash.fromFormattedString( + formattedContractHash + ); + expect(myHash.data).deep.eq(myHashFromString.data); + }); + + it('should throw error when invalid formatted string is provided', () => { + const badFn = () => + CLContractHash.fromFormattedString( + 'contrac-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a' + ); + expect(badFn).throw('Invalid Format'); + }); +}); diff --git a/src/lib/CLValue/ContractHash.ts b/src/lib/CLValue/ContractHash.ts new file mode 100644 index 000000000..0cf5ef1e4 --- /dev/null +++ b/src/lib/CLValue/ContractHash.ts @@ -0,0 +1,80 @@ +import { Err, Ok } from 'ts-results'; + +import { decodeBase16, encodeBase16 } from '../Conversions'; +import { ResultAndRemainder, resultHelper, ToBytesResult } from './Abstract'; +import { + CLByteArray, + CLByteArrayBytesParser, + CLByteArrayType +} from './ByteArray'; +import { + CLErrorCodes, + CONTRACT_HASH_TYPE, + CONTRACT_STRING_PREFIX, + KEY_HASH_LENGTH +} from './constants'; + +export class CLContractHashType extends CLByteArrayType { + linksTo = CONTRACT_HASH_TYPE; + + constructor() { + super(KEY_HASH_LENGTH); + } +} + +export class CLContractHashBytesParser extends CLByteArrayBytesParser { + toBytes(value: CLContractHash): ToBytesResult { + return Ok(value.data); + } + + fromBytesWithRemainder( + bytes: Uint8Array + ): ResultAndRemainder { + if (bytes.length < KEY_HASH_LENGTH) { + return resultHelper( + Err(CLErrorCodes.EarlyEndOfStream) + ); + } + + const contractHashBytes = bytes.subarray(0, KEY_HASH_LENGTH); + const contractHash = new CLContractHash(contractHashBytes); + return resultHelper(Ok(contractHash), bytes.subarray(KEY_HASH_LENGTH)); + } +} + +export class CLContractHash extends CLByteArray { + /** + * Constructs a new `CLContractHash`. + * + * @param v The bytes array with 32 length. + */ + constructor(v: Uint8Array) { + if (v.length !== KEY_HASH_LENGTH) { + throw new Error('Invalid length'); + } + super(v); + } + + /** + * Return ContractHash from formatted string (starts with `contract-`). + * @param v formatted string + * @returns CLContractHash + */ + static fromFormattedString(v: string): CLContractHash { + if (!v.startsWith(CONTRACT_STRING_PREFIX)) { + throw new Error('Invalid Format'); + } + + return new CLContractHash( + decodeBase16(v.slice(CONTRACT_STRING_PREFIX.length)) + ); + } + + /** + * Return formatted string (starts with `contract-`). + * @returns formatted string + */ + toFormattedString(): string { + return CONTRACT_STRING_PREFIX + encodeBase16(this.data); + } +} diff --git a/src/lib/CLValue/ContractPackageHash.test.ts b/src/lib/CLValue/ContractPackageHash.test.ts new file mode 100644 index 000000000..e2b19e637 --- /dev/null +++ b/src/lib/CLValue/ContractPackageHash.test.ts @@ -0,0 +1,65 @@ +import { expect } from 'chai'; + +import { CLValueParsers } from './Abstract'; +import { + CLContractPackageHash, + CLContractPackageHashType +} from './ContractPackageHash'; + +describe('CLAccountHash', () => { + it('Should be able to return proper value by calling .value()', () => { + const arr8 = Uint8Array.from(Array(32).fill(42)); + const myHash = new CLContractPackageHash(arr8); + + expect(myHash.value()).to.be.deep.eq(arr8); + }); + + it('toBytes() / fromBytes() do proper bytes serialization', () => { + const expectedBytes = Uint8Array.from(Array(32).fill(42)); + const expectedHash = new CLContractPackageHash(expectedBytes); + + const bytes = CLValueParsers.toBytes(expectedHash).unwrap(); + const hash = CLValueParsers.fromBytes( + bytes, + new CLContractPackageHashType() + ).unwrap(); + + expect(bytes).to.deep.eq(expectedBytes); + expect(hash).to.deep.eq(expectedHash); + }); + + it('should throw error when invalid bytearray is provided', () => { + const arr8 = Uint8Array.from(Array(31).fill(42)); + const badFn = () => new CLContractPackageHash(arr8); + + expect(badFn).throw('Invalid length'); + }); + + it('fromFormattedString / toFormattedString', () => { + const arr8 = Uint8Array.from(Array(32).fill(42)); + const myHash = new CLContractPackageHash(arr8); + const formattedContractHash = myHash.toFormattedString(); + expect(formattedContractHash).to.eq( + 'contract-package-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a' + ); + + const myHashFromString = CLContractPackageHash.fromFormattedString( + formattedContractHash + ); + expect(myHash.data).deep.eq(myHashFromString.data); + + // Should support legacy string + const myHashFromLegacyString = CLContractPackageHash.fromFormattedString( + 'contract-package-wasm2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a' + ); + expect(myHash.data).deep.eq(myHashFromLegacyString.data); + }); + + it('should throw error when invalid formatted string is provided', () => { + const badFn = () => + CLContractPackageHash.fromFormattedString( + 'contrac-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a' + ); + expect(badFn).throw('Invalid Format'); + }); +}); diff --git a/src/lib/CLValue/ContractPackageHash.ts b/src/lib/CLValue/ContractPackageHash.ts new file mode 100644 index 000000000..db68c89c6 --- /dev/null +++ b/src/lib/CLValue/ContractPackageHash.ts @@ -0,0 +1,89 @@ +import { Err, Ok } from 'ts-results'; + +import { decodeBase16, encodeBase16 } from '../Conversions'; +import { ResultAndRemainder, resultHelper, ToBytesResult } from './Abstract'; +import { + CLByteArray, + CLByteArrayBytesParser, + CLByteArrayType +} from './ByteArray'; +import { + CLErrorCodes, + KEY_HASH_LENGTH, + PACKAGE_STRING_PREFIX, + PACKAGE_STRING_LEGACY_EXTRA_PREFIX, + CONTRACT_PACKAGE_HASH_TYPE +} from './constants'; + +export class CLContractPackageHashType extends CLByteArrayType { + linksTo = CONTRACT_PACKAGE_HASH_TYPE; + constructor() { + super(KEY_HASH_LENGTH); + } +} + +export class CLContractPackageHashBytesParser extends CLByteArrayBytesParser { + toBytes(value: CLContractPackageHash): ToBytesResult { + return Ok(value.data); + } + + fromBytesWithRemainder( + bytes: Uint8Array + ): ResultAndRemainder { + if (bytes.length < KEY_HASH_LENGTH) { + return resultHelper( + Err(CLErrorCodes.EarlyEndOfStream) + ); + } + + const contractPackageHashBytes = bytes.subarray(0, KEY_HASH_LENGTH); + const contractPackageHash = new CLContractPackageHash( + contractPackageHashBytes + ); + return resultHelper( + Ok(contractPackageHash), + bytes.subarray(KEY_HASH_LENGTH) + ); + } +} + +export class CLContractPackageHash extends CLByteArray { + /** + * Constructs a new `CLContractPackageHash`. + * + * @param v The bytes array with 32 length. + */ + constructor(v: Uint8Array) { + if (v.length !== KEY_HASH_LENGTH) { + throw new Error('Invalid length'); + } + super(v); + } + + /** + * Return CLContractPackageHash from formatted string (starts with `contract-package-`). + * @param v formatted string + * @returns CLContractPackageHash + */ + static fromFormattedString(v: string): CLContractPackageHash { + if (!v.startsWith(PACKAGE_STRING_PREFIX)) { + throw new Error('Invalid Format'); + } + let packageHash = v.slice(PACKAGE_STRING_PREFIX.length); + + // We need to support the legacy prefix of "contract-package-wasm". + packageHash = packageHash.startsWith(PACKAGE_STRING_LEGACY_EXTRA_PREFIX) + ? packageHash.slice(PACKAGE_STRING_LEGACY_EXTRA_PREFIX.length) + : packageHash; + + return new CLContractPackageHash(decodeBase16(packageHash)); + } + + /** + * Return formatted string (starts with `contract-package-`). + * @returns formatted string + */ + toFormattedString(): string { + return PACKAGE_STRING_PREFIX + encodeBase16(this.data); + } +} diff --git a/src/lib/CLValue/constants.ts b/src/lib/CLValue/constants.ts index 096629c9b..d226af455 100644 --- a/src/lib/CLValue/constants.ts +++ b/src/lib/CLValue/constants.ts @@ -97,3 +97,11 @@ export const TUPLE3_TYPE = 'Tuple3'; export const ANY_TYPE = 'Any'; export const ACCOUNT_HASH_TYPE = 'AccountHash'; + +export const CONTRACT_STRING_PREFIX = 'contract-'; +export const PACKAGE_STRING_PREFIX = 'contract-package-'; +export const PACKAGE_STRING_LEGACY_EXTRA_PREFIX = 'wasm'; +export const CONTRACT_HASH_TYPE = 'ContractHash'; +export const CONTRACT_PACKAGE_HASH_TYPE = 'ContractPackageHash'; + +export const KEY_HASH_LENGTH = 32; diff --git a/src/lib/CLValue/index.ts b/src/lib/CLValue/index.ts index 3b95924b6..6e84fcee5 100644 --- a/src/lib/CLValue/index.ts +++ b/src/lib/CLValue/index.ts @@ -2,6 +2,8 @@ export * from './Abstract'; export * from './Builders'; export * from './ByteArray'; export * from './Any'; +export * from './ContractHash'; +export * from './ContractPackageHash'; export * from './Key'; export * from './List'; export * from './Map'; diff --git a/src/lib/CLValue/utils.ts b/src/lib/CLValue/utils.ts index a49db94bd..aca2cc2a9 100644 --- a/src/lib/CLValue/utils.ts +++ b/src/lib/CLValue/utils.ts @@ -25,8 +25,12 @@ import { TUPLE3_TYPE, OPTION_TYPE, ANY_TYPE, - CLTypeTag + CLTypeTag, + CONTRACT_PACKAGE_HASH_TYPE, + CONTRACT_HASH_TYPE } from './constants'; +import {} from './ContractHash'; +import {} from './ContractPackageHash'; import { CLValueBytesParsers, CLAccountHashBytesParser, @@ -75,6 +79,8 @@ import { CLMapBytesParser, CLUnitType, CLUnitBytesParser, + CLContractHashBytesParser, + CLContractPackageHashBytesParser, CLAnyType } from './index'; @@ -185,6 +191,10 @@ export const matchByteParserByCLType = ( return Ok(new CLU512BytesParser()); case BYTE_ARRAY_TYPE: return Ok(new CLByteArrayBytesParser()); + case CONTRACT_HASH_TYPE: + return Ok(new CLContractHashBytesParser()); + case CONTRACT_PACKAGE_HASH_TYPE: + return Ok(new CLContractPackageHashBytesParser()); case UREF_TYPE: return Ok(new CLURefBytesParser()); case KEY_TYPE: From 5130f15644384f463874937c02dac3a564e899db Mon Sep 17 00:00:00 2001 From: gyroflaw <83718263+gyroflaw@users.noreply.github.com> Date: Fri, 18 Aug 2023 17:49:35 +0900 Subject: [PATCH 2/5] remove unused import --- src/lib/CLValue/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/CLValue/utils.ts b/src/lib/CLValue/utils.ts index aca2cc2a9..e87ebd01a 100644 --- a/src/lib/CLValue/utils.ts +++ b/src/lib/CLValue/utils.ts @@ -29,8 +29,6 @@ import { CONTRACT_PACKAGE_HASH_TYPE, CONTRACT_HASH_TYPE } from './constants'; -import {} from './ContractHash'; -import {} from './ContractPackageHash'; import { CLValueBytesParsers, CLAccountHashBytesParser, From 6c17597fe90905217c343d0026e5fd34862dcbf5 Mon Sep 17 00:00:00 2001 From: gyroflaw <83718263+gyroflaw@users.noreply.github.com> Date: Wed, 17 Jan 2024 02:53:39 -0500 Subject: [PATCH 3/5] update formatted string types --- src/lib/CLValue/ContractHash.ts | 8 +++++--- src/lib/CLValue/ContractPackageHash.ts | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/CLValue/ContractHash.ts b/src/lib/CLValue/ContractHash.ts index 0cf5ef1e4..b4aedc162 100644 --- a/src/lib/CLValue/ContractHash.ts +++ b/src/lib/CLValue/ContractHash.ts @@ -14,6 +14,8 @@ import { KEY_HASH_LENGTH } from './constants'; +export type FormattedContractHash = `contact-${string}` + export class CLContractHashType extends CLByteArrayType { linksTo = CONTRACT_HASH_TYPE; @@ -60,7 +62,7 @@ export class CLContractHash extends CLByteArray { * @param v formatted string * @returns CLContractHash */ - static fromFormattedString(v: string): CLContractHash { + static fromFormattedString(v: FormattedContractHash): CLContractHash { if (!v.startsWith(CONTRACT_STRING_PREFIX)) { throw new Error('Invalid Format'); } @@ -74,7 +76,7 @@ export class CLContractHash extends CLByteArray { * Return formatted string (starts with `contract-`). * @returns formatted string */ - toFormattedString(): string { - return CONTRACT_STRING_PREFIX + encodeBase16(this.data); + toFormattedString(): FormattedContractHash { + return CONTRACT_STRING_PREFIX + encodeBase16(this.data) as FormattedContractHash; } } diff --git a/src/lib/CLValue/ContractPackageHash.ts b/src/lib/CLValue/ContractPackageHash.ts index db68c89c6..7d391bd27 100644 --- a/src/lib/CLValue/ContractPackageHash.ts +++ b/src/lib/CLValue/ContractPackageHash.ts @@ -15,6 +15,8 @@ import { CONTRACT_PACKAGE_HASH_TYPE } from './constants'; +export type FormattedContractPackageHash = `contact-package-${string}` + export class CLContractPackageHashType extends CLByteArrayType { linksTo = CONTRACT_PACKAGE_HASH_TYPE; constructor() { @@ -65,7 +67,7 @@ export class CLContractPackageHash extends CLByteArray { * @param v formatted string * @returns CLContractPackageHash */ - static fromFormattedString(v: string): CLContractPackageHash { + static fromFormattedString(v: FormattedContractPackageHash): CLContractPackageHash { if (!v.startsWith(PACKAGE_STRING_PREFIX)) { throw new Error('Invalid Format'); } @@ -83,7 +85,7 @@ export class CLContractPackageHash extends CLByteArray { * Return formatted string (starts with `contract-package-`). * @returns formatted string */ - toFormattedString(): string { - return PACKAGE_STRING_PREFIX + encodeBase16(this.data); + toFormattedString(): FormattedContractPackageHash { + return PACKAGE_STRING_PREFIX + encodeBase16(this.data) as FormattedContractPackageHash; } } From caf70332b6282733e362d5eb883fc3cf5b92fde6 Mon Sep 17 00:00:00 2001 From: gyroflaw <83718263+gyroflaw@users.noreply.github.com> Date: Wed, 17 Jan 2024 02:56:50 -0500 Subject: [PATCH 4/5] fix ts issue --- src/lib/CLValue/ContractHash.test.ts | 1 + src/lib/CLValue/ContractPackageHash.test.ts | 1 + src/lib/CLValue/ContractPackageHash.ts | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/CLValue/ContractHash.test.ts b/src/lib/CLValue/ContractHash.test.ts index 2d59b7674..b95df3104 100644 --- a/src/lib/CLValue/ContractHash.test.ts +++ b/src/lib/CLValue/ContractHash.test.ts @@ -49,6 +49,7 @@ describe('CLAccountHash', () => { it('should throw error when invalid formatted string is provided', () => { const badFn = () => CLContractHash.fromFormattedString( + // @ts-ignore 'contrac-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a' ); expect(badFn).throw('Invalid Format'); diff --git a/src/lib/CLValue/ContractPackageHash.test.ts b/src/lib/CLValue/ContractPackageHash.test.ts index e2b19e637..0993c8a95 100644 --- a/src/lib/CLValue/ContractPackageHash.test.ts +++ b/src/lib/CLValue/ContractPackageHash.test.ts @@ -58,6 +58,7 @@ describe('CLAccountHash', () => { it('should throw error when invalid formatted string is provided', () => { const badFn = () => CLContractPackageHash.fromFormattedString( + // @ts-ignore 'contrac-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a' ); expect(badFn).throw('Invalid Format'); diff --git a/src/lib/CLValue/ContractPackageHash.ts b/src/lib/CLValue/ContractPackageHash.ts index 7d391bd27..23e267476 100644 --- a/src/lib/CLValue/ContractPackageHash.ts +++ b/src/lib/CLValue/ContractPackageHash.ts @@ -67,7 +67,7 @@ export class CLContractPackageHash extends CLByteArray { * @param v formatted string * @returns CLContractPackageHash */ - static fromFormattedString(v: FormattedContractPackageHash): CLContractPackageHash { + static fromFormattedString(v: FormattedContractPackageHash | `contract-package-wasm${string}`): CLContractPackageHash { if (!v.startsWith(PACKAGE_STRING_PREFIX)) { throw new Error('Invalid Format'); } From d1a44f5e6f02e03c85a44f30c5024bbd992d462f Mon Sep 17 00:00:00 2001 From: gyroflaw <83718263+gyroflaw@users.noreply.github.com> Date: Mon, 1 Apr 2024 08:20:55 -0400 Subject: [PATCH 5/5] test: update test name --- src/lib/CLValue/ContractHash.test.ts | 2 +- src/lib/CLValue/ContractPackageHash.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/CLValue/ContractHash.test.ts b/src/lib/CLValue/ContractHash.test.ts index b95df3104..16868f5ed 100644 --- a/src/lib/CLValue/ContractHash.test.ts +++ b/src/lib/CLValue/ContractHash.test.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { CLValueParsers } from './Abstract'; import { CLContractHash, CLContractHashType } from './ContractHash'; -describe('CLAccountHash', () => { +describe('CLContractHash', () => { it('Should be able to return proper value by calling .value()', () => { const arr8 = Uint8Array.from(Array(32).fill(42)); const myHash = new CLContractHash(arr8); diff --git a/src/lib/CLValue/ContractPackageHash.test.ts b/src/lib/CLValue/ContractPackageHash.test.ts index 0993c8a95..f4c657367 100644 --- a/src/lib/CLValue/ContractPackageHash.test.ts +++ b/src/lib/CLValue/ContractPackageHash.test.ts @@ -6,7 +6,7 @@ import { CLContractPackageHashType } from './ContractPackageHash'; -describe('CLAccountHash', () => { +describe('CLContractPackageHash', () => { it('Should be able to return proper value by calling .value()', () => { const arr8 = Uint8Array.from(Array(32).fill(42)); const myHash = new CLContractPackageHash(arr8);