-
Notifications
You must be signed in to change notification settings - Fork 238
/
Copy pathBatchCall.ts
155 lines (139 loc) · 5.16 KB
/
BatchCall.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import { JsonFragment } from "@ethersproject/abi";
import { BigNumber, ethers } from "ethers";
import Host from "./Host";
import Operation, { BatchOperationType } from "./Operation";
import { SFError } from "./SFError";
import { getTransactionDescription } from "./utils";
export interface IBatchCallOptions {
hostAddress: string;
operations: ReadonlyArray<Operation>;
}
export interface OperationStruct {
readonly operationType: number;
readonly target: string;
readonly data: string;
readonly value?: ethers.BigNumber;
}
export const batchOperationTypeStringToTypeMap = new Map<
BatchOperationType,
number
>([
["ERC20_APPROVE", 1],
["ERC20_TRANSFER_FROM", 2],
["ERC777_SEND", 3],
["ERC20_INCREASE_ALLOWANCE", 4],
["ERC20_DECREASE_ALLOWANCE", 5],
["SUPERTOKEN_UPGRADE", 101],
["SUPERTOKEN_DOWNGRADE", 102],
["SUPERFLUID_CALL_AGREEMENT", 201],
["CALL_APP_ACTION", 202],
["SIMPLE_FORWARD_CALL", 301],
["ERC2771_FORWARD_CALL", 302],
]);
/**
* Gets function arguments given an ABI and callData.
* @param abi the abi fragments of a contract/function
* @param callData call data of the function
* @returns {ethers.utils.Result} call agreement function arguments
*/
export const getCallDataFunctionArgs = (
abi: string | readonly (string | ethers.utils.Fragment | JsonFragment)[],
callData: string
): ethers.utils.Result => getTransactionDescription(abi, callData).args;
/**
* BatchCall Helper Class
* @description A helper class to create `BatchCall` objects which can be executed.
*/
export default class BatchCall {
options: IBatchCallOptions;
host: Host;
constructor(options: IBatchCallOptions) {
this.options = options;
this.host = new Host(options.hostAddress);
}
getCallDataFunctionArgs = getCallDataFunctionArgs;
/**
* Given an `Operation` object, gets the `OperationStruct` object.
* @param operation an `Operation` object
* @param index the index of the `Operation` in the batchCall
* @returns {Promise<OperationStruct>} OperationStruct object for batchCall
*/
getOperationStruct = async (
operation: Operation,
index: number
): Promise<OperationStruct> => operation.toOperationStruct(index);
/**
* Gets an array of `OperationStruct` objects to be passed to batchCall.
* @returns {Promise<OperationStruct>[]} array of operation struct promises
*/
get getOperationStructArrayPromises(): Promise<OperationStruct>[] {
return this.options.operations.map((x, i) =>
this.getOperationStruct(x, i)
);
}
async toOperation() {
if (this.getOperationStructArrayPromises.length === 0) {
throw new SFError({
type: "BATCH_CALL_ERROR",
message: "There are no operations to execute in the batch.",
});
}
const operationStructArray = await Promise.all(
this.getOperationStructArrayPromises
);
const values = operationStructArray
.filter((x) => x.value?.gt(BigNumber.from(0)))
.map((x) => x.value);
if (values.length > 1) {
throw new SFError({
type: "BATCH_CALL_ERROR",
message:
"There are multiple values in the batch call. The value can only be forwarded to one receiving operation.",
});
}
const tx = this.host.contract.populateTransaction.batchCall(
operationStructArray,
{
value: values?.length > 0 ? values[0] : undefined,
}
);
return new Operation(tx, "UNSUPPORTED");
}
/**
* Executes a batch call given the operations on this class.
* @param signer the signer of the transaction
* @param gasLimitMultiplier A multiplier to provide gasLimit buffer on top of the estimated gas limit (1.2x is the default)
* @returns {Promise<ethers.ContractTransaction>} ContractTransaction object
*/
exec = async (
signer: ethers.Signer,
gasLimitMultiplier = 1.2
): Promise<ethers.ContractTransaction> => {
const operation = await this.toOperation();
return await operation.exec(signer, gasLimitMultiplier);
};
/* istanbul ignore next */
// TODO: user signs the transaction they'd like to execute and gives
// this data to the trusted forwarder to sign
/**
* Executes a forward batch call given the operations on this class.
* @param signer the signer of the transaction
* @returns {Promise<ethers.ContractTransaction>} ContractTransaction object
*/
execForward = async (
signer: ethers.Signer
): Promise<ethers.ContractTransaction> => {
if (this.getOperationStructArrayPromises.length === 0) {
throw new SFError({
type: "BATCH_CALL_ERROR",
message: "There are no operations to execute in the batch.",
});
}
const operationStructArray = await Promise.all(
this.getOperationStructArrayPromises
);
return await this.host.contract
.connect(signer)
.forwardBatchCall(operationStructArray);
};
}