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