Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add CLContractHash & CLContractPackageHash #414

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/lib/CLValue/ContractHash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { expect } from 'chai';

import { CLValueParsers } from './Abstract';
import { CLContractHash, CLContractHashType } from './ContractHash';

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);

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(
// @ts-ignore
'contrac-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a'
);
expect(badFn).throw('Invalid Format');
});
});
82 changes: 82 additions & 0 deletions src/lib/CLValue/ContractHash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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 type FormattedContractHash = `contact-${string}`

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<CLContractHash, CLErrorCodes> {
if (bytes.length < KEY_HASH_LENGTH) {
return resultHelper<CLContractHash, CLErrorCodes>(
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: FormattedContractHash): 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(): FormattedContractHash {
return CONTRACT_STRING_PREFIX + encodeBase16(this.data) as FormattedContractHash;
}
}
66 changes: 66 additions & 0 deletions src/lib/CLValue/ContractPackageHash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { expect } from 'chai';

import { CLValueParsers } from './Abstract';
import {
CLContractPackageHash,
CLContractPackageHashType
} from './ContractPackageHash';

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);

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(
// @ts-ignore
'contrac-2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a'
);
expect(badFn).throw('Invalid Format');
});
});
91 changes: 91 additions & 0 deletions src/lib/CLValue/ContractPackageHash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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 type FormattedContractPackageHash = `contact-package-${string}`

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<CLContractPackageHash, CLErrorCodes> {
if (bytes.length < KEY_HASH_LENGTH) {
return resultHelper<CLContractPackageHash, CLErrorCodes>(
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: FormattedContractPackageHash | `contract-package-wasm${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(): FormattedContractPackageHash {
return PACKAGE_STRING_PREFIX + encodeBase16(this.data) as FormattedContractPackageHash;
}
}
8 changes: 8 additions & 0 deletions src/lib/CLValue/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 2 additions & 0 deletions src/lib/CLValue/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
10 changes: 9 additions & 1 deletion src/lib/CLValue/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import {
TUPLE3_TYPE,
OPTION_TYPE,
ANY_TYPE,
CLTypeTag
CLTypeTag,
CONTRACT_PACKAGE_HASH_TYPE,
CONTRACT_HASH_TYPE
} from './constants';
import {
CLValueBytesParsers,
Expand Down Expand Up @@ -75,6 +77,8 @@ import {
CLMapBytesParser,
CLUnitType,
CLUnitBytesParser,
CLContractHashBytesParser,
CLContractPackageHashBytesParser,
CLAnyType
} from './index';

Expand Down Expand Up @@ -185,6 +189,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:
Expand Down
Loading