@ocap/tx-protocols 1.17.23 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/lib/pipes/ensure-cost.js +116 -0
  2. package/lib/pipes/ensure-gas.js +53 -0
  3. package/lib/protocols/account/delegate.js +30 -3
  4. package/lib/protocols/account/migrate.js +15 -1
  5. package/lib/protocols/account/revoke-delegate.js +14 -2
  6. package/lib/protocols/asset/acquire-v2.js +25 -2
  7. package/lib/protocols/asset/acquire-v3.js +34 -2
  8. package/lib/protocols/asset/create.js +9 -10
  9. package/lib/protocols/asset/mint.js +28 -2
  10. package/lib/protocols/asset/pipes/exec-mint-hook.js +1 -1
  11. package/lib/protocols/asset/update.js +11 -2
  12. package/lib/protocols/factory/create.js +8 -9
  13. package/lib/protocols/governance/claim-stake.js +41 -4
  14. package/lib/protocols/governance/revoke-stake.js +14 -3
  15. package/lib/protocols/governance/stake.js +53 -4
  16. package/lib/protocols/rollup/claim-reward.js +46 -16
  17. package/lib/protocols/rollup/create-block.js +35 -3
  18. package/lib/protocols/rollup/create.js +8 -18
  19. package/lib/protocols/rollup/join.js +25 -2
  20. package/lib/protocols/rollup/leave.js +15 -2
  21. package/lib/protocols/rollup/migrate-contract.js +13 -2
  22. package/lib/protocols/rollup/migrate-token.js +13 -2
  23. package/lib/protocols/rollup/pause.js +13 -2
  24. package/lib/protocols/rollup/resume.js +13 -2
  25. package/lib/protocols/rollup/update.js +13 -2
  26. package/lib/protocols/token/create.js +19 -9
  27. package/lib/protocols/token/deposit-v2.js +46 -6
  28. package/lib/protocols/token/withdraw-v2.js +43 -6
  29. package/lib/protocols/trade/exchange-v2.js +36 -3
  30. package/lib/protocols/trade/transfer-v2.js +27 -2
  31. package/lib/protocols/trade/transfer-v3.js +34 -1
  32. package/lib/util.js +7 -7
  33. package/package.json +13 -13
  34. package/lib/protocols/rollup/pipes/ensure-service-fee.js +0 -37
@@ -0,0 +1,116 @@
1
+ const noop = require('lodash/noop');
2
+ const { CustomError: Error } = require('@ocap/util/lib/error');
3
+ const { fromTokenToUnit, BN } = require('@ocap/util');
4
+ const { account } = require('@ocap/state');
5
+
6
+ // eslint-disable-next-line global-require
7
+ const debug = require('debug')(`${require('../../package.json').name}:pipes:ensure-cost`);
8
+
9
+ const { applyTokenUpdates } = require('../util');
10
+
11
+ // We have a layered transaction cost design
12
+ // - gas: charged for every tx for creating/updating states on the ledger
13
+ // - service fee: charged for creating tokens/assets/factories/rollups
14
+ // - protocol fee: charged for moving tokens across the bridge
15
+ module.exports = function CreateEnsureTxCostPipe({ attachSenderChanges = true } = {}) {
16
+ return async function EnsureTxCost(context, next) {
17
+ // TODO: we are using the sender as gas payer, this may change in future
18
+ const { config, statedb, txType, senderState, gasEstimate, gasVaultState, totalGas } = context;
19
+ const feeVaultState = await statedb.account.get(config.vaults.txFee, context);
20
+
21
+ const changes = {};
22
+ let txCost = new BN(0);
23
+
24
+ const txFee = config.transaction.txFee[txType];
25
+ if (txFee) {
26
+ const totalFee = fromTokenToUnit(txFee, config.token.decimal);
27
+ txCost = txCost.add(totalFee);
28
+ changes.fee = { address: config.token.address, value: totalFee.toString(10) };
29
+ context.feeVaultChange = changes.fee;
30
+ }
31
+ if (totalGas.gt(new BN(0))) {
32
+ txCost = txCost.add(totalGas);
33
+ changes.gas = { address: config.token.address, value: totalGas.toString(10) };
34
+ context.gasVaultChange = changes.gas;
35
+ }
36
+
37
+ if (senderState) {
38
+ const expected = new BN(gasEstimate.payment || 0).add(txCost);
39
+ const actual = new BN(senderState.tokens[config.token.address] || 0);
40
+ if (actual.lt(expected)) {
41
+ return next(
42
+ new Error(
43
+ 'INSUFFICIENT_FUND',
44
+ `Insufficient fund to pay for tx cost from ${senderState.address}, expected ${expected}, got ${actual}`
45
+ )
46
+ );
47
+ }
48
+
49
+ // to be merged into later pipe
50
+ context.senderUpdates = applyTokenUpdates(
51
+ [{ address: config.token.address, value: txCost.toString(10) }],
52
+ senderState,
53
+ 'sub'
54
+ );
55
+ context.senderChange = {
56
+ address: senderState.address,
57
+ token: config.token.address,
58
+ delta: `-${txCost.toString(10)}`,
59
+ action: 'fee',
60
+ };
61
+
62
+ debug({ changes, senderUpdates: context.senderUpdates });
63
+
64
+ // to be called in later pipes
65
+ context.updateVaults = async function updateVaults() {
66
+ const [newFeeVaultState, newGasVaultState] = await Promise.all([
67
+ changes.fee
68
+ ? statedb.account.update(
69
+ feeVaultState.address,
70
+ account.update(feeVaultState, applyTokenUpdates([changes.fee], feeVaultState, 'add'), context),
71
+ context
72
+ )
73
+ : Promise.resolve(feeVaultState),
74
+ changes.gas
75
+ ? statedb.account.update(
76
+ gasVaultState.address,
77
+ account.update(gasVaultState, applyTokenUpdates([changes.gas], gasVaultState, 'add'), context),
78
+ context
79
+ )
80
+ : Promise.resolve(gasVaultState),
81
+ ]);
82
+
83
+ context.feeVaultState = newFeeVaultState;
84
+ context.gasVaultState = newGasVaultState;
85
+
86
+ context.updatedAccounts = context.updatedAccounts || [];
87
+ if (changes.fee) {
88
+ context.updatedAccounts.push({
89
+ address: feeVaultState.address,
90
+ token: config.token.address,
91
+ delta: changes.fee.value,
92
+ action: 'fee',
93
+ });
94
+ }
95
+ if (changes.gas) {
96
+ context.updatedAccounts.push({
97
+ address: gasVaultState.address,
98
+ token: config.token.address,
99
+ delta: changes.gas.value,
100
+ action: 'gas',
101
+ });
102
+ }
103
+ if (attachSenderChanges) {
104
+ context.updatedAccounts.push(context.senderChange);
105
+ }
106
+ };
107
+ } else {
108
+ context.senderUpdates = {};
109
+ context.senderChange = null;
110
+ context.updateVaults = noop;
111
+ context.updatedAccounts = [];
112
+ }
113
+
114
+ return next();
115
+ };
116
+ };
@@ -0,0 +1,53 @@
1
+ const { CustomError: Error } = require('@ocap/util/lib/error');
2
+ const { BN, hexToNumber } = require('@ocap/util');
3
+
4
+ // eslint-disable-next-line global-require
5
+ const debug = require('debug')(`${require('../../package.json').name}:pipes:estimate-gas`);
6
+
7
+ // Must be called after all state ready
8
+ // Must be called before creating/updating any state
9
+ // estimateTxGas should be a function that returns the following data:
10
+ // - create: state creating ops
11
+ // - update: state updating ops
12
+ // - payment: sender payment amount of the gas token
13
+ module.exports = function CreateGasEnsureFn(estimateTxGas) {
14
+ return async function EnsureTxGas(context, next) {
15
+ const { txHash, statedb, config } = context;
16
+
17
+ // total gas in native token
18
+ const { txGas } = config.transaction;
19
+ let totalGas = new BN(txGas.price * txGas.dataStorage * context.txSize);
20
+ const estimate = estimateTxGas(context);
21
+ totalGas = totalGas.add(new BN(txGas.price * txGas.createState * (estimate.create + 1))); // 1 = tx insert
22
+ totalGas = totalGas.add(new BN(txGas.price * txGas.updateState * estimate.update));
23
+
24
+ // gas receiver address
25
+ const { txGas: gasVaults } = config.vaults;
26
+ const index = hexToNumber(txHash.slice(txHash.length - 4)) % gasVaults.length;
27
+ const gasVault = gasVaults[index];
28
+
29
+ // extract gasReceiver
30
+ if (gasVault) {
31
+ const gasVaultState = await statedb.account.get(gasVault, context);
32
+ if (!gasVaultState) {
33
+ return next(new Error('INVALID_GAS_VAULT', `Can not find gas vault state: ${gasVault}`));
34
+ }
35
+
36
+ context.gasVaultState = gasVaultState;
37
+ }
38
+
39
+ debug({
40
+ txType: context.txType,
41
+ txHash: context.txHash,
42
+ txSize: context.txSize,
43
+ totalGas: totalGas.toString(10),
44
+ gasVault,
45
+ estimate,
46
+ });
47
+
48
+ context.totalGas = totalGas;
49
+ context.gasEstimate = estimate;
50
+
51
+ return next();
52
+ };
53
+ };
@@ -10,6 +10,9 @@ const { toDelegateAddress } = require('@arcblock/did-util');
10
10
  // eslint-disable-next-line global-require
11
11
  const debug = require('debug')(`${require('../../../package.json').name}:delegate`);
12
12
 
13
+ const EnsureTxGas = require('../../pipes/ensure-gas');
14
+ const EnsureTxCost = require('../../pipes/ensure-cost');
15
+
13
16
  const runner = new Runner();
14
17
 
15
18
  runner.use(pipes.VerifyMultiSig(0));
@@ -62,17 +65,39 @@ runner.use(
62
65
  );
63
66
 
64
67
  // Ensure sender/receiver exist
65
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
68
+ runner.use(
69
+ pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })
70
+ );
66
71
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
67
72
  runner.use(pipes.ExtractState({ from: 'itx.to', to: 'receiverState', status: 'OK', table: 'account' }));
68
73
 
69
74
  // Extract delegation
70
75
  runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', table: 'delegation', status: 'OK' }));
71
76
 
77
+ // Ensure tx fee and gas
78
+ runner.use(
79
+ EnsureTxGas((context) => {
80
+ const result = { create: 0, update: 1, payment: 0 };
81
+ if (context.receiverState) {
82
+ result.update += 1;
83
+ } else {
84
+ result.create += 1;
85
+ }
86
+ if (context.delegationState) {
87
+ result.update += 1;
88
+ } else {
89
+ result.create += 1;
90
+ }
91
+
92
+ return result;
93
+ })
94
+ );
95
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
96
+
72
97
  // Create delegation state
73
98
  runner.use(
74
99
  async (context, next) => {
75
- const { tx, itx, ops, statedb, senderState, receiverState, delegationState } = context;
100
+ const { tx, itx, ops, statedb, senderState, receiverState, delegationState, senderUpdates, updateVaults } = context;
76
101
 
77
102
  const opsObj = ops.reduce(
78
103
  (acc, x) => {
@@ -94,7 +119,7 @@ runner.use(
94
119
  // update sender
95
120
  statedb.account.updateOrCreate(
96
121
  senderState,
97
- account.updateOrCreate(senderState, { address: sender, nonce: tx.nonce, pk: tx.pk }, context),
122
+ account.updateOrCreate(senderState, { ...senderUpdates, address: sender, nonce: tx.nonce, pk: tx.pk }, context),
98
123
  context
99
124
  ),
100
125
 
@@ -107,6 +132,8 @@ runner.use(
107
132
  delegationState
108
133
  ? statedb.delegation.update(itx.address, delegation.update(delegationState, { ...itx, ops: opsObj }, context), context) // prettier-ignore
109
134
  : statedb.delegation.create(itx.address, delegation.create({ ...itx, ops: opsObj }, context), context),
135
+
136
+ updateVaults(),
110
137
  ]);
111
138
 
112
139
  debug(delegationState ? 'update' : 'create', newDelegationState);
@@ -8,6 +8,9 @@ const { account } = require('@ocap/state');
8
8
  // eslint-disable-next-line global-require
9
9
  const debug = require('debug')(`${require('../../../package.json').name}:migrate`);
10
10
 
11
+ const EnsureTxGas = require('../../pipes/ensure-gas');
12
+ const EnsureTxCost = require('../../pipes/ensure-cost');
13
+
11
14
  const runner = new Runner();
12
15
 
13
16
  runner.use(pipes.VerifyMultiSig(0));
@@ -39,10 +42,18 @@ runner.use(
39
42
  runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
40
43
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
41
44
 
45
+ // Ensure tx fee and gas
46
+ runner.use(
47
+ EnsureTxGas((context) => {
48
+ return { create: 1, update: getRelatedAddresses(context.senderState).length, payment: 0 };
49
+ })
50
+ );
51
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
52
+
42
53
  // Create account state, and update old accounts
43
54
  runner.use(
44
55
  async (context, next) => {
45
- const { tx, itx, statedb } = context;
56
+ const { tx, itx, statedb, senderUpdates, updateVaults } = context;
46
57
 
47
58
  const sender = context.senderState;
48
59
  const receiver = await statedb.account.get(itx.address, context);
@@ -58,6 +69,7 @@ runner.use(
58
69
  account.create(
59
70
  {
60
71
  ...sender, // copy all old account data to new account
72
+ ...senderUpdates,
61
73
  address: itx.address,
62
74
  pk: itx.pk,
63
75
  migratedFrom: [sender.address],
@@ -86,6 +98,8 @@ runner.use(
86
98
  );
87
99
  debug('old accounts', states);
88
100
 
101
+ await updateVaults();
102
+
89
103
  // Update context
90
104
  context.receiverState = newAccount;
91
105
  context.senderState = states.find((x) => x.address === sender.from);
@@ -10,6 +10,9 @@ const { toDelegateAddress } = require('@arcblock/did-util');
10
10
  // eslint-disable-next-line global-require
11
11
  const debug = require('debug')(`${require('../../../package.json').name}:delegate`);
12
12
 
13
+ const EnsureTxGas = require('../../pipes/ensure-gas');
14
+ const EnsureTxCost = require('../../pipes/ensure-cost');
15
+
13
16
  const runner = new Runner();
14
17
 
15
18
  runner.use(pipes.VerifyMultiSig(0));
@@ -59,10 +62,14 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', stat
59
62
  runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
60
63
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
61
64
 
65
+ // Ensure tx fee and gas
66
+ runner.use(EnsureTxGas(() => ({ create: 0, update: 2, payment: 0 })));
67
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
68
+
62
69
  // Update delegation state
63
70
  runner.use(
64
71
  async (context, next) => {
65
- const { tx, itx, typeUrls, statedb, senderState, delegationState } = context;
72
+ const { tx, itx, typeUrls, statedb, senderState, senderUpdates, delegationState, updateVaults } = context;
66
73
 
67
74
  debug('update', { address: itx.address, typeUrls });
68
75
 
@@ -70,12 +77,17 @@ runner.use(
70
77
  typeUrls.forEach((x) => delete newOps[x]);
71
78
 
72
79
  const [newSenderState, newDelegationState] = await Promise.all([
73
- statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
80
+ statedb.account.update(
81
+ senderState.address,
82
+ account.update(senderState, { ...senderUpdates, nonce: tx.nonce }, context),
83
+ context
84
+ ),
74
85
  statedb.delegation.update(
75
86
  itx.address,
76
87
  delegation.update(context.delegationState, { ops: newOps }, context),
77
88
  context
78
89
  ),
90
+ updateVaults(),
79
91
  ]);
80
92
 
81
93
  // Update context
@@ -15,7 +15,10 @@ const verifyItxAddress = require('./pipes/verify-itx-address');
15
15
  const execMintHook = require('./pipes/exec-mint-hook');
16
16
  const extractFactoryTokens = require('./pipes/extract-factory-tokens');
17
17
 
18
- const { applyTokenUpdates } = require('../../util');
18
+ const EnsureTxGas = require('../../pipes/ensure-gas');
19
+ const EnsureTxCost = require('../../pipes/ensure-cost');
20
+
21
+ const { applyTokenUpdates, applyTokenChange } = require('../../util');
19
22
 
20
23
  const verifyTokenBalance = promisify(pipes.VerifyTokenBalance({ ownerKey: 'senders', conditionKey: 'conditions' }));
21
24
 
@@ -79,6 +82,22 @@ runner.use(async (context, next) => {
79
82
  runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'assetOwner' }));
80
83
  runner.use(verifyItxAssets);
81
84
 
85
+ // Ensure tx fee and gas
86
+ runner.use(
87
+ EnsureTxGas((context) => {
88
+ // FIXME: payment check
89
+ const result = { create: 1, update: 2, payment: 0 };
90
+ if (context.assetStates) {
91
+ result.update += context.assetStates.length;
92
+ }
93
+ if (context.delegatorState) {
94
+ result.update += 1;
95
+ }
96
+ return result;
97
+ })
98
+ );
99
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
100
+
82
101
  // update statedb
83
102
  runner.use(
84
103
  async (context, next) => {
@@ -93,6 +112,8 @@ runner.use(
93
112
  factoryTokens,
94
113
  mintedAsset,
95
114
  mintedAddress,
115
+ senderChange,
116
+ updateVaults,
96
117
  assetStates = [],
97
118
  } = context;
98
119
 
@@ -100,7 +121,7 @@ runner.use(
100
121
  const senderUpdates = {
101
122
  nonce: tx.nonce,
102
123
  pk: tx.pk,
103
- ...applyTokenUpdates(factoryTokens, senderState, 'sub'),
124
+ ...applyTokenUpdates(factoryTokens, applyTokenChange(senderState, senderChange), 'sub'),
104
125
  };
105
126
 
106
127
  // Owner updates
@@ -139,6 +160,8 @@ runner.use(
139
160
  context.delegatorState
140
161
  ? statedb.account.update(assetOwner.address, account.update(assetOwner, ownerUpdates, context), context)
141
162
  : null,
163
+
164
+ updateVaults(),
142
165
  ]);
143
166
 
144
167
  context.senderState = newSenderState;
@@ -10,7 +10,7 @@ const { account, asset, factory } = require('@ocap/state');
10
10
  // eslint-disable-next-line global-require
11
11
  const debug = require('debug')(`${require('../../../package.json').name}:acquire-asset-v3`);
12
12
 
13
- const { applyTokenUpdates } = require('../../util');
13
+ const { applyTokenUpdates, applyTokenChange } = require('../../util');
14
14
 
15
15
  // custom pipes
16
16
  const verifyAcquireParams = require('./pipes/verify-acquire-params');
@@ -21,6 +21,9 @@ const verifyItxAddress = require('./pipes/verify-itx-address');
21
21
  const execMintHook = require('./pipes/exec-mint-hook');
22
22
  const extractFactoryTokens = require('./pipes/extract-factory-tokens');
23
23
 
24
+ const EnsureTxGas = require('../../pipes/ensure-gas');
25
+ const EnsureTxCost = require('../../pipes/ensure-cost');
26
+
24
27
  const verifyAssetOwner = promisify(pipes.VerifyUpdater({ assetKey: 'assets', ownerKey: 'owner' }));
25
28
 
26
29
  const runner = new Runner();
@@ -136,6 +139,25 @@ runner.use(
136
139
  runner.use(verifyItxAddress('acquire-v3'));
137
140
  runner.use(verifyItxAssets);
138
141
 
142
+ // Ensure tx fee and gas
143
+ runner.use(
144
+ EnsureTxGas((context) => {
145
+ // FIXME: payment check
146
+ const result = { create: 1, update: 2, payment: 0 };
147
+ if (context.assetStates) {
148
+ result.update += context.assetStates.length;
149
+ }
150
+ if (context.signerStates) {
151
+ result.update += context.signerStates.length;
152
+ }
153
+ if (!context.ownerState) {
154
+ result.create += 1;
155
+ }
156
+ return result;
157
+ })
158
+ );
159
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
160
+
139
161
  // update statedb
140
162
  runner.use(
141
163
  async (context, next) => {
@@ -152,6 +174,8 @@ runner.use(
152
174
  mintedAsset,
153
175
  mintedAddress,
154
176
  signerStates,
177
+ senderChange,
178
+ updateVaults,
155
179
  assetStates = [],
156
180
  } = context;
157
181
 
@@ -163,6 +187,9 @@ runner.use(
163
187
  signerStates.find((s) => s.address === owner),
164
188
  'sub'
165
189
  );
190
+ if (senderChange && owner === senderChange.address) {
191
+ signerUpdates[owner] = applyTokenChange(signerUpdates[owner], senderChange);
192
+ }
166
193
  });
167
194
 
168
195
  const isAlsoSigner = !!signerUpdates[tx.from];
@@ -184,7 +211,10 @@ runner.use(
184
211
  senderState,
185
212
  account.updateOrCreate(
186
213
  senderState,
187
- Object.assign({ address: tx.from, nonce: tx.nonce, pk: tx.pk }, signerUpdates[tx.from] || {}),
214
+ Object.assign(
215
+ { address: tx.from, nonce: tx.nonce, pk: tx.pk },
216
+ signerUpdates[tx.from] || (senderChange ? applyTokenChange(senderState, senderChange) : {})
217
+ ),
188
218
  context
189
219
  ),
190
220
  context
@@ -220,6 +250,8 @@ runner.use(
220
250
  statedb.asset.update(x.address, asset.update(x, { consumedTime: txTime }, context), context)
221
251
  )
222
252
  ),
253
+
254
+ updateVaults(),
223
255
  ]);
224
256
 
225
257
  context.senderState = newSenderState;
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable indent */
2
2
  const { CustomError: Error } = require('@ocap/util/lib/error');
3
- const isEmpty = require('lodash/isEmpty');
4
3
  const cloneDeep = require('lodash/cloneDeep');
5
4
  const { Runner, pipes } = require('@ocap/tx-pipeline');
6
5
  const { account, asset } = require('@ocap/state');
@@ -11,7 +10,9 @@ const { toAssetAddress } = require('@arcblock/did-util');
11
10
  const debug = require('debug')(`${require('../../../package.json').name}:create-asset`);
12
11
 
13
12
  const { decodeAnySafe } = require('../../util');
14
- const ensureServiceFee = require('../rollup/pipes/ensure-service-fee');
13
+
14
+ const EnsureTxGas = require('../../pipes/ensure-gas');
15
+ const EnsureTxCost = require('../../pipes/ensure-cost');
15
16
 
16
17
  const runner = new Runner();
17
18
 
@@ -89,16 +90,17 @@ runner.use(
89
90
  // Ensure parent exist
90
91
  runner.use(pipes.ExtractState({ from: 'itx.parent', to: 'parentAsset', status: 'INVALID_ASSET', table: 'asset' }));
91
92
 
92
- runner.use(ensureServiceFee);
93
+ // Ensure tx fee and gas
94
+ runner.use(EnsureTxGas(() => ({ create: 1, update: 1, payment: 0 })));
95
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
93
96
 
94
97
  // Update asset state
95
98
  runner.use(
96
99
  async (context, next) => {
97
- const { tx, itx, assetData, statedb, senderState, delegatorState, senderUpdates, vaultState, vaultUpdates } =
98
- context;
100
+ const { tx, itx, assetData, statedb, senderState, delegatorState, senderUpdates, updateVaults } = context;
99
101
  const owner = delegatorState ? delegatorState.address : senderState.address;
100
102
 
101
- const [newSenderState, assetState, newVaultState] = await Promise.all([
103
+ const [newSenderState, assetState] = await Promise.all([
102
104
  statedb.account.update(
103
105
  senderState.address,
104
106
  account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
@@ -111,14 +113,11 @@ runner.use(
111
113
  context
112
114
  ),
113
115
 
114
- isEmpty(vaultUpdates)
115
- ? vaultState
116
- : statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
116
+ updateVaults(),
117
117
  ]);
118
118
 
119
119
  context.senderState = newSenderState;
120
120
  context.assetState = assetState;
121
- context.vaultState = newVaultState;
122
121
 
123
122
  debug('createAsset', assetState);
124
123
 
@@ -13,6 +13,9 @@ const verifyItxAddress = require('./pipes/verify-itx-address');
13
13
 
14
14
  const runner = new Runner();
15
15
 
16
+ const EnsureTxGas = require('../../pipes/ensure-gas');
17
+ const EnsureTxCost = require('../../pipes/ensure-cost');
18
+
16
19
  runner.use(pipes.VerifyMultiSig(0));
17
20
  runner.use(verifyAcquireParams('mint'));
18
21
 
@@ -69,6 +72,22 @@ runner.use(verifyItxAddress('mint'));
69
72
  runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'ownerState' }));
70
73
  runner.use(verifyItxAssets);
71
74
 
75
+ // Ensure tx fee and gas
76
+ runner.use(
77
+ EnsureTxGas((context) => {
78
+ const result = { create: 1, update: 2, payment: 0 };
79
+ if (!context.ownerState) {
80
+ result.create += 1;
81
+ }
82
+ if (context.assetStates) {
83
+ result.update += context.assetStates.length;
84
+ }
85
+
86
+ return result;
87
+ })
88
+ );
89
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
90
+
72
91
  // update statedb
73
92
  runner.use(
74
93
  async (context, next) => {
@@ -82,17 +101,22 @@ runner.use(
82
101
  mintedAsset,
83
102
  mintedAddress,
84
103
  ownerState,
104
+ senderUpdates,
85
105
  assetStates = [],
106
+ updateVaults,
86
107
  } = context;
87
108
 
88
109
  const owner = ownerState ? ownerState.address : itx.owner;
89
110
 
90
- const senderUpdates = { nonce: tx.nonce, pk: tx.pk };
91
111
  const factoryUpdates = { numMinted: factoryState.numMinted + 1 };
92
112
 
93
113
  const [newSenderState, newReceiverState, assetState, newFactoryState, newAssetStates] = await Promise.all([
94
114
  // update sender
95
- statedb.account.update(senderState.address, account.update(senderState, senderUpdates, context), context),
115
+ statedb.account.update(
116
+ senderState.address,
117
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
118
+ context
119
+ ),
96
120
 
97
121
  // create receiver if not exist: asset owner
98
122
  ownerState
@@ -115,6 +139,8 @@ runner.use(
115
139
  statedb.asset.update(x.address, asset.update(x, { consumedTime: txTime }, context), context)
116
140
  )
117
141
  ),
142
+
143
+ updateVaults(),
118
144
  ]);
119
145
 
120
146
  context.senderState = newSenderState;
@@ -15,7 +15,7 @@ module.exports = async (context, next) => {
15
15
  if (factoryState.settlement === 'instant') {
16
16
  const mintHook = factoryState.hooks.find((x) => x.name === 'mint');
17
17
  if (mintHook && mintHook.type === 'contract') {
18
- context.updatedAccounts = [];
18
+ context.updatedAccounts = context.updatedAccounts || [];
19
19
 
20
20
  // These calls must be run sequentially, because different calls may update same account
21
21
  const clone = cloneDeep(mintHook.compiled);
@@ -9,6 +9,9 @@ const debug = require('debug')(`${require('../../../package.json').name}:update-
9
9
 
10
10
  const { decodeAnySafe } = require('../../util');
11
11
 
12
+ const EnsureTxGas = require('../../pipes/ensure-gas');
13
+ const EnsureTxCost = require('../../pipes/ensure-cost');
14
+
12
15
  const runner = new Runner();
13
16
 
14
17
  runner.use(pipes.VerifyMultiSig(0));
@@ -74,16 +77,20 @@ runner.use(async (context, next) => {
74
77
  return next();
75
78
  });
76
79
 
80
+ // Ensure tx fee and gas
81
+ runner.use(EnsureTxGas(() => ({ create: 0, update: 2, payment: 0 })));
82
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
83
+
77
84
  // Update asset state
78
85
  runner.use(
79
86
  async (context, next) => {
80
- const { tx, itx, newData, statedb, senderState, assetState } = context;
87
+ const { tx, itx, newData, statedb, senderState, senderUpdates, assetState, updateVaults } = context;
81
88
 
82
89
  const [newSenderState, newAssetState] = await Promise.all([
83
90
  // update owner state
84
91
  statedb.account.update(
85
92
  senderState.address,
86
- account.update(senderState, { nonce: tx.nonce, pk: tx.pk }, context),
93
+ account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
87
94
  context
88
95
  ),
89
96
 
@@ -97,6 +104,8 @@ runner.use(
97
104
  ),
98
105
  context
99
106
  ),
107
+
108
+ updateVaults(),
100
109
  ]);
101
110
 
102
111
  context.senderState = newSenderState;