Skip to content

Commit

Permalink
Handle counted vs. regular variadic. Handle multiple counted variadic.
Browse files Browse the repository at this point in the history
  • Loading branch information
andreibancioiu committed Aug 30, 2023
1 parent 6e2e695 commit 84e2782
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 9 deletions.
68 changes: 65 additions & 3 deletions src/smartcontracts/nativeSerializer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import BigNumber from "bignumber.js";
import { assert } from "chai";
import { Address } from "../address";
import { ErrInvalidArgument } from "../errors";
import { NativeSerializer } from "./nativeSerializer";
import { AbiRegistry, AddressType, AddressValue, BigUIntType, BooleanType, BooleanValue, CompositeType, CompositeValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition, ListType, NullType, OptionalType, OptionalValue, OptionType, OptionValue, TupleType, U32Type, U64Type, U64Value, U8Type, U8Value, VariadicType, VariadicValue } from "./typesystem";
import { AbiRegistry, AddressType, AddressValue, BigUIntType, BooleanType, BooleanValue, CompositeType, CompositeValue, EndpointDefinition, EndpointModifiers, EndpointParameterDefinition, ListType, NullType, OptionalType, OptionalValue, OptionType, OptionValue, TupleType, TypePlaceholder, U32Type, U32Value, U64Type, U64Value, U8Type, U8Value, VariadicType, VariadicValue } from "./typesystem";
import { BytesType, BytesValue } from "./typesystem/bytes";

describe("test native serializer", () => {
Expand Down Expand Up @@ -57,12 +58,73 @@ describe("test native serializer", () => {

it("should perform type inference (counted-variadic arguments)", async () => {
const endpointModifiers = new EndpointModifiers("", []);
const inputParameters = [new EndpointParameterDefinition("", "", new VariadicType(new U32Type(), true))];
const inputParameters = [
new EndpointParameterDefinition("", "", new VariadicType(new U32Type(), true)),
new EndpointParameterDefinition("", "", new VariadicType(new BytesType(), true))
];
const endpoint = new EndpointDefinition("foo", inputParameters, [], endpointModifiers);
const typedValues = NativeSerializer.nativeToTypedValues([8, 9, 10], endpoint);

// Implicit counted-variadic (not supported).
assert.throws(() => NativeSerializer.nativeToTypedValues([8, 9, 10, "a", "b", "c"], endpoint), ErrInvalidArgument);

// Explicit, non-empty counted-variadic.
let typedValues = NativeSerializer.nativeToTypedValues([
VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10)),
VariadicValue.fromItemsCounted(BytesValue.fromUTF8("a"), BytesValue.fromUTF8("b"), BytesValue.fromUTF8("c"))
], endpoint);

assert.lengthOf(typedValues, 2);
assert.deepEqual(typedValues[0].getType(), new VariadicType(new U32Type(), true));
assert.deepEqual(typedValues[0].valueOf(), [new BigNumber(8), new BigNumber(9), new BigNumber(10)]);
assert.deepEqual(typedValues[1].getType(), new VariadicType(new BytesType(), true));
assert.deepEqual(typedValues[1].valueOf(), [Buffer.from("a"), Buffer.from("b"), Buffer.from("c")]);

// Explicit, empty counted-variadic.
typedValues = NativeSerializer.nativeToTypedValues([
VariadicValue.fromItemsCounted(),
VariadicValue.fromItemsCounted()
], endpoint);

assert.lengthOf(typedValues, 2);
assert.deepEqual(typedValues[0].getType(), new VariadicType(new TypePlaceholder(), true));
assert.deepEqual(typedValues[0].valueOf(), []);
assert.deepEqual(typedValues[1].getType(), new VariadicType(new TypePlaceholder(), true));
assert.deepEqual(typedValues[1].valueOf(), []);
});

it("should perform type inference (counted-variadic and regular variadic arguments)", async () => {
const endpointModifiers = new EndpointModifiers("", []);
const inputParameters = [
new EndpointParameterDefinition("", "", new VariadicType(new U32Type(), true)),
new EndpointParameterDefinition("", "", new VariadicType(new BytesType(), false))
];
const endpoint = new EndpointDefinition("foo", inputParameters, [], endpointModifiers);

// Implicit counted-variadic (not supported).
assert.throws(() => NativeSerializer.nativeToTypedValues([8, 9, 10], endpoint), ErrInvalidArgument);

// Explicit counted-variadic, empty implicit regular variadic.
let typedValues = NativeSerializer.nativeToTypedValues([
VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10))
], endpoint);

assert.lengthOf(typedValues, 2);
assert.deepEqual(typedValues[0].getType(), new VariadicType(new U32Type(), true));
assert.deepEqual(typedValues[0].valueOf(), [new BigNumber(8), new BigNumber(9), new BigNumber(10)]);
assert.deepEqual(typedValues[1].getType(), new VariadicType(new BytesType(), false));
assert.deepEqual(typedValues[1].valueOf(), []);

// Explicit counted-variadic, non-empty implicit regular variadic.
typedValues = NativeSerializer.nativeToTypedValues([
VariadicValue.fromItemsCounted(new U32Value(8), new U32Value(9), new U32Value(10)),
"a", "b", "c"
], endpoint);

assert.lengthOf(typedValues, 2);
assert.deepEqual(typedValues[0].getType(), new VariadicType(new U32Type(), true));
assert.deepEqual(typedValues[0].valueOf(), [new BigNumber(8), new BigNumber(9), new BigNumber(10)]);
assert.deepEqual(typedValues[1].getType(), new VariadicType(new BytesType(), false));
assert.deepEqual(typedValues[1].valueOf(), [Buffer.from("a"), Buffer.from("b"), Buffer.from("c")]);
});

it("should should handle optionals in a strict manner (but it does not)", async () => {
Expand Down
38 changes: 32 additions & 6 deletions src/smartcontracts/nativeSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ export namespace NativeSerializer {
*/
export function nativeToTypedValues(args: any[], endpoint: EndpointDefinition): TypedValue[] {
args = args || [];
args = handleVariadicArgsAndRePack(args, endpoint);

checkArgumentsCardinality(args, endpoint);

if (hasNonCountedVariadicParameter(endpoint)) {
args = repackNonCountedVariadicParameters(args, endpoint);
} else {
// Repacking makes sense (it's possible) only for regular, non-counted variadic parameters.
}

let parameters = endpoint.input;
let values: TypedValue[] = [];
Expand All @@ -33,16 +40,31 @@ export namespace NativeSerializer {
return values;
}

function handleVariadicArgsAndRePack(args: any[], endpoint: EndpointDefinition) {
let parameters = endpoint.input;

let { min, max, variadic } = getArgumentsCardinality(parameters);
function checkArgumentsCardinality(args: any[], endpoint: EndpointDefinition) {
// With respect to the notes of "repackNonCountedVariadicParameters", "getArgumentsCardinality" will not be needed anymore.
// Currently, it is used only for a arguments count check, which will become redundant.
const { min, max } = getArgumentsCardinality(endpoint.input);

if (!(min <= args.length && args.length <= max)) {
throw new ErrInvalidArgument(`Wrong number of arguments for endpoint ${endpoint.name}: expected between ${min} and ${max} arguments, have ${args.length}`);
}
}

if (variadic) {
function hasNonCountedVariadicParameter(endpoint: EndpointDefinition): boolean {
const lastParameter = endpoint.input[endpoint.input.length - 1];
return lastParameter?.type instanceof VariadicType && !lastParameter.type.isCounted;
}

// In a future version of the type inference system, re-packing logic will be removed.
// The client code will be responsible for passing the correctly packed arguments (variadic arguments explicitly packed as arrays).
// For developers, calling `foo(["erd1", 42, [1, 2, 3]])` will be less ambiguous than `foo(["erd1", 42, 1, 2, 3])`.
// Furthermore, multiple counted-variadic arguments cannot be expressed in the current variant.
// E.g. now, it's unreasonable to decide that `foo([1, 2, 3, "a", "b", "c"])` calls `foo(counted-variadic<int>, counted-variadic<string>)`.
function repackNonCountedVariadicParameters(args: any[], endpoint: EndpointDefinition) {
let parameters = endpoint.input;

// TODO: Remove after first review. Temporarily left this way to make the review easier.
if (true) {
const lastEndpointParamIndex = parameters.length - 1;
const argAtIndex = args[lastEndpointParamIndex];

Expand Down Expand Up @@ -136,6 +158,10 @@ export namespace NativeSerializer {
}

function toVariadicValue(native: any, type: VariadicType, errorContext: ArgumentErrorContext): TypedValue {
if (type.isCounted) {
throw new ErrInvalidArgument(`Counted variadic arguments must be explicitly typed. E.g. use "VariadicValue.fromItemsCounted()"`);
}

if (native == null) {
native = [];
}
Expand Down

0 comments on commit 84e2782

Please sign in to comment.