@ocap/tx-protocols 1.27.7 → 1.27.9

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.
@@ -15,6 +15,7 @@ const debug = require('debug')(`${require('../../../package.json').name}:migrate
15
15
 
16
16
  const EnsureTxGas = require('../../pipes/ensure-gas');
17
17
  const EnsureTxCost = require('../../pipes/ensure-cost');
18
+ const { decodeAnySafe } = require('../../util');
18
19
 
19
20
  const runner = new Runner();
20
21
 
@@ -61,6 +62,15 @@ runner.use(
61
62
  return { create: 0, update: 0, payment: 0 };
62
63
  }
63
64
 
65
+ // Do not charge gas for connecting wallet from application
66
+ if (itx.data) {
67
+ const decoded = decodeAnySafe(itx.data);
68
+ if (decoded?.value && decoded.value.purpose === 'connect-wallet') {
69
+ Object.defineProperty(context, 'txBaseGas', { value: false });
70
+ return { create: 0, update: 0, payment: 0 };
71
+ }
72
+ }
73
+
64
74
  return {
65
75
  create: context.senderState ? 1 : 2,
66
76
  update: context.senderState ? getRelatedAddresses(context.senderState).length : 0,
@@ -88,6 +88,15 @@ runner.use(pipes.VerifyAccountMigration({ stateKey: 'receiverState', addressKey:
88
88
  runner.use(pipes.ExtractState({ from: 'tokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
89
89
  runner.use(pipes.VerifyTokenBalance({ ownerKey: 'signerStates', conditionKey: 'inputs' }));
90
90
 
91
+ // verify token spenders
92
+ runner.use(
93
+ pipes.verifyTokenAccess({
94
+ statesKey: 'tokenStates',
95
+ listFieldKey: 'spenders',
96
+ accountKeys: ['signerStates'],
97
+ errorMessage: 'Account {address} is not allowed to stake token {tokenAddress}',
98
+ })
99
+ );
91
100
  // 7. verify asset state and ownership
92
101
  runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'OK', table: 'asset' }));
93
102
  runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
@@ -1,5 +1,6 @@
1
1
  const isEmpty = require('empty-value');
2
2
  const cloneDeep = require('lodash/cloneDeep');
3
+ const uniq = require('lodash/uniq');
3
4
  const { Joi, schemas } = require('@arcblock/validator');
4
5
  const { CustomError: Error } = require('@ocap/util/lib/error');
5
6
  const { Runner, pipes } = require('@ocap/tx-pipeline');
@@ -35,16 +36,22 @@ const schema = Joi.object({
35
36
  .optional()
36
37
  .allow(null),
37
38
  foreignToken: schemas.foreignToken.optional().allow(null).default(null),
39
+ spendersList: Joi.array().items(Joi.DID().prefix()).max(30).optional().allow(null).default(null),
38
40
  tokenFactoryAddress: Joi.forbidden(),
39
41
  data: Joi.any().optional().allow(null),
40
42
  }).options({ stripUnknown: true, noDefaults: false });
41
43
 
42
44
  // 1. verify itx
43
45
  runner.use(({ itx }, next) => {
44
- const { error } = schema.validate(itx);
46
+ const { error, value } = schema.validate(itx);
45
47
  if (error) {
46
48
  return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
47
49
  }
50
+
51
+ // convert spendersList to spenders
52
+ itx.spenders = value.spendersList?.length ? uniq(value.spendersList) : null;
53
+ delete itx.spendersList;
54
+
48
55
  return next();
49
56
  });
50
57
 
@@ -152,7 +159,11 @@ runner.use(
152
159
  context
153
160
  ),
154
161
 
155
- statedb.token.create(itx.address, token.create({ ...cloneDeep(itx), data, issuer: owner }, context), context),
162
+ statedb.token.create(
163
+ itx.address,
164
+ token.create({ ...cloneDeep(itx), data, issuer: owner, type: 'Token' }, context),
165
+ context
166
+ ),
156
167
 
157
168
  delegatorState
158
169
  ? statedb.account.update(
@@ -29,6 +29,26 @@ runner.use(({ itx }, next) => {
29
29
  return next();
30
30
  });
31
31
 
32
+ // verify token factory
33
+ runner.use(
34
+ pipes.ExtractState({
35
+ from: 'itx.tokenFactory',
36
+ to: 'tokenFactoryState',
37
+ status: 'INVALID_TOKEN_FACTORY',
38
+ table: 'tokenFactory',
39
+ })
40
+ );
41
+
42
+ // ensure token state
43
+ runner.use(
44
+ pipes.ExtractState({
45
+ from: 'tokenFactoryState.tokenAddress',
46
+ to: 'tokenState',
47
+ status: 'INVALID_TOKEN',
48
+ table: 'token',
49
+ })
50
+ );
51
+
32
52
  // verify inputs
33
53
  runner.use(
34
54
  pipes.VerifyTxInput({
@@ -46,16 +66,6 @@ runner.use(pipes.VerifyListSize({ listKey: ['inputs', 'senders', 'tokens', 'asse
46
66
  // verify multi sig
47
67
  runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }));
48
68
 
49
- // verify token factory
50
- runner.use(
51
- pipes.ExtractState({
52
- from: 'itx.tokenFactory',
53
- to: 'tokenFactoryState',
54
- status: 'INVALID_TOKEN_FACTORY',
55
- table: 'tokenFactory',
56
- })
57
- );
58
-
59
69
  // verify inputs token
60
70
  runner.use((context, next) => {
61
71
  const { tokenFactoryState, inputs } = context;
@@ -98,13 +108,13 @@ runner.use(
98
108
  })
99
109
  );
100
110
 
101
- // ensure token state
111
+ // verify minters restriction
102
112
  runner.use(
103
- pipes.ExtractState({
104
- from: 'tokenFactoryState.tokenAddress',
105
- to: 'tokenState',
106
- status: 'INVALID_TOKEN',
107
- table: 'token',
113
+ pipes.verifyTokenAccess({
114
+ statesKey: 'tokenState',
115
+ listFieldKey: 'minters',
116
+ accountKeys: ['signerStates', 'senderState'],
117
+ errorMessage: 'Account {address} is not allowed to burn token {tokenAddress}',
108
118
  })
109
119
  );
110
120
 
@@ -135,25 +145,29 @@ runner.use(
135
145
  feeKey: 'reserveFee',
136
146
  amountKey: 'burnAmount',
137
147
  direction: 'burn',
138
- })
148
+ }),
149
+ { shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve }
139
150
  );
140
151
 
141
152
  // verify slippage
142
- runner.use((context, next) => {
143
- const { reserveAmount, reserveFee } = context;
144
- const { minReserve } = context.itx;
145
-
146
- if (minReserve && new BN(reserveAmount).lt(new BN(minReserve).add(new BN(reserveFee || '0')))) {
147
- return next(
148
- new Error(
149
- 'SLIPPAGE_EXCEEDED',
150
- `Burn token failed due to price movement. Expected minimum: ${fromUnitToToken(minReserve)}, actual: ${fromUnitToToken(reserveAmount)}. Try reducing your minReserve.`
151
- )
152
- );
153
- }
153
+ runner.use(
154
+ (context, next) => {
155
+ const { reserveAmount, reserveFee } = context;
156
+ const { minReserve } = context.itx;
157
+
158
+ if (minReserve && new BN(reserveAmount).lt(new BN(minReserve).add(new BN(reserveFee || '0')))) {
159
+ return next(
160
+ new Error(
161
+ 'SLIPPAGE_EXCEEDED',
162
+ `Burn token failed due to price movement. Expected minimum: ${fromUnitToToken(minReserve)}, actual: ${fromUnitToToken(reserveAmount)}. Try reducing your minReserve.`
163
+ )
164
+ );
165
+ }
154
166
 
155
- return next();
156
- });
167
+ return next();
168
+ },
169
+ { shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve }
170
+ );
157
171
 
158
172
  // verify balance
159
173
  runner.use(pipes.ExtractState({ from: 'tokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
@@ -165,7 +179,7 @@ runner.use((context, next) => {
165
179
  if (new BN(tokenFactoryState.currentSupply).lt(new BN(context.burnAmount))) {
166
180
  return next(new Error('INSUFFICIENT_FUND', 'Token factory supply is not enough'));
167
181
  }
168
- if (new BN(tokenFactoryState.reserveBalance).lt(new BN(context.reserveAmount))) {
182
+ if (new BN(tokenFactoryState.reserveBalance).lt(new BN(context.reserveAmount || '0'))) {
169
183
  return next(new Error('INSUFFICIENT_FUND', 'Token factory reserve balance is not enough'));
170
184
  }
171
185
  return next();
@@ -182,10 +196,12 @@ runner.use(
182
196
  result.create += 1;
183
197
  }
184
198
 
185
- if (context.receiverState) {
186
- result.update += 1;
187
- } else {
188
- result.create += 1;
199
+ if (context.tokenFactoryState.curve) {
200
+ if (context.receiverState) {
201
+ result.update += 1;
202
+ } else {
203
+ result.create += 1;
204
+ }
189
205
  }
190
206
 
191
207
  if (context.reserveFee) {
@@ -262,14 +278,17 @@ runner.use(
262
278
  }
263
279
 
264
280
  // update receiver
265
- accountUpdates[receiver] = applyTokenChange(
266
- accountUpdates[receiver] || receiverState || { address: receiver, tokens: {} },
267
- {
268
- address: receiver,
269
- token: reserveAddress,
270
- delta: new BN(reserveAmount).sub(new BN(reserveFee || '0')).toString(), // reserveAmount - reserveFee
271
- }
272
- );
281
+ // should not update receiver for credit token
282
+ if (tokenFactoryState.curve) {
283
+ accountUpdates[receiver] = applyTokenChange(
284
+ accountUpdates[receiver] || receiverState || { address: receiver, tokens: {} },
285
+ {
286
+ address: receiver,
287
+ token: reserveAddress,
288
+ delta: new BN(reserveAmount).sub(new BN(reserveFee || '0')).toString(), // reserveAmount - reserveFee
289
+ }
290
+ );
291
+ }
273
292
 
274
293
  // update sender
275
294
  if (senderChange) {
@@ -307,7 +326,7 @@ runner.use(
307
326
  tokenFactoryState,
308
327
  {
309
328
  currentSupply: new BN(tokenFactoryState.currentSupply).sub(new BN(burnAmount)).toString(),
310
- reserveBalance: new BN(tokenFactoryState.reserveBalance).sub(new BN(reserveAmount)).toString(),
329
+ reserveBalance: new BN(tokenFactoryState.reserveBalance).sub(new BN(reserveAmount || '0')).toString(),
311
330
  },
312
331
  context
313
332
  ),
@@ -1,11 +1,12 @@
1
1
  const isEmpty = require('empty-value');
2
2
  const cloneDeep = require('lodash/cloneDeep');
3
+ const uniq = require('lodash/uniq');
3
4
  const { Joi } = require('@arcblock/validator');
4
5
  const { CustomError: Error } = require('@ocap/util/lib/error');
5
6
  const { Runner, pipes } = require('@ocap/tx-pipeline');
6
7
  const { account, token, tokenFactory, delegation, stake } = require('@ocap/state');
7
8
  const { toTokenFactoryAddress, toTokenAddress, toStakeAddress } = require('@arcblock/did-util');
8
- const { BN, fromTokenToUnit } = require('@ocap/util');
9
+ const { BN, fromTokenToUnit, fromUnitToToken } = require('@ocap/util');
9
10
 
10
11
  // eslint-disable-next-line global-require, import/order
11
12
  const debug = require('debug')(`${require('../../../package.json').name}:create-token-factory`);
@@ -26,7 +27,13 @@ runner.use(pipes.VerifyMultiSig(0));
26
27
  const schema = Joi.object({
27
28
  address: Joi.DID().prefix().role('ROLE_TOKEN_FACTORY').required(),
28
29
  feeRate: Joi.number().min(0).max(2000).required(),
29
- curve: tokenFactory.curveSchema.required(),
30
+ curve: Joi.when('token.type', {
31
+ is: 'CreditToken',
32
+ then: Joi.valid(null).optional().messages({
33
+ 'any.only': 'Should not provide curve for CreditToken',
34
+ }),
35
+ otherwise: tokenFactory.curveSchema.required(),
36
+ }),
30
37
  reserveAddress: Joi.DID().prefix().role('ROLE_TOKEN').required(),
31
38
  token: Joi.object({
32
39
  name: Joi.string().min(1).max(32).required(),
@@ -45,6 +52,9 @@ const schema = Joi.object({
45
52
  .optional()
46
53
  .allow(null, ''),
47
54
  metadata: Joi.any().required(),
55
+ spendersList: Joi.array().items(Joi.DID().prefix()).max(30).optional().allow(null).default(null),
56
+ mintersList: Joi.array().items(Joi.DID().prefix()).max(30).optional().allow(null).default(null),
57
+ type: Joi.string().valid('CreditToken', 'BondingCurveToken').required(),
48
58
  }).required(),
49
59
  data: Joi.any().optional().allow(null),
50
60
  }).options({ stripUnknown: true, noDefaults: false });
@@ -69,6 +79,14 @@ runner.use((context, next) => {
69
79
  context.itx.token.metadata = { ...metadata, value: metadataValue };
70
80
  }
71
81
 
82
+ // convert spendersList to spenders
83
+ context.itx.token.spenders = value.token.spendersList?.length ? uniq(value.token.spendersList) : null;
84
+ delete context.itx.token.spendersList;
85
+
86
+ // convert mintersList to minters
87
+ context.itx.token.minters = value.token.mintersList?.length ? uniq(value.token.mintersList) : null;
88
+ delete context.itx.token.mintersList;
89
+
72
90
  return next();
73
91
  });
74
92
 
@@ -162,19 +180,24 @@ runner.use(
162
180
  pipes.ExtractState({ from: 'stakeAddress', to: 'stakeState', status: 'INVALID_STAKE_STATE', table: 'stake' })
163
181
  );
164
182
  runner.use((context, next) => {
165
- const { stakeState, config } = context;
183
+ const { stakeState, config, itx } = context;
166
184
 
167
185
  if (stakeState.revocable === false) {
168
186
  return next(new Error('INVALID_STAKE_STATE', `Staking for token creating already locked: ${stakeState.address}`));
169
187
  }
170
188
 
171
189
  const actualStake = new BN(stakeState.tokens[config.token.address] || 0);
172
- const requiredStake = fromTokenToUnit(config.transaction.txStake.createToken, config.token.decimal);
190
+ const requiredStake = fromTokenToUnit(
191
+ itx.token.type === 'CreditToken'
192
+ ? config.transaction.txStake.createCreditToken
193
+ : config.transaction.txStake.createToken,
194
+ config.token.decimal
195
+ );
173
196
  if (actualStake.lt(requiredStake)) {
174
197
  return next(
175
198
  new Error(
176
199
  'INVALID_STAKE_STATE',
177
- `Insufficient stake amount: required ${config.transaction.txStake.createToken} ${config.token.symbol}`
200
+ `Insufficient stake amount: required ${fromUnitToToken(requiredStake, config.token.decimal)} ${config.token.symbol}`
178
201
  )
179
202
  );
180
203
  }
@@ -17,7 +17,7 @@ const schema = Joi.object({
17
17
  receiver: schemas.tokenHolder.required(),
18
18
  amount: Joi.BN().greater(0).required(),
19
19
  data: Joi.any().optional().allow(null),
20
- inputsList: schemas.multiInput.min(1).required(),
20
+ inputsList: schemas.multiInput.optional(),
21
21
  }).options({ stripUnknown: true, noDefaults: false });
22
22
 
23
23
  // verify itx
@@ -29,7 +29,26 @@ runner.use(({ itx }, next) => {
29
29
  return next();
30
30
  });
31
31
 
32
- // verify inputs
32
+ // ensure token factory state
33
+ runner.use(
34
+ pipes.ExtractState({
35
+ from: 'itx.tokenFactory',
36
+ to: 'tokenFactoryState',
37
+ status: 'INVALID_TOKEN_FACTORY',
38
+ table: 'tokenFactory',
39
+ })
40
+ );
41
+
42
+ // ensure token state
43
+ runner.use(
44
+ pipes.ExtractState({
45
+ from: 'tokenFactoryState.tokenAddress',
46
+ to: 'tokenState',
47
+ status: 'INVALID_TOKEN',
48
+ table: 'token',
49
+ })
50
+ );
51
+
33
52
  runner.use(
34
53
  pipes.VerifyTxInput({
35
54
  fieldKey: 'itx.inputs',
@@ -37,47 +56,48 @@ runner.use(
37
56
  sendersKey: 'senders',
38
57
  tokensKey: 'tokens',
39
58
  assetsKey: null,
40
- })
59
+ }),
60
+ { shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve }
41
61
  );
42
62
 
43
63
  // 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'] }));
64
+ runner.use(pipes.VerifyListSize({ listKey: ['inputs', 'senders', 'tokens', 'assets'] }), {
65
+ shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve,
66
+ });
45
67
 
46
68
  // verify multi sig
47
- runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }));
48
-
49
- // verify token factory
50
- runner.use(
51
- pipes.ExtractState({
52
- from: 'itx.tokenFactory',
53
- to: 'tokenFactoryState',
54
- status: 'INVALID_TOKEN_FACTORY',
55
- table: 'tokenFactory',
56
- })
57
- );
69
+ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }), {
70
+ shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve,
71
+ });
58
72
 
59
73
  // 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
- );
74
+ runner.use(
75
+ (context, next) => {
76
+ const { tokenFactoryState, inputs } = context;
77
+
78
+ const isAccepted = inputs.every(
79
+ (input) =>
80
+ input.tokensList.length &&
81
+ input.tokensList.every((x) => x.address === tokenFactoryState.reserveAddress) &&
82
+ !input.assetsList.length
83
+ );
69
84
 
70
- if (!isAccepted) {
71
- return next(new Error('INVALID_TX', `Inputs only accept ${tokenFactoryState.reserveAddress}`));
72
- }
85
+ if (!isAccepted) {
86
+ return next(new Error('INVALID_TX', `Inputs only accept ${tokenFactoryState.reserveAddress}`));
87
+ }
73
88
 
74
- return next();
75
- });
89
+ return next();
90
+ },
91
+ { shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve }
92
+ );
76
93
 
77
94
  // ensure sender
78
95
  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' }));
96
+ runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' }), { shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve, }); // prettier-ignore
97
+ runner.use(
98
+ pipes.VerifyAccountMigration({ signerKey: 'signerStates', stateKey: 'senderState', addressKey: 'tx.from' }),
99
+ { shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve }
100
+ );
81
101
 
82
102
  // ensure receiver
83
103
  runner.use(pipes.ExtractState({ from: 'itx.receiver', to: 'receiverState', status: 'OK', table: 'account' }));
@@ -96,13 +116,13 @@ runner.use(
96
116
  })
97
117
  );
98
118
 
99
- // ensure token state
119
+ // verify minters restriction
100
120
  runner.use(
101
- pipes.ExtractState({
102
- from: 'tokenFactoryState.tokenAddress',
103
- to: 'tokenState',
104
- status: 'INVALID_TOKEN',
105
- table: 'token',
121
+ pipes.verifyTokenAccess({
122
+ statesKey: 'tokenState',
123
+ listFieldKey: 'minters',
124
+ accountKeys: ['senderState', 'receiverState', 'itx.receiver'],
125
+ errorMessage: 'Account {address} is not allowed to mint token {tokenAddress}',
106
126
  })
107
127
  );
108
128
 
@@ -113,7 +133,8 @@ runner.use(
113
133
  tokenStateKey: 'tokenState',
114
134
  reserveKey: 'reserveAmount',
115
135
  amountKey: 'itx.amount',
116
- })
136
+ }),
137
+ { shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve }
117
138
  );
118
139
 
119
140
  // verify max supply
@@ -137,33 +158,38 @@ runner.use((context, next) => {
137
158
  });
138
159
 
139
160
  // verify slippage
140
- runner.use((context, next) => {
141
- const { reserveAmount, reserveFee, inputs, tokenFactoryState } = context;
161
+ runner.use(
162
+ (context, next) => {
163
+ const { reserveAmount, reserveFee, inputs, tokenFactoryState } = context;
164
+
165
+ const maxReserve = inputs.reduce((total, input) => {
166
+ const reserveToken = input.tokensList.find((x) => x.address === tokenFactoryState.reserveAddress);
167
+ if (reserveToken) {
168
+ return total.add(new BN(reserveToken.value));
169
+ }
170
+ return total;
171
+ }, new BN(0));
142
172
 
143
- const maxReserve = inputs.reduce((total, input) => {
144
- const reserveToken = input.tokensList.find((x) => x.address === tokenFactoryState.reserveAddress);
145
- if (reserveToken) {
146
- return total.add(new BN(reserveToken.value));
173
+ const actual = new BN(reserveAmount).add(new BN(reserveFee || '0'));
174
+ if (actual.gt(maxReserve)) {
175
+ return next(
176
+ new Error(
177
+ 'SLIPPAGE_EXCEEDED',
178
+ `Mint token failed due to price movement. Expected maximum: ${fromUnitToToken(maxReserve)}, actual: ${fromUnitToToken(actual)}. Try increasing your inputs.`
179
+ )
180
+ );
147
181
  }
148
- return total;
149
- }, new BN(0));
150
-
151
- const actual = new BN(reserveAmount).add(new BN(reserveFee || '0'));
152
- if (actual.gt(maxReserve)) {
153
- return next(
154
- new Error(
155
- 'SLIPPAGE_EXCEEDED',
156
- `Mint token failed due to price movement. Expected maximum: ${fromUnitToToken(maxReserve)}, actual: ${fromUnitToToken(actual)}. Try increasing your inputs.`
157
- )
158
- );
159
- }
160
182
 
161
- next();
162
- });
183
+ next();
184
+ },
185
+ { shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve }
186
+ );
163
187
 
164
188
  // verify balance
165
189
  runner.use(pipes.ExtractState({ from: 'tokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
166
- runner.use(pipes.VerifyTokenBalance({ ownerKey: 'signerStates', conditionKey: 'inputs' }));
190
+ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'signerStates', conditionKey: 'inputs' }), {
191
+ shouldSkip: ({ tokenFactoryState }) => !tokenFactoryState.curve,
192
+ });
167
193
 
168
194
  // Ensure tx fee and gas
169
195
  runner.use(
@@ -210,17 +236,19 @@ runner.use(
210
236
  senderChange,
211
237
  updateVaults,
212
238
  tokenFactoryState,
213
- signerStates,
214
239
  tokenState,
215
240
  reserveAmount,
216
241
  reserveFee,
217
242
  itx,
218
- inputs,
219
243
  } = context;
220
244
  const { amount, receiver } = itx;
221
245
  const { reserveAddress, tokenAddress } = tokenFactoryState;
222
246
 
223
- const totalReserveCost = new BN(reserveAmount).add(new BN(reserveFee || '0'));
247
+ // signerStates and inputs are not exist in context for credit token
248
+ const signerStates = tokenFactoryState.curve ? context.signerStates : [];
249
+ const inputs = tokenFactoryState.curve ? context.inputs : [];
250
+
251
+ const totalReserveCost = new BN(reserveAmount || '0').add(new BN(reserveFee || '0'));
224
252
  let currentReserveCost = new BN('0');
225
253
 
226
254
  const accountUpdates = {};
@@ -311,7 +339,7 @@ runner.use(
311
339
  tokenFactoryState,
312
340
  {
313
341
  currentSupply: new BN(tokenFactoryState.currentSupply).add(new BN(amount)).toString(),
314
- reserveBalance: new BN(tokenFactoryState.reserveBalance).add(new BN(reserveAmount)).toString(),
342
+ reserveBalance: new BN(tokenFactoryState.reserveBalance).add(new BN(reserveAmount || '0')).toString(),
315
343
  },
316
344
  context
317
345
  ),
@@ -28,10 +28,10 @@ module.exports =
28
28
  const { decimal } = tokenState;
29
29
 
30
30
  const reserveAmount = calcCost({ amount, decimal, currentSupply, direction, curve });
31
- set(context, reserveKey, reserveAmount.toString());
31
+ set(context, reserveKey, reserveAmount?.toString());
32
32
 
33
33
  const reserveFee = calcFee({ reserveAmount, feeRate });
34
- set(context, feeKey, reserveFee.toString());
34
+ set(context, feeKey, reserveFee?.toString());
35
35
 
36
36
  return next();
37
37
  };
@@ -122,6 +122,14 @@ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'receiverState', conditionKey: '
122
122
 
123
123
  runner.use(pipes.AntiLandAttack({ senderState: 'senderState', receiverState: 'receiverState' }));
124
124
  runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState', 'receiverState'] }));
125
+ runner.use(
126
+ pipes.verifyTokenAccess({
127
+ statesKey: 'tokenStates',
128
+ listFieldKey: 'spenders',
129
+ accountKeys: ['senderState', 'receiverState'],
130
+ errorMessage: 'Account {address} is not allowed to exchange token {tokenAddress}',
131
+ })
132
+ );
125
133
 
126
134
  runner.use(pipes.ExtractState({ from: 'senderAssets', to: 'priv.senderAssets', status: 'INVALID_ASSET', table: 'asset' })); // prettier-ignore
127
135
  runner.use(pipes.VerifyTransferrable({ assets: 'priv.senderAssets' }));
@@ -142,6 +142,14 @@ runner.use(pipes.ExtractReceiver({ from: 'itx.to', to: 'receiver' }));
142
142
  runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'OK', table: 'account' }));
143
143
  runner.use(pipes.AntiLandAttack({ senderState: 'senderState', receiverState: 'receiverState' }));
144
144
  runner.use(pipes.VerifyBlocked({ stateKeys: ['senderState', 'receiverState'] }));
145
+ runner.use(
146
+ pipes.verifyTokenAccess({
147
+ statesKey: 'tokenStates',
148
+ listFieldKey: 'spenders',
149
+ accountKeys: ['senderState', 'receiverState', 'itx.to'],
150
+ errorMessage: 'Account {address} is not allowed to transfer token {tokenAddress}',
151
+ })
152
+ );
145
153
 
146
154
  runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'INVALID_ASSET', table: 'asset' }));
147
155
  runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
@@ -134,6 +134,14 @@ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', stateKey: '
134
134
  // 5. verify token state and balance
135
135
  runner.use(pipes.ExtractState({ from: 'inputTokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
136
136
  runner.use(pipes.VerifyTokenBalance({ ownerKey: 'signerStates', conditionKey: 'inputs' }));
137
+ runner.use(
138
+ pipes.verifyTokenAccess({
139
+ statesKey: 'tokenStates',
140
+ listFieldKey: 'spenders',
141
+ accountKeys: ['signerStates', 'receiverStates'],
142
+ errorMessage: 'Account {address} is not allowed to transfer token {tokenAddress}',
143
+ })
144
+ );
137
145
 
138
146
  // 6. verify asset state and transferrable, ownership
139
147
  runner.use(pipes.ExtractState({ from: 'inputAssets', to: 'assetStates', status: 'INVALID_ASSET', table: 'asset' }));
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.27.7",
6
+ "version": "1.27.9",
7
7
  "description": "Predefined tx pipeline sets to execute certain type of transactions",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,20 +19,20 @@
19
19
  "empty-value": "^1.0.1",
20
20
  "lodash": "^4.17.21",
21
21
  "url-join": "^4.0.1",
22
- "@arcblock/did-util": "1.27.7",
23
- "@arcblock/jwt": "1.27.7",
24
- "@arcblock/vc": "1.27.7",
25
- "@arcblock/did": "1.27.7",
26
- "@ocap/asset": "1.27.7",
27
- "@ocap/client": "1.27.7",
28
- "@ocap/mcrypto": "1.27.7",
29
- "@ocap/merkle-tree": "1.27.7",
30
- "@ocap/message": "1.27.7",
31
- "@ocap/state": "1.27.7",
32
- "@ocap/tx-pipeline": "1.27.7",
33
- "@ocap/util": "1.27.7",
34
- "@ocap/wallet": "1.27.7",
35
- "@arcblock/validator": "1.27.7"
22
+ "@arcblock/did": "1.27.9",
23
+ "@arcblock/did-util": "1.27.9",
24
+ "@arcblock/jwt": "1.27.9",
25
+ "@arcblock/validator": "1.27.9",
26
+ "@arcblock/vc": "1.27.9",
27
+ "@ocap/asset": "1.27.9",
28
+ "@ocap/client": "1.27.9",
29
+ "@ocap/mcrypto": "1.27.9",
30
+ "@ocap/merkle-tree": "1.27.9",
31
+ "@ocap/message": "1.27.9",
32
+ "@ocap/state": "1.27.9",
33
+ "@ocap/tx-pipeline": "1.27.9",
34
+ "@ocap/util": "1.27.9",
35
+ "@ocap/wallet": "1.27.9"
36
36
  },
37
37
  "resolutions": {
38
38
  "bn.js": "5.2.2",
@@ -41,8 +41,8 @@
41
41
  "devDependencies": {
42
42
  "jest": "^29.7.0",
43
43
  "start-server-and-test": "^1.14.0",
44
- "@ocap/e2e-test": "1.27.7",
45
- "@ocap/statedb-memory": "1.27.7"
44
+ "@ocap/e2e-test": "1.27.9",
45
+ "@ocap/statedb-memory": "1.27.9"
46
46
  },
47
47
  "scripts": {
48
48
  "lint": "eslint tests lib",