@ocap/tx-protocols 1.13.63 → 1.13.67

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 (31) hide show
  1. package/lib/execute.js +1 -1
  2. package/lib/protocols/account/declare.js +2 -1
  3. package/lib/protocols/account/delegate.js +23 -6
  4. package/lib/protocols/account/migrate.js +4 -3
  5. package/lib/protocols/account/revoke-delegate.js +4 -4
  6. package/lib/protocols/asset/acquire-v2.js +3 -2
  7. package/lib/protocols/asset/acquire-v3.js +47 -37
  8. package/lib/protocols/asset/create.js +22 -9
  9. package/lib/protocols/asset/mint.js +20 -9
  10. package/lib/protocols/asset/pipes/verify-itx-address.js +3 -3
  11. package/lib/protocols/asset/pipes/verify-mint-limit.js +1 -1
  12. package/lib/protocols/asset/update.js +7 -2
  13. package/lib/protocols/factory/create.js +21 -17
  14. package/lib/protocols/governance/claim-stake.js +6 -5
  15. package/lib/protocols/governance/revoke-stake.js +2 -2
  16. package/lib/protocols/governance/stake.js +3 -2
  17. package/lib/protocols/rollup/claim-reward.js +4 -3
  18. package/lib/protocols/rollup/create-block.js +8 -7
  19. package/lib/protocols/rollup/create.js +18 -5
  20. package/lib/protocols/rollup/join.js +4 -3
  21. package/lib/protocols/rollup/leave.js +4 -3
  22. package/lib/protocols/rollup/pipes/ensure-tx-fee.js +38 -0
  23. package/lib/protocols/rollup/update.js +2 -1
  24. package/lib/protocols/token/create.js +22 -11
  25. package/lib/protocols/token/deposit-v2.js +26 -20
  26. package/lib/protocols/token/withdraw-v2.js +6 -5
  27. package/lib/protocols/trade/exchange-v2.js +6 -6
  28. package/lib/protocols/trade/transfer-v2.js +21 -13
  29. package/lib/protocols/trade/transfer-v3.js +27 -13
  30. package/lib/util.js +10 -10
  31. package/package.json +13 -12
@@ -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
 
@@ -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
 
@@ -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
@@ -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 }, 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
@@ -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
  ]);