Skip to content

Commit

Permalink
fix: the idx of a pair inside or inside pair is not calculated …
Browse files Browse the repository at this point in the history
…correctly (#2929)

* fix: the idx of a pair inside or inside pair is not calculated correctly

* chore: update tests

* test: fix failing unit tests

* test: fix failing integration test

* Using static variable to configure field numbering strategy (#2939)

* feat: option for the user to choose between legacy and updated field numbering

* chore: fix typo

* chore: add typedoc comments for Token.fieldNumberingStrategy

* test: fix failing integration test

* test: comment in the integration test to make it clear that we are testing all three strategies
  • Loading branch information
ac10n committed May 2, 2024
1 parent 8f30cf3 commit 55f6772
Show file tree
Hide file tree
Showing 13 changed files with 1,850 additions and 414 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FieldNumberingStrategy } from "@taquito/michelson-encoder";
import { CONFIGS } from "../../config";
import { noAnnotCode, noAnnotInit } from "../../data/token_without_annotation";

Expand All @@ -10,59 +11,70 @@ CONFIGS().forEach(({ lib, rpc, setup }) => {
beforeEach(async () => {
await setup()
})
it('Verify contract.originate for a contract with no annotations for methods using methodObjects', async () => {
// Constants to replace annotations
const ACCOUNTS = '0';
const BALANCE = '0';
const ALLOWANCES = '1';
const TRANSFER = '0';
const APPROVE = '2';

// Actual tests
// Constants to replace annotations
const ACCOUNTS = '0';
const BALANCE = '0';
const ALLOWANCES = '1';
const TRANSFER = '0';
const APPROVE = '2';

const ACCOUNT1_ADDRESS = await Tezos.signer.publicKeyHash()
const ACCOUNT2_ADDRESS = 'tz1ZfrERcALBwmAqwonRXYVQBDT9BjNjBHJu'
// Actual tests

// Originate a contract with a known state
const op = await Tezos.contract.originate({
balance: "1",
code: noAnnotCode,
init: noAnnotInit(await Tezos.signer.publicKeyHash())
})
await op.confirmation()
const contract = await op.contract()
const ACCOUNT2_ADDRESS = 'tz1ZfrERcALBwmAqwonRXYVQBDT9BjNjBHJu'

// Runs the entire tests for a given fieldNumberingStrategy
const testContract = (strategy: FieldNumberingStrategy, innerObjectStartingIndex: number) => {
it(`Verify contract.originate for a contract with no annotations for methods using methodObjects with fieldNumberingStrategy: ${strategy}`, async () => {
Tezos.setFieldNumberingStrategy(strategy);
const ACCOUNT1_ADDRESS = await Tezos.signer.publicKeyHash()
// Originate a contract with a known state
const op = await Tezos.contract.originate({
balance: "1",
code: noAnnotCode,
init: noAnnotInit(await Tezos.signer.publicKeyHash())
})
await op.confirmation()
const contract = await op.contract()

// Make a transfer
// Make a transfer

const operation = await contract.methodsObject[TRANSFER]({
0: ACCOUNT1_ADDRESS,
1: ACCOUNT2_ADDRESS,
2: "1"
}).send();
const operation = await contract.methodsObject[TRANSFER]({
0: ACCOUNT1_ADDRESS,
1: ACCOUNT2_ADDRESS,
2: "1"
}).send();

await operation.confirmation();
expect(operation.status).toEqual('applied')
await operation.confirmation();
expect(operation.status).toEqual('applied')

// Verify that the transfer was done as expected
const storage = await contract.storage<any>()
let account1 = await storage[ACCOUNTS].get(ACCOUNT1_ADDRESS)
expect(account1[BALANCE].toString()).toEqual('16')
// Verify that the transfer was done as expected
const storage = await contract.storage<any>()
let account1 = await storage[ACCOUNTS].get(ACCOUNT1_ADDRESS)
expect(account1[BALANCE].toString()).toEqual('16')

const account2 = await storage[ACCOUNTS].get(ACCOUNT2_ADDRESS)
expect(account2[BALANCE].toString()).toEqual('1')
const account2 = await storage[ACCOUNTS].get(ACCOUNT2_ADDRESS)
expect(account2[BALANCE].toString()).toEqual('1')

// Approve
const operation2 = await contract.methodsObject[APPROVE]({
2: ACCOUNT2_ADDRESS,
3: "1"
}).send();
// Approve
const operation2 = await contract.methodsObject[APPROVE]({
[innerObjectStartingIndex]: ACCOUNT2_ADDRESS,
[innerObjectStartingIndex + 1]: "1"
}).send();

await operation2.confirmation();
expect(operation2.status).toEqual('applied')
await operation2.confirmation();
expect(operation2.status).toEqual('applied')

// Verify that the allowance was done as expected
account1 = await storage[ACCOUNTS].get(ACCOUNT1_ADDRESS)
expect(account1[ALLOWANCES].get(ACCOUNT2_ADDRESS).toString()).toEqual('1')
});
};

// Run the tests for all fieldNumberingStrategies
testContract('Legacy', 2);
testContract('ResetFieldNumbersInNestedObjects', 0);
testContract('Latest', 0);

// Verify that the allowance was done as expected
account1 = await storage[ACCOUNTS].get(ACCOUNT1_ADDRESS)
expect(account1[ALLOWANCES].get(ACCOUNT2_ADDRESS).toString()).toEqual('1')
})
});
})
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ export const UnitValue = Symbol();
export const SaplingStateValue = {};
export * from './michelson-map';
export { VERSION } from './version';
export { Token } from './tokens/token';
export { FieldNumberingStrategy, Token } from './tokens/token';
15 changes: 11 additions & 4 deletions packages/taquito-michelson-encoder/src/tokens/createToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { TaquitoError } from '@taquito/core';
*/
export class InvalidTokenError extends TaquitoError {
name = 'Invalid token error';
constructor(public message: string, public data: any) {
constructor(
public message: string,
public data: any
) {
super(message);
}
}
Expand All @@ -19,9 +22,13 @@ export class InvalidTokenError extends TaquitoError {
* @description Create a token from a value
* @throws {@link InvalidTokenError} If the value passed is not supported by the Michelson Encoder
*/
export function createToken(val: any, idx: number): Token {
export function createToken(
val: any,
idx: number,
parentTokenType?: 'Or' | 'Pair' | 'Other' | undefined
): Token {
if (Array.isArray(val)) {
return new PairToken(val, idx, createToken);
return new PairToken(val, idx, createToken, parentTokenType);
}

const t = tokens.find((x) => x.prim === val.prim);
Expand All @@ -31,5 +38,5 @@ export function createToken(val: any, idx: number): Token {
val
);
}
return new t(val, idx, createToken);
return new t(val, idx, createToken, parentTokenType);
}
66 changes: 51 additions & 15 deletions packages/taquito-michelson-encoder/src/tokens/or.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
*/
export class OrValidationError extends TokenValidationError {
name = 'OrValidationError';
constructor(public value: any, public token: OrToken, message: string) {
constructor(
public value: any,
public token: OrToken,
message: string
) {
super(value, token, message);
}
}
Expand All @@ -25,21 +29,26 @@ export class OrToken extends ComparableToken {
constructor(
protected val: { prim: string; args: any[]; annots: any[] },
protected idx: number,
protected fac: TokenFactory
protected fac: TokenFactory,
protected parentTokenType?: 'Or' | 'Pair' | 'Other' | undefined
) {
super(val, idx, fac);
super(val, idx, fac, parentTokenType);
}

public Encode(args: any[]): any {
const label = args[args.length - 1];

const leftToken = this.createToken(this.val.args[0], this.idx);
const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
let keyCount = 1;
if (leftToken instanceof OrToken) {
keyCount = Object.keys(leftToken.ExtractSchema()).length;
}

const rightToken = this.createToken(this.val.args[1], this.idx + keyCount);
const rightToken = this.createToken(
this.val.args[1],
this.getIdxForChildren() + keyCount,
'Or'
);

if (String(leftToken.annot()) === String(label) && !(leftToken instanceof OrToken)) {
args.pop();
Expand All @@ -66,13 +75,17 @@ export class OrToken extends ComparableToken {
}

public ExtractSignature(): any {
const leftToken = this.createToken(this.val.args[0], this.idx);
const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
let keyCount = 1;
if (leftToken instanceof OrToken) {
keyCount = Object.keys(leftToken.ExtractSchema()).length;
}

const rightToken = this.createToken(this.val.args[1], this.idx + keyCount);
const rightToken = this.createToken(
this.val.args[1],
this.getIdxForChildren() + keyCount,
'Or'
);

const newSig = [];

Expand Down Expand Up @@ -102,13 +115,17 @@ export class OrToken extends ComparableToken {
this.validateJavascriptObject(args);
const label = Object.keys(args)[0];

const leftToken = this.createToken(this.val.args[0], this.idx);
const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
let keyCount = 1;
if (leftToken instanceof OrToken) {
keyCount = Object.keys(leftToken.ExtractSchema()).length;
}

const rightToken = this.createToken(this.val.args[1], this.idx + keyCount);
const rightToken = this.createToken(
this.val.args[1],
this.getIdxForChildren() + keyCount,
'Or'
);

if (String(leftToken.annot()) === String(label) && !(leftToken instanceof OrToken)) {
return { prim: 'Left', args: [leftToken.EncodeObject(args[label], semantic)] };
Expand Down Expand Up @@ -154,12 +171,16 @@ export class OrToken extends ComparableToken {
* @throws {@link OrValidationError}
*/
public Execute(val: any, semantics?: Semantic): any {
const leftToken = this.createToken(this.val.args[0], this.idx);
const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
let keyCount = 1;
if (leftToken instanceof OrToken) {
keyCount = Object.keys(leftToken.ExtractSchema()).length;
}
const rightToken = this.createToken(this.val.args[1], this.idx + keyCount);
const rightToken = this.createToken(
this.val.args[1],
this.getIdxForChildren() + keyCount,
'Or'
);

if (val.prim === 'Right') {
if (rightToken instanceof OrToken) {
Expand Down Expand Up @@ -190,7 +211,7 @@ export class OrToken extends ComparableToken {
getRightValue: (token: Token) => any,
concat: (left: any, right: any) => any
) {
const leftToken = this.createToken(this.val.args[0], this.idx);
const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
let keyCount = 1;
let leftValue;
if (leftToken instanceof OrToken) {
Expand All @@ -200,7 +221,11 @@ export class OrToken extends ComparableToken {
leftValue = { [leftToken.annot()]: getLeftValue(leftToken) };
}

const rightToken = this.createToken(this.val.args[1], this.idx + keyCount);
const rightToken = this.createToken(
this.val.args[1],
this.getIdxForChildren() + keyCount,
'Or'
);
let rightValue;
if (rightToken instanceof OrToken) {
rightValue = getRightValue(rightToken);
Expand Down Expand Up @@ -255,13 +280,17 @@ export class OrToken extends ComparableToken {
}

private findToken(label: any): Token | null {
const leftToken = this.createToken(this.val.args[0], this.idx);
const leftToken = this.createToken(this.val.args[0], this.getIdxForChildren(), 'Or');
let keyCount = 1;
if (leftToken instanceof OrToken) {
keyCount = Object.keys(leftToken.ExtractSchema()).length;
}

const rightToken = this.createToken(this.val.args[1], this.idx + keyCount);
const rightToken = this.createToken(
this.val.args[1],
this.getIdxForChildren() + keyCount,
'Or'
);

if (
String(leftToken.annot()) === String(label) &&
Expand Down Expand Up @@ -334,4 +363,11 @@ export class OrToken extends ComparableToken {
);
return tokens;
}

protected getIdxForChildren(): number {
if (Token.fieldNumberingStrategy === 'Legacy') {
return this.idx;
}
return this.parentTokenType === 'Or' ? this.idx : 0;
}
}
Loading

0 comments on commit 55f6772

Please sign in to comment.