-
Notifications
You must be signed in to change notification settings - Fork 37
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
Add managed decimal support (as in the rust framework) #477
Changes from 11 commits
615034c
c59657d
ad8d581
ab476cf
9dceb18
06b85fe
e35ad8f
6065dee
0d9e486
5a11659
42bf1c6
dada969
73670bd
1f65ffe
8e054c6
9135302
33a875d
14b2f5f
275bb49
0ca88e9
3838dc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,21 @@ | ||
export class TypeFormula { | ||
name: string; | ||
metadata: any; | ||
typeParameters: TypeFormula[]; | ||
|
||
constructor(name: string, typeParameters: TypeFormula[]) { | ||
constructor(name: string, typeParameters: TypeFormula[], metadata?: any) { | ||
this.name = name; | ||
this.typeParameters = typeParameters; | ||
this.metadata = metadata; | ||
} | ||
|
||
toString(): string { | ||
if (this.typeParameters.length > 0) { | ||
const typeParameters = this.typeParameters.map((typeParameter) => typeParameter.toString()).join(", "); | ||
return `${this.name}<${typeParameters}>`; | ||
} else { | ||
return this.name; | ||
} | ||
const hasTypeParameters = this.typeParameters.length > 0; | ||
const typeParameters = hasTypeParameters | ||
? `<${this.typeParameters.map((tp) => tp.toString()).join(", ")}>` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if move down when there will be no typeParameters we will have <> with empty string |
||
: ""; | ||
const baseName = `${this.name}${typeParameters}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. did you intentionally left out the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. <> is already added when we build the variable typeParameters |
||
|
||
return this.metadata !== undefined ? `${baseName}*${this.metadata}*` : baseName; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All good to append the metadata after an asterisk 👍 |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,6 @@ export class TypeFormulaParser { | |
|
||
parseExpression(expression: string): TypeFormula { | ||
expression = expression.trim(); | ||
|
||
const tokens = this.tokenizeExpression(expression).filter((token) => token !== TypeFormulaParser.COMMA); | ||
const stack: any[] = []; | ||
|
||
|
@@ -32,7 +31,6 @@ export class TypeFormulaParser { | |
stack.push(token); | ||
} | ||
} | ||
|
||
if (stack.length !== 1) { | ||
throw new Error(`Unexpected stack length at end of parsing: ${stack.length}`); | ||
} | ||
|
@@ -83,6 +81,11 @@ export class TypeFormulaParser { | |
private acquireTypeWithParameters(stack: any[]): TypeFormula { | ||
const typeParameters = this.acquireTypeParameters(stack); | ||
const typeName = stack.pop(); | ||
|
||
if (typeName === "ManagedDecimal" || typeName === "ManagedDecimalSigned") { | ||
const typeFormula = new TypeFormula(typeName, [], typeParameters[0].name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can define a separate variable above, such as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done 👍 |
||
return typeFormula; | ||
} | ||
const typeFormula = new TypeFormula(typeName, typeParameters.reverse()); | ||
return typeFormula; | ||
} | ||
|
@@ -92,7 +95,6 @@ export class TypeFormulaParser { | |
|
||
while (true) { | ||
const item = stack.pop(); | ||
|
||
if (item === undefined) { | ||
throw new Error("Badly specified type parameters"); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import BigNumber from "bignumber.js"; | ||
import { BigUIntValue, ManagedDecimalType, ManagedDecimalValue, U32Value } from "../typesystem"; | ||
import { BinaryCodec } from "./binary"; | ||
import { bufferToBigInt } from "./utils"; | ||
|
||
export class ManagedDecimalCodec { | ||
private readonly binaryCodec: BinaryCodec; | ||
|
||
constructor(binaryCodec: BinaryCodec) { | ||
this.binaryCodec = binaryCodec; | ||
} | ||
|
||
decodeNested(buffer: Buffer, type: ManagedDecimalType): [ManagedDecimalValue, number] { | ||
const length = buffer.readUInt32BE(0); | ||
const payload = buffer.slice(0, length); | ||
|
||
const result = this.decodeTopLevel(payload, type); | ||
const decodedLength = length; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
return [result, decodedLength]; | ||
} | ||
|
||
decodeTopLevel(buffer: Buffer, type: ManagedDecimalType): ManagedDecimalValue { | ||
if (buffer.length === 0) { | ||
return new ManagedDecimalValue(new BigNumber(0), 2); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Magic number There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
} | ||
|
||
const isUsize = type.getMetadata() === "usize"; | ||
|
||
if (isUsize) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth to add maybe a function on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
const u32Size = 4; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a constant here: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
const bigUintSize = buffer.length - u32Size; | ||
|
||
const bigUint = new BigNumber(buffer.slice(0, bigUintSize).toString("hex"), 16); | ||
const u32 = buffer.readUInt32BE(bigUintSize); | ||
|
||
return new ManagedDecimalValue(bigUint, u32); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variables should be named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
} | ||
|
||
const value = bufferToBigInt(buffer); | ||
return new ManagedDecimalValue(value, parseInt(type.getMetadata())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can have a variable By the way, no issue with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there will be no issue because here we know is not a variable decimal There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
} | ||
|
||
encodeNested(value: ManagedDecimalValue): Buffer { | ||
let buffers: Buffer[] = []; | ||
if (value.getType().getMetadata() == "usize") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's worth to add a function on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can also say (optional):
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not working becauze if is not variable the value is encodet top level if is variable the value is encodedNested There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done, I add it on type and use it here |
||
buffers.push(Buffer.from(this.binaryCodec.encodeNested(new BigUIntValue(value.valueOf())))); | ||
buffers.push(Buffer.from(this.binaryCodec.encodeNested(new U32Value(value.getScale())))); | ||
} else { | ||
buffers.push(Buffer.from(this.binaryCodec.encodeTopLevel(new BigUIntValue(value.valueOf())))); | ||
} | ||
return Buffer.concat(buffers); | ||
} | ||
|
||
encodeTopLevel(value: ManagedDecimalValue): Buffer { | ||
return this.encodeNested(value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ import { ResultsParser } from "./resultsParser"; | |
import { TransactionWatcher } from "../transactionWatcher"; | ||
import { SmartContractQueriesController } from "../smartContractQueriesController"; | ||
import { QueryRunnerAdapter } from "../adapters/queryRunnerAdapter"; | ||
import { ManagedDecimalSignedValue, ManagedDecimalValue } from "./typesystem"; | ||
|
||
describe("test smart contract interactor", function () { | ||
let provider = createLocalnetProvider(); | ||
|
@@ -184,6 +185,118 @@ describe("test smart contract interactor", function () { | |
assert.isTrue(typedBundle.returnCode.equals(ReturnCode.Ok)); | ||
}); | ||
|
||
it("should interact with 'basic-features' (local testnet) using the SmartContractTransactionsFactory", async function () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test is very nice. For education purposes, maybe it's better to have it using the new transactions factory. |
||
this.timeout(140000); | ||
|
||
let abiRegistry = await loadAbiRegistry("src/testdata/basic-features.abi.json"); | ||
let contract = new SmartContract({ abi: abiRegistry }); | ||
let controller = new ContractController(provider); | ||
|
||
let network = await provider.getNetworkConfig(); | ||
await alice.sync(provider); | ||
|
||
// Deploy the contract | ||
let deployTransaction = await prepareDeployment({ | ||
contract: contract, | ||
deployer: alice, | ||
codePath: "src/testdata/basic-features.wasm", | ||
gasLimit: 600000000, | ||
initArguments: [], | ||
chainID: network.ChainID, | ||
}); | ||
|
||
let { | ||
bundle: { returnCode }, | ||
} = await controller.deploy(deployTransaction); | ||
assert.isTrue(returnCode.isSuccess()); | ||
|
||
let returnEgldInteraction = <Interaction>( | ||
contract.methods | ||
.returns_egld_decimal([]) | ||
.withGasLimit(10000000) | ||
.withChainID(network.ChainID) | ||
.withSender(alice.address) | ||
.withValue(1) | ||
); | ||
|
||
// returnEgld() | ||
let returnEgldTransaction = returnEgldInteraction | ||
.withSender(alice.address) | ||
.useThenIncrementNonceOf(alice.account) | ||
.buildTransaction(); | ||
|
||
let additionInteraction = <Interaction>contract.methods | ||
.managed_decimal_addition([new ManagedDecimalValue(2, 2), new ManagedDecimalValue(3, 2)]) | ||
.withGasLimit(10000000) | ||
.withChainID(network.ChainID) | ||
.withSender(alice.address) | ||
.withValue(0); | ||
|
||
// addition() | ||
let additionTransaction = additionInteraction | ||
.withSender(alice.address) | ||
.useThenIncrementNonceOf(alice.account) | ||
.buildTransaction(); | ||
|
||
// log | ||
let mdLnInteraction = <Interaction>contract.methods | ||
.managed_decimal_ln([new ManagedDecimalValue(23, 9)]) | ||
.withGasLimit(10000000) | ||
.withChainID(network.ChainID) | ||
.withSender(alice.address) | ||
.withValue(0); | ||
|
||
// mdLn() | ||
let mdLnTransaction = mdLnInteraction | ||
.withSender(alice.address) | ||
.useThenIncrementNonceOf(alice.account) | ||
.buildTransaction(); | ||
|
||
let additionVarInteraction = <Interaction>contract.methods | ||
.managed_decimal_addition_var([ | ||
new ManagedDecimalValue(378298000000, 9, true), | ||
new ManagedDecimalValue(378298000000, 9, true), | ||
]) | ||
.withGasLimit(50000000) | ||
.withChainID(network.ChainID) | ||
.withSender(alice.address) | ||
.withValue(0); | ||
|
||
// addition() | ||
let additionVarTransaction = additionVarInteraction | ||
.withSender(alice.address) | ||
.useThenIncrementNonceOf(alice.account) | ||
.buildTransaction(); | ||
|
||
// returnEgld() | ||
await signTransaction({ transaction: returnEgldTransaction, wallet: alice }); | ||
let { bundle: bundleEgld } = await controller.execute(returnEgldInteraction, returnEgldTransaction); | ||
assert.isTrue(bundleEgld.returnCode.equals(ReturnCode.Ok)); | ||
assert.lengthOf(bundleEgld.values, 1); | ||
assert.deepEqual(bundleEgld.values[0], new ManagedDecimalValue(1, 18)); | ||
|
||
// addition with const decimals() | ||
await signTransaction({ transaction: additionTransaction, wallet: alice }); | ||
let { bundle: bundleAdditionConst } = await controller.execute(additionInteraction, additionTransaction); | ||
assert.isTrue(bundleAdditionConst.returnCode.equals(ReturnCode.Ok)); | ||
assert.lengthOf(bundleAdditionConst.values, 1); | ||
assert.deepEqual(bundleAdditionConst.values[0], new ManagedDecimalValue(5, 2)); | ||
|
||
// log | ||
await signTransaction({ transaction: mdLnTransaction, wallet: alice }); | ||
let { bundle: bundleMDLn } = await controller.execute(mdLnInteraction, mdLnTransaction); | ||
assert.isTrue(bundleMDLn.returnCode.equals(ReturnCode.Ok)); | ||
assert.lengthOf(bundleMDLn.values, 1); | ||
assert.deepEqual(bundleMDLn.values[0], new ManagedDecimalSignedValue(3.135553845, 9)); | ||
|
||
// addition with var decimals | ||
await signTransaction({ transaction: additionVarTransaction, wallet: alice }); | ||
let { bundle: bundleAddition } = await controller.execute(additionVarInteraction, additionVarTransaction); | ||
assert.isTrue(bundleAddition.returnCode.equals(ReturnCode.Ok)); | ||
assert.lengthOf(bundleAddition.values, 1); | ||
assert.deepEqual(bundleAddition.values[0], new ManagedDecimalValue(new BigNumber(6254154138880), 9)); | ||
}); | ||
|
||
it("should interact with 'counter' (local testnet)", async function () { | ||
this.timeout(120000); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe change type to
string
here, as wellThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we will let any because is the general object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you think it would be better to use
object
type instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it can be also a primitive that's why I think any is a better option here