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

Fix: CLValueMap deserialization from bytes, added unit tests for Tuple, Map, Numeric CLValues #484

Merged
merged 1 commit into from
Jan 3, 2025
Merged
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
32 changes: 0 additions & 32 deletions src/types/StoredValue.test.ts

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions src/types/clvalue/Map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { expect } from 'chai';

import { CLValueMap } from './Map';
import { CLTypeBool, CLTypeInt32, CLTypeMap, CLTypeString } from './cltype';
import { CLValueBool } from './Bool';
import { CLValueString } from './String';
import { CLValueInt32 } from './Numeric';
import { CLValueParser } from './Parser';

describe('CLValue CLMap implementation', () => {
it('Maps should return proper clType', () => {
const mapType = new CLTypeMap(CLTypeBool, CLTypeBool);
const testMap = new CLValueMap(mapType);
testMap.append(
CLValueBool.newCLValueBool(true),
CLValueBool.newCLValueBool(false)
);

expect(testMap.toString()).to.be.eq('(true="false")');
});

it('Should be able to create Map with proper values - correct by construction', () => {
const myKey = CLValueString.newCLString('ABC');
const myVal = CLValueInt32.newCLInt32(123);
const mapType = new CLTypeMap(CLTypeString, CLTypeInt32);
const testMap = new CLValueMap(mapType);
testMap.append(myKey, myVal);

expect(testMap).to.be.an.instanceof(CLValueMap);
});

it('Should be able to return proper values by calling .get() on Map', () => {
const myKey = CLValueString.newCLString('ABC');
const myVal = CLValueInt32.newCLInt32(10);
const mapType = new CLTypeMap(CLTypeString, CLTypeInt32);
const testMap = new CLValueMap(mapType);
testMap.append(myKey, myVal);

expect(testMap.get('ABC')).to.be.deep.eq(myVal);
});

it('Get() should return undefined on non-existing key', () => {
const mapType = new CLTypeMap(CLTypeString, CLTypeInt32);
const testMap = new CLValueMap(mapType);

expect(testMap.get('DEF')).to.be.deep.eq(undefined);
});

it('Should able to create empty Map by providing type', () => {
const mapType = new CLTypeMap(CLTypeString, CLTypeString);
const testMap = new CLValueMap(mapType);
const len = testMap.length();

expect(len).to.equal(0);
});

it('fromBytes() / toBytes()', () => {
const myKey = CLValueString.newCLString('ABC');
const myVal = CLValueInt32.newCLInt32(10);
const clValueMap = CLValueMap.newCLMap(CLTypeString, CLTypeInt32);
clValueMap.map?.append(myKey, myVal);

const bytes = clValueMap.bytes();
const fromBytes = CLValueParser.fromBytesByType(bytes, clValueMap.type)
.result;

expect(fromBytes).to.be.deep.eq(clValueMap);
});

it('fromJSON() / toJSON()', () => {
const myKey = CLValueString.newCLString('ABC');
const myVal = CLValueInt32.newCLInt32(10);
const clValueMap = CLValueMap.newCLMap(CLTypeString, CLTypeInt32);
clValueMap.map?.append(myKey, myVal);

const json = CLValueParser.toJSON(clValueMap);
const expectedJson = JSON.parse(
'{"bytes":"01000000030000004142430a000000","cl_type":{"Map":{"key":"String","value":"I32"}}}'
);

const fromJson = CLValueParser.fromJSON(expectedJson);

expect(fromJson).to.be.deep.eq(clValueMap);
expect(json).to.be.deep.eq(expectedJson);
});
});
10 changes: 6 additions & 4 deletions src/types/clvalue/Map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export class CLValueMap {

const { result: u32, bytes: u32Bytes } = CLValueUInt32.fromBytes(bytes);
const size = u32.toNumber();
const remainder = u32Bytes;
let remainder = u32Bytes;

if (size === 0) {
return { result: mapResult, bytes: remainder };
Expand All @@ -204,14 +204,16 @@ export class CLValueMap {
for (let i = 0; i < size; i++) {
if (remainder.length) {
const keyVal = CLValueParser.fromBytesByType(remainder, mapType.key);
remainder = keyVal.bytes;

if (!keyVal.result) {
if (!keyVal.bytes || !keyVal.result) {
continue;
}

const valVal = CLValueParser.fromBytesByType(remainder, mapType.val);
remainder = valVal.bytes;

if (!valVal.result) {
if (!valVal.bytes || !valVal.result) {
continue;
}

Expand All @@ -232,6 +234,6 @@ export class CLValueMap {
const mapType = new CLTypeMap(keyType, valType);
const clValue = new CLValue(mapType);
clValue.map = new CLValueMap(mapType);
return new CLValue(mapType);
return clValue;
}
}
9 changes: 4 additions & 5 deletions src/types/clvalue/Numeric/Abstract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { BigNumber, BigNumberish } from '@ethersproject/bignumber';
* Provides common methods and properties for numeric types.
*/
export abstract class CLValueNumeric {
protected value: BigNumberish;
protected value: BigNumber;

/**
* The constructor is protected to ensure this class cannot be instantiated directly.
* Subclasses can call this constructor using `super`.
*/
protected constructor(value: BigNumberish) {
this.value = value;
this.value = BigNumber.from(value);
}

/**
Expand All @@ -34,8 +34,7 @@ export abstract class CLValueNumeric {
* @returns The numeric value as a JavaScript number.
*/
public toNumber(): number {
const bigNumber = BigNumber.from(this.value);
return bigNumber.toNumber();
return this.value.toNumber();
}

/**
Expand All @@ -50,7 +49,7 @@ export abstract class CLValueNumeric {
* Retrieves the numeric value.
* @returns The numeric representation of the value.
*/
public getValue(): BigNumberish {
public getValue(): BigNumber {
return this.value;
}
}
2 changes: 1 addition & 1 deletion src/types/clvalue/Numeric/Int32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class CLValueInt32 extends CLValueNumeric {
throw new Error('buffer size is too small');
}
const i32Bytes = Uint8Array.from(source.subarray(0, 4));
const i32 = BigNumber.from(i32Bytes.slice().reverse());
const i32 = BigNumber.from(i32Bytes.slice().reverse()).fromTwos(32);

return { result: new CLValueInt32(i32), bytes: source.subarray(4) };
}
Expand Down
98 changes: 98 additions & 0 deletions src/types/clvalue/Numeric/Numeric.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { expect } from 'chai';
import { CLValueUInt32 } from './Uint32';
import { CLValueUInt128 } from './Uint128';
import { CLValueParser } from '../Parser';
import {
CLTypeInt64,
CLTypeUInt32,
CLTypeUInt64,
CLTypeUInt8
} from '../cltype';
import { CLValueUInt64 } from './Uint64';
import { CLValueUInt8 } from './Uint8';
import { CLValueInt64 } from './Int64';

const MAX_I64 = '9223372036854775807';
const MAX_U8 = 255;
const MAX_U32 = 4294967295;
const MAX_U64 = '18446744073709551615';

describe('Numeric implementation tests', () => {
it('Numeric value() should return proper value', () => {
const num = new CLValueUInt32(10);
expect(num.toNumber()).to.be.eq(10);
});

it('Numeric clType() should return proper type', () => {
const num = CLValueUInt128.newCLUInt128(20000);
expect(num.getType().toString()).to.be.eq('U128');
});

it('CLI32 do proper toBytes()/fromBytes()', () => {
const num1 = CLValueUInt32.newCLUInt32(10);
const num1bytes = num1.bytes();

const num2 = CLValueUInt32.newCLUInt32(1);
const num2bytes = num2.bytes();

expect(
CLValueParser.fromBytesByType(num1bytes, CLTypeUInt32).result
).to.be.deep.eq(num1);
expect(
CLValueParser.fromBytesByType(num2bytes, CLTypeUInt32).result
).to.be.deep.eq(num2);
});

it('CLI64 do proper toBytes()/fromBytes()', () => {
const num1 = CLValueInt64.newCLInt64(10);
const num1bytes = num1.bytes();

const num2 = CLValueInt64.newCLInt64(MAX_I64);
const num2bytes = num2.bytes();

expect(
CLValueParser.fromBytesByType(num1bytes, CLTypeInt64).result
).to.be.deep.eq(num1);
expect(
CLValueParser.fromBytesByType(num2bytes, CLTypeInt64).result
).to.be.deep.eq(num2);
});

it('CLU8 do proper toBytes()/fromBytes()', () => {
const num1 = CLValueUInt8.newCLUint8(MAX_U8);
const num1bytes = num1.bytes();

expect(
CLValueParser.fromBytesByType(num1bytes, CLTypeUInt8).result
).to.be.deep.eq(num1);
});

it('CLU32 do proper toBytes()/fromBytes()', () => {
const num1 = CLValueUInt32.newCLUInt32(MAX_U32);
const num1bytes = num1.bytes();

expect(
CLValueParser.fromBytesByType(num1bytes, CLTypeUInt32).result
).to.be.deep.eq(num1);
});

it('CLU64 do proper toBytes()/fromBytes()', () => {
const num1 = CLValueUInt64.newCLUint64(MAX_U64);
const num1bytes = num1.bytes();

expect(
CLValueParser.fromBytesByType(num1bytes, CLTypeUInt64).result
).to.be.deep.eq(num1);
});

it('CLU64 toJSON() / fromJSON()', () => {
const num1 = CLValueUInt64.newCLUint64(MAX_U64);
const num1JSON = CLValueParser.toJSON(num1);
const expectedJson = JSON.parse(
'{"bytes":"ffffffffffffffff","cl_type":"U64"}'
);

expect(num1JSON).to.be.deep.eq(expectedJson);
expect(CLValueParser.fromJSON(expectedJson)).to.be.deep.eq(num1);
});
});
2 changes: 1 addition & 1 deletion src/types/clvalue/Result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class CLValueResult {
clType: CLTypeResult
): IResultWithBytes<CLValueResult> {
const { result: u8, bytes: u8Bytes } = CLValueUInt8.fromBytes(source);
const resultTag = u8?.getValue();
const resultTag = u8?.toNumber();
const isSuccess = resultTag === 1;
const innerType = isSuccess ? clType.innerOk : clType.innerErr;

Expand Down
Loading
Loading