Skip to content
Merged
41 changes: 40 additions & 1 deletion packages/taco/integration-test/condition-lingo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe.skipIf(!process.env.RUNNING_IN_CI)(
});
const conditionExpr = new ConditionExpression(overallCondition);
await validateConditionExpression(conditionExpr);
}, 15000);
}, 20000);
test('validate ecdsa condition lingo and supported curves consistency', async () => {
// Split SUPPORTED_ECDSA_CURVES into chunks of 5 to respect the operand limit
const preGeneratedVerifyingKeys = {
Expand Down Expand Up @@ -182,5 +182,44 @@ describe.skipIf(!process.env.RUNNING_IN_CI)(
const conditionExpr = new ConditionExpression(jsonCondition);
await validateConditionExpression(conditionExpr);
}, 15000);

test('validate conditions with operations', async () => {
const sequentialCondition = new SequentialCondition({
conditionVariables: [
{
varName: 'rpcValue',
condition: testRpcConditionObj,
operations: [
{ operation: 'abs' },
{ operation: '+=', value: BigInt('1000000000000000') },
],
},
{
// add some operations
varName: 'timeValue',
condition: {
...testTimeConditionObj,
returnValueTest: {
comparator: '>',
value: 100,
operations: [
{ operation: 'abs' },
{ operation: 'index', value: 0 },
{ operation: '+=', value: 5 },
{ operation: '*=', value: ':multiplier' }, // context variable
],
},
},
},
{
varName: 'contractValue',
condition: testContractConditionObj,
operations: [{ operation: '-=', value: 5.5 }],
},
],
});
const conditionExpr = new ConditionExpression(sequentialCondition);
await validateConditionExpression(conditionExpr);
}, 15000);
},
);
18 changes: 18 additions & 0 deletions packages/taco/integration-test/encrypt-decrypt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ describe.skipIf(!process.env.RUNNING_IN_CI)(
},
});

const forcedZeroBalanceDueToOperations =
new conditions.base.rpc.RpcCondition({
chain: CHAIN_ID,
method: 'eth_getBalance',
parameters: [':userAddress', 'latest'],
returnValueTest: {
comparator: '==',
value: 0,
operations: [
{ operation: '*=', value: 0 },
// no-op additional operations
{ operation: 'abs' },
{ operation: 'int' },
],
},
});

const balanceLessThanMaxUintBigInt = new conditions.base.rpc.RpcCondition(
{
chain: CHAIN_ID,
Expand All @@ -84,6 +101,7 @@ describe.skipIf(!process.env.RUNNING_IN_CI)(
const compoundCondition = CompoundCondition.and([
hasPositiveBalance,
balanceLessThanMaxUintBigInt,
forcedZeroBalanceDueToOperations,
]);

const messageKit = await encrypt(
Expand Down
50 changes: 36 additions & 14 deletions packages/taco/schema-docs/condition-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,12 @@ _(\*) Required._

_Object containing the following properties:_

| Property | Type |
| :-------------------- | :-------------------------------------------------------------- |
| `index` | `number` (_int, ≥0_) |
| **`comparator`** (\*) | `'==' \| '>' \| '<' \| '>=' \| '<=' \| '!=' \| 'in' \| '!in'` |
| **`value`** (\*) | [BlockchainParamOrContextParam](#blockchainparamorcontextparam) |
| Property | Description | Type |
| :-------------------- | :---------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| `index` | | `number` (_int, ≥0_) |
| **`comparator`** (\*) | | `'==' \| '>' \| '<' \| '>=' \| '<=' \| '!=' \| 'in' \| '!in'` |
| `operations` | Optional operations to perform on the obtained result before comparison | _Array of at least 1 and at most 5 [VariableOperation](#variableoperation) items_ |
| **`value`** (\*) | | [BlockchainParamOrContextParam](#blockchainparamorcontextparam) |

_(\*) Required._

Expand All @@ -263,11 +264,12 @@ Test to perform on a value. Supports comparison operators like ==, >, <, >=, <=,

_Object containing the following properties:_

| Property | Type |
| :-------------------- | :------------------------------------------------------------ |
| `index` | `number` (_int, ≥0_) |
| **`comparator`** (\*) | `'==' \| '>' \| '<' \| '>=' \| '<=' \| '!=' \| 'in' \| '!in'` |
| **`value`** (\*) | [ParamOrContextParam](#paramorcontextparam) |
| Property | Description | Type |
| :-------------------- | :---------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| `index` | | `number` (_int, ≥0_) |
| **`comparator`** (\*) | | `'==' \| '>' \| '<' \| '>=' \| '<=' \| '!=' \| 'in' \| '!in'` |
| `operations` | Optional operations to perform on the obtained result before comparison | _Array of at least 1 and at most 5 [VariableOperation](#variableoperation) items_ |
| **`value`** (\*) | | [ParamOrContextParam](#paramorcontextparam) |

_(\*) Required._

Expand All @@ -291,10 +293,11 @@ _(\*) Required._

_Object containing the following properties:_

| Property | Type |
| :------------------- | :---------------------------- |
| **`varName`** (\*) | [PlainString](#plainstring) |
| **`condition`** (\*) | [AnyCondition](#anycondition) |
| Property | Description | Type |
| :------------------- | :-------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| **`varName`** (\*) | Any string that is not a Context Parameter i.e. does not start with `:`. | [PlainString](#plainstring) |
| **`condition`** (\*) | | [AnyCondition](#anycondition) |
| `operations` | Optional operations to perform on the obtained condition result before storing it | _Array of at least 1 and at most 5 [VariableOperation](#variableoperation) items_ |

_(\*) Required._

Expand Down Expand Up @@ -373,6 +376,25 @@ _Object containing the following properties:_

_(\*) Required._

## VariableOperation

An operation that can be performed on an obtained result.

_Object containing the following properties:_

| Property | Type |
| :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`operation`** (\*) | `'+=' \| '-=' \| '*=' \| '/=' \| '%=' \| 'index' \| 'round' \| 'abs' \| 'avg' \| 'ceil' \| 'ethToWei' \| 'floor' \| 'len' \| 'max' \| 'min' \| 'sum' \| 'weiToEth' \| 'bool' \| 'float' \| 'int' \| ...` |
| `value` | [ParamOrContextParam](#paramorcontextparam) |

_(\*) Required._

## VariableOperationsArray

Optional operations to perform on the obtained result

_Array of at least 1 and at most 5 [VariableOperation](#variableoperation) items._ (_optional_)

## More resources

For more information, please refer to the TACo documentation:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export * from './rpc';
export * from './sequential';
export * from './signing';
export * from './time';
export * from './variable-operation';
4 changes: 4 additions & 0 deletions packages/taco/src/conditions/schemas/return-value-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import {
blockchainParamOrContextParamSchema,
paramOrContextParamSchema,
} from './context';
import { variableOperationsArraySchema } from './variable-operation';

const returnValueTestBaseSchema = z.object({
index: z.number().int().nonnegative().optional(),
comparator: z.enum(['==', '>', '<', '>=', '<=', '!=', 'in', '!in']),
operations: variableOperationsArraySchema.describe(
'Optional operations to perform on the obtained result before comparison',
),
});

const requireNonEmptyArrayIfComparatorIsIn = (data: {
Expand Down
17 changes: 13 additions & 4 deletions packages/taco/src/conditions/schemas/sequential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { baseConditionSchema, plainStringSchema } from './common';
import { CompoundConditionType } from './compound';
import { IfThenElseConditionType } from './if-then-else';
import { anyConditionSchema } from './utils';
import { variableOperationsArraySchema } from './variable-operation';

const getAllNestedConditionVariableNames = (
condition: ConditionProps,
Expand Down Expand Up @@ -50,11 +51,19 @@ const noDuplicateVarNames = (condition: ConditionProps): boolean => {
export const SequentialConditionType = 'sequential';

export const conditionVariableSchema: z.ZodSchema = z.lazy(() =>
z.object({
varName: plainStringSchema,
condition: anyConditionSchema,
}),
z
.object({
varName: plainStringSchema,
condition: anyConditionSchema,
operations: variableOperationsArraySchema.describe(
'Optional operations to perform on the obtained condition result before storing it',
),
})
.describe(
'Executes a condition and stores the result as a variable within a sequential condition.',
),
);

export type ConditionVariableProps = z.infer<typeof conditionVariableSchema>;

export const sequentialConditionSchema: z.ZodSchema = baseConditionSchema
Expand Down
93 changes: 93 additions & 0 deletions packages/taco/src/conditions/schemas/variable-operation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { z } from 'zod';

import { paramOrContextParamSchema } from './context';

export const OPERATOR_FUNCTIONS = [
'+=',
'-=',
'*=',
'/=',
'%=',
'index',
'round',
// operations that don't require 2nd value
'abs',
'avg',
'ceil',
'ethToWei',
'floor',
'len',
'max',
'min',
'sum',
'weiToEth',
// casting
'bool',
'float',
'int',
'str',
] as const;

export const UNARY_OPERATOR_FUNCTIONS = [
'abs',
'avg',
'ceil',
'ethToWei',
'floor',
'len',
'max',
'min',
'sum',
'weiToEth',
// casting
'bool',
'float',
'int',
'str',
];

export const variableOperationSchema = z
.object({
operation: z.enum(OPERATOR_FUNCTIONS),
value: paramOrContextParamSchema.optional(),
})
.refine(
(data) => {
if (
UNARY_OPERATOR_FUNCTIONS.includes(data.operation) &&
data.value !== undefined
) {
return false;
}
return true;
},
{
message: 'Value not allowed for this operation',
path: ['value'],
},
)
.refine(
(data) => {
if (
!UNARY_OPERATOR_FUNCTIONS.includes(data.operation) &&
data.value === undefined
) {
return false;
}
return true;
},
{
message: 'Value must be defined for operation',
path: ['value'],
},
)
.describe('An operation that can be performed on an obtained result.');

export const MAX_VARIABLE_OPERATIONS = 5;

export const variableOperationsArraySchema = z
.array(variableOperationSchema)
.min(1)
.max(MAX_VARIABLE_OPERATIONS)
.optional()
.describe('Optional operations to perform on the obtained result');
70 changes: 70 additions & 0 deletions packages/taco/test/conditions/return-value-test.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { describe, expect, it } from 'vitest';

import {
MAX_VARIABLE_OPERATIONS,
OPERATOR_FUNCTIONS,
UNARY_OPERATOR_FUNCTIONS,
} from '../../src/conditions/schemas/variable-operation';
import {
blockchainReturnValueTestSchema,
returnValueTestSchema,
Expand Down Expand Up @@ -64,5 +69,70 @@ import {
});
},
);
it.each(OPERATOR_FUNCTIONS)('allows valid operations', (operation) => {
const result = schema.safeParse({
comparator: '==',
value: 10,
operations: [
{
operation: operation,
value: UNARY_OPERATOR_FUNCTIONS.includes(operation) ? undefined : 5,
},
],
});
expect(result.success).toBe(true);
expect(result.error).toBeUndefined();
});
it('requires at least one operation if defined', () => {
const result = schema.safeParse({
comparator: '==',
value: 10,
operations: [],
});
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
expect(result.error!.format()).toMatchObject({
operations: {
_errors: ['Array must contain at least 1 element(s)'],
},
});
});
it(`allows at most ${MAX_VARIABLE_OPERATIONS} operations`, () => {
const result = schema.safeParse({
comparator: '==',
value: 10,
operations: Array.from(
{ length: MAX_VARIABLE_OPERATIONS + 1 },
(_, i) => ({
operation: '+=',
value: i + 1,
}),
),
});
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
expect(result.error!.format()).toMatchObject({
operations: {
_errors: [
`Array must contain at most ${MAX_VARIABLE_OPERATIONS} element(s)`,
],
},
});
});
it('allows multiple valid operations', () => {
const result = schema.safeParse({
comparator: '==',
value: 10,
operations: [
{ operation: 'index', value: 1 },
{ operation: '+=', value: 5 },
{ operation: '*=', value: 2.5 },
{ operation: 'abs' },
{ operation: '+=', value: BigInt('1000000000000000') },
],
});
expect(result.success).toBe(true);
expect(result.error).toBeUndefined();
});
});
});
Loading