@ocap/tx-protocols 1.22.3 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/protocols/token-factory/burn.js +172 -84
- package/lib/protocols/token-factory/create.js +31 -7
- package/lib/protocols/token-factory/mint.js +161 -81
- package/lib/protocols/token-factory/pipes/calc-reserve.js +5 -8
- package/lib/protocols/token-factory/update.js +38 -13
- package/package.json +16 -16
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
2
2
|
const { Joi, schemas } = require('@arcblock/validator');
|
|
3
|
-
const { BN,
|
|
3
|
+
const { BN, fromUnitToToken } = require('@ocap/util');
|
|
4
4
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
5
5
|
const { account, tokenFactory, token } = require('@ocap/state');
|
|
6
|
+
const pick = require('lodash/pick');
|
|
6
7
|
|
|
7
8
|
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
8
9
|
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
9
|
-
const
|
|
10
|
-
const { applyTokenChange } = require('../../util');
|
|
10
|
+
const CalcReserve = require('./pipes/calc-reserve');
|
|
11
|
+
const { applyTokenChange, applyTokenUpdates } = require('../../util');
|
|
11
12
|
|
|
12
13
|
const runner = new Runner();
|
|
13
14
|
|
|
14
|
-
runner.use(pipes.VerifyMultiSig(0));
|
|
15
|
-
|
|
16
15
|
const schema = Joi.object({
|
|
17
16
|
tokenFactory: Joi.DID().prefix().role('ROLE_TOKEN_FACTORY').required(),
|
|
18
17
|
receiver: schemas.tokenHolder.required(),
|
|
19
|
-
amount: Joi.BN().greater(0).required(),
|
|
20
18
|
minReserve: Joi.alternatives().try(Joi.BN().greater(0), Joi.equal('')).optional(),
|
|
19
|
+
inputsList: schemas.multiInput.min(1).required(),
|
|
21
20
|
data: Joi.any().optional().allow(null),
|
|
22
21
|
}).options({ stripUnknown: true, noDefaults: false });
|
|
23
22
|
|
|
@@ -30,6 +29,23 @@ runner.use(({ itx }, next) => {
|
|
|
30
29
|
return next();
|
|
31
30
|
});
|
|
32
31
|
|
|
32
|
+
// verify inputs
|
|
33
|
+
runner.use(
|
|
34
|
+
pipes.VerifyTxInput({
|
|
35
|
+
fieldKey: 'itx.inputs',
|
|
36
|
+
inputsKey: 'inputs',
|
|
37
|
+
sendersKey: 'senders',
|
|
38
|
+
tokensKey: 'tokens',
|
|
39
|
+
assetsKey: null,
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// verify itx size: set hard limit here because more inputs leads to longer tx execute time
|
|
44
|
+
runner.use(pipes.VerifyListSize({ listKey: ['inputs', 'senders', 'tokens', 'assets'] }));
|
|
45
|
+
|
|
46
|
+
// verify multi sig
|
|
47
|
+
runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }));
|
|
48
|
+
|
|
33
49
|
// verify token factory
|
|
34
50
|
runner.use(
|
|
35
51
|
pipes.ExtractState({
|
|
@@ -40,14 +56,37 @@ runner.use(
|
|
|
40
56
|
})
|
|
41
57
|
);
|
|
42
58
|
|
|
59
|
+
// verify inputs token
|
|
60
|
+
runner.use((context, next) => {
|
|
61
|
+
const { tokenFactoryState, inputs } = context;
|
|
62
|
+
|
|
63
|
+
const isAccepted = inputs.every(
|
|
64
|
+
(input) =>
|
|
65
|
+
input.tokensList.length &&
|
|
66
|
+
input.tokensList.every((x) => x.address === tokenFactoryState.tokenAddress) &&
|
|
67
|
+
!input.assetsList.length
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!isAccepted) {
|
|
71
|
+
return next(new Error('INVALID_TX', `Inputs only accept ${tokenFactoryState.tokenAddress}`));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return next();
|
|
75
|
+
});
|
|
76
|
+
|
|
43
77
|
// ensure sender
|
|
44
|
-
runner.use(
|
|
45
|
-
pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })
|
|
46
|
-
);
|
|
78
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
|
|
47
79
|
runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
48
80
|
|
|
81
|
+
runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
|
|
82
|
+
runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
83
|
+
|
|
49
84
|
// ensure receiver
|
|
50
85
|
runner.use(pipes.ExtractState({ from: 'itx.receiver', to: 'receiverState', status: 'OK', table: 'account' }));
|
|
86
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'receiverState', addressKey: 'itx.receiver' }));
|
|
87
|
+
|
|
88
|
+
// verify blocked
|
|
89
|
+
runner.use(pipes.VerifyBlocked({ stateKeys: ['signerStates', 'receiverState'] }));
|
|
51
90
|
|
|
52
91
|
// ensure owner
|
|
53
92
|
runner.use(
|
|
@@ -59,9 +98,6 @@ runner.use(
|
|
|
59
98
|
})
|
|
60
99
|
);
|
|
61
100
|
|
|
62
|
-
// verify blocked
|
|
63
|
-
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState', 'receiverState'] }));
|
|
64
|
-
|
|
65
101
|
// ensure token state
|
|
66
102
|
runner.use(
|
|
67
103
|
pipes.ExtractState({
|
|
@@ -72,13 +108,32 @@ runner.use(
|
|
|
72
108
|
})
|
|
73
109
|
);
|
|
74
110
|
|
|
75
|
-
// calculate
|
|
111
|
+
// calculate burn amount
|
|
112
|
+
runner.use((context, next) => {
|
|
113
|
+
const { inputs, tokenFactoryState } = context;
|
|
114
|
+
let amount = new BN('0');
|
|
115
|
+
|
|
116
|
+
for (const { tokensList } of inputs) {
|
|
117
|
+
const delta = tokensList.find((x) => x.address === tokenFactoryState.tokenAddress)?.value;
|
|
118
|
+
if (!delta) {
|
|
119
|
+
return next(new Error('INVALID_TX', 'Invalid inputs'));
|
|
120
|
+
}
|
|
121
|
+
amount = amount.add(new BN(delta));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
context.burnAmount = amount.toString();
|
|
125
|
+
|
|
126
|
+
return next();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// calculate reserve amount / fee
|
|
76
130
|
runner.use(
|
|
77
|
-
|
|
131
|
+
CalcReserve({
|
|
78
132
|
tokenFactoryKey: 'tokenFactoryState',
|
|
79
133
|
tokenStateKey: 'tokenState',
|
|
80
134
|
reserveKey: 'reserveAmount',
|
|
81
135
|
feeKey: 'reserveFee',
|
|
136
|
+
amountKey: 'burnAmount',
|
|
82
137
|
direction: 'burn',
|
|
83
138
|
})
|
|
84
139
|
);
|
|
@@ -88,7 +143,7 @@ runner.use((context, next) => {
|
|
|
88
143
|
const { reserveAmount, reserveFee } = context;
|
|
89
144
|
const { minReserve } = context.itx;
|
|
90
145
|
|
|
91
|
-
if (minReserve && new BN(reserveAmount).lt(new BN(minReserve).add(new BN(reserveFee)))) {
|
|
146
|
+
if (minReserve && new BN(reserveAmount).lt(new BN(minReserve).add(new BN(reserveFee || '0')))) {
|
|
92
147
|
return next(
|
|
93
148
|
new Error(
|
|
94
149
|
'SLIPPAGE_EXCEEDED',
|
|
@@ -97,36 +152,48 @@ runner.use((context, next) => {
|
|
|
97
152
|
);
|
|
98
153
|
}
|
|
99
154
|
|
|
100
|
-
next();
|
|
155
|
+
return next();
|
|
101
156
|
});
|
|
102
157
|
|
|
103
|
-
// verify
|
|
104
|
-
runner.use((
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
context.tokenConditions = {
|
|
108
|
-
owner: context.senderState.address,
|
|
109
|
-
tokens: [{ address: tokenFactoryState.tokenAddress, value: itx.amount }],
|
|
110
|
-
};
|
|
158
|
+
// verify balance
|
|
159
|
+
runner.use(pipes.ExtractState({ from: 'tokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
|
|
160
|
+
runner.use(pipes.VerifyTokenBalance({ ownerKey: 'signerStates', conditionKey: 'inputs' }));
|
|
111
161
|
|
|
112
|
-
|
|
113
|
-
|
|
162
|
+
// verify token factory balance
|
|
163
|
+
runner.use((context, next) => {
|
|
164
|
+
const { tokenFactoryState } = context;
|
|
165
|
+
if (new BN(tokenFactoryState.currentSupply).lt(new BN(context.burnAmount))) {
|
|
166
|
+
return next(new Error('INSUFFICIENT_FUND', 'Token factory supply is not enough'));
|
|
114
167
|
}
|
|
115
|
-
|
|
116
|
-
|
|
168
|
+
if (new BN(tokenFactoryState.reserveBalance).lt(new BN(context.reserveAmount))) {
|
|
169
|
+
return next(new Error('INSUFFICIENT_FUND', 'Token factory reserve balance is not enough'));
|
|
170
|
+
}
|
|
171
|
+
return next();
|
|
117
172
|
});
|
|
118
|
-
runner.use(pipes.VerifyTokenBalance({ ownerKey: 'senderState', conditionKey: 'tokenConditions' }));
|
|
119
173
|
|
|
120
174
|
// Ensure tx fee and gas
|
|
121
175
|
runner.use(
|
|
122
176
|
EnsureTxGas((context) => {
|
|
123
|
-
const result = { create: 0, update:
|
|
177
|
+
const result = { create: 0, update: 2, payment: 0 };
|
|
178
|
+
|
|
179
|
+
if (context.senderState) {
|
|
180
|
+
result.update += 1;
|
|
181
|
+
} else {
|
|
182
|
+
result.create += 1;
|
|
183
|
+
}
|
|
124
184
|
|
|
125
185
|
if (context.receiverState) {
|
|
126
186
|
result.update += 1;
|
|
187
|
+
} else {
|
|
188
|
+
result.create += 1;
|
|
127
189
|
}
|
|
190
|
+
|
|
128
191
|
if (context.reserveFee) {
|
|
129
|
-
result.update += 1;
|
|
192
|
+
result.update += 1; // owner
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (context.signerStates) {
|
|
196
|
+
result.update += context.signerStates.length;
|
|
130
197
|
}
|
|
131
198
|
|
|
132
199
|
return result;
|
|
@@ -153,62 +220,78 @@ runner.use(
|
|
|
153
220
|
reserveAmount,
|
|
154
221
|
reserveFee,
|
|
155
222
|
itx,
|
|
223
|
+
inputs,
|
|
224
|
+
signerStates,
|
|
225
|
+
burnAmount,
|
|
156
226
|
} = context;
|
|
157
|
-
const {
|
|
227
|
+
const { receiver } = itx;
|
|
158
228
|
const { reserveAddress, tokenAddress } = tokenFactoryState;
|
|
159
229
|
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
230
|
+
const accountUpdates = {};
|
|
231
|
+
const inputChanges = [];
|
|
232
|
+
|
|
233
|
+
// update signer burns
|
|
234
|
+
inputs.forEach(({ owner, tokensList }) => {
|
|
235
|
+
const tokenChange = tokensList.find((item) => item.address === tokenAddress);
|
|
236
|
+
|
|
237
|
+
accountUpdates[owner] = applyTokenUpdates(
|
|
238
|
+
[tokenChange],
|
|
239
|
+
signerStates.find((s) => s.address === owner),
|
|
240
|
+
'sub'
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
inputChanges.push({
|
|
244
|
+
address: owner,
|
|
245
|
+
token: tokenAddress,
|
|
246
|
+
delta: new BN(tokenChange.value).neg().toString(),
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// update sender
|
|
251
|
+
if (senderChange) {
|
|
252
|
+
accountUpdates[senderChange.address] = applyTokenChange(accountUpdates[senderChange.address], senderChange);
|
|
171
253
|
}
|
|
172
|
-
if
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (isSameDid(receiver, ownerState.address)) {
|
|
177
|
-
receiverTokens[reserveAddress] = new BN(receiverTokens[reserveAddress] || '0').add(fee).toString();
|
|
178
|
-
} else {
|
|
179
|
-
ownerTokens[reserveAddress] = new BN(ownerTokens[reserveAddress] || '0').add(fee).toString();
|
|
180
|
-
}
|
|
254
|
+
// Ensure the sender exists in accountUpdates, because even if there is no balance change, we need to update his nonce
|
|
255
|
+
if (!accountUpdates[tx.from]) {
|
|
256
|
+
accountUpdates[tx.from] = senderState || {};
|
|
181
257
|
}
|
|
182
258
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
259
|
+
// update owner
|
|
260
|
+
if (reserveFee && new BN(reserveFee).gt(0)) {
|
|
261
|
+
accountUpdates[ownerState.address] = applyTokenChange(accountUpdates[ownerState.address] || ownerState, {
|
|
262
|
+
address: ownerState.address,
|
|
263
|
+
token: reserveAddress,
|
|
264
|
+
delta: reserveFee,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
186
267
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
268
|
+
// update receiver
|
|
269
|
+
accountUpdates[receiver] = applyTokenChange(
|
|
270
|
+
accountUpdates[receiver] || receiverState || { address: receiver, tokens: {} },
|
|
271
|
+
{
|
|
272
|
+
address: receiver,
|
|
273
|
+
token: reserveAddress,
|
|
274
|
+
delta: new BN(reserveAmount).sub(new BN(reserveFee || '0')).toString(), // reserveAmount - reserveFee
|
|
275
|
+
}
|
|
276
|
+
);
|
|
194
277
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
278
|
+
const [newAccountStates, newTokenFactoryState, newTokenState] = await Promise.all([
|
|
279
|
+
// update accounts
|
|
280
|
+
Promise.all(
|
|
281
|
+
Object.entries(accountUpdates).map(([address, updates]) => {
|
|
282
|
+
// We can use updateOrCreate here because the owner and signer have already been validated to exist earlier,
|
|
283
|
+
// the sender and receiver are allowed to be created in the transaction.
|
|
284
|
+
const state = [senderState, receiverState, ownerState, ...signerStates].find((x) => x?.address === address);
|
|
285
|
+
|
|
286
|
+
// Should only update nonce and pk for sender and new accounts
|
|
287
|
+
const actualUpdates =
|
|
288
|
+
address === tx.from || !state
|
|
289
|
+
? { ...updates, address, nonce: tx.nonce, pk: tx.pk }
|
|
290
|
+
: pick(updates, ['tokens']);
|
|
291
|
+
|
|
292
|
+
return statedb.account.updateOrCreate(state, account.updateOrCreate(state, actualUpdates, context), context);
|
|
293
|
+
})
|
|
294
|
+
),
|
|
212
295
|
|
|
213
296
|
// Update token factory state
|
|
214
297
|
statedb.tokenFactory.update(
|
|
@@ -216,7 +299,7 @@ runner.use(
|
|
|
216
299
|
tokenFactory.update(
|
|
217
300
|
tokenFactoryState,
|
|
218
301
|
{
|
|
219
|
-
currentSupply: new BN(tokenFactoryState.currentSupply).sub(new BN(
|
|
302
|
+
currentSupply: new BN(tokenFactoryState.currentSupply).sub(new BN(burnAmount)).toString(),
|
|
220
303
|
reserveBalance: new BN(tokenFactoryState.reserveBalance).sub(new BN(reserveAmount)).toString(),
|
|
221
304
|
},
|
|
222
305
|
context
|
|
@@ -229,7 +312,7 @@ runner.use(
|
|
|
229
312
|
token.update(
|
|
230
313
|
tokenState,
|
|
231
314
|
{
|
|
232
|
-
totalSupply: new BN(tokenState.totalSupply).sub(new BN(
|
|
315
|
+
totalSupply: new BN(tokenState.totalSupply).sub(new BN(burnAmount)).toString(),
|
|
233
316
|
},
|
|
234
317
|
context
|
|
235
318
|
),
|
|
@@ -237,12 +320,17 @@ runner.use(
|
|
|
237
320
|
),
|
|
238
321
|
]);
|
|
239
322
|
|
|
240
|
-
context.senderState =
|
|
241
|
-
context.receiverState =
|
|
242
|
-
|
|
323
|
+
context.senderState = newAccountStates.find((x) => x.address === tx.from);
|
|
324
|
+
context.receiverState = newAccountStates.find((x) => x.address === receiver);
|
|
325
|
+
// owner maybe not updated
|
|
326
|
+
context.ownerState = newAccountStates.find((x) => x.address === ownerState.address) || ownerState;
|
|
327
|
+
context.signerStates = newAccountStates.filter((x) => signerStates.find((s) => s.address === x.address));
|
|
243
328
|
context.tokenFactoryState = newTokenFactoryState;
|
|
244
329
|
context.tokenState = newTokenState;
|
|
245
330
|
|
|
331
|
+
// save this for receipt generation
|
|
332
|
+
context.inputChanges = inputChanges;
|
|
333
|
+
|
|
246
334
|
await updateVaults();
|
|
247
335
|
|
|
248
336
|
next();
|
|
@@ -3,14 +3,14 @@ const cloneDeep = require('lodash/cloneDeep');
|
|
|
3
3
|
const { Joi } = require('@arcblock/validator');
|
|
4
4
|
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
5
5
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
6
|
-
const { account, token, tokenFactory } = require('@ocap/state');
|
|
6
|
+
const { account, token, tokenFactory, delegation } = require('@ocap/state');
|
|
7
7
|
const { toTokenFactoryAddress, toTokenAddress } = require('@arcblock/did-util');
|
|
8
8
|
const { applyTokenChange } = require('../../util');
|
|
9
9
|
|
|
10
10
|
// eslint-disable-next-line global-require, import/order
|
|
11
11
|
const debug = require('debug')(`${require('../../../package.json').name}:create-token-factory`);
|
|
12
12
|
|
|
13
|
-
const { decodeAnySafe } = require('../../util');
|
|
13
|
+
const { decodeAnySafe, getDelegationRequirements } = require('../../util');
|
|
14
14
|
|
|
15
15
|
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
16
16
|
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
@@ -121,6 +121,18 @@ runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: '
|
|
|
121
121
|
|
|
122
122
|
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState'] }));
|
|
123
123
|
|
|
124
|
+
// Ensure delegation
|
|
125
|
+
runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
|
|
126
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'delegatorState', addressKey: 'tx.delegator' }));
|
|
127
|
+
runner.use(
|
|
128
|
+
pipes.VerifyDelegation({
|
|
129
|
+
type: 'signature',
|
|
130
|
+
signerKey: 'senderState',
|
|
131
|
+
delegatorKey: 'delegatorState',
|
|
132
|
+
getRequirements: getDelegationRequirements,
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
|
|
124
136
|
// Ensure uniqueness of token symbol
|
|
125
137
|
runner.use(
|
|
126
138
|
async function EnsureTokenSymbol(context, next) {
|
|
@@ -138,8 +150,14 @@ runner.use(
|
|
|
138
150
|
|
|
139
151
|
// Ensure tx fee and gas
|
|
140
152
|
runner.use(
|
|
141
|
-
EnsureTxGas(() => {
|
|
142
|
-
|
|
153
|
+
EnsureTxGas((context) => {
|
|
154
|
+
const result = { create: 2, update: 1, payment: 0 };
|
|
155
|
+
|
|
156
|
+
if (context.isDelegationChanged) {
|
|
157
|
+
result.update += 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
143
161
|
})
|
|
144
162
|
);
|
|
145
163
|
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
@@ -150,9 +168,9 @@ runner.use(pipes.TakeStateSnapshot());
|
|
|
150
168
|
// Update sender state, token factory state
|
|
151
169
|
runner.use(
|
|
152
170
|
async (context, next) => {
|
|
153
|
-
const { tx, itx, statedb, senderState, senderChange, updateVaults } = context;
|
|
171
|
+
const { tx, itx, statedb, senderState, delegatorState, delegationState, senderChange, updateVaults } = context;
|
|
154
172
|
const data = decodeAnySafe(itx.data);
|
|
155
|
-
const owner = senderState.address;
|
|
173
|
+
const owner = delegatorState ? delegatorState.address : senderState.address;
|
|
156
174
|
|
|
157
175
|
const itxToken = itx.token;
|
|
158
176
|
|
|
@@ -162,7 +180,7 @@ runner.use(
|
|
|
162
180
|
? applyTokenChange({ tokens: senderTokens }, senderChange)
|
|
163
181
|
: { tokens: senderTokens };
|
|
164
182
|
|
|
165
|
-
const [newSenderState, newTokenState, newTokenFactoryState] = await Promise.all([
|
|
183
|
+
const [newSenderState, newTokenState, newTokenFactoryState, newDelegationState] = await Promise.all([
|
|
166
184
|
statedb.account.update(
|
|
167
185
|
senderState.address,
|
|
168
186
|
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
@@ -189,6 +207,11 @@ runner.use(
|
|
|
189
207
|
tokenFactory.create({ ...cloneDeep(itx), owner, tokenAddress: itxToken.address, data }, context),
|
|
190
208
|
context
|
|
191
209
|
),
|
|
210
|
+
|
|
211
|
+
// Update delegation state
|
|
212
|
+
context.isDelegationChanged
|
|
213
|
+
? statedb.delegation.update(delegationState.address, delegation.update(delegationState, {}, context), context)
|
|
214
|
+
: delegationState,
|
|
192
215
|
]);
|
|
193
216
|
|
|
194
217
|
await updateVaults();
|
|
@@ -196,6 +219,7 @@ runner.use(
|
|
|
196
219
|
context.senderState = newSenderState;
|
|
197
220
|
context.tokenState = newTokenState;
|
|
198
221
|
context.tokenFactoryState = newTokenFactoryState;
|
|
222
|
+
context.delegationState = newDelegationState;
|
|
199
223
|
|
|
200
224
|
debug('create token factory', newTokenFactoryState);
|
|
201
225
|
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
2
2
|
const { Joi, schemas } = require('@arcblock/validator');
|
|
3
|
-
const { BN,
|
|
3
|
+
const { BN, fromUnitToToken } = require('@ocap/util');
|
|
4
4
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
5
5
|
const { account, tokenFactory, token } = require('@ocap/state');
|
|
6
|
+
const pick = require('lodash/pick');
|
|
6
7
|
|
|
7
8
|
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
8
9
|
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
9
10
|
const CalcReserve = require('./pipes/calc-reserve');
|
|
10
|
-
const { applyTokenChange } = require('../../util');
|
|
11
|
+
const { applyTokenChange, applyTokenUpdates } = require('../../util');
|
|
11
12
|
|
|
12
13
|
const runner = new Runner();
|
|
13
14
|
|
|
14
|
-
runner.use(pipes.VerifyMultiSig(0));
|
|
15
|
-
|
|
16
15
|
const schema = Joi.object({
|
|
17
16
|
tokenFactory: Joi.DID().prefix().role('ROLE_TOKEN_FACTORY').required(),
|
|
18
17
|
receiver: schemas.tokenHolder.required(),
|
|
19
18
|
amount: Joi.BN().greater(0).required(),
|
|
20
|
-
maxReserve: Joi.alternatives().try(Joi.BN().greater(0), Joi.equal('')).optional(),
|
|
21
19
|
data: Joi.any().optional().allow(null),
|
|
20
|
+
inputsList: schemas.multiInput.min(1).required(),
|
|
22
21
|
}).options({ stripUnknown: true, noDefaults: false });
|
|
23
22
|
|
|
24
23
|
// verify itx
|
|
@@ -30,6 +29,23 @@ runner.use(({ itx }, next) => {
|
|
|
30
29
|
return next();
|
|
31
30
|
});
|
|
32
31
|
|
|
32
|
+
// verify inputs
|
|
33
|
+
runner.use(
|
|
34
|
+
pipes.VerifyTxInput({
|
|
35
|
+
fieldKey: 'itx.inputs',
|
|
36
|
+
inputsKey: 'inputs',
|
|
37
|
+
sendersKey: 'senders',
|
|
38
|
+
tokensKey: 'tokens',
|
|
39
|
+
assetsKey: null,
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// verify itx size: set hard limit here because more inputs leads to longer tx execute time
|
|
44
|
+
runner.use(pipes.VerifyListSize({ listKey: ['inputs', 'senders', 'tokens', 'assets'] }));
|
|
45
|
+
|
|
46
|
+
// verify multi sig
|
|
47
|
+
runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }));
|
|
48
|
+
|
|
33
49
|
// verify token factory
|
|
34
50
|
runner.use(
|
|
35
51
|
pipes.ExtractState({
|
|
@@ -40,14 +56,35 @@ runner.use(
|
|
|
40
56
|
})
|
|
41
57
|
);
|
|
42
58
|
|
|
59
|
+
// verify inputs token
|
|
60
|
+
runner.use((context, next) => {
|
|
61
|
+
const { tokenFactoryState, inputs } = context;
|
|
62
|
+
|
|
63
|
+
const isAccepted = inputs.every(
|
|
64
|
+
(input) =>
|
|
65
|
+
input.tokensList.length &&
|
|
66
|
+
input.tokensList.every((x) => x.address === tokenFactoryState.reserveAddress) &&
|
|
67
|
+
!input.assetsList.length
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!isAccepted) {
|
|
71
|
+
return next(new Error('INVALID_TX', `Inputs only accept ${tokenFactoryState.reserveAddress}`));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return next();
|
|
75
|
+
});
|
|
76
|
+
|
|
43
77
|
// ensure sender
|
|
44
|
-
runner.use(
|
|
45
|
-
|
|
46
|
-
);
|
|
47
|
-
runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
78
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
|
|
79
|
+
runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
|
|
80
|
+
runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
48
81
|
|
|
49
82
|
// ensure receiver
|
|
50
83
|
runner.use(pipes.ExtractState({ from: 'itx.receiver', to: 'receiverState', status: 'OK', table: 'account' }));
|
|
84
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'receiverState', addressKey: 'itx.receiver' }));
|
|
85
|
+
|
|
86
|
+
// verify blocked
|
|
87
|
+
runner.use(pipes.VerifyBlocked({ stateKeys: ['signerStates', 'receiverState'] }));
|
|
51
88
|
|
|
52
89
|
// ensure owner
|
|
53
90
|
runner.use(
|
|
@@ -59,9 +96,6 @@ runner.use(
|
|
|
59
96
|
})
|
|
60
97
|
);
|
|
61
98
|
|
|
62
|
-
// verify blocked
|
|
63
|
-
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState', 'receiverState'] }));
|
|
64
|
-
|
|
65
99
|
// ensure token state
|
|
66
100
|
runner.use(
|
|
67
101
|
pipes.ExtractState({
|
|
@@ -74,20 +108,32 @@ runner.use(
|
|
|
74
108
|
|
|
75
109
|
// calculate reserve amount
|
|
76
110
|
runner.use(
|
|
77
|
-
CalcReserve({
|
|
111
|
+
CalcReserve({
|
|
112
|
+
tokenFactoryKey: 'tokenFactoryState',
|
|
113
|
+
tokenStateKey: 'tokenState',
|
|
114
|
+
reserveKey: 'reserveAmount',
|
|
115
|
+
amountKey: 'itx.amount',
|
|
116
|
+
})
|
|
78
117
|
);
|
|
79
118
|
|
|
80
119
|
// verify slippage
|
|
81
120
|
runner.use((context, next) => {
|
|
82
|
-
const { reserveAmount, reserveFee } = context;
|
|
83
|
-
|
|
121
|
+
const { reserveAmount, reserveFee, inputs, tokenFactoryState } = context;
|
|
122
|
+
|
|
123
|
+
const maxReserve = inputs.reduce((total, input) => {
|
|
124
|
+
const reserveToken = input.tokensList.find((x) => x.address === tokenFactoryState.reserveAddress);
|
|
125
|
+
if (reserveToken) {
|
|
126
|
+
return total.add(new BN(reserveToken.value));
|
|
127
|
+
}
|
|
128
|
+
return total;
|
|
129
|
+
}, new BN(0));
|
|
84
130
|
|
|
85
131
|
const actual = new BN(reserveAmount).add(new BN(reserveFee || '0'));
|
|
86
|
-
if (
|
|
132
|
+
if (actual.gt(maxReserve)) {
|
|
87
133
|
return next(
|
|
88
134
|
new Error(
|
|
89
135
|
'SLIPPAGE_EXCEEDED',
|
|
90
|
-
`Mint token failed due to price movement. Expected maximum: ${fromUnitToToken(maxReserve)}, actual: ${fromUnitToToken(actual)}. Try increasing your
|
|
136
|
+
`Mint token failed due to price movement. Expected maximum: ${fromUnitToToken(maxReserve)}, actual: ${fromUnitToToken(actual)}. Try increasing your inputs.`
|
|
91
137
|
)
|
|
92
138
|
);
|
|
93
139
|
}
|
|
@@ -95,32 +141,33 @@ runner.use((context, next) => {
|
|
|
95
141
|
next();
|
|
96
142
|
});
|
|
97
143
|
|
|
98
|
-
// verify
|
|
99
|
-
runner.use((
|
|
100
|
-
|
|
101
|
-
context.tokenConditions = {
|
|
102
|
-
owner: context.senderState.address,
|
|
103
|
-
tokens: [
|
|
104
|
-
{
|
|
105
|
-
address: tokenFactoryState.reserveAddress,
|
|
106
|
-
value: new BN(context.reserveAmount).add(new BN(context.reserveFee || '0')).toString(),
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
};
|
|
110
|
-
next();
|
|
111
|
-
});
|
|
112
|
-
runner.use(pipes.VerifyTokenBalance({ ownerKey: 'senderState', conditionKey: 'tokenConditions' }));
|
|
144
|
+
// verify balance
|
|
145
|
+
runner.use(pipes.ExtractState({ from: 'tokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
|
|
146
|
+
runner.use(pipes.VerifyTokenBalance({ ownerKey: 'signerStates', conditionKey: 'inputs' }));
|
|
113
147
|
|
|
114
148
|
// Ensure tx fee and gas
|
|
115
149
|
runner.use(
|
|
116
150
|
EnsureTxGas((context) => {
|
|
117
|
-
const result = { create: 0, update:
|
|
151
|
+
const result = { create: 0, update: 2, payment: 0 };
|
|
152
|
+
|
|
153
|
+
if (context.senderState) {
|
|
154
|
+
result.update += 1;
|
|
155
|
+
} else {
|
|
156
|
+
result.create += 1;
|
|
157
|
+
}
|
|
118
158
|
|
|
119
159
|
if (context.receiverState) {
|
|
120
160
|
result.update += 1;
|
|
161
|
+
} else {
|
|
162
|
+
result.create += 1;
|
|
121
163
|
}
|
|
164
|
+
|
|
122
165
|
if (context.reserveFee) {
|
|
123
|
-
result.update += 1;
|
|
166
|
+
result.update += 1; // owner
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (context.signerStates) {
|
|
170
|
+
result.update += context.signerStates.length;
|
|
124
171
|
}
|
|
125
172
|
|
|
126
173
|
return result;
|
|
@@ -143,65 +190,93 @@ runner.use(
|
|
|
143
190
|
senderChange,
|
|
144
191
|
updateVaults,
|
|
145
192
|
tokenFactoryState,
|
|
193
|
+
signerStates,
|
|
146
194
|
tokenState,
|
|
147
195
|
reserveAmount,
|
|
148
196
|
reserveFee,
|
|
149
197
|
itx,
|
|
198
|
+
inputs,
|
|
150
199
|
} = context;
|
|
151
200
|
const { amount, receiver } = itx;
|
|
152
201
|
const { reserveAddress, tokenAddress } = tokenFactoryState;
|
|
153
202
|
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
const { tokens: ownerTokens = {} } = ownerState;
|
|
203
|
+
const totalReserveCost = new BN(reserveAmount).add(new BN(reserveFee || '0'));
|
|
204
|
+
let currentReserveCost = new BN('0');
|
|
157
205
|
|
|
158
|
-
|
|
159
|
-
|
|
206
|
+
const accountUpdates = {};
|
|
207
|
+
const inputChanges = [];
|
|
160
208
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
} else {
|
|
172
|
-
ownerTokens[reserveAddress] = new BN(ownerTokens[reserveAddress] || '0').add(fee).toString();
|
|
209
|
+
// update signer costs
|
|
210
|
+
inputs.forEach(({ owner, tokensList }) => {
|
|
211
|
+
const reserveTokenChange = tokensList.find((item) => item.address === reserveAddress);
|
|
212
|
+
let reserveValue = new BN(reserveTokenChange.value);
|
|
213
|
+
|
|
214
|
+
// The inputs represent the estimated maximum cost from the frontend.
|
|
215
|
+
// If the actual cost is less than the maximum cost, we need to adjust the deduction correctly.
|
|
216
|
+
if (currentReserveCost.add(reserveValue).gt(totalReserveCost)) {
|
|
217
|
+
reserveTokenChange.value = totalReserveCost.sub(currentReserveCost).toString();
|
|
218
|
+
reserveValue = new BN(reserveTokenChange.value);
|
|
173
219
|
}
|
|
220
|
+
currentReserveCost = currentReserveCost.add(reserveValue);
|
|
221
|
+
|
|
222
|
+
accountUpdates[owner] = applyTokenUpdates(
|
|
223
|
+
[reserveTokenChange],
|
|
224
|
+
signerStates.find((s) => s.address === owner),
|
|
225
|
+
'sub'
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
inputChanges.push({
|
|
229
|
+
address: owner,
|
|
230
|
+
token: reserveAddress,
|
|
231
|
+
delta: reserveValue.neg().toString(),
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// update sender
|
|
236
|
+
if (senderChange) {
|
|
237
|
+
accountUpdates[senderChange.address] = applyTokenChange(accountUpdates[senderChange.address], senderChange);
|
|
238
|
+
}
|
|
239
|
+
// Ensure the sender exists in accountUpdates, because even if there is no balance change, we need to update his nonce
|
|
240
|
+
if (!accountUpdates[tx.from]) {
|
|
241
|
+
accountUpdates[tx.from] = senderState || {};
|
|
174
242
|
}
|
|
175
243
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
244
|
+
// update owner
|
|
245
|
+
if (reserveFee && new BN(reserveFee).gt(0)) {
|
|
246
|
+
accountUpdates[ownerState.address] = applyTokenChange(accountUpdates[ownerState.address] || ownerState, {
|
|
247
|
+
address: ownerState.address,
|
|
248
|
+
token: reserveAddress,
|
|
249
|
+
delta: reserveFee,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
179
252
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
253
|
+
// update receiver
|
|
254
|
+
accountUpdates[receiver] = applyTokenChange(
|
|
255
|
+
accountUpdates[receiver] || receiverState || { address: receiver, tokens: {} },
|
|
256
|
+
{
|
|
257
|
+
address: receiver,
|
|
258
|
+
token: tokenAddress,
|
|
259
|
+
delta: amount,
|
|
260
|
+
}
|
|
261
|
+
);
|
|
187
262
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
263
|
+
const [newAccountStates, newTokenFactoryState, newTokenState] = await Promise.all([
|
|
264
|
+
// update accounts
|
|
265
|
+
Promise.all(
|
|
266
|
+
Object.entries(accountUpdates).map(([address, updates]) => {
|
|
267
|
+
// We can use updateOrCreate here because the owner and signer have already been validated to exist earlier,
|
|
268
|
+
// the sender and receiver are allowed to be created in the transaction.
|
|
269
|
+
const state = [senderState, receiverState, ownerState, ...signerStates].find((x) => x?.address === address);
|
|
270
|
+
|
|
271
|
+
// Should only update nonce and pk for sender and new accounts
|
|
272
|
+
const actualUpdates =
|
|
273
|
+
address === tx.from || !state
|
|
274
|
+
? { ...updates, address, nonce: tx.nonce, pk: tx.pk }
|
|
275
|
+
: pick(updates, ['tokens']);
|
|
276
|
+
|
|
277
|
+
return statedb.account.updateOrCreate(state, account.updateOrCreate(state, actualUpdates, context), context);
|
|
278
|
+
})
|
|
279
|
+
),
|
|
205
280
|
|
|
206
281
|
// Update token factory state
|
|
207
282
|
statedb.tokenFactory.update(
|
|
@@ -230,12 +305,17 @@ runner.use(
|
|
|
230
305
|
),
|
|
231
306
|
]);
|
|
232
307
|
|
|
233
|
-
context.senderState =
|
|
234
|
-
context.receiverState =
|
|
235
|
-
|
|
308
|
+
context.senderState = newAccountStates.find((x) => x.address === tx.from);
|
|
309
|
+
context.receiverState = newAccountStates.find((x) => x.address === receiver);
|
|
310
|
+
// owner maybe not updated
|
|
311
|
+
context.ownerState = newAccountStates.find((x) => x.address === ownerState.address) || ownerState;
|
|
312
|
+
context.signerStates = newAccountStates.filter((x) => signerStates.find((s) => s.address === x.address));
|
|
236
313
|
context.tokenFactoryState = newTokenFactoryState;
|
|
237
314
|
context.tokenState = newTokenState;
|
|
238
315
|
|
|
316
|
+
// save this for receipt generation
|
|
317
|
+
context.inputChanges = inputChanges;
|
|
318
|
+
|
|
239
319
|
await updateVaults();
|
|
240
320
|
|
|
241
321
|
next();
|
|
@@ -7,15 +7,15 @@ module.exports =
|
|
|
7
7
|
({
|
|
8
8
|
tokenFactoryKey = 'tokenFactoryState',
|
|
9
9
|
tokenStateKey = 'tokenState',
|
|
10
|
-
senderStateKey = 'senderState',
|
|
11
10
|
reserveKey = 'reserveAmount',
|
|
12
11
|
feeKey = 'reserveFee',
|
|
12
|
+
amountKey = 'itx.amount',
|
|
13
13
|
direction = 'mint',
|
|
14
14
|
} = {}) =>
|
|
15
15
|
(context, next) => {
|
|
16
16
|
const tokenFactoryState = get(context, tokenFactoryKey);
|
|
17
17
|
const tokenState = get(context, tokenStateKey);
|
|
18
|
-
const
|
|
18
|
+
const amount = get(context, amountKey);
|
|
19
19
|
|
|
20
20
|
if (!tokenFactoryState) {
|
|
21
21
|
return next(new Error('INVALID_TOKEN_FACTORY', 'Token factory state not found'));
|
|
@@ -26,15 +26,12 @@ module.exports =
|
|
|
26
26
|
|
|
27
27
|
const { curve, currentSupply, feeRate } = tokenFactoryState;
|
|
28
28
|
const { decimal } = tokenState;
|
|
29
|
-
const { amount } = context.itx;
|
|
30
29
|
|
|
31
30
|
const reserveAmount = calcCost({ amount, decimal, currentSupply, direction, curve });
|
|
32
31
|
set(context, reserveKey, reserveAmount.toString());
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
set(context, feeKey, reserveFee.toString());
|
|
37
|
-
}
|
|
33
|
+
const reserveFee = calcFee({ reserveAmount, feeRate });
|
|
34
|
+
set(context, feeKey, reserveFee.toString());
|
|
38
35
|
|
|
39
|
-
next();
|
|
36
|
+
return next();
|
|
40
37
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const { Joi } = require('@arcblock/validator');
|
|
2
2
|
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
3
3
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
4
|
-
const { account, tokenFactory } = require('@ocap/state');
|
|
5
|
-
const { applyTokenChange } = require('../../util');
|
|
4
|
+
const { account, tokenFactory, delegation } = require('@ocap/state');
|
|
5
|
+
const { applyTokenChange, getDelegationRequirements } = require('../../util');
|
|
6
6
|
|
|
7
7
|
// eslint-disable-next-line global-require, import/order
|
|
8
8
|
const debug = require('debug')(`${require('../../../package.json').name}:update-token-factory`);
|
|
@@ -40,25 +40,44 @@ runner.use(
|
|
|
40
40
|
})
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
+
// Ensure sender
|
|
44
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
45
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
46
|
+
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState'] }));
|
|
47
|
+
|
|
48
|
+
// Ensure delegation
|
|
49
|
+
runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
|
|
50
|
+
runner.use(pipes.VerifyAccountMigration({ stateKey: 'delegatorState', addressKey: 'tx.delegator' }));
|
|
51
|
+
runner.use(
|
|
52
|
+
pipes.VerifyDelegation({
|
|
53
|
+
type: 'signature',
|
|
54
|
+
signerKey: 'senderState',
|
|
55
|
+
delegatorKey: 'delegatorState',
|
|
56
|
+
getRequirements: getDelegationRequirements,
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
|
|
43
60
|
// verify owner
|
|
44
61
|
runner.use((context, next) => {
|
|
45
|
-
const {
|
|
46
|
-
|
|
62
|
+
const { tokenFactoryState, delegatorState, senderState } = context;
|
|
63
|
+
const owner = delegatorState ? delegatorState.address : senderState.address;
|
|
64
|
+
if (owner !== tokenFactoryState.owner) {
|
|
47
65
|
return next(new Error('FORBIDDEN', 'Token factory can only be updated by owner'));
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
return next();
|
|
51
69
|
});
|
|
52
70
|
|
|
53
|
-
// Ensure sender
|
|
54
|
-
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
55
|
-
runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
|
|
56
|
-
runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState'] }));
|
|
57
|
-
|
|
58
71
|
// Ensure tx fee and gas
|
|
59
72
|
runner.use(
|
|
60
|
-
EnsureTxGas(() => {
|
|
61
|
-
|
|
73
|
+
EnsureTxGas((context) => {
|
|
74
|
+
const result = { create: 0, update: 2, payment: 0 };
|
|
75
|
+
|
|
76
|
+
if (context.isDelegationChanged) {
|
|
77
|
+
result.update += 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
62
81
|
})
|
|
63
82
|
);
|
|
64
83
|
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
@@ -69,7 +88,7 @@ runner.use(pipes.TakeStateSnapshot());
|
|
|
69
88
|
// Update sender state, token factory state
|
|
70
89
|
runner.use(
|
|
71
90
|
async (context, next) => {
|
|
72
|
-
const { tx, itx, statedb, senderState, tokenFactoryState, senderChange, updateVaults } = context;
|
|
91
|
+
const { tx, itx, statedb, senderState, tokenFactoryState, senderChange, updateVaults, delegationState } = context;
|
|
73
92
|
|
|
74
93
|
const { tokens: senderTokens = {} } = senderState;
|
|
75
94
|
|
|
@@ -77,7 +96,7 @@ runner.use(
|
|
|
77
96
|
? applyTokenChange({ tokens: senderTokens }, senderChange)
|
|
78
97
|
: { tokens: senderTokens };
|
|
79
98
|
|
|
80
|
-
const [newSenderState, newTokenFactoryState] = await Promise.all([
|
|
99
|
+
const [newSenderState, newTokenFactoryState, newDelegationState] = await Promise.all([
|
|
81
100
|
statedb.account.update(
|
|
82
101
|
senderState.address,
|
|
83
102
|
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
@@ -89,12 +108,18 @@ runner.use(
|
|
|
89
108
|
tokenFactory.update(tokenFactoryState, { feeRate: itx.feeRate }, context),
|
|
90
109
|
context
|
|
91
110
|
),
|
|
111
|
+
|
|
112
|
+
// Update delegation state
|
|
113
|
+
context.isDelegationChanged
|
|
114
|
+
? statedb.delegation.update(delegationState.address, delegation.update(delegationState, {}, context), context)
|
|
115
|
+
: delegationState,
|
|
92
116
|
]);
|
|
93
117
|
|
|
94
118
|
await updateVaults();
|
|
95
119
|
|
|
96
120
|
context.senderState = newSenderState;
|
|
97
121
|
context.tokenFactoryState = newTokenFactoryState;
|
|
122
|
+
context.delegationState = newDelegationState;
|
|
98
123
|
|
|
99
124
|
debug('update token factory', newTokenFactoryState);
|
|
100
125
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.23.1",
|
|
7
7
|
"description": "Predefined tx pipeline sets to execute certain type of transactions",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -18,19 +18,19 @@
|
|
|
18
18
|
"empty-value": "^1.0.1",
|
|
19
19
|
"lodash": "^4.17.21",
|
|
20
20
|
"url-join": "^4.0.1",
|
|
21
|
-
"@arcblock/did": "1.
|
|
22
|
-
"@arcblock/did-util": "1.
|
|
23
|
-
"@arcblock/jwt": "1.
|
|
24
|
-
"@arcblock/validator": "1.
|
|
25
|
-
"@ocap/asset": "1.
|
|
26
|
-
"@ocap/client": "1.
|
|
27
|
-
"@ocap/
|
|
28
|
-
"@ocap/
|
|
29
|
-
"@ocap/message": "1.
|
|
30
|
-
"@ocap/state": "1.
|
|
31
|
-
"@ocap/tx-pipeline": "1.
|
|
32
|
-
"@ocap/util": "1.
|
|
33
|
-
"@ocap/wallet": "1.
|
|
21
|
+
"@arcblock/did": "1.23.1",
|
|
22
|
+
"@arcblock/did-util": "1.23.1",
|
|
23
|
+
"@arcblock/jwt": "1.23.1",
|
|
24
|
+
"@arcblock/validator": "1.23.1",
|
|
25
|
+
"@ocap/asset": "1.23.1",
|
|
26
|
+
"@ocap/client": "1.23.1",
|
|
27
|
+
"@ocap/mcrypto": "1.23.1",
|
|
28
|
+
"@ocap/merkle-tree": "1.23.1",
|
|
29
|
+
"@ocap/message": "1.23.1",
|
|
30
|
+
"@ocap/state": "1.23.1",
|
|
31
|
+
"@ocap/tx-pipeline": "1.23.1",
|
|
32
|
+
"@ocap/util": "1.23.1",
|
|
33
|
+
"@ocap/wallet": "1.23.1"
|
|
34
34
|
},
|
|
35
35
|
"resolutions": {
|
|
36
36
|
"bn.js": "5.2.1",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"jest": "^29.7.0",
|
|
41
41
|
"start-server-and-test": "^1.14.0",
|
|
42
|
-
"@ocap/e2e-test": "1.
|
|
43
|
-
"@ocap/statedb-memory": "1.
|
|
42
|
+
"@ocap/e2e-test": "1.23.1",
|
|
43
|
+
"@ocap/statedb-memory": "1.23.1"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"lint": "eslint tests lib",
|