@instadapp/interop-x 0.0.0-dev.a168c79 → 0.0.0-dev.adea608

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,121 @@
1
+ import { BaseTask } from "../BaseTask";
2
+ import Logger from '@/logger';
3
+ import { ethers } from "ethers";
4
+ import abi from "@/abi";
5
+ import { Transaction } from "@/db";
6
+ import { generateInteropTransactionHash, getContract, getRpcProviderUrl } from "@/utils";
7
+ import { ChainId } from "@/types";
8
+ import config from "@/config";
9
+ import { InteropBridgeToken } from "@/typechain";
10
+
11
+ class SyncWithdrawEvents extends BaseTask {
12
+ contractAddress: string;
13
+ provider: ethers.providers.JsonRpcProvider;
14
+ contract: InteropBridgeToken;
15
+ chainId: ChainId;
16
+ itokenAddress: string;
17
+
18
+ constructor({ chainId, itokenAddress }: { chainId: ChainId, itokenAddress: string }) {
19
+ super({
20
+ logger: new Logger("InteropBridgeToken::SyncWithdrawEvents"),
21
+ })
22
+ this.chainId = chainId;
23
+ this.itokenAddress = itokenAddress;
24
+ }
25
+
26
+ async pollHandler() {
27
+ const currentBlock = await this.provider.getBlockNumber();
28
+
29
+ const events = await this.contract.queryFilter(
30
+ this.contract.filters.Burn(),
31
+ currentBlock - 2000,
32
+ currentBlock,
33
+ );
34
+
35
+ let processedEvents = 0;
36
+
37
+ for (const event of events) {
38
+
39
+ try {
40
+ if (!event.args) {
41
+ continue;
42
+ }
43
+
44
+ const { to, amount, chainId } = event.args;
45
+
46
+ const uniqueIdentifier = {
47
+ action: 'withdraw',
48
+ submitTransactionHash: event.transactionHash,
49
+ sourceChainId:this.chainId,
50
+ targetChainId: chainId.toNumber(),
51
+ }
52
+
53
+ if (await Transaction.findOne({ where: uniqueIdentifier })) {
54
+ continue;
55
+ }
56
+
57
+ const tx = await event.getTransaction()
58
+
59
+ await Transaction.create({
60
+ ...uniqueIdentifier,
61
+ transactionHash: generateInteropTransactionHash(uniqueIdentifier),
62
+ from: tx.from,
63
+ to,
64
+
65
+
66
+ submitTransactionHash: event.transactionHash,
67
+ submitBlockNumber: event.blockNumber,
68
+
69
+ // submit & source are the same
70
+ sourceTransactionHash: event.transactionHash,
71
+ sourceBlockNumber: event.blockNumber,
72
+ sourceStatus: "success",
73
+
74
+ targetStatus: "uninitialised",
75
+
76
+ submitEvent: {
77
+ to,
78
+ amount: amount.toString(),
79
+ itoken: this.itokenAddress,
80
+ chainId: chainId.toString()
81
+ },
82
+
83
+ sourceEvent: {
84
+ to,
85
+ amount: amount.toString(),
86
+ itoken: this.itokenAddress,
87
+ chainId: chainId.toString(),
88
+ },
89
+ status: "pending",
90
+ })
91
+
92
+ this.logger.info(
93
+ `Withdraw queued: ${event.transactionHash} ${event.blockNumber}`
94
+ );
95
+ } catch (error) {
96
+ this.logger.error(error);
97
+ }
98
+ }
99
+
100
+ if (processedEvents > 0)
101
+ this.logger.info(`${processedEvents} events processed`);
102
+ }
103
+
104
+ async start(): Promise<void> {
105
+ this.logger.info(`Starting execution watcher on interop chain`);
106
+
107
+ this.provider = new ethers.providers.JsonRpcProvider(
108
+ getRpcProviderUrl(this.chainId)
109
+ );
110
+
111
+ this.contract = getContract<InteropBridgeToken>(
112
+ this.itokenAddress,
113
+ abi.interopBridgeToken,
114
+ new ethers.Wallet(config.privateKey!, this.provider)
115
+ );
116
+
117
+ await super.start()
118
+ }
119
+ }
120
+
121
+ export default SyncWithdrawEvents;
@@ -3,7 +3,7 @@ import Logger from '@/logger';
3
3
  import { BigNumber, ethers } from "ethers";
4
4
  import abi from "@/abi";
5
5
  import { Transaction } from "@/db";
6
- import { buildDataForTransaction, buildSignatureBytes, getRpcProviderUrl, Signature } from "@/utils";
6
+ import { buildDataForTransaction, buildSignatureBytes, getContract, getRpcProviderUrl, Signature } from "@/utils";
7
7
  import { addresses } from "@/constants";
8
8
  import { ChainId } from "@/types";
9
9
  import config from "@/config";
@@ -13,7 +13,9 @@ import wait from "waait";
13
13
  import { peerPool, protocol } from "@/net";
14
14
  import { LogDescription } from "ethers/lib/utils";
15
15
 
16
- const generateGnosisTransaction = async (transactionData: any, safeContract: any) => {
16
+ const generateGnosisTransaction = async (transactionData: any, safeContract: GnosisSafe) => {
17
+ console.log(transactionData);
18
+
17
19
  let isExecuted = await safeContract.dataHashes(
18
20
  await safeContract.getTransactionHash(
19
21
  transactionData.to,
@@ -72,13 +74,21 @@ class ProcessDepositEvents extends BaseTask {
72
74
  where: {
73
75
  status: 'pending',
74
76
  sourceStatus: 'success',
77
+ targetStatus: 'uninitialised',
75
78
  action: 'deposit',
76
79
  sourceCreatedAt: {
77
80
  [Op.gte]: new Date(Date.now() - 12 * 60 * 60 * 1000),
78
81
  },
82
+ targetDelayUntil: {
83
+ [Op.or]: {
84
+ [Op.is]: null,
85
+ [Op.lt]: new Date(),
86
+ }
87
+ },
79
88
  sourceBlockNumber: {
80
89
  [Op.lt]: blockNumber - 12,
81
- }
90
+ },
91
+ sourceChainId: this.chainId,
82
92
  }
83
93
  })
84
94
 
@@ -86,11 +96,11 @@ class ProcessDepositEvents extends BaseTask {
86
96
  return;
87
97
  }
88
98
 
89
-
90
- transaction.sourceStatus = 'pending';
99
+ console.log(`Processing transaction ${transaction.transactionHash}`);
100
+
101
+ transaction.targetStatus = 'pending';
91
102
  await transaction.save();
92
103
 
93
-
94
104
  // refresh event data?
95
105
 
96
106
  const targetChainProvider = new ethers.providers.JsonRpcProvider(
@@ -101,19 +111,19 @@ class ProcessDepositEvents extends BaseTask {
101
111
 
102
112
  const safeAddress = addresses[transaction.targetChainId].gnosisSafe;
103
113
 
104
- const safeContract = new ethers.Contract(
114
+
115
+ const safeContract = getContract<GnosisSafe>(
105
116
  safeAddress,
106
117
  abi.gnosisSafe,
107
118
  targetWallet
108
- ) as GnosisSafe;
119
+ )
109
120
 
110
121
  const ownersThreshold = await safeContract.getThreshold();
111
-
112
122
  await wait(10000);
113
123
 
114
124
  let gnosisTx = await generateGnosisTransaction({
115
125
  baseGas: "0",
116
- data: buildDataForTransaction(transaction),
126
+ data: await buildDataForTransaction(transaction),
117
127
  gasPrice: "0",
118
128
  gasToken: "0x0000000000000000000000000000000000000000",
119
129
  nonce: '0',
@@ -131,6 +141,8 @@ class ProcessDepositEvents extends BaseTask {
131
141
 
132
142
  console.log(`Collecting signatures for execution ${transaction.transactionHash}`)
133
143
 
144
+ console.log(ownerPeerIds);
145
+
134
146
  const signatures = await protocol.requestSignatures({
135
147
  type: 'source',
136
148
  transactionHash: transaction.transactionHash,
@@ -145,33 +157,17 @@ class ProcessDepositEvents extends BaseTask {
145
157
 
146
158
  if (validSignatures.length === 0 || ownersThreshold.gt(validSignatures.length)) {
147
159
  await transaction.save();
148
- transaction.sourceDelayUntil = new Date(Date.now() + 30 * 1000);
149
- transaction.sourceStatus = 'pending'
160
+ transaction.targetDelayUntil = new Date(Date.now() + 30 * 1000);
161
+ transaction.targetStatus = 'uninitialised'
150
162
 
151
163
  await transaction.save();
152
164
  const errorMessage = signatures.find(s => !!s.error)?.error;
153
165
  throw new Error(`Not enough signatures` + (errorMessage ? `: ${errorMessage}` : ''));
154
166
  }
155
167
 
156
- const execTransactionParams = [
157
- gnosisTx.to,
158
- gnosisTx.value,
159
- gnosisTx.data,
160
- gnosisTx.operation,
161
- gnosisTx.safeTxGas,
162
- gnosisTx.baseGas,
163
- gnosisTx.gasPrice,
164
- gnosisTx.gasToken,
165
- gnosisTx.refundReceiver,
166
- buildSignatureBytes(validSignatures),
167
- ];
168
168
 
169
169
  console.log(`Executing transaction for execution ${transaction.transactionHash}`)
170
170
 
171
- console.log({
172
- execTransactionParams
173
- })
174
-
175
171
  const { data: txData } = await safeContract.populateTransaction.execTransaction(
176
172
  gnosisTx.to,
177
173
  gnosisTx.value,
@@ -185,10 +181,17 @@ class ProcessDepositEvents extends BaseTask {
185
181
  buildSignatureBytes(validSignatures)
186
182
  );
187
183
 
184
+ console.log({
185
+ from: targetWallet.address,
186
+ gasPrice: BigNumber.from(120 * 10 ** 9).toString(),
187
+ to: safeAddress,
188
+ data: txData,
189
+ })
190
+
191
+
188
192
  const txSent = await targetWallet.sendTransaction({
189
193
  from: targetWallet.address,
190
194
  gasPrice: BigNumber.from(120 * 10 ** 9),
191
- gasLimit: BigNumber.from(6_000_000),
192
195
  to: safeAddress,
193
196
  data: txData,
194
197
  })
@@ -205,9 +208,19 @@ class ProcessDepositEvents extends BaseTask {
205
208
 
206
209
  if (parsedLogs.find(e => e.name === 'ExecutionSuccess')) {
207
210
  console.log('ExecutionSuccess')
211
+ transaction.targetStatus = 'success'
212
+ transaction.targetTransactionHash = txSent.hash
213
+ transaction.status = 'success'
214
+ await transaction.save();
208
215
  } else {
209
216
  console.log('ExecutionFailure')
217
+ transaction.targetStatus = 'failed'
218
+ transaction.targetTransactionHash = txSent.hash
219
+ transaction.status = 'failed'
220
+ await transaction.save();
210
221
  }
222
+
223
+ protocol.sendTransaction(transaction)
211
224
  }
212
225
 
213
226
  async start(): Promise<void> {
@@ -219,11 +232,11 @@ class ProcessDepositEvents extends BaseTask {
219
232
  getRpcProviderUrl(this.chainId)
220
233
  );
221
234
 
222
- this.contract = new ethers.Contract(
235
+ this.contract = getContract<InteropXGateway>(
223
236
  this.contractAddress,
224
237
  abi.interopXGateway,
225
238
  new ethers.Wallet(config.privateKey!, this.provider)
226
- ) as InteropXGateway;
239
+ );
227
240
 
228
241
  await super.start()
229
242
  }
@@ -3,7 +3,7 @@ import Logger from '@/logger';
3
3
  import { ethers } from "ethers";
4
4
  import abi from "@/abi";
5
5
  import { Transaction } from "@/db";
6
- import { generateInteropTransactionHash, getRpcProviderUrl } from "@/utils";
6
+ import { generateInteropTransactionHash, getContract, getRpcProviderUrl } from "@/utils";
7
7
  import { addresses } from "@/constants";
8
8
  import { ChainId } from "@/types";
9
9
  import config from "@/config";
@@ -77,7 +77,7 @@ class SyncDepositEvents extends BaseTask {
77
77
  sourceChainId: sourceChainId.toString(),
78
78
  targetChainId: targetChainId.toString(),
79
79
  token: token,
80
- ammout: amount.toString(),
80
+ amount: amount.toString(),
81
81
  vnonce: vnonce.toString(),
82
82
  },
83
83
 
@@ -86,14 +86,14 @@ class SyncDepositEvents extends BaseTask {
86
86
  sourceChainId: sourceChainId.toString(),
87
87
  targetChainId: targetChainId.toString(),
88
88
  token: token,
89
- ammout: amount.toString(),
89
+ amount: amount.toString(),
90
90
  vnonce: vnonce.toString(),
91
91
  },
92
92
  status: "pending",
93
93
  })
94
94
 
95
95
  this.logger.info(
96
- `Execution queued: ${event.transactionHash} ${event.blockNumber}`
96
+ `Deposit queued: ${event.transactionHash} ${event.blockNumber}`
97
97
  );
98
98
  } catch (error) {
99
99
  this.logger.error(error);
@@ -113,11 +113,11 @@ class SyncDepositEvents extends BaseTask {
113
113
  getRpcProviderUrl(this.chainId)
114
114
  );
115
115
 
116
- this.contract = new ethers.Contract(
116
+ this.contract = getContract<InteropXGateway>(
117
117
  this.contractAddress,
118
118
  abi.interopXGateway,
119
119
  new ethers.Wallet(config.privateKey!, this.provider)
120
- ) as InteropXGateway;
120
+ );
121
121
 
122
122
  await super.start()
123
123
  }
@@ -1,11 +1,28 @@
1
1
  import { BaseTask } from "./BaseTask";
2
- import SyncInteropXGatewayDepositEvents from "./InteropXGateway/SyncDepositEvents";
2
+ import InteropXGatewayProcessDepositEvents from "./InteropXGateway/ProcessDepositEvents";
3
+ import InteropXGatewaySyncDepositEvents from "./InteropXGateway/SyncDepositEvents";
4
+
5
+ import InteropBridgeSyncWithdrawEvents from "./InteropBridge/SyncWithdrawEvents";
6
+ import InteropBridgeProcessWithdrawEvents from "./InteropBridge/ProcessWithdrawEvents";
3
7
 
4
8
  export class Tasks {
5
9
 
6
10
  tasks: BaseTask[] = [
7
- new SyncInteropXGatewayDepositEvents({
11
+ new InteropXGatewaySyncDepositEvents({
12
+ chainId: 43114
13
+ }),
14
+
15
+ new InteropXGatewayProcessDepositEvents({
8
16
  chainId: 43114
17
+ }),
18
+
19
+ new InteropBridgeSyncWithdrawEvents({
20
+ chainId: 137,
21
+ itokenAddress: '0xEab02fe1F016eE3e4106c1C6aad35FeEe657268E',
22
+ }),
23
+
24
+ new InteropBridgeProcessWithdrawEvents({
25
+ chainId: 137,
9
26
  })
10
27
  ];
11
28
 
@@ -10,7 +10,7 @@ import { encodeMulti, MetaTransaction, OperationType } from 'ethers-multisend';
10
10
  import { Transaction } from '@/db';
11
11
  import config from '@/config';
12
12
  import abi from '@/abi';
13
- import { InteropBridgeToken } from '@/typechain';
13
+ import { InteropBridgeToken, InteropXGateway } from '@/typechain';
14
14
 
15
15
  export const http = axios.create();
16
16
 
@@ -136,10 +136,21 @@ export const generateInteropTransactionHash = (data: { action: string, submitTra
136
136
  export const buildDataForTransaction = async (transaction: Transaction, type?: 'source' | 'target') => {
137
137
  type = type || transaction.sourceStatus === 'pending' ? 'source' : 'target';
138
138
 
139
+ switch (transaction.action) {
140
+ case "deposit":
141
+ return await buildDepositDataForTransaction(transaction, type);
142
+ case "withdraw":
143
+ return await buildWithdrawDataForTransaction(transaction, type);
144
+ default:
145
+ throw new Error(`Unknown action: ${transaction.action}`);
146
+ }
147
+ }
148
+
149
+ export const buildDepositDataForTransaction = async (transaction: Transaction, type: 'source' | 'target') => {
139
150
  const transactions: MetaTransaction[] = [];
140
151
 
141
- if(transaction.action != 'deposit') {
142
- throw new Error('Invalid action');
152
+ if (transaction.action !== 'deposit') {
153
+ throw new Error(`Invalid action: ${transaction.action}`)
143
154
  }
144
155
 
145
156
  if (transaction.action === 'deposit' && transaction.sourceStatus === 'pending') {
@@ -165,13 +176,13 @@ export const buildDataForTransaction = async (transaction: Transaction, type?: '
165
176
 
166
177
  const targetChainProvider = new ethers.providers.JsonRpcProvider(getRpcProviderUrl(transaction.targetChainId as ChainId));
167
178
  const targetWallet = new ethers.Wallet(config.privateKey, targetChainProvider);
168
- const interopBridgeContract = new ethers.Contract(itoken.address, abi.interopBridgeToken, targetWallet) as InteropBridgeToken;
179
+ const interopBridgeContract = getContract<InteropBridgeToken>(itoken.address, abi.interopBridgeToken, targetWallet);
169
180
 
170
181
  const { data } = await interopBridgeContract.populateTransaction.mint(
171
- transaction.submitEvent.to,
172
- transaction.submitEvent.amount,
173
- transaction.sourceChainId,
174
- transaction.sourceTransactionHash,
182
+ transaction.submitEvent.user,
183
+ ethers.BigNumber.from(transaction.submitEvent.amount.toString()),
184
+ ethers.BigNumber.from(transaction.submitEvent.sourceChainId.toString()),
185
+ transaction.submitTransactionHash,
175
186
  );
176
187
 
177
188
  transactions.push({
@@ -183,3 +194,108 @@ export const buildDataForTransaction = async (transaction: Transaction, type?: '
183
194
 
184
195
  return encodeMulti(transactions).data
185
196
  }
197
+
198
+ export const buildWithdrawDataForTransaction = async (transaction: Transaction, type: 'source' | 'target') => {
199
+ const transactions: MetaTransaction[] = [];
200
+
201
+ if (transaction.action !== 'withdraw') {
202
+ throw new Error(`Invalid action: ${transaction.action}`)
203
+ }
204
+
205
+ if (transaction.action === 'withdraw' && transaction.sourceStatus === 'pending') {
206
+ throw Error('Cannot build data for pending withdraw transaction');
207
+ }
208
+
209
+ if (!transaction.submitEvent) {
210
+ throw Error('Cannot build data for transaction without submitEvent');
211
+ }
212
+
213
+ const { to, amount, chainId, itoken: itokenAddress } = transaction.submitEvent;
214
+
215
+ const itoken = itokens[transaction.sourceChainId].find(token => token.address.toLowerCase() === itokenAddress.toLowerCase());
216
+
217
+ if (!itoken) {
218
+ throw Error('Cannot build data for transaction without itoken');
219
+ }
220
+
221
+ const token = tokens[chainId].find(t => t.symbol.toLowerCase() === itoken.symbol.toLowerCase());
222
+
223
+ if (!token) {
224
+ throw Error('Cannot build data for transaction without token');
225
+ }
226
+
227
+ const targetChainProvider = new ethers.providers.JsonRpcProvider(getRpcProviderUrl(transaction.targetChainId as ChainId));
228
+ const targetWallet = new ethers.Wallet(config.privateKey, targetChainProvider);
229
+ const gatewayAddress = addresses[chainId].interopXGateway;
230
+ const interopBridgeContract = getContract<InteropXGateway>(gatewayAddress, abi.interopXGateway, targetWallet);
231
+
232
+ const { data } = await interopBridgeContract.populateTransaction.systemWithdraw(
233
+ ethers.BigNumber.from(amount.toString()),
234
+ to,
235
+ token.address,
236
+ ethers.BigNumber.from(transaction.sourceChainId.toString()),
237
+ transaction.submitTransactionHash,
238
+ );
239
+
240
+ transactions.push({
241
+ to: gatewayAddress,
242
+ data: data!,
243
+ value: '0',
244
+ operation: OperationType.Call,
245
+ });
246
+
247
+ return encodeMulti(transactions).data
248
+ }
249
+
250
+
251
+ export function getContract<TContract extends ethers.Contract>(address: string, contractInterface: ethers.ContractInterface | any, signerOrProvider?: ethers.Signer | ethers.providers.Provider) {
252
+ if (!ethers.utils.getAddress(address) || address === ethers.constants.AddressZero) {
253
+ throw Error(`Invalid 'address' parameter '${address}'.`)
254
+ }
255
+
256
+ const contract = new ethers.Contract(
257
+ address,
258
+ contractInterface,
259
+ signerOrProvider
260
+ ) as TContract
261
+
262
+
263
+ return new Proxy(contract, {
264
+ get(target, prop, receiver) {
265
+ const value = Reflect.get(target, prop, receiver);
266
+
267
+ if (typeof value === 'function' && (contract.functions.hasOwnProperty(prop) || ['queryFilter'].includes(String(prop)))) {
268
+ return async (...args: any[]) => {
269
+ try {
270
+ return await value.bind(contract)(...args);
271
+ } catch (error) {
272
+ throw new Error(`Error calling "${String(prop)}" on "${address}": ${error.reason || error.message}`)
273
+ }
274
+ }
275
+ }
276
+
277
+
278
+ if (typeof value === 'object' && ['populateTransaction', 'estimateGas', 'functions', 'callStatic'].includes(String(prop))) {
279
+ const parentProp = String(prop);
280
+
281
+ return new Proxy(value, {
282
+ get(target, prop, receiver) {
283
+ const value = Reflect.get(target, prop, receiver);
284
+
285
+ if (typeof value === 'function') {
286
+ return async (...args: any[]) => {
287
+ try {
288
+ return await value.bind(contract)(...args);
289
+ } catch (error) {
290
+ throw new Error(`Error calling "${String(prop)}" using "${parentProp}" on "${address}": ${error.reason || error.message}`)
291
+ }
292
+ }
293
+ }
294
+ }
295
+ })
296
+ }
297
+
298
+ return value;
299
+ },
300
+ });
301
+ }