Skip to content
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

Sync SDK with AVS API to v1.3.0 #66

Merged
merged 8 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add taskcount+filter node
  • Loading branch information
v9n committed Jan 31, 2025
commit afd937658b44eb577a018950071e400ba7bfe139
5 changes: 5 additions & 0 deletions packages/sdk-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ export default class Client extends BaseClient {
address: result.getAddress(),
salt: result.getSalt(),
factory: result.getFactoryAddress(),
totalTaskCount: result.getTotalTaskCount(),
activeTaskCount: result.getActiveTaskCount(),
completedTaskCount: result.getCompletedTaskCount(),
failedTaskCount: result.getFailedTaskCount(),
canceledTaskCount: result.getCanceledTaskCount(),
};
}

Expand Down
7 changes: 7 additions & 0 deletions packages/sdk-js/src/models/node/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import RestAPINode, { RestAPINodeProps } from "./restApi";
import ContractReadNode, { ContractReadNodeProps } from "./contractRead";
import ETHTransferNode, { ETHTransferNodeProps } from "./ethTransfer";
import BranchNode, { BranchNodeProps, BranchNodeData } from "./branch";
import FilterNode, { FilterNodeProps } from "./filter";
import { NodeType } from "@avaprotocol/types";
class NodeFactory {
static create(props: NodeProps): Node {
Expand All @@ -29,6 +30,8 @@ class NodeFactory {
return new GraphQLQueryNode(props as GraphQLQueryNodeProps);
case NodeType.Branch:
return new BranchNode(props as BranchNodeProps);
case NodeType.Filter:
return new FilterNode(props as FilterNodeProps);
default:
throw new Error(`Unsupported node type: ${props.type}`);
}
Expand All @@ -54,6 +57,8 @@ class NodeFactory {
return CustomCodeNode.fromResponse(raw);
case !!raw.getBranch():
return BranchNode.fromResponse(raw);
case !!raw.getFilter():
return FilterNode.fromResponse(raw);
default:
throw new Error(`Unsupported node type: ${raw.getName()}`);
}
Expand All @@ -72,6 +77,7 @@ export {
RestAPINode,
CustomCodeNode,
CustomCodeLangs,
FilterNode,
};

export type {
Expand All @@ -84,4 +90,5 @@ export type {
GraphQLQueryNodeProps,
RestAPINodeProps,
CustomCodeNodeProps,
FilterNodeProps,
};
42 changes: 42 additions & 0 deletions packages/sdk-js/src/models/node/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { NodeProps } from "./interface";
import Node from "./interface";
import * as avs_pb from "@/grpc_codegen/avs_pb";
import { NodeType } from "@avaprotocol/types";

// Required props for constructor: id, name, type and data: { expression, input }
type FilterNodeData = avs_pb.FilterNode.AsObject;
export type FilterNodeProps = NodeProps & {
data: FilterNodeData;
};

class FilterNode extends Node {
constructor(props: FilterNodeProps) {
super({ ...props, type: NodeType.Filter, data: props.data });
}

static fromResponse(raw: avs_pb.TaskNode): FilterNode {
// Convert the raw object to FilterNodeProps, which should keep name and id
const obj = raw.toObject() as unknown as FilterNodeProps;
return new FilterNode({
...obj,
type: NodeType.Filter,
data: raw.getFilter()!.toObject() as FilterNodeData,
});
}

toRequest(): avs_pb.TaskNode {
const request = new avs_pb.TaskNode();

request.setId(this.id);
request.setName(this.name);

const nodeData = new avs_pb.FilterNode();
nodeData.setExpression((this.data as FilterNodeData).expression);
nodeData.setInput((this.data as FilterNodeData).input);

request.setFilter(nodeData);
return request;
}
}

export default FilterNode;
Empty file.
8 changes: 7 additions & 1 deletion packages/sdk-js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ export enum TriggerType {
Unset = "unset",
}

export type SmartWallet = avs_pb.SmartWallet.AsObject;
export type SmartWallet = avs_pb.SmartWallet.AsObject & {
totalTaskCount?: number;
activeTaskCount?: number;
completedTaskCount?: number;
failedTaskCount?: number;
canceledTaskCount?: number;
};

export const ExecutionStatus = avs_pb.ExecutionStatus;
42 changes: 42 additions & 0 deletions tests/createWorkflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import {
EXPIRED_AT,
FACTORY_ADDRESS,
defaultTriggerId,
blockTriggerEvery5,
restApiNodeProps,
filterNodeProps,
} from "./templates";

// Update the dotenv configuration
Expand Down Expand Up @@ -387,6 +390,45 @@ describe("createWorkflow Tests", () => {
);
});

test("create filter node task", async () => {
const wallet = await client.getWallet({ salt: "0" });

const workflowData = {
smartWalletAddress: wallet.address,
nodes: NodeFactory.createNodes([restApiNodeProps, filterNodeProps]),
edges: [new Edge({id: getNextId(), source: defaultTriggerId, target: restApiNodeProps.id})],
trigger: blockTriggerEvery5,
startAt: WorkflowTemplate.startAt,
expiredAt: WorkflowTemplate.expiredAt,
maxExecution: 1,
}

const submitResult = await client.submitWorkflow(
client.createWorkflow(workflowData)
);

queueForRemoval(createdWorkflows, submitResult);

const task = await client.getWorkflow(submitResult);

compareResults(
{
smartWalletAddress: wallet.address,
status: WorkflowStatus.Active,
id: submitResult,
owner: eoaAddress,
nodes: NodeFactory.createNodes([restApiNodeProps, filterNodeProps]),
edges: [new Edge({id: getNextId(), source: defaultTriggerId, target: restApiNodeProps.id})],
trigger: blockTriggerEvery5,
startAt: WorkflowTemplate.startAt,
expiredAt: WorkflowTemplate.expiredAt,
maxExecution: 1,
},
task
);
});


// TODO: add detailed verification for each node in the workflow
// expect(task.nodes[0].contractWrite.contractAddress).toEqual(
// WorkflowTemplate.nodes[0].contractWrite.contractAddress
Expand Down
100 changes: 97 additions & 3 deletions tests/getWallet.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
import { describe, beforeAll, test, expect } from "@jest/globals";
import Client from "@/sdk-js/dist";
import Client, {
TriggerFactory,
TriggerType,
} from "@/sdk-js/dist";

import dotenv from "dotenv";
import path from "path";
import { getAddress, generateSignature, requireEnvVar } from "./utils";
import { EXPIRED_AT, FACTORY_ADDRESS } from "./templates";
import {
getAddress, generateSignature, requireEnvVar,
queueForRemoval,
removeCreatedWorkflows,
} from "./utils";

import {
WorkflowTemplate,
EXPIRED_AT,
FACTORY_ADDRESS,
defaultTriggerId,
} from "./templates";

// Map of created workflows and isDeleting status tracking of those that need to be cleaned up after the test
const createdWorkflows: Map<string, boolean> = new Map();


// Update the dotenv configuration
dotenv.config({ path: path.resolve(__dirname, "..", ".env.test") });
Expand Down Expand Up @@ -31,6 +49,7 @@ describe("getAddresses Tests", () => {
const res = await client.authWithSignature(signature);
client.setAuthKey(res.authKey);
});
afterAll(async () => await removeCreatedWorkflows(client, createdWorkflows));

test("should get wallet client.factoryAddress", async () => {
// Set the factory address in client
Expand All @@ -45,6 +64,81 @@ describe("getAddresses Tests", () => {
expect(result.address).toHaveLength(42);
});

test("should return correct task count", async () => {
// Set the factory address in client
client.setFactoryAddress(FACTORY_ADDRESS);
expect(client.getFactoryAddress()).toEqual(FACTORY_ADDRESS);

const salt = parseInt(process.env.RUN_ID || Math.floor(Math.random() * 100000));
let wallet = await client.getWallet({ salt });

const initialStat = {
totalTaskCount: wallet.totalTaskCount,
activeTaskCount: wallet.activeTaskCount,
completedTaskCount: wallet.completedTaskCount,
canceledTaskCount: wallet.canceledTaskCount,
failedTaskCount: wallet.failedTaskCount,
};

const workflowId = await client.submitWorkflow(
client.createWorkflow({
...WorkflowTemplate,
smartWalletAddress: wallet.address,
trigger: TriggerFactory.create({
id: defaultTriggerId,
name: "blockTrigger",
type: TriggerType.Block,
data: { interval: 102 },
}),
})
);
queueForRemoval(createdWorkflows, workflowId);

wallet = await client.getWallet({ salt });

expect(wallet.totalTaskCount).toEqual(initialStat.totalTaskCount+1);
expect(wallet.activeTaskCount).toEqual(initialStat.activeTaskCount+1);
expect(wallet.completedTaskCount).toEqual(initialStat.completedTaskCount);
expect(wallet.canceledTaskCount).toEqual(initialStat.canceledTaskCount);

// Trigger the task and wait for it finished to check the stat
const result = await client.triggerWorkflow({
id: workflowId,
data: {
type: TriggerType.Block,
block_number: 1, // set epoch to 1 minute later
},
isBlocking: true,
});


wallet = await client.getWallet({ salt });
expect(wallet.totalTaskCount).toEqual(initialStat.totalTaskCount+1);
expect(wallet.activeTaskCount).toEqual(initialStat.activeTaskCount);
expect(wallet.completedTaskCount).toEqual(initialStat.completedTaskCount+1);
expect(wallet.canceledTaskCount).toEqual(initialStat.canceledTaskCount);

// Now test the cancel metric
const workflowId2 = await client.submitWorkflow(
client.createWorkflow({
...WorkflowTemplate,
smartWalletAddress: wallet.address,
})
);
queueForRemoval(createdWorkflows, workflowId2);

await client.cancelWorkflow(workflowId2);

wallet = await client.getWallet({ salt });
expect(wallet.totalTaskCount).toEqual(initialStat.totalTaskCount+2);
expect(wallet.activeTaskCount).toEqual(initialStat.activeTaskCount);
expect(wallet.completedTaskCount).toEqual(initialStat.completedTaskCount+1);
expect(wallet.canceledTaskCount).toEqual(initialStat.canceledTaskCount+1);


});


test("should get wallet with options.factoryAddress", async () => {
// Unset the factory address in client
const invalidAddress = "0x0000000000000000000000000000000000000000";
Expand Down
23 changes: 21 additions & 2 deletions tests/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
GraphQLQueryNodeProps,
BranchNodeProps,
CustomCodeLangs,
FilterNodeProps,
} from "@/sdk-js/dist";
import { getNextId } from "./utils";
import { NodeType } from "@avaprotocol/types";
Expand Down Expand Up @@ -77,9 +78,9 @@ const ethTransferNodeProps: ETHTransferNodeProps = {
},
};

const restApiNodeProps: RestAPINodeProps = {
export const restApiNodeProps: RestAPINodeProps = {
id: getNextId(),
name: "rest api call",
name: "rest_api_call",
type: NodeType.RestAPI,
data: {
url: "http://endpoint002",
Expand All @@ -89,6 +90,16 @@ const restApiNodeProps: RestAPINodeProps = {
},
};

export const filterNodeProps: FilterNodeProps = {
id: getNextId(),
name: "filterNode",
type: NodeType.Filter,
data: {
input: "rest_api_call",
expression: "value >= 1",
},
};

const graphqlQueryNodeProps: GraphQLQueryNodeProps = {
id: getNextId(),
name: "graphql call",
Expand Down Expand Up @@ -205,3 +216,11 @@ export const MultiNodeWithBranch = {
name: `Test task`,
maxExecution: 1,
};

export const blockTriggerEvery5 = TriggerFactory.create({
id: defaultTriggerId,
name: "blockTrigger",
type: TriggerType.Block,
data: { interval: 5 },
});

Loading