@ocap/tx-protocols 1.13.65 → 1.13.69

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/execute.js CHANGED
@@ -29,7 +29,7 @@ module.exports = ({ filter, runAsLambda }) => {
29
29
  // Since bloom-filters may give false positive, we need to check for statedb if replay detected
30
30
  const exist = await context.statedb.tx.get(context.txHash, context);
31
31
  if (exist) {
32
- return next(new Error('FORBIDDEN', 'Can not execute duplicate transaction'));
32
+ return next(new Error('DUPLICATE_TX', 'Can not execute duplicate transaction'));
33
33
  }
34
34
  }
35
35
 
@@ -1,6 +1,7 @@
1
1
  const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@ocap/validator');
2
3
  const { Runner, pipes } = require('@ocap/tx-pipeline');
3
- const { account, Joi } = require('@ocap/state');
4
+ const { account } = require('@ocap/state');
4
5
  const { toBase58 } = require('@ocap/util');
5
6
 
6
7
  const runner = new Runner();
@@ -1,8 +1,9 @@
1
1
  /* eslint-disable max-len */
2
2
  const get = require('lodash/get');
3
+ const Joi = require('@ocap/validator');
3
4
  const Error = require('@ocap/util/lib/error');
4
5
  const getListField = require('@ocap/util/lib/get-list-field');
5
- const { delegation, account, Joi } = require('@ocap/state');
6
+ const { delegation, account } = require('@ocap/state');
6
7
  const { Runner, pipes } = require('@ocap/tx-pipeline');
7
8
  const { toDelegateAddress } = require('@arcblock/did-util');
8
9
 
@@ -1,8 +1,9 @@
1
1
  const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@ocap/validator');
2
3
  const getRelatedAddresses = require('@ocap/util/lib/get-related-addr');
3
4
  const { Runner, pipes } = require('@ocap/tx-pipeline');
4
5
  const { fromPublicKey, toTypeInfo } = require('@arcblock/did');
5
- const { account, Joi } = require('@ocap/state');
6
+ const { account } = require('@ocap/state');
6
7
 
7
8
  // eslint-disable-next-line global-require
8
9
  const debug = require('debug')(`${require('../../../package.json').name}:migrate`);
@@ -1,8 +1,9 @@
1
1
  const get = require('lodash/get');
2
2
  const cloneDeep = require('lodash/cloneDeep');
3
+ const Joi = require('@ocap/validator');
3
4
  const getListField = require('@ocap/util/lib/get-list-field');
4
5
  const { Runner, pipes } = require('@ocap/tx-pipeline');
5
- const { delegation, account, Joi } = require('@ocap/state');
6
+ const { delegation, account } = require('@ocap/state');
6
7
  const { toDelegateAddress } = require('@arcblock/did-util');
7
8
 
8
9
  // eslint-disable-next-line global-require
@@ -82,7 +82,7 @@ runner.use(
82
82
  );
83
83
 
84
84
  // 5. verify sender & signer & owner
85
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
85
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' })); // prettier-ignore
86
86
  runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
87
87
  runner.use(pipes.ExtractState({ from: 'itx.owner', to: 'ownerState', status: 'OK', table: 'account' }));
88
88
  runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
@@ -152,7 +152,7 @@ runner.use(
152
152
  );
153
153
  });
154
154
 
155
- const isAlsoSigner = !!signerUpdates[senderState.address];
155
+ const isAlsoSigner = !!signerUpdates[tx.from];
156
156
  const owner = ownerState ? ownerState.address : itx.owner;
157
157
 
158
158
  // Factory updates
@@ -166,12 +166,12 @@ runner.use(
166
166
 
167
167
  const [newSenderState, newReceiverState, assetState, newSignerStates, newFactoryState, newAssetStates] =
168
168
  await Promise.all([
169
- // Update sender state
170
- statedb.account.update(
171
- senderState.address,
172
- account.update(
169
+ // update sender
170
+ statedb.account.updateOrCreate(
171
+ senderState,
172
+ account.updateOrCreate(
173
173
  senderState,
174
- Object.assign({ nonce: tx.nonce, pk: tx.pk }, signerUpdates[senderState.address] || {}),
174
+ Object.assign({ address: tx.from, nonce: tx.nonce, pk: tx.pk }, signerUpdates[tx.from] || {}),
175
175
  context
176
176
  ),
177
177
  context
@@ -192,7 +192,7 @@ runner.use(
192
192
  // Update signer state
193
193
  Promise.all(
194
194
  signerStates
195
- .filter((x) => x.address !== senderState.address)
195
+ .filter((x) => x.address !== tx.from)
196
196
  .map((x) =>
197
197
  statedb.account.update(x.address, account.update(x, signerUpdates[x.address], context), context)
198
198
  )
@@ -1,14 +1,17 @@
1
1
  /* eslint-disable indent */
2
2
  const Error = require('@ocap/util/lib/error');
3
+ const isEmpty = require('lodash/isEmpty');
3
4
  const cloneDeep = require('lodash/cloneDeep');
5
+ const Joi = require('@ocap/validator');
4
6
  const { Runner, pipes } = require('@ocap/tx-pipeline');
5
- const { account, asset, Joi } = require('@ocap/state');
7
+ const { account, asset } = require('@ocap/state');
6
8
  const { toAssetAddress } = require('@arcblock/did-util');
7
9
 
8
10
  // eslint-disable-next-line global-require
9
11
  const debug = require('debug')(`${require('../../../package.json').name}:create-asset`);
10
12
 
11
13
  const { decodeAnySafe } = require('../../util');
14
+ const ensureTxFee = require('../rollup/pipes/ensure-tx-fee');
12
15
 
13
16
  const runner = new Runner();
14
17
 
@@ -74,7 +77,7 @@ runner.use(
74
77
  // For security consideration, user can not create fake assets from a factory
75
78
  // https://github.com/ArcBlock/asset-chain/issues/97
76
79
  {
77
- error: 'FORBIDDEN',
80
+ error: 'CREATE_FROM_FACTORY',
78
81
  message: 'You can only get asset from factory from acquire or mint',
79
82
  fn: ({ itx, factoryState }) => !(itx.parent && factoryState),
80
83
  persist: true,
@@ -85,28 +88,34 @@ runner.use(
85
88
  // Ensure parent exist
86
89
  runner.use(pipes.ExtractState({ from: 'itx.parent', to: 'parentAsset', status: 'INVALID_ASSET', table: 'asset' }));
87
90
 
91
+ runner.use(ensureTxFee);
92
+
88
93
  // Update asset state
89
94
  runner.use(
90
95
  async (context, next) => {
91
- const { tx, itx, assetData, statedb, senderState } = context;
96
+ const { tx, itx, assetData, statedb, senderState, senderUpdates, vaultState, vaultUpdates } = context;
92
97
 
93
- const [newSenderState, assetState] = await Promise.all([
94
- // Update owner state
98
+ const [newSenderState, assetState, newVaultState] = await Promise.all([
95
99
  statedb.account.update(
96
100
  senderState.address,
97
- account.update(senderState, { nonce: tx.nonce, pk: tx.pk }, context),
101
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
98
102
  context
99
103
  ),
100
- // Create asset state
104
+
101
105
  statedb.asset.create(
102
106
  itx.address,
103
107
  asset.create({ ...itx, data: assetData, owner: senderState.address }, context),
104
108
  context
105
109
  ),
110
+
111
+ isEmpty(vaultUpdates)
112
+ ? vaultState
113
+ : statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
106
114
  ]);
107
115
 
108
116
  context.senderState = newSenderState;
109
117
  context.assetState = assetState;
118
+ context.vaultState = newVaultState;
110
119
 
111
120
  debug('createAsset', assetState);
112
121
 
@@ -31,7 +31,7 @@ runner.use(pipes.ExtractState({ from: 'itx.owner', to: 'ownerState', status: 'OK
31
31
  runner.use(
32
32
  pipes.VerifyInfo([
33
33
  {
34
- error: 'FORBIDDEN',
34
+ error: 'OWNER_ONLY_OPERATION',
35
35
  message: 'Only factory owner is allowed to mint',
36
36
  fn: ({ factoryState, senderState }) => factoryState.owner === senderState.address,
37
37
  persist: true,
@@ -9,5 +9,5 @@ module.exports = ({ factoryState }, next) => {
9
9
  return next();
10
10
  }
11
11
 
12
- return next(new Error('FORBIDDEN', 'This request will exceed factory mint limit'));
12
+ return next(new Error('EXCEED_MINT_LIMIT', 'This request will exceed factory mint limit'));
13
13
  };
@@ -1,6 +1,7 @@
1
1
  const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@ocap/validator');
2
3
  const { Runner, pipes } = require('@ocap/tx-pipeline');
3
- const { account, asset, Joi } = require('@ocap/state');
4
+ const { account, asset } = require('@ocap/state');
4
5
 
5
6
  // eslint-disable-next-line global-require
6
7
  const debug = require('debug')(`${require('../../../package.json').name}:update-asset`);
@@ -14,6 +14,7 @@ const { toFactoryAddress } = require('@arcblock/did-util');
14
14
  const debug = require('debug')(`${require('../../../package.json').name}:create-factory`);
15
15
 
16
16
  const { decodeAnySafe } = require('../../util');
17
+ const ensureTxFee = require('../rollup/pipes/ensure-tx-fee');
17
18
 
18
19
  const runner = new Runner();
19
20
 
@@ -71,7 +72,7 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'factoryState', status:
71
72
  runner.use(
72
73
  pipes.VerifyInfo([
73
74
  {
74
- error: 'FORBIDDEN',
75
+ error: 'DUPLICATE_FACTORY',
75
76
  message: 'This asset factory already exist on chain',
76
77
  fn: (context) => isEmpty(context.factoryState),
77
78
  },
@@ -101,18 +102,19 @@ runner.use((context, next) => {
101
102
  return next(new Error('INVALID_FACTORY_INPUT', 'Not all input.assets exist on chain'));
102
103
  });
103
104
 
105
+ runner.use(ensureTxFee);
106
+
104
107
  // Create factory state
105
- // TODO: we need to support charging when creating new nft-factory
106
108
  runner.use(
107
109
  async (context, next) => {
108
- const { tx, itx, statedb, senderState, factoryProps } = context;
110
+ const { tx, itx, statedb, senderState, senderUpdates, vaultState, vaultUpdates, factoryProps } = context;
109
111
  const tokens = { [context.config.token.address]: '0' };
110
112
 
111
- const [newSenderState, factoryState] = await Promise.all([
113
+ const [newSenderState, factoryState, newVaultState] = await Promise.all([
112
114
  // Update owner state
113
115
  statedb.account.update(
114
116
  senderState.address,
115
- account.update(senderState, { nonce: tx.nonce, pk: tx.pk }, context),
117
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
116
118
  context
117
119
  ),
118
120
 
@@ -122,10 +124,15 @@ runner.use(
122
124
  factory.create({ ...factoryProps, tokens, owner: senderState.address }, context),
123
125
  context
124
126
  ),
127
+
128
+ isEmpty(vaultUpdates)
129
+ ? vaultState
130
+ : statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
125
131
  ]);
126
132
 
127
133
  context.senderState = newSenderState;
128
134
  context.factoryState = factoryState;
135
+ context.vaultState = newVaultState;
129
136
 
130
137
  debug('createFactory', factoryState);
131
138
 
@@ -1,7 +1,8 @@
1
1
  const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@ocap/validator');
2
3
  const { BN } = require('@ocap/util');
3
4
  const { Runner, pipes } = require('@ocap/tx-pipeline');
4
- const { account, asset, stake, evidence, Joi } = require('@ocap/state');
5
+ const { account, asset, stake, evidence } = require('@ocap/state');
5
6
 
6
7
  // eslint-disable-next-line global-require
7
8
  const debug = require('debug')(`${require('../../../package.json').name}:acquire-asset-v2`);
@@ -16,7 +17,7 @@ runner.use(pipes.VerifyMultiSig(0));
16
17
  const schema = Joi.object({
17
18
  address: Joi.DID().role('ROLE_STAKE').required(),
18
19
  evidence: Joi.object({
19
- hash: Joi.string().regex(Joi.hashRegexp).required(),
20
+ hash: Joi.string().regex(Joi.patterns.txHash).required(),
20
21
  }).required(),
21
22
  data: Joi.any().optional(),
22
23
  }).options({ stripUnknown: true, noDefaults: false });
@@ -30,7 +31,7 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'stakeState', status: '
30
31
  runner.use(
31
32
  pipes.VerifyInfo([
32
33
  {
33
- error: 'FORBIDDEN',
34
+ error: 'SENDER_NOT_MATCH',
34
35
  message: 'You are not allowed to claim stake from this address',
35
36
  fn: ({ tx, stakeState }) => tx.from === stakeState.sender,
36
37
  },
@@ -42,7 +43,7 @@ runner.use(pipes.ExtractState({ from: 'itx.evidence.hash', to: 'evidenceState',
42
43
  runner.use(
43
44
  pipes.VerifyInfo([
44
45
  {
45
- error: 'FORBIDDEN',
46
+ error: 'ALREADY_CLAIMED',
46
47
  message: 'Revoke evidence already seen on this chain',
47
48
  fn: ({ evidenceState }) => !evidenceState,
48
49
  },
@@ -69,7 +70,7 @@ runner.use(
69
70
  fn: ({ txState, itx }) => txState.tx.itxJson.address === itx.address,
70
71
  },
71
72
  {
72
- error: 'FORBIDDEN',
73
+ error: 'WITHIN_WAITING_PERIOD',
73
74
  message: 'You can not claim stake before waiting period ends',
74
75
  fn: ({ txState, stakeState, txTime }) => {
75
76
  const end = +new Date(txState.time) + (stakeState.revokeWaitingPeriod || 0) * 1000;
@@ -49,12 +49,12 @@ runner.use(
49
49
  runner.use(
50
50
  pipes.VerifyInfo([
51
51
  {
52
- error: 'FORBIDDEN',
52
+ error: 'SENDER_NOT_MATCH',
53
53
  message: 'You are not allowed to revoke stake from this address',
54
54
  fn: ({ tx, stakeState }) => tx.from === stakeState.sender,
55
55
  },
56
56
  {
57
- error: 'FORBIDDEN',
57
+ error: 'STAKE_NOT_REVOCABLE',
58
58
  message: 'This stake address is not revocable',
59
59
  fn: ({ stakeState }) => stakeState.revocable,
60
60
  },
@@ -1,10 +1,11 @@
1
1
  const pick = require('lodash/pick');
2
2
  const { promisify } = require('util');
3
3
  const isEmpty = require('empty-value');
4
+ const Joi = require('@ocap/validator');
4
5
  const Error = require('@ocap/util/lib/error');
5
6
  const { Runner, pipes } = require('@ocap/tx-pipeline');
6
7
  const { toStakeAddress } = require('@arcblock/did-util');
7
- const { account, asset, stake, Joi } = require('@ocap/state');
8
+ const { account, asset, stake } = require('@ocap/state');
8
9
 
9
10
  // eslint-disable-next-line global-require
10
11
  const debug = require('debug')(`${require('../../../package.json').name}:acquire-asset-v2`);
@@ -63,9 +64,9 @@ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }));
63
64
  runner.use(pipes.ExtractState({ from: 'itx.address', to: 'stakeState', status: 'OK', table: 'stake' }));
64
65
 
65
66
  // 5. verify sender & signer & receiver
66
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
67
- runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE' }));
68
- runner.use(pipes.ExtractState({ from: 'itx.receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' }));
67
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
68
+ runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
69
+ runner.use(pipes.ExtractState({ from: 'itx.receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' })); // can by any type
69
70
  runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
70
71
 
71
72
  // 6. verify token state and balance
@@ -114,15 +115,15 @@ runner.use(
114
115
  stakeUpdates.assets.push(...assetsList);
115
116
  });
116
117
 
117
- const isAlsoSigner = !!signerUpdates[senderState.address];
118
+ const isAlsoSigner = !!signerUpdates[tx.from];
118
119
 
119
120
  const [newSenderState, newSignerStates, newStakeState, newAssetStates] = await Promise.all([
120
121
  // Update sender state
121
- statedb.account.update(
122
- senderState.address,
123
- account.update(
122
+ statedb.account.updateOrCreate(
123
+ senderState,
124
+ account.updateOrCreate(
124
125
  senderState,
125
- Object.assign({ nonce: tx.nonce, pk: tx.pk }, signerUpdates[senderState.address] || {}),
126
+ Object.assign({ address: tx.from, nonce: tx.nonce, pk: tx.pk }, signerUpdates[tx.from] || {}),
126
127
  context
127
128
  ),
128
129
  context
@@ -131,7 +132,7 @@ runner.use(
131
132
  // Update signer state
132
133
  Promise.all(
133
134
  signerStates
134
- .filter((x) => x.address !== senderState.address)
135
+ .filter((x) => x.address !== tx.from)
135
136
  .map((x) => statedb.account.update(x.address, account.update(x, signerUpdates[x.address], context), context))
136
137
  ),
137
138
 
@@ -1,10 +1,11 @@
1
1
  const uniqBy = require('lodash/uniqBy');
2
2
  const groupBy = require('lodash/groupBy');
3
3
  const cloneDeep = require('lodash/cloneDeep');
4
+ const Joi = require('@ocap/validator');
4
5
  const Error = require('@ocap/util/lib/error');
5
6
  const { BN } = require('@ocap/util');
6
7
  const { Runner, pipes } = require('@ocap/tx-pipeline');
7
- const { account, stake, evidence, rollupBlock, Joi } = require('@ocap/state');
8
+ const { account, stake, evidence, rollupBlock } = require('@ocap/state');
8
9
 
9
10
  // eslint-disable-next-line global-require
10
11
  const debug = require('debug')(`${require('../../../package.json').name}:claim-block-reward`);
@@ -19,9 +20,9 @@ const runner = new Runner();
19
20
  const schema = Joi.object({
20
21
  rollup: Joi.DID().role('ROLE_ROLLUP').required(),
21
22
  blockHeight: Joi.number().integer().greater(0).required(),
22
- blockHash: Joi.string().regex(Joi.hashRegexp).required(),
23
+ blockHash: Joi.string().regex(Joi.patterns.txHash).required(),
23
24
  evidence: Joi.object({
24
- hash: Joi.string().regex(Joi.hashRegexp).required(),
25
+ hash: Joi.string().regex(Joi.patterns.txHash).required(),
25
26
  }).required(),
26
27
  publisher: Joi.DID().wallet('ethereum').required(),
27
28
  data: Joi.any().optional(),
@@ -263,7 +264,10 @@ runner.use(
263
264
  ),
264
265
  ]);
265
266
 
266
- context.stakeStates = newStakeStates;
267
+ // to avoid this to be incorrectly indexed
268
+ delete context.stakeState;
269
+
270
+ context.stakeStates = newStakeStates.filter((x) => updates.stake[x.address]);
267
271
  context.accountStates = newAccountStates;
268
272
  context.blockState = newBlockState;
269
273
 
@@ -2,10 +2,11 @@
2
2
  const pick = require('lodash/pick');
3
3
  const Error = require('@ocap/util/lib/error');
4
4
  const MerkleTree = require('@ocap/merkle-tree');
5
+ const Joi = require('@ocap/validator');
5
6
  const { formatMessage } = require('@ocap/message');
6
7
  const { toStakeAddress } = require('@arcblock/did-util');
7
8
  const { Runner, pipes } = require('@ocap/tx-pipeline');
8
- const { account, stake, rollup, rollupBlock, tx: Tx, Joi } = require('@ocap/state');
9
+ const { account, stake, rollup, rollupBlock, tx: Tx } = require('@ocap/state');
9
10
 
10
11
  // eslint-disable-next-line global-require
11
12
  const debug = require('debug')(`${require('../../../package.json').name}:create-rollup-block`);
@@ -19,18 +20,18 @@ const runner = new Runner();
19
20
 
20
21
  // 1. verify itx
21
22
  const schema = Joi.object({
22
- hash: Joi.string().regex(Joi.hashRegexp).required(),
23
+ hash: Joi.string().regex(Joi.patterns.txHash).required(),
23
24
  height: Joi.number().integer().greater(0).required(),
24
- merkleRoot: Joi.string().regex(Joi.hashRegexp).required(),
25
+ merkleRoot: Joi.string().regex(Joi.patterns.txHash).required(),
25
26
  previousHash: Joi.string().when('height', {
26
27
  is: 1,
27
28
  then: Joi.string().optional().allow(''),
28
- otherwise: Joi.string().regex(Joi.hashRegexp).required(),
29
+ otherwise: Joi.string().regex(Joi.patterns.txHash).required(),
29
30
  }),
30
- txsHash: Joi.string().regex(Joi.hashRegexp).required(),
31
- txsList: Joi.array().items(Joi.string().regex(Joi.hashRegexp).required()).min(1).unique().required(),
31
+ txsHash: Joi.string().regex(Joi.patterns.txHash).required(),
32
+ txsList: Joi.array().items(Joi.string().regex(Joi.patterns.txHash).required()).min(1).unique().required(),
32
33
  proposer: Joi.DID().wallet('ethereum').required(),
33
- signaturesList: Joi.multiSigSchema.min(1).required(),
34
+ signaturesList: Joi.schemas.multiSig.min(1).required(),
34
35
  rollup: Joi.DID().role('ROLE_ROLLUP').required(),
35
36
  minReward: Joi.BN().min(0).required(),
36
37
  data: Joi.any().optional(),
@@ -311,7 +312,7 @@ runner.use(
311
312
  ]);
312
313
 
313
314
  context.senderState = newSenderState;
314
- context.stakeStates = newStakeStates;
315
+ context.stakeStates = newStakeStates.filter((x) => context.stakeUpdates[x.address]);
315
316
  context.newSenderStates = newSenderStates;
316
317
  context.rollupState = newRollupState;
317
318
  context.rollupBlockState = rollupBlockState;
@@ -12,6 +12,7 @@ const { toRollupAddress } = require('@arcblock/did-util');
12
12
  const debug = require('debug')(`${require('../../../package.json').name}:create-rollup`);
13
13
 
14
14
  const { decodeAnySafe } = require('../../util');
15
+ const ensureTxFee = require('./pipes/ensure-tx-fee');
15
16
 
16
17
  const runner = new Runner();
17
18
 
@@ -87,7 +88,7 @@ runner.use(async (context, next) => {
87
88
  const { statedb, itx } = context;
88
89
  const exist = await statedb.rollup.existByToken(itx.tokenAddress, context);
89
90
  if (exist) {
90
- return next(new Error('FORBIDDEN', 'Only 1 rollup can be created for the token'));
91
+ return next(new Error('DUPLICATE_ROLLUP', 'Only 1 rollup can be created for the token'));
91
92
  }
92
93
 
93
94
  return next();
@@ -98,20 +99,27 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'rollupState', status:
98
99
  runner.use(
99
100
  pipes.VerifyInfo([
100
101
  {
101
- error: 'FORBIDDEN',
102
+ error: 'DUPLICATE_ROLLUP',
102
103
  message: 'This rollup already exist on chain',
103
104
  fn: (context) => isEmpty(context.rollupState),
104
105
  },
105
106
  ])
106
107
  );
107
108
 
109
+ runner.use(ensureTxFee);
110
+
108
111
  // 5. create rollup state
109
112
  runner.use(
110
113
  async (context, next) => {
111
- const { tx, formattedItx, rollupData, statedb, senderState } = context;
114
+ const { tx, formattedItx, rollupData, statedb, senderState, senderUpdates, vaultState, vaultUpdates } = context;
115
+
116
+ const [newSenderState, rollupState, newVaultState] = await Promise.all([
117
+ statedb.account.update(
118
+ senderState.address,
119
+ account.update(senderState, { nonce: tx.nonce, ...senderUpdates }, context),
120
+ context
121
+ ),
112
122
 
113
- const [newSenderState, rollupState] = await Promise.all([
114
- statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
115
123
  statedb.rollup.create(
116
124
  formattedItx.address,
117
125
  rollup.create(
@@ -128,6 +136,10 @@ runner.use(
128
136
  ),
129
137
  context
130
138
  ),
139
+
140
+ isEmpty(vaultUpdates)
141
+ ? vaultState
142
+ : statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
131
143
  ]);
132
144
 
133
145
  // FIXME: create-rollup 的时候不能校验 stake,还是在这里创建 stake?如果在这里创建 stake,是否需要多签?
@@ -135,6 +147,7 @@ runner.use(
135
147
 
136
148
  context.senderState = newSenderState;
137
149
  context.rollupState = rollupState;
150
+ context.vaultState = newVaultState;
138
151
 
139
152
  debug('create-rollup', rollupState);
140
153
 
@@ -2,9 +2,10 @@
2
2
  const MerkleTree = require('@ocap/merkle-tree');
3
3
  const joinUrl = require('url-join');
4
4
  const Error = require('@ocap/util/lib/error');
5
+ const Joi = require('@ocap/validator');
5
6
  const { BN, toHex } = require('@ocap/util');
6
7
  const { Runner, pipes } = require('@ocap/tx-pipeline');
7
- const { account, rollup, stake, evidence, Joi } = require('@ocap/state');
8
+ const { account, rollup, stake, evidence } = require('@ocap/state');
8
9
  const { toStakeAddress } = require('@arcblock/did-util');
9
10
  const { isEthereumDid } = require('@arcblock/did');
10
11
 
@@ -24,9 +25,9 @@ const schema = Joi.object({
24
25
  .uri({ scheme: [/https?/] })
25
26
  .required(),
26
27
  evidence: Joi.object({
27
- hash: Joi.string().regex(Joi.hashRegexp).required(),
28
+ hash: Joi.string().regex(Joi.patterns.txHash).required(),
28
29
  }).required(),
29
- signaturesList: Joi.multiSigSchema.min(1).required(),
30
+ signaturesList: Joi.schemas.multiSig.min(1).required(),
30
31
  data: Joi.any().optional(),
31
32
  }).options({ stripUnknown: true, noDefaults: false });
32
33
  runner.use(({ itx }, next) => {
@@ -1,8 +1,9 @@
1
1
  /* eslint-disable indent */
2
2
  const MerkleTree = require('@ocap/merkle-tree');
3
3
  const Error = require('@ocap/util/lib/error');
4
+ const Joi = require('@ocap/validator');
4
5
  const { Runner, pipes } = require('@ocap/tx-pipeline');
5
- const { account, rollup, stake, evidence, Joi } = require('@ocap/state');
6
+ const { account, rollup, stake, evidence } = require('@ocap/state');
6
7
  const { toStakeAddress } = require('@arcblock/did-util');
7
8
 
8
9
  // eslint-disable-next-line global-require
@@ -18,9 +19,9 @@ const runner = new Runner();
18
19
  const schema = Joi.object({
19
20
  rollup: Joi.DID().role('ROLE_ROLLUP').required(),
20
21
  evidence: Joi.object({
21
- hash: Joi.string().regex(Joi.hashRegexp).required(),
22
+ hash: Joi.string().regex(Joi.patterns.txHash).required(),
22
23
  }).required(),
23
- signaturesList: Joi.multiSigSchema.min(1).required(),
24
+ signaturesList: Joi.schemas.multiSig.min(1).required(),
24
25
  data: Joi.any().optional(),
25
26
  }).options({ stripUnknown: true, noDefaults: false });
26
27
  runner.use(({ itx }, next) => {
@@ -0,0 +1,38 @@
1
+ const Error = require('@ocap/util/lib/error');
2
+ const { fromTokenToUnit, BN } = require('@ocap/util');
3
+
4
+ const { applyTokenUpdates } = require('../../../util');
5
+
6
+ // FIXME: There maybe bug for tx that decrease senderState token balance
7
+ module.exports = async (context, next) => {
8
+ const { config, statedb, txType, senderState } = context;
9
+ const txFee = config.transaction.txFee[txType];
10
+ const vaultState = await statedb.account.get(config.vaults.txFee, context);
11
+
12
+ if (!txFee) {
13
+ context.senderUpdates = {};
14
+ context.vaultUpdates = {};
15
+ context.vaultState = vaultState;
16
+ context.updatedAccounts = [];
17
+ return next();
18
+ }
19
+
20
+ const expected = fromTokenToUnit(txFee, config.token.decimal);
21
+ const actual = new BN(senderState.tokens[config.token.address] || 0);
22
+ if (actual.lt(expected)) {
23
+ return next(new Error('INSUFFICIENT_FUND', `Insufficient fund to pay for tx fee, expected ${txFee}`));
24
+ }
25
+
26
+ const tokenChange = { address: config.token.address, value: expected.toString(10) };
27
+
28
+ context.senderUpdates = applyTokenUpdates([tokenChange], senderState, 'sub');
29
+ context.vaultUpdates = applyTokenUpdates([tokenChange], vaultState, 'add');
30
+ context.vaultState = vaultState;
31
+
32
+ context.updatedAccounts = [
33
+ { address: senderState.address, token: config.token.address, delta: `-${tokenChange.value}`, action: 'fee' },
34
+ { address: vaultState.address, token: config.token.address, delta: tokenChange.value, action: 'fee' },
35
+ ];
36
+
37
+ return next();
38
+ };
@@ -1,6 +1,7 @@
1
1
  const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@ocap/validator');
2
3
  const { Runner, pipes } = require('@ocap/tx-pipeline');
3
- const { account, rollup, Joi } = require('@ocap/state');
4
+ const { account, rollup } = require('@ocap/state');
4
5
 
5
6
  // eslint-disable-next-line global-require
6
7
  const debug = require('debug')(`${require('../../../package.json').name}:update-rollup`);
@@ -1,14 +1,16 @@
1
1
  const isEmpty = require('empty-value');
2
2
  const cloneDeep = require('lodash/cloneDeep');
3
+ const Joi = require('@ocap/validator');
3
4
  const Error = require('@ocap/util/lib/error');
4
5
  const { Runner, pipes } = require('@ocap/tx-pipeline');
5
- const { account, token, Joi } = require('@ocap/state');
6
+ const { account, token } = require('@ocap/state');
6
7
  const { toTokenAddress } = require('@arcblock/did-util');
7
8
  const { fromTokenToUnit } = require('@ocap/util');
8
9
 
9
10
  // eslint-disable-next-line global-require
10
11
  const debug = require('debug')(`${require('../../../package.json').name}:create-token`);
11
12
  const { decodeAnySafe } = require('../../util');
13
+ const ensureTxFee = require('../rollup/pipes/ensure-tx-fee');
12
14
 
13
15
  const MAX_TOTAL_SUPPLY = fromTokenToUnit(10000 * 100000000, 18); // 32
14
16
 
@@ -26,7 +28,7 @@ const schema = Joi.object({
26
28
  icon: Joi.string().optional().valid(''),
27
29
  totalSupply: Joi.BN().greater(0).max(MAX_TOTAL_SUPPLY).required(),
28
30
  initialSupply: Joi.BN().greater(0).max(Joi.ref('totalSupply')).required(),
29
- foreignToken: Joi.foreignTokenSchema.optional().default(null),
31
+ foreignToken: Joi.schemas.foreignToken.optional().default(null),
30
32
  data: Joi.any().optional(),
31
33
  }).options({ stripUnknown: true, noDefaults: false });
32
34
 
@@ -64,7 +66,7 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'tokenState', table: 't
64
66
  runner.use(
65
67
  pipes.VerifyInfo([
66
68
  {
67
- error: 'FORBIDDEN',
69
+ error: 'DUPLICATE_TOKEN',
68
70
  message: 'Token address already exists on chain',
69
71
  fn: (context) => isEmpty(context.tokenState),
70
72
  },
@@ -75,37 +77,46 @@ runner.use(
75
77
  runner.use(async (context, next) => {
76
78
  const { symbol } = context.config.token;
77
79
  if (symbol.toLowerCase() === context.itx.symbol.toLowerCase()) {
78
- return next(new Error('FORBIDDEN', `Token symbol can not be ${symbol}`));
80
+ return next(new Error('DUPLICATE_SYMBOL', `Token symbol can not be ${symbol}`));
79
81
  }
80
82
 
81
83
  const exist = await context.statedb.token.existBySymbol(context.itx.symbol, context);
82
84
  if (exist) {
83
- return next(new Error('FORBIDDEN', 'Token symbol already exists'));
85
+ return next(new Error('DUPLICATE_SYMBOL', 'Token symbol already exists'));
84
86
  }
85
87
 
86
88
  return next();
87
89
  });
88
90
 
91
+ runner.use(ensureTxFee);
92
+
89
93
  // Update sender state, token state
90
94
  runner.use(
91
95
  async (context, next) => {
92
- const { tx, itx, statedb, senderState } = context;
96
+ const { tx, itx, statedb, senderState, senderUpdates, vaultState, vaultUpdates } = context;
93
97
  const data = decodeAnySafe(itx.data);
94
98
 
95
- const senderTokens = senderState.tokens || {};
96
- senderTokens[itx.address] = itx.initialSupply;
99
+ // We are definitely creating a different token, so it is safe to set tokens to initial supply
100
+ senderUpdates.tokens = senderUpdates.tokens || {};
101
+ senderUpdates.tokens[itx.address] = itx.initialSupply;
97
102
 
98
- const [newSenderState, tokenState] = await Promise.all([
103
+ const [newSenderState, tokenState, newVaultState] = await Promise.all([
99
104
  statedb.account.update(
100
105
  senderState.address,
101
- account.update(senderState, { tokens: senderTokens, nonce: tx.nonce, pk: tx.pk }, context),
106
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
102
107
  context
103
108
  ),
109
+
104
110
  statedb.token.create(itx.address, token.create({ ...itx, data, issuer: senderState.address }, context), context),
111
+
112
+ isEmpty(vaultUpdates)
113
+ ? vaultState
114
+ : statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
105
115
  ]);
106
116
 
107
117
  context.senderState = newSenderState;
108
118
  context.tokenState = tokenState;
119
+ context.vaultState = newVaultState;
109
120
 
110
121
  debug('create token v2', tokenState);
111
122
 
@@ -1,9 +1,10 @@
1
1
  /* eslint-disable indent */
2
2
  const Error = require('@ocap/util/lib/error');
3
+ const Joi = require('@ocap/validator');
3
4
  const getListField = require('@ocap/util/lib/get-list-field');
4
5
  const { BN, fromUnitToToken } = require('@ocap/util');
5
6
  const { Runner, pipes } = require('@ocap/tx-pipeline');
6
- const { account, stake, evidence, Joi } = require('@ocap/state');
7
+ const { account, stake, evidence } = require('@ocap/state');
7
8
  const { toStakeAddress } = require('@arcblock/did-util');
8
9
 
9
10
  // eslint-disable-next-line global-require
@@ -14,11 +15,11 @@ const VerifyPaused = require('../rollup/pipes/verify-paused');
14
15
  const { applyTokenUpdates, getTxFee, getBNSum, getRewardLocker } = require('../../util');
15
16
 
16
17
  const schema = Joi.object({
17
- token: Joi.tokenInputSchema.required(),
18
+ token: Joi.schemas.tokenInput.required(),
18
19
  to: Joi.DID().wallet('ethereum').required(),
19
20
  proposer: Joi.DID().wallet('ethereum').required(),
20
21
  evidence: Joi.object({
21
- hash: Joi.string().regex(Joi.hashRegexp).required(),
22
+ hash: Joi.string().regex(Joi.patterns.txHash).required(),
22
23
  }).required(),
23
24
  rollup: Joi.DID().role('ROLE_ROLLUP').required(),
24
25
  actualFee: Joi.BN().min(0).required(),
@@ -1,9 +1,10 @@
1
1
  /* eslint-disable indent */
2
2
  const Error = require('@ocap/util/lib/error');
3
+ const Joi = require('@ocap/validator');
3
4
  const getListField = require('@ocap/util/lib/get-list-field');
4
5
  const { BN, fromUnitToToken } = require('@ocap/util');
5
6
  const { Runner, pipes } = require('@ocap/tx-pipeline');
6
- const { account, stake, Joi } = require('@ocap/state');
7
+ const { account, stake } = require('@ocap/state');
7
8
  const { toStakeAddress } = require('@arcblock/did-util');
8
9
 
9
10
  // eslint-disable-next-line global-require
@@ -15,7 +16,7 @@ const { applyTokenUpdates, getTxFee, getBNSum, getRewardLocker } = require('../.
15
16
  const verifyMultiSigV2 = pipes.VerifyMultiSigV2({ signersKey: 'signers' });
16
17
 
17
18
  const schema = Joi.object({
18
- token: Joi.tokenInputSchema.required(),
19
+ token: Joi.schemas.tokenInput.required(),
19
20
  to: Joi.DID().wallet('ethereum').required(),
20
21
  rollup: Joi.DID().role('ROLE_ROLLUP').required(),
21
22
  proposer: Joi.DID().wallet('ethereum').optional().allow('').default(''),
package/lib/util.js CHANGED
@@ -54,7 +54,7 @@ const decodeAnySafe = (encoded) => {
54
54
 
55
55
  const applyTokenUpdates = (tokens, state, operator) => {
56
56
  if (['add', 'sub'].includes(operator) === false) {
57
- throw new Error('FORBIDDEN', `Invalid operator when applyTokenUpdates: ${operator}`);
57
+ throw new Error('UNEXPECTED_OPERATOR', `Invalid operator when applyTokenUpdates: ${operator}`);
58
58
  }
59
59
 
60
60
  if (!state) {
@@ -69,7 +69,7 @@ const applyTokenUpdates = (tokens, state, operator) => {
69
69
  const balance = new BN(oldTokens[address] || 0);
70
70
  const newBalance = balance[operator](requirement);
71
71
  if (newBalance.lt(ZERO)) {
72
- throw new Error('FORBIDDEN', `Negative token balance when applyTokenUpdates for ${address}`);
72
+ throw new Error('NEGATIVE_TOKEN_BALANCE', `Negative token balance when applyTokenUpdates for ${address}`);
73
73
  }
74
74
  newTokens[address] = newBalance.toString(10);
75
75
  }
@@ -102,16 +102,16 @@ const getTxFee = ({ amount, feeRate, maxFee, minFee, stringify = true }) => {
102
102
  const minFeeAmount = new BN(minFee);
103
103
 
104
104
  if (feeRate < 0) {
105
- throw new Error('FORBIDDEN', 'Unexpected negative feeRate when getTxFee, abort!');
105
+ throw new Error('NEGATIVE_FEE_RATE', 'Unexpected negative feeRate when getTxFee, abort!');
106
106
  }
107
107
  if (userAmount.lt(ZERO)) {
108
- throw new Error('FORBIDDEN', 'Unexpected negative amount when getTxFee, abort!');
108
+ throw new Error('NEGATIVE_AMOUNT', 'Unexpected negative amount when getTxFee, abort!');
109
109
  }
110
110
  if (maxFeeAmount.lt(ZERO)) {
111
- throw new Error('FORBIDDEN', 'Unexpected negative maxFee when getTxFee, abort!');
111
+ throw new Error('NEGATIVE_MAX_FEE', 'Unexpected negative maxFee when getTxFee, abort!');
112
112
  }
113
113
  if (minFeeAmount.lt(ZERO)) {
114
- throw new Error('FORBIDDEN', 'Unexpected negative minFee when getTxFee, abort!');
114
+ throw new Error('NEGATIVE_MIN_FEE', 'Unexpected negative minFee when getTxFee, abort!');
115
115
  }
116
116
 
117
117
  // total fee
@@ -144,11 +144,11 @@ const getTxFee = ({ amount, feeRate, maxFee, minFee, stringify = true }) => {
144
144
  const splitTxFee = ({ total, shares = {}, stringify = true }) => {
145
145
  const totalAmount = new BN(total);
146
146
  if (totalAmount.lt(ZERO)) {
147
- throw new Error('FORBIDDEN', 'Unexpected negative total when splitTxFee, abort!');
147
+ throw new Error('NEGATIVE_TOTAL_AMOUNT', 'Unexpected negative total when splitTxFee, abort!');
148
148
  }
149
149
  Object.keys(shares).forEach((key) => {
150
150
  if (shares[key] < 0) {
151
- throw new Error('FORBIDDEN', `Unexpected negative shares[${key}] when splitTxFee, abort!`);
151
+ throw new Error('NEGATIVE_FEE_SHARE', `Unexpected negative shares[${key}] when splitTxFee, abort!`);
152
152
  }
153
153
  });
154
154
 
@@ -217,7 +217,7 @@ const ensureBlockReward = (rollupState, minReward, txStates) => {
217
217
  }
218
218
 
219
219
  if (actualFee.lt(ZERO)) {
220
- throw new Error('FORBIDDEN', 'Got negative actualFee for tx, abort!');
220
+ throw new Error('NEGATIVE_ACTUAL_FEE', 'Got negative actualFee for tx, abort!');
221
221
  }
222
222
 
223
223
  // If the actualFee is less than the maxFee, user will have a refund
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.13.65",
6
+ "version": "1.13.69",
7
7
  "description": "Predefined tx pipeline sets to execute certain type of transactions",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,16 +19,17 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@arcblock/did": "1.13.65",
23
- "@arcblock/did-util": "1.13.65",
24
- "@ocap/asset": "1.13.65",
25
- "@ocap/mcrypto": "1.13.65",
26
- "@ocap/merkle-tree": "1.13.65",
27
- "@ocap/message": "1.13.65",
28
- "@ocap/state": "1.13.65",
29
- "@ocap/tx-pipeline": "1.13.65",
30
- "@ocap/util": "1.13.65",
31
- "@ocap/wallet": "1.13.65",
22
+ "@arcblock/did": "1.13.69",
23
+ "@arcblock/did-util": "1.13.69",
24
+ "@ocap/asset": "1.13.69",
25
+ "@ocap/mcrypto": "1.13.69",
26
+ "@ocap/merkle-tree": "1.13.69",
27
+ "@ocap/message": "1.13.69",
28
+ "@ocap/state": "1.13.69",
29
+ "@ocap/tx-pipeline": "1.13.69",
30
+ "@ocap/util": "1.13.69",
31
+ "@ocap/validator": "1.13.69",
32
+ "@ocap/wallet": "1.13.69",
32
33
  "debug": "^4.3.2",
33
34
  "empty-value": "^1.0.1",
34
35
  "lodash": "^4.17.21",
@@ -41,5 +42,5 @@
41
42
  "devDependencies": {
42
43
  "jest": "^27.3.1"
43
44
  },
44
- "gitHead": "4011996e1800845142aa5c889b58726129a99ec3"
45
+ "gitHead": "7520d994d592dc4fe50612a665c94d7fa4d5b143"
45
46
  }