@ocap/tx-protocols 1.13.62 → 1.13.66

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.
@@ -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
 
@@ -61,9 +62,9 @@ runner.use(
61
62
  );
62
63
 
63
64
  // Ensure sender/receiver exist
64
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
65
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
65
66
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
66
- runner.use(pipes.ExtractState({ from: 'itx.to', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' }));
67
+ runner.use(pipes.ExtractState({ from: 'itx.to', to: 'receiverState', status: 'OK', table: 'account' }));
67
68
 
68
69
  // Extract delegation
69
70
  runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', table: 'delegation', status: 'OK' }));
@@ -71,7 +72,7 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', tabl
71
72
  // Create delegation state
72
73
  runner.use(
73
74
  async (context, next) => {
74
- const { tx, itx, ops, statedb, senderState, delegationState } = context;
75
+ const { tx, itx, ops, statedb, senderState, receiverState, delegationState } = context;
75
76
 
76
77
  const opsObj = ops.reduce(
77
78
  (acc, x) => {
@@ -86,8 +87,23 @@ runner.use(
86
87
  delegationState ? delegationState.ops : {}
87
88
  );
88
89
 
89
- const [newSenderState, newDelegationState] = await Promise.all([
90
- statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
90
+ const sender = senderState ? senderState.address : tx.from;
91
+ const receiver = receiverState ? receiverState.address : itx.to;
92
+
93
+ const [newSenderState, newReceiverState, newDelegationState] = await Promise.all([
94
+ // update sender
95
+ statedb.account.updateOrCreate(
96
+ senderState,
97
+ account.updateOrCreate(senderState, { address: sender, nonce: tx.nonce, pk: tx.pk }, context),
98
+ context
99
+ ),
100
+
101
+ // update receiver
102
+ receiverState
103
+ ? Promise.resolve(receiverState)
104
+ : statedb.account.create(receiver, account.create({ address: receiver }, context), context),
105
+
106
+ // update delegation
91
107
  delegationState
92
108
  ? statedb.delegation.update(itx.address, delegation.update(delegationState, { ...itx, ops: opsObj }, context), context) // prettier-ignore
93
109
  : statedb.delegation.create(itx.address, delegation.create({ ...itx, ops: opsObj }, context), context),
@@ -97,6 +113,7 @@ runner.use(
97
113
 
98
114
  // Update context
99
115
  context.senderState = newSenderState;
116
+ context.receiverState = newReceiverState;
100
117
  context.delegationState = newDelegationState;
101
118
 
102
119
  return next();
@@ -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`);
@@ -35,7 +36,7 @@ runner.use(
35
36
  );
36
37
 
37
38
  // Ensure sender exist
38
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
39
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
39
40
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
40
41
 
41
42
  // Create account state, and update old accounts
@@ -48,7 +49,7 @@ runner.use(
48
49
 
49
50
  // Ensure receiver does not exist
50
51
  if (receiver) {
51
- return next(new Error('INVALID_SENDER_STATE', 'Can not migrate to an existing account'));
52
+ return next(new Error('INVALID_RECEIVER_STATE', 'Can not migrate to an existing account'));
52
53
  }
53
54
 
54
55
  // Create new account
@@ -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
@@ -53,10 +54,9 @@ runner.use(
53
54
  );
54
55
 
55
56
  // Ensure sender/receiver/delegation exist
56
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
57
+ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', status: 'INVALID_DELEGATION', table: 'delegation' })); // prettier-ignore
58
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
57
59
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
58
- runner.use(pipes.ExtractState({ from: 'itx.to', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' }));
59
- runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', status: 'INVALID_DELEGATION' }));
60
60
 
61
61
  // Update delegation state
62
62
  runner.use(
@@ -24,7 +24,7 @@ const runner = new Runner();
24
24
  runner.use(pipes.VerifyMultiSig(0));
25
25
  runner.use(verifyAcquireParams('acquire-v2'));
26
26
 
27
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
27
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
28
28
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
29
29
 
30
30
  runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK' }));
@@ -43,7 +43,7 @@ runner.use(
43
43
 
44
44
  // Though tokens are verified on factory creating phase, we need them here to construct tokenSymbols
45
45
  runner.use(extractFactoryTokens);
46
- runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN' }));
46
+ runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
47
47
 
48
48
  // Verify itx
49
49
  runner.use(verifyMintLimit);
@@ -86,6 +86,7 @@ runner.use(
86
86
  // Sender updates
87
87
  const senderUpdates = {
88
88
  nonce: tx.nonce,
89
+ pk: tx.pk,
89
90
  ...applyTokenUpdates(factoryTokens, senderState, 'sub'),
90
91
  };
91
92
 
@@ -82,9 +82,9 @@ 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' }));
86
- runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE' }));
87
- runner.use(pipes.ExtractState({ from: 'itx.owner', to: 'ownerState', status: 'INVALID_RECEIVER_STATE' }));
85
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
86
+ runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
87
+ runner.use(pipes.ExtractState({ from: 'itx.owner', to: 'ownerState', status: 'OK', table: 'account' }));
88
88
  runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
89
89
 
90
90
  // 6. verify token state and balance
@@ -153,6 +153,7 @@ runner.use(
153
153
  });
154
154
 
155
155
  const isAlsoSigner = !!signerUpdates[senderState.address];
156
+ const owner = ownerState ? ownerState.address : itx.owner;
156
157
 
157
158
  // Factory updates
158
159
  const factoryUpdates = { numMinted: factoryState.numMinted + 1 };
@@ -163,44 +164,53 @@ runner.use(
163
164
  context.tokenUpdates = applyTokenUpdates(factoryTokens, { tokens: {} }, 'add');
164
165
  }
165
166
 
166
- const [newSenderState, assetState, newSignerStates, newFactoryState, newAssetStates] = await Promise.all([
167
- // Update sender state
168
- statedb.account.update(
169
- senderState.address,
170
- account.update(
171
- senderState,
172
- Object.assign({ nonce: tx.nonce }, signerUpdates[senderState.address] || {}),
167
+ const [newSenderState, newReceiverState, assetState, newSignerStates, newFactoryState, newAssetStates] =
168
+ await Promise.all([
169
+ // Update sender state
170
+ statedb.account.update(
171
+ senderState.address,
172
+ account.update(
173
+ senderState,
174
+ Object.assign({ nonce: tx.nonce, pk: tx.pk }, signerUpdates[senderState.address] || {}),
175
+ context
176
+ ),
173
177
  context
174
178
  ),
175
- context
176
- ),
177
-
178
- // Create asset
179
- statedb.asset.create(
180
- itx.address,
181
- asset.create({ ...mintedAsset, owner: ownerState.address, address: mintedAddress }, context),
182
- context
183
- ),
184
-
185
- // Update signer state
186
- Promise.all(
187
- signerStates
188
- .filter((x) => x.address !== senderState.address)
189
- .map((x) => statedb.account.update(x.address, account.update(x, signerUpdates[x.address], context), context))
190
- ),
191
-
192
- // Update factory state
193
- statedb.factory.update(factoryState.address, factory.update(factoryState, factoryUpdates, context), context),
194
-
195
- // Mark asset as consumed
196
- Promise.all(
197
- assetStates.map((x) =>
198
- statedb.asset.update(x.address, asset.update(x, { consumedTime: txTime }, context), context)
199
- )
200
- ),
201
- ]);
179
+
180
+ // create receiver if not exist: asset owner
181
+ ownerState
182
+ ? Promise.resolve(ownerState)
183
+ : statedb.account.create(owner, account.create({ address: owner }, context), context),
184
+
185
+ // Create asset
186
+ statedb.asset.create(
187
+ itx.address,
188
+ asset.create({ ...mintedAsset, owner, address: mintedAddress }, context),
189
+ context
190
+ ),
191
+
192
+ // Update signer state
193
+ Promise.all(
194
+ signerStates
195
+ .filter((x) => x.address !== senderState.address)
196
+ .map((x) =>
197
+ statedb.account.update(x.address, account.update(x, signerUpdates[x.address], context), context)
198
+ )
199
+ ),
200
+
201
+ // Update factory state
202
+ statedb.factory.update(factoryState.address, factory.update(factoryState, factoryUpdates, context), context),
203
+
204
+ // Mark asset as consumed
205
+ Promise.all(
206
+ assetStates.map((x) =>
207
+ statedb.asset.update(x.address, asset.update(x, { consumedTime: txTime }, context), context)
208
+ )
209
+ ),
210
+ ]);
202
211
 
203
212
  context.senderState = newSenderState;
213
+ context.receiverState = newReceiverState;
204
214
  context.signerStates = isAlsoSigner ? newSignerStates.concat(newSenderState) : newSignerStates;
205
215
  context.assetState = assetState;
206
216
  context.factoryState = newFactoryState;
@@ -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
 
@@ -64,7 +67,7 @@ runner.use(
64
67
  );
65
68
 
66
69
  // Ensure sender exist
67
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
70
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
68
71
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
69
72
 
70
73
  // Check if parent is a factory
@@ -83,26 +86,36 @@ runner.use(
83
86
  );
84
87
 
85
88
  // Ensure parent exist
86
- runner.use(pipes.ExtractState({ from: 'itx.parent', to: 'parentAsset', status: 'INVALID_ASSET' }));
89
+ runner.use(pipes.ExtractState({ from: 'itx.parent', to: 'parentAsset', status: 'INVALID_ASSET', table: 'asset' }));
90
+
91
+ runner.use(ensureTxFee);
87
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;
97
+
98
+ const [newSenderState, assetState, newVaultState] = await Promise.all([
99
+ statedb.account.update(
100
+ senderState.address,
101
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
102
+ context
103
+ ),
92
104
 
93
- const [newSenderState, assetState] = await Promise.all([
94
- // Update owner state
95
- statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
96
- // Create asset state
97
105
  statedb.asset.create(
98
106
  itx.address,
99
107
  asset.create({ ...itx, data: assetData, owner: senderState.address }, context),
100
108
  context
101
109
  ),
110
+
111
+ isEmpty(vaultUpdates)
112
+ ? vaultState
113
+ : statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
102
114
  ]);
103
115
 
104
116
  context.senderState = newSenderState;
105
117
  context.assetState = assetState;
118
+ context.vaultState = newVaultState;
106
119
 
107
120
  debug('createAsset', assetState);
108
121
 
@@ -17,17 +17,17 @@ runner.use(pipes.VerifyMultiSig(0));
17
17
  runner.use(verifyAcquireParams('mint'));
18
18
 
19
19
  // Ensure sender exist
20
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
20
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
21
21
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
22
22
 
23
23
  // Ensure factory exist
24
24
  runner.use(pipes.ExtractState({ from: 'itx.factory', to: 'factoryState', status: 'INVALID_FACTORY_STATE', table: 'factory' })); // prettier-ignore
25
25
 
26
26
  // Used to assemble issuer object
27
- runner.use(pipes.ExtractState({ from: 'factoryState.owner', to: 'factoryOwnerState', status: 'INVALID_OWNER' }));
27
+ runner.use(pipes.ExtractState({ from: 'factoryState.owner', to: 'factoryOwnerState', status: 'INVALID_OWNER', table: 'account' })); // prettier-ignore
28
28
 
29
29
  // Ensure factory owner exist and equal to sender
30
- runner.use(pipes.ExtractState({ from: 'itx.owner', to: 'ownerState', status: 'INVALID_OWNER' }));
30
+ runner.use(pipes.ExtractState({ from: 'itx.owner', to: 'ownerState', status: 'OK', table: 'account' }));
31
31
  runner.use(
32
32
  pipes.VerifyInfo([
33
33
  {
@@ -72,20 +72,30 @@ runner.use(
72
72
  assetStates = [],
73
73
  } = context;
74
74
 
75
- // account updates
76
- const senderUpdates = { nonce: tx.nonce };
75
+ const owner = ownerState ? ownerState.address : itx.owner;
77
76
 
78
- // Factory updates
77
+ const senderUpdates = { nonce: tx.nonce, pk: tx.pk };
79
78
  const factoryUpdates = { numMinted: factoryState.numMinted + 1 };
80
79
 
81
- const [newSenderState, assetState, newFactoryState, newAssetStates] = await Promise.all([
80
+ const [newSenderState, newReceiverState, assetState, newFactoryState, newAssetStates] = await Promise.all([
81
+ // update sender
82
82
  statedb.account.update(senderState.address, account.update(senderState, senderUpdates, context), context),
83
+
84
+ // create receiver if not exist: asset owner
85
+ ownerState
86
+ ? Promise.resolve(ownerState)
87
+ : statedb.account.create(owner, account.create({ address: owner }, context), context),
88
+
89
+ // create asset
83
90
  statedb.asset.create(
84
91
  itx.address,
85
- asset.create({ ...mintedAsset, owner: ownerState.address, address: mintedAddress }, context),
92
+ asset.create({ ...mintedAsset, owner, address: mintedAddress }, context),
86
93
  context
87
94
  ),
95
+
96
+ // update factory
88
97
  statedb.factory.update(factoryState.address, factory.update(factoryState, factoryUpdates, context), context),
98
+
89
99
  // mark input assets as consumed
90
100
  Promise.all(
91
101
  assetStates.map((x) =>
@@ -95,6 +105,7 @@ runner.use(
95
105
  ]);
96
106
 
97
107
  context.senderState = newSenderState;
108
+ context.receiverState = newReceiverState;
98
109
  context.assetState = assetState;
99
110
  context.factoryState = newFactoryState;
100
111
  context.assetStates = newAssetStates;
@@ -10,11 +10,12 @@ module.exports = (mode) => (context, next) => {
10
10
  if (mode === 'acquire-v2') {
11
11
  owner = delegatorState || senderState;
12
12
  issuer = itx.issuer;
13
+ context.assetOwner = owner;
13
14
  } else if (mode === 'acquire-v3') {
14
- owner = ownerState;
15
+ owner = ownerState || { address: itx.owner };
15
16
  issuer = itx.issuer;
16
17
  } else if (mode === 'mint') {
17
- owner = ownerState;
18
+ owner = ownerState || { address: itx.owner };
18
19
  issuer = {
19
20
  id: factoryOwnerState.address,
20
21
  pk: factoryOwnerState.pk,
@@ -33,7 +34,6 @@ module.exports = (mode) => (context, next) => {
33
34
  return next(new Error('INVALID_ASSET', 'Invalid itx.address: does not match with minted asset address'));
34
35
  }
35
36
 
36
- context.assetOwner = owner;
37
37
  context.mintedAsset = minted.asset;
38
38
  context.mintedAddress = minted.address;
39
39
 
@@ -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`);
@@ -52,7 +53,11 @@ runner.use(
52
53
 
53
54
  const [newSenderState, newAssetState] = await Promise.all([
54
55
  // update owner state
55
- statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
56
+ statedb.account.update(
57
+ senderState.address,
58
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk }, context),
59
+ context
60
+ ),
56
61
 
57
62
  // update asset state
58
63
  statedb.asset.update(
@@ -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
 
@@ -79,23 +80,15 @@ runner.use(
79
80
  );
80
81
 
81
82
  // Ensure sender exist
82
- runner.use(
83
- pipes.ExtractState({ from: 'tx.from', to: 'senderState', table: 'account', status: 'INVALID_SENDER_STATE' })
84
- );
83
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', table: 'account', status: 'INVALID_SENDER_STATE' })); // prettier-ignore
85
84
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
86
85
 
87
86
  // Ensure tokens exist if we are creating an factory that consume tokens
88
- runner.use(
89
- pipes.ExtractState({ from: 'factoryTokens', to: 'tokenStates', table: 'token', status: 'INVALID_FACTORY_INPUT' })
90
- );
87
+ runner.use(pipes.ExtractState({ from: 'factoryTokens', to: 'tokenStates', table: 'token', status: 'INVALID_FACTORY_INPUT' })); // prettier-ignore
91
88
 
92
89
  // ensure input.assets all exist on chain
93
- runner.use(
94
- pipes.ExtractState({ from: 'factoryProps.input.assets', to: 'inputAssetStates', table: 'asset', status: 'OK' })
95
- );
96
- runner.use(
97
- pipes.ExtractState({ from: 'factoryProps.input.assets', to: 'inputFactoryStates', table: 'factory', status: 'OK' })
98
- );
90
+ runner.use(pipes.ExtractState({ from: 'factoryProps.input.assets', to: 'inputAssetStates', table: 'asset', status: 'OK' })); // prettier-ignore
91
+ runner.use(pipes.ExtractState({ from: 'factoryProps.input.assets', to: 'inputFactoryStates', table: 'factory', status: 'OK' })); // prettier-ignore
99
92
  runner.use((context, next) => {
100
93
  const { inputAssetStates = [], inputFactoryStates = [], factoryProps } = context;
101
94
  if (inputAssetStates.some((x) => !!x.consumedTime)) {
@@ -109,26 +102,37 @@ runner.use((context, next) => {
109
102
  return next(new Error('INVALID_FACTORY_INPUT', 'Not all input.assets exist on chain'));
110
103
  });
111
104
 
105
+ runner.use(ensureTxFee);
106
+
112
107
  // Create factory state
113
- // TODO: we need to support charging when creating new nft-factory
114
108
  runner.use(
115
109
  async (context, next) => {
116
- const { tx, itx, statedb, senderState, factoryProps } = context;
110
+ const { tx, itx, statedb, senderState, senderUpdates, vaultState, vaultUpdates, factoryProps } = context;
117
111
  const tokens = { [context.config.token.address]: '0' };
118
112
 
119
- const [newSenderState, factoryState] = await Promise.all([
113
+ const [newSenderState, factoryState, newVaultState] = await Promise.all([
120
114
  // Update owner state
121
- statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
115
+ statedb.account.update(
116
+ senderState.address,
117
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
118
+ context
119
+ ),
120
+
122
121
  // Create factory state
123
122
  statedb.factory.create(
124
123
  itx.address,
125
124
  factory.create({ ...factoryProps, tokens, owner: senderState.address }, context),
126
125
  context
127
126
  ),
127
+
128
+ isEmpty(vaultUpdates)
129
+ ? vaultState
130
+ : statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
128
131
  ]);
129
132
 
130
133
  context.senderState = newSenderState;
131
134
  context.factoryState = factoryState;
135
+ context.vaultState = newVaultState;
132
136
 
133
137
  debug('createFactory', factoryState);
134
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 });
@@ -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`);
@@ -122,7 +123,7 @@ runner.use(
122
123
  senderState.address,
123
124
  account.update(
124
125
  senderState,
125
- Object.assign({ nonce: tx.nonce }, signerUpdates[senderState.address] || {}),
126
+ Object.assign({ nonce: tx.nonce, pk: tx.pk }, signerUpdates[senderState.address] || {}),
126
127
  context
127
128
  ),
128
129
  context
@@ -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(),
@@ -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(),
@@ -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
 
@@ -105,13 +106,20 @@ runner.use(
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 = { tokens: {} };
14
+ context.vaultUpdates = { tokens: {} };
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
 
@@ -56,7 +58,7 @@ runner.use(
56
58
  );
57
59
 
58
60
  // Ensure sender exist
59
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
61
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
60
62
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
61
63
 
62
64
  // Ensure token not exist
@@ -86,26 +88,34 @@ runner.use(async (context, next) => {
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[itx.address] = itx.initialSupply;
97
101
 
98
- const [newSenderState, tokenState] = await Promise.all([
102
+ const [newSenderState, tokenState, newVaultState] = await Promise.all([
99
103
  statedb.account.update(
100
104
  senderState.address,
101
- account.update(senderState, { tokens: senderTokens, nonce: tx.nonce }, context),
105
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
102
106
  context
103
107
  ),
108
+
104
109
  statedb.token.create(itx.address, token.create({ ...itx, data, issuer: senderState.address }, context), context),
110
+
111
+ isEmpty(vaultUpdates)
112
+ ? vaultState
113
+ : statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
105
114
  ]);
106
115
 
107
116
  context.senderState = newSenderState;
108
117
  context.tokenState = tokenState;
118
+ context.vaultState = newVaultState;
109
119
 
110
120
  debug('create token v2', tokenState);
111
121
 
@@ -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
@@ -11,14 +12,14 @@ const debug = require('debug')(`${require('../../../package.json').name}:deposit
11
12
 
12
13
  const VerifySigners = require('../rollup/pipes/verify-signers');
13
14
  const VerifyPaused = require('../rollup/pipes/verify-paused');
14
- const { applyTokenUpdates, getTxFee, getRewardLocker } = require('../../util');
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(),
@@ -138,15 +139,15 @@ runner.use((context, next) => {
138
139
  const { itx, rollupState, tokenState } = context;
139
140
  const { depositFeeRate, maxDepositFee, minDepositFee } = rollupState;
140
141
 
141
- context.txFee = getTxFee({
142
+ const { reward } = getTxFee({
142
143
  amount: itx.token.value,
143
144
  feeRate: depositFeeRate,
144
145
  maxFee: maxDepositFee,
145
146
  minFee: minDepositFee,
146
147
  });
147
148
 
148
- if (new BN(itx.actualFee).lt(new BN(context.txFee.reward))) {
149
- const expected = fromUnitToToken(context.txFee.reward, tokenState.decimal);
149
+ if (new BN(itx.actualFee).lt(new BN(reward))) {
150
+ const expected = fromUnitToToken(reward, tokenState.decimal);
150
151
  const actual = fromUnitToToken(itx.actualFee, tokenState.decimal);
151
152
  return next(new Error('INVALID_TX', `itx.actualFee too low, expect at least ${expected}, got ${actual}`));
152
153
  }
@@ -155,28 +156,33 @@ runner.use((context, next) => {
155
156
  });
156
157
 
157
158
  // 7. verify sender and signer states
158
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
159
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
159
160
  runner.use(pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
160
161
  runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
161
162
 
162
163
  // 8. update state: the token minting is done when deposit finalized in rollup-block
163
164
  runner.use(
164
165
  async (context, next) => {
165
- const { tx, itx, txFee, statedb, senderState, stakeState, stakeAddress, lockerState, lockerAddress } = context;
166
+ const { tx, itx, statedb, senderState, stakeState, stakeAddress, lockerState, lockerAddress } = context;
166
167
 
167
- const stakeUpdates = applyTokenUpdates([{ address: itx.token.address, value: txFee.total }], stakeState, 'sub');
168
- const senderUpdates = applyTokenUpdates([{ address: itx.token.address, value: txFee.user }], senderState, 'add');
168
+ const user = itx.token.value;
169
+ const fee = itx.actualFee;
170
+ const total = getBNSum(user, fee);
171
+
172
+ const stakeUpdates = applyTokenUpdates([{ address: itx.token.address, value: total }], stakeState, 'sub');
173
+ const senderUpdates = applyTokenUpdates([{ address: itx.token.address, value: user }], senderState || {}, 'add');
169
174
  const lockerUpdates = applyTokenUpdates(
170
- [{ address: itx.token.address, value: txFee.reward }],
175
+ [{ address: itx.token.address, value: fee }],
171
176
  lockerState || { tokens: {} },
172
177
  'add'
173
178
  );
174
179
 
180
+ const sender = senderState ? senderState.address : tx.from;
175
181
  const [newSenderState, newStakeState, newLockerState, evidenceState] = await Promise.all([
176
- // update user account
177
- statedb.account.update(
178
- senderState.address,
179
- account.update(senderState, { nonce: tx.nonce, ...senderUpdates }, context),
182
+ // updateOrCreate user account
183
+ statedb.account.updateOrCreate(
184
+ senderState,
185
+ account.updateOrCreate(senderState, { address: sender, nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
180
186
  context
181
187
  ),
182
188
 
@@ -218,11 +224,11 @@ runner.use(
218
224
 
219
225
  context.updatedAccounts = [
220
226
  // stake for tx proposer is decreased
221
- { address: stakeAddress, token: itx.token.address, delta: `-${txFee.total}`, action: 'unlock' },
227
+ { address: stakeAddress, token: itx.token.address, delta: `-${total}`, action: 'unlock' },
222
228
  // mint to depositor from stake
223
- { address: senderState.address, token: itx.token.address, delta: txFee.user, action: 'unlock' },
224
- // block reward is locked for later claiming
225
- { address: lockerAddress, token: itx.token.address, delta: txFee.reward, action: 'pending' },
229
+ { address: sender, token: itx.token.address, delta: user, action: 'unlock' },
230
+ // tx fee is locked for later claiming
231
+ { address: lockerAddress, token: itx.token.address, delta: fee, action: 'pending' },
226
232
  ];
227
233
 
228
234
  debug('deposit-token-v2', itx);
@@ -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(''),
@@ -99,15 +100,15 @@ runner.use((context, next) => {
99
100
 
100
101
  const isFixedFee = new BN(itx.maxFee).isZero();
101
102
  if (isFixedFee) {
102
- context.txFee = getTxFee({
103
+ const { reward } = getTxFee({
103
104
  amount: itx.token.value,
104
105
  feeRate: withdrawFeeRate,
105
106
  maxFee: maxWithdrawFee,
106
107
  minFee: minWithdrawFee,
107
108
  });
108
109
 
109
- if (new BN(itx.actualFee).lt(new BN(context.txFee.reward))) {
110
- const expected = fromUnitToToken(context.txFee.reward, tokenState.decimal);
110
+ if (new BN(itx.actualFee).lt(new BN(reward))) {
111
+ const expected = fromUnitToToken(reward, tokenState.decimal);
111
112
  const actual = fromUnitToToken(itx.actualFee, tokenState.decimal);
112
113
  return next(new Error('INVALID_TX', `itx.actualFee too low, expect at least ${expected}, got ${actual}`));
113
114
  }
@@ -81,8 +81,8 @@ runner.use(pipes.VerifyListSize({ listKey: ['senderTokens', 'receiverTokens'] })
81
81
 
82
82
  // TODO: anti-replay-exchange-attack
83
83
 
84
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
85
- runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK' }));
84
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
85
+ runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
86
86
  runner.use(pipes.VerifyDelegation({ type: 'signature', signerKey: 'senderState', delegatorKey: 'delegatorState' }));
87
87
  runner.use(pipes.ExtractSigner({ signerKey: 'signerStates', delegatorKey: 'delegatorStates' }));
88
88
  runner.use(
@@ -94,9 +94,9 @@ runner.use(
94
94
  })
95
95
  );
96
96
 
97
- runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN' }));
97
+ runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
98
98
 
99
- runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' }));
99
+ runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE', table: 'account' })); // prettier-ignore
100
100
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState', signerKey: 'signerStates' }));
101
101
  runner.use((context, next) => {
102
102
  context.senderTokenConditions = {
@@ -114,11 +114,11 @@ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'receiverState', conditionKey: '
114
114
 
115
115
  runner.use(pipes.AntiLandAttack({ senderState: 'senderState', receiverState: 'receiverState' }));
116
116
 
117
- runner.use(pipes.ExtractState({ from: 'senderAssets', to: 'priv.senderAssets', status: 'INVALID_ASSET' }));
117
+ runner.use(pipes.ExtractState({ from: 'senderAssets', to: 'priv.senderAssets', status: 'INVALID_ASSET', table: 'asset' })); // prettier-ignore
118
118
  runner.use(pipes.VerifyTransferrable({ assets: 'priv.senderAssets' }));
119
119
  runner.use(pipes.VerifyUpdater({ assetKey: 'priv.senderAssets', ownerKey: 'senderState' }));
120
120
 
121
- runner.use(pipes.ExtractState({ from: 'receiverAssets', to: 'priv.receiverAssets', status: 'INVALID_ASSET' }));
121
+ runner.use(pipes.ExtractState({ from: 'receiverAssets', to: 'priv.receiverAssets', status: 'INVALID_ASSET', table: 'asset' })); // prettier-ignore
122
122
  runner.use(pipes.VerifyTransferrable({ assets: 'priv.receiverAssets' }));
123
123
  runner.use(pipes.VerifyUpdater({ assetKey: 'priv.receiverAssets', ownerKey: 'receiverState' }));
124
124
 
@@ -62,10 +62,10 @@ runner.use((context, next) => {
62
62
  next();
63
63
  });
64
64
 
65
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
65
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
66
66
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
67
67
 
68
- runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN' }));
68
+ runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
69
69
  runner.use((context, next) => {
70
70
  context.tokenConditions = {
71
71
  owner: context.senderState.address,
@@ -75,24 +75,32 @@ runner.use((context, next) => {
75
75
  });
76
76
  runner.use(pipes.VerifyTokenBalance({ ownerKey: 'senderState', conditionKey: 'tokenConditions' }));
77
77
 
78
- runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK' }));
78
+ runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
79
79
  runner.use(pipes.VerifyDelegation({ type: 'signature', signerKey: 'senderState', delegatorKey: 'delegatorState' }));
80
80
 
81
- runner.use(pipes.ExtractReceiver({ from: 'itx.to' }));
82
- runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' }));
81
+ runner.use(pipes.ExtractReceiver({ from: 'itx.to', to: 'receiver' }));
82
+ runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'OK', table: 'account' }));
83
83
  runner.use(pipes.AntiLandAttack({ senderState: 'senderState', receiverState: 'receiverState' }));
84
84
 
85
- runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'INVALID_ASSET' }));
85
+ runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'INVALID_ASSET', table: 'asset' }));
86
86
  runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
87
87
  runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'senderState' }));
88
88
 
89
- runner.use(pipes.UpdateOwner({ assets: 'assetStates', owner: 'receiverState' }));
89
+ // transfer assets to new owner
90
+ runner.use((context, next) => {
91
+ const { itx, receiverState } = context;
92
+ context.receiverAddr = receiverState ? receiverState.address : itx.to;
93
+ return next();
94
+ });
95
+ runner.use(pipes.UpdateOwner({ assets: 'assetStates', owner: 'receiverAddr' }));
96
+
97
+ // update statedb: transfer tokens to new owner
90
98
  runner.use(
91
99
  async (context, next) => {
92
- const { tx, itx, tokens, senderState, receiverState, statedb } = context;
100
+ const { tx, itx, tokens, senderState, receiverAddr, receiverState, statedb } = context;
93
101
 
94
102
  const { tokens: senderTokens = {} } = senderState;
95
- const { tokens: receiverTokens = {} } = receiverState;
103
+ const { tokens: receiverTokens = {} } = receiverState || {};
96
104
  for (const token of tokens) {
97
105
  const delta = new BN(token.value);
98
106
  senderTokens[token.address] = new BN(senderTokens[token.address]).sub(delta).toString();
@@ -103,14 +111,14 @@ runner.use(
103
111
  // Update sender state
104
112
  statedb.account.update(
105
113
  senderState.address,
106
- account.update(senderState, { nonce: tx.nonce, tokens: senderTokens }, context),
114
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, tokens: senderTokens }, context),
107
115
  context
108
116
  ),
109
117
 
110
118
  // Update receiver state
111
- statedb.account.update(
112
- receiverState.address,
113
- account.update(receiverState, { tokens: receiverTokens }, context),
119
+ statedb.account.updateOrCreate(
120
+ receiverState,
121
+ account.updateOrCreate(receiverState, { address: receiverAddr, tokens: receiverTokens }, context),
114
122
  context
115
123
  ),
116
124
  ]);
@@ -108,9 +108,9 @@ runner.use(
108
108
  runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }));
109
109
 
110
110
  // 4. verify sender & signer & receiver
111
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
112
- runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE' }));
113
- runner.use(pipes.ExtractState({ from: 'receivers', to: 'receiverStates', status: 'INVALID_RECEIVER_STATE' }));
111
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
112
+ runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
113
+ runner.use(pipes.ExtractState({ from: 'receivers', to: 'receiverStates', status: 'OK', table: 'account' }));
114
114
  runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
115
115
 
116
116
  // 5. verify token state and balance
@@ -140,7 +140,17 @@ runner.use(async (context, next) => {
140
140
 
141
141
  runner.use(
142
142
  async (context, next) => {
143
- const { tx, inputs, outputs, senderState, signerStates, receiverStates, assetStates = [], statedb } = context;
143
+ const {
144
+ tx,
145
+ inputs,
146
+ outputs,
147
+ receivers,
148
+ senderState,
149
+ signerStates,
150
+ receiverStates = [],
151
+ assetStates = [],
152
+ statedb,
153
+ } = context;
144
154
 
145
155
  const signerUpdates = {};
146
156
  inputs.forEach((x) => {
@@ -157,7 +167,7 @@ runner.use(
157
167
  const { owner, tokensList } = x;
158
168
  receiverUpdates[owner] = applyTokenUpdates(
159
169
  tokensList,
160
- receiverStates.find((s) => s.address === owner),
170
+ receiverStates.find((s) => s.address === owner) || {},
161
171
  'add'
162
172
  );
163
173
  });
@@ -175,7 +185,6 @@ runner.use(
175
185
 
176
186
  debug('transfer-v3', { signerUpdates, receiverUpdates, assetUpdates, isAlsoSigner, isAlsoReceiver });
177
187
 
178
- // FIXME: skip statedb update if account not changed at all
179
188
  const [newSenderState, newSignerStates, newReceiverStates, newAssetStates] = await Promise.all([
180
189
  // Update sender state
181
190
  statedb.account.update(
@@ -183,7 +192,7 @@ runner.use(
183
192
  account.update(
184
193
  senderState,
185
194
  Object.assign(
186
- { nonce: tx.nonce },
195
+ { nonce: tx.nonce, pk: tx.pk },
187
196
  signerUpdates[senderState.address] || {},
188
197
  receiverUpdates[senderState.address] || {}
189
198
  ),
@@ -199,13 +208,18 @@ runner.use(
199
208
  .map((x) => statedb.account.update(x.address, account.update(x, signerUpdates[x.address], context), context))
200
209
  ),
201
210
 
202
- // Update receiver state
211
+ // UpdateOrCreate receiver state
203
212
  Promise.all(
204
- receiverStates
205
- .filter((x) => x.address !== senderState.address)
206
- .map((x) =>
207
- statedb.account.update(x.address, account.update(x, receiverUpdates[x.address], context), context)
208
- )
213
+ receivers
214
+ .filter((x) => x !== senderState.address)
215
+ .map((x) => {
216
+ const receiverState = receiverStates.find((s) => s.address === x);
217
+ return statedb.account.updateOrCreate(
218
+ receiverState,
219
+ account.updateOrCreate(receiverState, { ...receiverUpdates[x], address: x }, context),
220
+ context
221
+ );
222
+ })
209
223
  ),
210
224
 
211
225
  // Update asset state
package/lib/util.js CHANGED
@@ -165,7 +165,7 @@ const splitTxFee = ({ total, shares = {}, stringify = true }) => {
165
165
 
166
166
  const getRewardLocker = (rollupAddress) => toStakeAddress(rollupAddress, rollupAddress);
167
167
  const getBNSum = (...args) => flattenDeep(args).reduce((sum, x) => sum.add(new BN(x)), new BN(0)).toString(10); // prettier-ignore
168
- const isFixedFee = (x) => new BN(x.tx.itxJson.maxFee).isZero();
168
+ const isFixedFee = (x) => !x.tx.itxJson.maxFee || new BN(x.tx.itxJson.maxFee).isZero();
169
169
 
170
170
  const ensureBlockReward = (rollupState, minReward, txStates) => {
171
171
  const { address, withdrawFeeRate, minWithdrawFee, maxWithdrawFee, tokenAddress } = rollupState;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.13.62",
6
+ "version": "1.13.66",
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.62",
23
- "@arcblock/did-util": "1.13.62",
24
- "@ocap/asset": "1.13.62",
25
- "@ocap/mcrypto": "1.13.62",
26
- "@ocap/merkle-tree": "1.13.62",
27
- "@ocap/message": "1.13.62",
28
- "@ocap/state": "1.13.62",
29
- "@ocap/tx-pipeline": "1.13.62",
30
- "@ocap/util": "1.13.62",
31
- "@ocap/wallet": "1.13.62",
22
+ "@arcblock/did": "1.13.66",
23
+ "@arcblock/did-util": "1.13.66",
24
+ "@ocap/asset": "1.13.66",
25
+ "@ocap/mcrypto": "1.13.66",
26
+ "@ocap/merkle-tree": "1.13.66",
27
+ "@ocap/message": "1.13.66",
28
+ "@ocap/state": "1.13.66",
29
+ "@ocap/tx-pipeline": "1.13.66",
30
+ "@ocap/util": "1.13.66",
31
+ "@ocap/validator": "1.13.66",
32
+ "@ocap/wallet": "1.13.66",
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": "a14f8d2ade8dc0c58fd9fbf331a4eb9b842de6ed"
45
+ "gitHead": "1395a8dccee398dd0aed47c2b54a2c6e350d8621"
45
46
  }