diff --git a/src/abi/typeFormulaParser.ts b/src/abi/typeFormulaParser.ts index e1868353..2a582479 100644 --- a/src/abi/typeFormulaParser.ts +++ b/src/abi/typeFormulaParser.ts @@ -83,7 +83,7 @@ export class TypeFormulaParser { private acquireTypeWithParameters(stack: any[]): TypeFormula { const typeParameters = this.acquireTypeParameters(stack); const typeName = stack.pop(); - if (typeName === "ManagedDecimal" || "ManagedDecimalSigned") { + if (typeName === "ManagedDecimal" || typeName === "ManagedDecimalSigned") { const typeFormula = new TypeFormula(typeName, [], typeParameters[0].name); return typeFormula; } diff --git a/src/smartcontracts/codec/managedDecimal.ts b/src/smartcontracts/codec/managedDecimal.ts index 04b6689e..69dad84a 100644 --- a/src/smartcontracts/codec/managedDecimal.ts +++ b/src/smartcontracts/codec/managedDecimal.ts @@ -1,6 +1,5 @@ import BigNumber from "bignumber.js"; -import { ManagedDecimalType, ManagedDecimalValue, NumericalValue } from "../typesystem"; -import { BytesValue } from "../typesystem/bytes"; +import { BigUIntValue, ManagedDecimalType, ManagedDecimalValue, U64Value } from "../typesystem"; import { BinaryCodec } from "./binary"; import { cloneBuffer } from "./utils"; @@ -12,43 +11,42 @@ export class ManagedDecimalCodec { } decodeNested(buffer: Buffer, type: ManagedDecimalType): [ManagedDecimalValue, number] { - let [bytesValue, length] = this.binaryCodec.decodeNested(buffer); - console.log({ bytesValue, length }); + let [bytesValue, length] = this.binaryCodec.decodeNested(buffer, type); + console.log(11111, { bytesValue }); return [new ManagedDecimalValue(new BigNumber(1), 1), length]; } decodeTopLevel(buffer: Buffer, type: ManagedDecimalType): ManagedDecimalValue { let payload = cloneBuffer(buffer); - let empty = buffer.length == 0; if (empty) { - return new ManagedDecimalValue(new BigNumber(0)); + return new ManagedDecimalValue(new BigNumber(0), type.getScale()); } - let isPositive = !type.withSign || isMsbZero(payload); - if (isPositive) { - let value = bufferToBigInt(payload); - return new NumericalValue(type, value); - } + console.log({ bsc: type }); + const decimalBuff = Buffer.from(this.binaryCodec.encodeTopLevel(new U64Value(type.getScale()))); + const bigUintSize = buffer.length - decimalBuff.length; // Remaining bytes are for BigUInt + console.log({ buffer, l: buffer.length, d: decimalBuff.length, bigUintSize, decimalBuff, sc: type }); - // Also see: https://github.com/multiversx/mx-components-big-int/blob/master/twos-complement/twos2bigint.go - flipBufferBitsInPlace(payload); - let value = bufferToBigInt(payload); - let negativeValue = value.multipliedBy(new BigNumber(-1)); - let negativeValueMinusOne = negativeValue.minus(new BigNumber(1)); + // Read BigUInt (dynamic size) + const bigUintBuffer = payload.slice(0, bigUintSize); + const u64Buffer = payload.slice(bigUintSize, payload.length); + const bigUint = new BigNumber(bigUintBuffer.toString("hex"), 16); + console.log({ payload, bigUintBuffer, u64Buffer }); + const u64Value = new U64Value(u64Buffer.toString("hex")).toString(); - return new NumericalValue(type, negativeValueMinusOne); - let bytesValue = this.binaryCodec.decodeTopLevel(buffer); - console.log({ bytesValue }); - return new ManagedDecimalValue(new BigNumber(1), 1); + console.log({ payload, bigUintBuffer, u64Buffer, u64Value }); + return new ManagedDecimalValue(bigUint, type.getScale()); } encodeNested(value: ManagedDecimalValue): Buffer { - let bytesValue = new BytesValue(Buffer.from(value)); - return this.binaryCodec.encodeNested(bytesValue); + let buffers: Buffer[] = []; + buffers.push(Buffer.from(this.binaryCodec.encodeTopLevel(new BigUIntValue(value.valueOf())))); + buffers.push(Buffer.from(this.binaryCodec.encodeTopLevel(new U64Value(value.getScale())))); + return Buffer.concat(buffers); } - encodeTopLevel(tokenIdentifier: ManagedDecimalValue): Buffer { - return Buffer.from(tokenIdentifier.valueOf()); + encodeTopLevel(value: ManagedDecimalValue): Buffer { + return this.encodeNested(value); } } diff --git a/src/smartcontracts/typesystem/managedDecimal.ts b/src/smartcontracts/typesystem/managedDecimal.ts index eb5f19e5..bb48a426 100644 --- a/src/smartcontracts/typesystem/managedDecimal.ts +++ b/src/smartcontracts/typesystem/managedDecimal.ts @@ -13,6 +13,10 @@ export class ManagedDecimalType extends Type { getClassName(): string { return ManagedDecimalType.ClassName; } + + getScale(): number { + return this.scale; + } } export class ManagedDecimalValue extends TypedValue { @@ -30,6 +34,10 @@ export class ManagedDecimalValue extends TypedValue { return ManagedDecimalValue.ClassName; } + getScale(): number { + return this.scale; + } + getPrecision(): number { return this.value.toString(this.scale).replace(".", "").length; } @@ -50,7 +58,7 @@ export class ManagedDecimalValue extends TypedValue { } toString(): string { - return this.value.toString(); + return this.value.toFixed(this.scale); } } @@ -66,6 +74,10 @@ export class ManagedDecimalSignedType extends Type { getClassName(): string { return ManagedDecimalType.ClassName; } + + getScale(): number { + return this.scale; + } } export class ManagedDecimalSignedValue extends TypedValue { diff --git a/src/smartcontracts/typesystem/typeMapper.ts b/src/smartcontracts/typesystem/typeMapper.ts index 3ea9a5e4..e156b71f 100644 --- a/src/smartcontracts/typesystem/typeMapper.ts +++ b/src/smartcontracts/typesystem/typeMapper.ts @@ -10,7 +10,7 @@ import { FieldDefinition } from "./fields"; import { ListType, OptionType } from "./generic"; import { ArrayVecType } from "./genericArray"; import { H256Type } from "./h256"; -import { ManagedDecimalType } from "./managedDecimal"; +import { ManagedDecimalSignedType, ManagedDecimalType } from "./managedDecimal"; import { NothingType } from "./nothing"; import { BigIntType, @@ -32,14 +32,15 @@ import { CustomType, Type } from "./types"; import { VariadicType } from "./variadic"; type TypeFactory = (...typeParameters: Type[]) => Type; +type TypeWithMetadataFactory = (...metadata: any) => Type; export class TypeMapper { - private readonly openTypesFactories: Map; + private readonly openTypesFactories: Map; private readonly closedTypesMap: Map; private readonly learnedTypesMap: Map; constructor(learnedTypes: CustomType[] = []) { - this.openTypesFactories = new Map([ + this.openTypesFactories = new Map([ ["Option", (...typeParameters: Type[]) => new OptionType(typeParameters[0])], ["List", (...typeParameters: Type[]) => new ListType(typeParameters[0])], // For the following open generics, we use a slightly different typing than the one defined by mx-sdk-rs (temporary workaround). @@ -76,6 +77,7 @@ export class TypeMapper { ["array128", (...typeParameters: Type[]) => new ArrayVecType(128, typeParameters[0])], ["array256", (...typeParameters: Type[]) => new ArrayVecType(256, typeParameters[0])], ["ManagedDecimal", (...metadata: any) => new ManagedDecimalType(parseInt(metadata))], + ["ManagedDecimalSigned", (...metadata: any) => new ManagedDecimalSignedType(parseInt(metadata))], ]); // For closed types, we hold actual type instances instead of type constructors / factories (no type parameters needed). @@ -108,7 +110,11 @@ export class TypeMapper { // Boostrap from previously learned types, if any. for (const type of learnedTypes) { - this.learnedTypesMap.set(type.getName(), type); + if (type.getName() === "ManagedDecimal" || type.getName() === "ManagedDecimalSigned") { + this.learnedTypesMap.set(`${type.getName()}_${type.getMetadata()}`, type); + } else { + this.learnedTypesMap.set(type.getName(), type); + } } } @@ -167,8 +173,13 @@ export class TypeMapper { } private learnType(type: Type): void { - this.learnedTypesMap.delete(type.getName()); - this.learnedTypesMap.set(type.getName(), type); + if (type.getName() === "ManagedDecimal" || type.getName() === "ManagedDecimalSigned") { + this.learnedTypesMap.delete(type.getName()); + this.learnedTypesMap.set(`${type.getName()}_${type.getMetadata()}`, type); + } else { + this.learnedTypesMap.delete(type.getName()); + this.learnedTypesMap.set(type.getName(), type); + } } private mapStructType(type: StructType): StructType { @@ -204,6 +215,9 @@ export class TypeMapper { if (!factory) { throw new errors.ErrTypingSystem(`Cannot map the generic type "${type.getName()}" to a known type`); } + if (type.hasMetadata()) { + return factory(type.getMetadata()); + } return factory(...mappedTypeParameters); } diff --git a/src/smartcontracts/typesystem/types.ts b/src/smartcontracts/typesystem/types.ts index 9f9e8382..b6bb20b3 100644 --- a/src/smartcontracts/typesystem/types.ts +++ b/src/smartcontracts/typesystem/types.ts @@ -20,7 +20,6 @@ export class Type { metadata?: any, ) { guardValueIsSet("name", name); - this.name = name; this.typeParameters = typeParameters; this.cardinality = cardinality; @@ -67,6 +66,10 @@ export class Type { return this.typeParameters; } + getMetadata(): any { + return this.metadata; + } + isGenericType(): boolean { return this.typeParameters.length > 0; } diff --git a/src/testdata/adder.abi.json b/src/testdata/adder.abi.json index 1786ce21..7bfbf6fe 100644 --- a/src/testdata/adder.abi.json +++ b/src/testdata/adder.abi.json @@ -64,6 +64,25 @@ } ] }, + { + "name": "managed_decimal_subtraction", + "mutability": "mutable", + "inputs": [ + { + "name": "first", + "type": "ManagedDecimal<2>" + }, + { + "name": "second", + "type": "ManagedDecimal<2>" + } + ], + "outputs": [ + { + "type": "ManagedDecimal<2>" + } + ] + }, { "docs": [ "Add desired amount to the storage variable." diff --git a/src/testdata/basic-features.wasm b/src/testdata/basic-features.wasm new file mode 100755 index 00000000..a0da77ca Binary files /dev/null and b/src/testdata/basic-features.wasm differ