Skip to content

Commit

Permalink
Merge pull request #484 from casper-ecosystem/bugfix/map-deserializat…
Browse files Browse the repository at this point in the history
…ion-from-bytes

Fix: CLValueMap deserialization from bytes, added unit tests for Tuple, Map, Numeric CLValues
  • Loading branch information
alexmyshchyshyn authored Jan 3, 2025
2 parents 28e794d + 975c9d1 commit e5358ed
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 43 deletions.
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

0 comments on commit e5358ed

Please sign in to comment.