@ocap/tx-protocols 1.18.33 → 1.18.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -25,11 +25,11 @@ const updateRollup = require('./protocols/rollup/update');
25
25
  const joinRollup = require('./protocols/rollup/join');
26
26
  const leaveRollup = require('./protocols/rollup/leave');
27
27
  const pauseRollup = require('./protocols/rollup/pause');
28
+ const closeRollup = require('./protocols/rollup/close');
28
29
  const resumeRollup = require('./protocols/rollup/resume');
29
30
  const createRollupBlock = require('./protocols/rollup/create-block');
30
31
  const claimBlockReward = require('./protocols/rollup/claim-reward');
31
- const migrateRollupContract = require('./protocols/rollup/migrate-contract');
32
- const migrateRollupToken = require('./protocols/rollup/migrate-token');
32
+ const migrateRollup = require('./protocols/rollup/migrate');
33
33
 
34
34
  const executor = require('./execute');
35
35
 
@@ -74,11 +74,11 @@ const createExecutor = ({ filter, runAsLambda }) => {
74
74
  joinRollup,
75
75
  leaveRollup,
76
76
  pauseRollup,
77
+ closeRollup,
77
78
  resumeRollup,
78
79
  createRollupBlock,
79
80
  claimBlockReward,
80
- migrateRollupContract,
81
- migrateRollupToken,
81
+ migrateRollup,
82
82
  };
83
83
 
84
84
  const execute = executor({ filter, runAsLambda });
@@ -31,10 +31,8 @@ runner.use(pipes.VerifyMultiSig(0));
31
31
  const schema = Joi.object({
32
32
  factory: Joi.DID().prefix().role('ROLE_FACTORY').required(),
33
33
  address: Joi.DID().prefix().role('ROLE_ASSET').required(),
34
- assetsList: Joi.array()
35
- .items(Joi.alternatives().try(Joi.DID().prefix().role('ROLE_ASSET'), Joi.DID().prefix().role('ROLE_FACTORY')))
36
- .default([]),
37
- variablesList: Joi.array().items(schemas.variableInput).min(1).required(),
34
+ assetsList: Joi.array().items(Joi.DID().prefix().role('ROLE_ASSET')).default([]),
35
+ variablesList: Joi.array().items(schemas.variableInput).optional().default([]),
38
36
  issuer: schemas.nftIssuer.required(),
39
37
  data: Joi.any().optional(),
40
38
  }).options({ stripUnknown: true, noDefaults: false });
@@ -34,8 +34,8 @@ const schema = Joi.object({
34
34
  factory: Joi.DID().prefix().role('ROLE_FACTORY').required(),
35
35
  address: Joi.DID().prefix().role('ROLE_ASSET').required(),
36
36
  inputsList: schemas.multiInput.min(1).required(),
37
- owner: Joi.DID().prefix().required(),
38
- variablesList: Joi.array().items(schemas.variableInput).min(1).required(),
37
+ owner: schemas.tokenHolder.required(),
38
+ variablesList: Joi.array().items(schemas.variableInput).optional().default([]),
39
39
  issuer: schemas.nftIssuer.required(),
40
40
  data: Joi.any().optional(),
41
41
  }).options({ stripUnknown: true, noDefaults: false });
@@ -23,11 +23,9 @@ runner.use(pipes.VerifyMultiSig(0));
23
23
  const schema = Joi.object({
24
24
  factory: Joi.DID().prefix().role('ROLE_FACTORY').required(),
25
25
  address: Joi.DID().prefix().role('ROLE_ASSET').required(),
26
- assetsList: Joi.array()
27
- .items(Joi.alternatives().try(Joi.DID().prefix().role('ROLE_ASSET'), Joi.DID().prefix().role('ROLE_FACTORY')))
28
- .default([]),
29
- variablesList: Joi.array().items(schemas.variableInput).min(1).required(),
30
- owner: Joi.DID().prefix().required(),
26
+ assetsList: Joi.array().items(Joi.DID().prefix().role('ROLE_ASSET')).default([]),
27
+ variablesList: Joi.array().items(schemas.variableInput).optional().default([]),
28
+ owner: schemas.tokenHolder.required(),
31
29
  data: Joi.any().optional(),
32
30
  }).options({ stripUnknown: true, noDefaults: false });
33
31
  runner.use(({ itx }, next) => {
@@ -12,6 +12,8 @@ const debug = require('debug')(`${require('../../../package.json').name}:claim-b
12
12
 
13
13
  const { toStakeAddress } = require('@arcblock/did-util');
14
14
  const VerifySigners = require('./pipes/verify-signers');
15
+ const VerifyStatus = require('./pipes/verify-status');
16
+ const EnsureValidator = require('./pipes/ensure-validator');
15
17
  const EnsureTxGas = require('../../pipes/ensure-gas');
16
18
  const EnsureTxCost = require('../../pipes/ensure-cost');
17
19
  const { applyTokenChange, splitTxFee, getBNSum, getRewardLocker, RATE_BASE } = require('../../util');
@@ -58,6 +60,8 @@ runner.use(({ evidenceSeen, blockClaimed }, next) => {
58
60
 
59
61
  // 3. verify rollup and block state
60
62
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
63
+ runner.use(EnsureValidator());
64
+ runner.use(VerifyStatus({ closed: false }));
61
65
  runner.use(pipes.ExtractState({ from: 'itx.blockHash', to: 'blockState', status: 'INVALID_ROLLUP_BLOCK', table: 'rollupBlock' })); // prettier-ignore
62
66
  runner.use((context, next) => {
63
67
  const { blockState, rollupState, itx } = context;
@@ -1,9 +1,11 @@
1
1
  const { CustomError: Error } = require('@ocap/util/lib/error');
2
2
  const { Joi } = require('@arcblock/validator');
3
3
  const { Runner, pipes } = require('@ocap/tx-pipeline');
4
- const { account } = require('@ocap/state');
4
+ const { account, rollup, stake } = require('@ocap/state');
5
5
 
6
+ const { toStakeAddress } = require('@arcblock/did-util');
6
7
  const VerifySigners = require('./pipes/verify-signers');
8
+ const VerifyStatus = require('./pipes/verify-status');
7
9
  const EnsureValidator = require('./pipes/ensure-validator');
8
10
  const EnsureTxGas = require('../../pipes/ensure-gas');
9
11
  const EnsureTxCost = require('../../pipes/ensure-cost');
@@ -13,8 +15,7 @@ const runner = new Runner();
13
15
  // 1. verify itx
14
16
  const schema = Joi.object({
15
17
  rollup: Joi.DID().prefix().role('ROLE_ROLLUP').required(),
16
- from: Joi.DID().prefix().wallet('ethereum').required(),
17
- to: Joi.DID().prefix().wallet('ethereum').required(),
18
+ message: Joi.string().trim().min(1).max(512).required(),
18
19
  token: Joi.object({
19
20
  address: Joi.DID().prefix().wallet('ethereum').required(),
20
21
  value: Joi.BN().positive().required(),
@@ -29,16 +30,7 @@ runner.use(({ itx }, next) => {
29
30
  // 2. verify rollup
30
31
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
31
32
  runner.use(EnsureValidator(true));
32
- runner.use(({ itx, rollupState }, next) => {
33
- if (rollupState.migrateHistory.includes(itx.from) === false) {
34
- return next(new Error('INVALID_MIGRATE_ATTEMPT', 'itx.from must exist in rollup migrate history'));
35
- }
36
- if (itx.to !== rollupState.contractAddress) {
37
- return next(new Error('INVALID_MIGRATE_ATTEMPT', 'itx.to must equal to latest rollup contract address'));
38
- }
39
-
40
- return next();
41
- });
33
+ runner.use(VerifyStatus({ paused: true, closed: false }));
42
34
 
43
35
  // 3. verify tx signers and signatures
44
36
  runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
@@ -49,25 +41,51 @@ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INV
49
41
  runner.use(pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
50
42
  runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
51
43
 
52
- // Ensure tx fee and gas
53
- runner.use(EnsureTxGas(() => ({ create: 0, update: 1, payment: 0 })));
44
+ // 5. extract validator stake stakes
45
+ runner.use((context, next) => {
46
+ const { validators, address } = context.rollupState;
47
+ context.stakes = validators.map((x) => toStakeAddress(x.address, address));
48
+ return next();
49
+ });
50
+ runner.use(pipes.ExtractState({ from: 'stakes', to: 'stakeStates', status: 'INVALID_STAKE_STATE', table: 'stake' }));
51
+
52
+ // 6. Ensure tx fee and gas
53
+ runner.use(EnsureTxGas((context) => ({ create: 0, update: context.stakeStates.length + 2, payment: 0 })));
54
54
  runner.use(EnsureTxCost({ attachSenderChanges: true }));
55
55
 
56
- // 5. update state
56
+ // 7. update rollup state
57
57
  runner.use(
58
58
  async (context, next) => {
59
- const { tx, statedb, senderState, senderUpdates, updateVaults } = context;
59
+ const { tx, itx, rollupState, statedb, senderState, stakeStates, senderUpdates, updateVaults } = context;
60
60
 
61
- const [newSenderState] = await Promise.all([
61
+ const [newSenderState, newRollupState, newStakeStates] = await Promise.all([
62
+ // update sender state
62
63
  statedb.account.update(
63
64
  senderState.address,
64
65
  account.update(senderState, { nonce: tx.nonce, ...senderUpdates }, context),
65
66
  context
66
67
  ),
68
+
69
+ // update rollup
70
+ statedb.rollup.update(itx.rollup, rollup.close(rollupState, context), context),
71
+
72
+ // update stake states
73
+ Promise.all(
74
+ stakeStates.map((x) =>
75
+ statedb.stake.update(
76
+ x.address,
77
+ stake.update(x, { revocable: true, leaveWaitingPeriod: rollupState.leaveWaitingPeriod }, context),
78
+ context
79
+ )
80
+ )
81
+ ),
82
+
67
83
  updateVaults(),
68
84
  ]);
69
85
 
70
86
  context.senderState = newSenderState;
87
+ context.rollupState = newRollupState;
88
+ context.stakeStates = newStakeStates;
71
89
 
72
90
  next();
73
91
  },
@@ -14,7 +14,8 @@ const debug = require('debug')(`${require('../../../package.json').name}:create-
14
14
 
15
15
  const VerifySigners = require('./pipes/verify-signers');
16
16
  const VerifyEvidence = require('./pipes/verify-evidence');
17
- const VerifyPaused = require('./pipes/verify-paused');
17
+ const VerifyStatus = require('./pipes/verify-status');
18
+ const EnsureValidator = require('./pipes/ensure-validator');
18
19
  const EnsureTxGas = require('../../pipes/ensure-gas');
19
20
  const EnsureTxCost = require('../../pipes/ensure-cost');
20
21
 
@@ -38,6 +39,7 @@ const schema = Joi.object({
38
39
  signaturesList: schemas.multiSig.min(1).required(),
39
40
  rollup: Joi.DID().prefix().role('ROLE_ROLLUP').required(),
40
41
  minReward: Joi.BN().min(0).required(),
42
+ governance: Joi.boolean().default(false),
41
43
  data: Joi.any().optional(),
42
44
  }).options({ stripUnknown: true, noDefaults: false });
43
45
 
@@ -81,31 +83,43 @@ runner.use((context, next) => {
81
83
 
82
84
  // 2. verify rollup state & height
83
85
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
84
- runner.use(VerifyPaused());
86
+ runner.use(EnsureValidator());
87
+ runner.use(VerifyStatus({ closed: false }));
85
88
  runner.use((context, next) => {
86
89
  const { itx, rollupState } = context;
90
+
87
91
  if (itx.height !== rollupState.blockHeight + 1) {
88
92
  return next(
89
93
  new Error('INVALID_TX', `Expect new block height to be ${rollupState.blockHeight + 1}, got ${itx.height}`)
90
94
  );
91
95
  }
92
96
 
93
- // ensure proposer in validator list
94
- if (rollupState.validators.some((x) => x.address === itx.proposer) === false) {
95
- return next(new Error('INVALID_TX', 'itx.proposer does not exist in validators'));
96
- }
97
+ if (itx.governance) {
98
+ // ensure minReward for governance block
99
+ if (itx.minReward !== '0') {
100
+ return next(new Error('INVALID_TX', 'itx.minReward should be 0 for governance block'));
101
+ }
102
+ // ensure blockSize for governance block
103
+ if (itx.txsList.length !== 1) {
104
+ return next(new Error('INVALID_TX', 'itx.txs should contain only 1 tx for governance block'));
105
+ }
106
+ } else {
107
+ if (rollupState.paused) {
108
+ return next(new Error('INVALID_TX', 'Normal block is only allowed when rollup is not paused'));
109
+ }
97
110
 
98
- // ensure block size
99
- const blockSize = itx.txsList.length;
100
- if (blockSize < rollupState.minBlockSize) {
101
- return next(
102
- new Error('INVALID_TX', `Expect at least ${rollupState.minBlockSize} tx in the block, got ${blockSize}`)
103
- );
104
- }
105
- if (blockSize > rollupState.maxBlockSize) {
106
- return next(
107
- new Error('INVALID_TX', `Expect at most ${rollupState.maxBlockSize} tx in the block, got ${blockSize}`)
108
- );
111
+ // ensure block size
112
+ const blockSize = itx.txsList.length;
113
+ if (blockSize < rollupState.minBlockSize) {
114
+ return next(
115
+ new Error('INVALID_TX', `Expect at least ${rollupState.minBlockSize} tx in the block, got ${blockSize}`)
116
+ );
117
+ }
118
+ if (blockSize > rollupState.maxBlockSize) {
119
+ return next(
120
+ new Error('INVALID_TX', `Expect at most ${rollupState.maxBlockSize} tx in the block, got ${blockSize}`)
121
+ );
122
+ }
109
123
  }
110
124
 
111
125
  return next();
@@ -146,13 +160,18 @@ runner.use((context, next) => {
146
160
  );
147
161
  }
148
162
 
149
- // ensure minBlockInterval
150
- const minBlockTime = +new Date(previousBlock.context.genesisTime) + rollupState.minBlockInterval * 1000;
151
- const newBlockTime = +new Date(txTime);
152
- if (newBlockTime < minBlockTime) {
153
- return next(
154
- new Error('INVALID_TX', `Block does not comply with minBlockInterval: ${new Date(minBlockTime).toISOString()}`)
155
- );
163
+ // ensure minBlockInterval for normal blocks
164
+ if (itx.governance === false) {
165
+ const minBlockTime = +new Date(previousBlock.context.genesisTime) + rollupState.minBlockInterval * 1000;
166
+ const newBlockTime = +new Date(txTime);
167
+ if (newBlockTime < minBlockTime) {
168
+ return next(
169
+ new Error(
170
+ 'INVALID_TX',
171
+ `Block does not comply with minBlockInterval: ${new Date(minBlockTime).toISOString()}`
172
+ )
173
+ );
174
+ }
156
175
  }
157
176
  }
158
177
 
@@ -177,6 +196,9 @@ runner.use(
177
196
  // 8. verify txs
178
197
  runner.use(pipes.ExtractState({ from: 'itx.txsList', to: 'txStates', status: 'INVALID_TX', table: 'tx' }));
179
198
  runner.use(({ itx, txStates }, next) => {
199
+ const normalTxTypes = ['deposit_token_v2', 'withdraw_token_v2'];
200
+ const governanceTxTypes = ['pause_rollup', 'resume_rollup', 'migrate_rollup'];
201
+
180
202
  // ensure not finalized
181
203
  const finalizedTx = txStates.some((x) => x.finalized);
182
204
  if (finalizedTx) {
@@ -189,10 +211,19 @@ runner.use(({ itx, txStates }, next) => {
189
211
  return next(new Error('INVALID_TX', 'Rollup block can only include successful executed transactions'));
190
212
  }
191
213
 
192
- // ensure tx type
193
- const invalidTypeTx = txStates.some((x) => ['deposit_token_v2', 'withdraw_token_v2'].includes(x.type) === false);
194
- if (invalidTypeTx) {
195
- return next(new Error('INVALID_TX', "Rollup block can only include 'withdraw_token' and 'deposit_token' tx"));
214
+ // ensure governance block only contains governance tx
215
+ if (itx.governance) {
216
+ if (txStates.some((x) => governanceTxTypes.includes(x.type) === false)) {
217
+ return next(
218
+ new Error('INVALID_TX', `Governance block can only include governance txs: ${governanceTxTypes.join(',')}`)
219
+ );
220
+ }
221
+ } else {
222
+ // ensure tx type for normal block
223
+ const invalidTypeTx = txStates.some((x) => normalTxTypes.includes(x.type) === false);
224
+ if (invalidTypeTx) {
225
+ return next(new Error('INVALID_TX', `Rollup block can only include normal txs: ${normalTxTypes.join(',')}`));
226
+ }
196
227
  }
197
228
 
198
229
  // ensure rollup belonging
@@ -201,15 +232,25 @@ runner.use(({ itx, txStates }, next) => {
201
232
  return next(new Error('INVALID_TX', `Rollup block can only include tx from rollup ${itx.rollup}`));
202
233
  }
203
234
 
204
- // ensure merkleRoot
235
+ // ensure merkleRoot: deposit_token tx not included in the merkleTree
205
236
  const merkleTree = MerkleTree.getBlockMerkleTree(
206
237
  txStates
207
- .filter((x) => x.type === 'withdraw_token_v2')
208
- .map((x) => ({
209
- hash: x.hash,
210
- to: x.tx.itxJson.to,
211
- amount: x.tx.itxJson.token.value,
212
- }))
238
+ .map((x) => {
239
+ if (x.type === 'withdraw_token_v2') {
240
+ return { hash: x.hash, to: x.tx.itxJson.to, amount: x.tx.itxJson.token.value };
241
+ }
242
+ if (x.type === 'pause_rollup') {
243
+ return { hash: x.hash, to: MerkleTree.BLACK_HOLE, amount: '0' };
244
+ }
245
+ if (x.type === 'resume_rollup') {
246
+ return { hash: x.hash, to: MerkleTree.BLACK_HOLE, amount: '0' };
247
+ }
248
+ if (x.type === 'migrate_rollup') {
249
+ return { hash: x.hash, to: x.tx.itxJson.to, amount: '0' };
250
+ }
251
+ return null;
252
+ })
253
+ .filter(Boolean)
213
254
  );
214
255
  if (merkleTree.getHexRoot() !== itx.merkleRoot) {
215
256
  return next(new Error('INVALID_TX', 'Invalid rollup block merkle root'));
@@ -144,7 +144,7 @@ runner.use(
144
144
  ]);
145
145
 
146
146
  // FIXME: create-rollup 的时候不能校验 stake,还是在这里创建 stake?如果在这里创建 stake,是否需要多签?
147
- // FIXME: seed validator 无法离开, account migration 也需要测试下
147
+ // FIXME: account migration 也需要测试下
148
148
 
149
149
  context.senderState = newSenderState;
150
150
  context.rollupState = rollupState;
@@ -14,7 +14,7 @@ const debug = require('debug')(`${require('../../../package.json').name}:join-ro
14
14
 
15
15
  const VerifySigners = require('./pipes/verify-signers');
16
16
  const VerifyEvidence = require('./pipes/verify-evidence');
17
- const VerifyPaused = require('./pipes/verify-paused');
17
+ const VerifyStatus = require('./pipes/verify-status');
18
18
  const EnsureTxGas = require('../../pipes/ensure-gas');
19
19
  const EnsureTxCost = require('../../pipes/ensure-cost');
20
20
 
@@ -39,7 +39,7 @@ runner.use(({ itx }, next) => {
39
39
 
40
40
  // 2. verify rollup
41
41
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
42
- runner.use(VerifyPaused());
42
+ runner.use(VerifyStatus({ paused: false }));
43
43
 
44
44
  // 3. verify new validator
45
45
  runner.use((context, next) => {
@@ -11,7 +11,8 @@ const debug = require('debug')(`${require('../../../package.json').name}:leave-r
11
11
 
12
12
  const VerifySigners = require('./pipes/verify-signers');
13
13
  const VerifyEvidence = require('./pipes/verify-evidence');
14
- const VerifyPaused = require('./pipes/verify-paused');
14
+ const VerifyStatus = require('./pipes/verify-status');
15
+ const EnsureValidator = require('./pipes/ensure-validator');
15
16
  const EnsureTxGas = require('../../pipes/ensure-gas');
16
17
  const EnsureTxCost = require('../../pipes/ensure-cost');
17
18
 
@@ -33,7 +34,8 @@ runner.use(({ itx }, next) => {
33
34
 
34
35
  // 2. verify rollup
35
36
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
36
- runner.use(VerifyPaused());
37
+ runner.use(EnsureValidator());
38
+ runner.use(VerifyStatus({ paused: false }));
37
39
 
38
40
  // 3. verify evidence signers, signatures, state
39
41
  runner.use((context, next) => {
@@ -4,6 +4,7 @@ const { Runner, pipes } = require('@ocap/tx-pipeline');
4
4
  const { account, rollup } = require('@ocap/state');
5
5
 
6
6
  const VerifySigners = require('./pipes/verify-signers');
7
+ const VerifyStatus = require('./pipes/verify-status');
7
8
  const EnsureValidator = require('./pipes/ensure-validator');
8
9
  const EnsureTxGas = require('../../pipes/ensure-gas');
9
10
  const EnsureTxCost = require('../../pipes/ensure-cost');
@@ -14,6 +15,8 @@ const runner = new Runner();
14
15
  const schema = Joi.object({
15
16
  rollup: Joi.DID().prefix().role('ROLE_ROLLUP').required(),
16
17
  to: Joi.DID().prefix().wallet('ethereum').required(),
18
+ type: Joi.string().trim().valid('vault', 'contract').required(),
19
+ message: Joi.string().trim().min(1).max(512).required(),
17
20
  data: Joi.any().optional(),
18
21
  }).options({ stripUnknown: true, noDefaults: false });
19
22
  runner.use(({ itx }, next) => {
@@ -24,6 +27,7 @@ runner.use(({ itx }, next) => {
24
27
  // 2. verify rollup
25
28
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
26
29
  runner.use(EnsureValidator(true));
30
+ runner.use(VerifyStatus({ paused: true, closed: false }));
27
31
 
28
32
  // 3. verify tx signers and signatures
29
33
  runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
@@ -38,10 +42,17 @@ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey:
38
42
  runner.use(EnsureTxGas(() => ({ create: 0, update: 2, payment: 0 })));
39
43
  runner.use(EnsureTxCost({ attachSenderChanges: true }));
40
44
 
41
- // 5. update rollup state
45
+ // 5. update state
42
46
  runner.use(
43
47
  async (context, next) => {
44
- const { tx, itx, rollupState, statedb, senderState, senderUpdates, updateVaults } = context;
48
+ const { tx, itx, statedb, rollupState, senderState, senderUpdates, updateVaults } = context;
49
+ let updates = {};
50
+ if (itx.type === 'vault') {
51
+ updates = rollup.migrateVault(rollupState, itx.to, context);
52
+ }
53
+ if (itx.type === 'contract') {
54
+ updates = rollup.migrateContract(rollupState, itx.to, context);
55
+ }
45
56
 
46
57
  const [newSenderState, newRollupState] = await Promise.all([
47
58
  statedb.account.update(
@@ -49,7 +60,7 @@ runner.use(
49
60
  account.update(senderState, { nonce: tx.nonce, ...senderUpdates }, context),
50
61
  context
51
62
  ),
52
- statedb.rollup.update(itx.rollup, rollup.migrate(rollupState, itx.to, context), context),
63
+ statedb.rollup.update(itx.rollup, updates, context),
53
64
  updateVaults(),
54
65
  ]);
55
66
 
@@ -4,6 +4,7 @@ const { Runner, pipes } = require('@ocap/tx-pipeline');
4
4
  const { account, rollup } = require('@ocap/state');
5
5
 
6
6
  const VerifySigners = require('./pipes/verify-signers');
7
+ const VerifyStatus = require('./pipes/verify-status');
7
8
  const EnsureValidator = require('./pipes/ensure-validator');
8
9
  const EnsureTxGas = require('../../pipes/ensure-gas');
9
10
  const EnsureTxCost = require('../../pipes/ensure-cost');
@@ -23,6 +24,7 @@ runner.use(({ itx }, next) => {
23
24
  // 2. verify rollup
24
25
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
25
26
  runner.use(EnsureValidator(true));
27
+ runner.use(VerifyStatus({ paused: false }));
26
28
 
27
29
  // 3. verify tx signers and signatures
28
30
  runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
@@ -1,4 +1,6 @@
1
- module.exports = (isSeed) => async (context, next) => {
1
+ const { CustomError: Error } = require('@ocap/util/lib/error');
2
+
3
+ module.exports = (isSeed) => (context, next) => {
2
4
  const { rollupState, tx } = context;
3
5
 
4
6
  const validators = isSeed ? rollupState.seedValidators : rollupState.validators;
@@ -0,0 +1,30 @@
1
+ const { pipes } = require('@ocap/tx-pipeline');
2
+
3
+ module.exports = ({ paused, closed } = {}) => {
4
+ const fns = [];
5
+ if (paused === false) {
6
+ fns.push({
7
+ error: 'INVALID_TX',
8
+ message: 'The rollup is paused',
9
+ fn: (context) => !context.rollupState.paused,
10
+ });
11
+ }
12
+
13
+ if (paused === true) {
14
+ fns.push({
15
+ error: 'INVALID_TX',
16
+ message: 'The rollup is not paused',
17
+ fn: (context) => context.rollupState.paused,
18
+ });
19
+ }
20
+
21
+ if (closed === false) {
22
+ fns.push({
23
+ error: 'INVALID_TX',
24
+ message: 'The rollup is closed',
25
+ fn: (context) => !context.rollupState.closed,
26
+ });
27
+ }
28
+
29
+ return pipes.VerifyInfo(fns);
30
+ };
@@ -4,6 +4,7 @@ const { Runner, pipes } = require('@ocap/tx-pipeline');
4
4
  const { account, rollup } = require('@ocap/state');
5
5
 
6
6
  const VerifySigners = require('./pipes/verify-signers');
7
+ const VerifyStatus = require('./pipes/verify-status');
7
8
  const EnsureValidator = require('./pipes/ensure-validator');
8
9
  const EnsureTxGas = require('../../pipes/ensure-gas');
9
10
  const EnsureTxCost = require('../../pipes/ensure-cost');
@@ -23,6 +24,7 @@ runner.use(({ itx }, next) => {
23
24
  // 2. verify rollup
24
25
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
25
26
  runner.use(EnsureValidator(true));
27
+ runner.use(VerifyStatus({ paused: true, closed: false }));
26
28
 
27
29
  // 3. verify tx signers and signatures
28
30
  runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
@@ -7,6 +7,8 @@ const { account, rollup } = require('@ocap/state');
7
7
  const debug = require('debug')(`${require('../../../package.json').name}:update-rollup`);
8
8
 
9
9
  const VerifySigners = require('./pipes/verify-signers');
10
+ const VerifyStatus = require('./pipes/verify-status');
11
+ const EnsureValidator = require('./pipes/ensure-validator');
10
12
  const EnsureTxGas = require('../../pipes/ensure-gas');
11
13
  const EnsureTxCost = require('../../pipes/ensure-cost');
12
14
 
@@ -62,6 +64,8 @@ runner.use(
62
64
 
63
65
  // 2. verify rollup
64
66
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
67
+ runner.use(EnsureValidator(true));
68
+ runner.use(VerifyStatus({ paused: false }));
65
69
 
66
70
  // 3. verify tx signers and signatures
67
71
  runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
@@ -14,7 +14,7 @@ const EnsureTxGas = require('../../pipes/ensure-gas');
14
14
  const EnsureTxCost = require('../../pipes/ensure-cost');
15
15
 
16
16
  const VerifySigners = require('../rollup/pipes/verify-signers');
17
- const VerifyPaused = require('../rollup/pipes/verify-paused');
17
+ const VerifyStatus = require('../rollup/pipes/verify-status');
18
18
  const { applyTokenUpdates, applyTokenChange, getTxFee, getBNSum, getRewardLocker } = require('../../util');
19
19
 
20
20
  const schema = Joi.object({
@@ -58,7 +58,7 @@ runner.use(({ tx, itx }, next) => {
58
58
 
59
59
  // 2. verify rollup against itx
60
60
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
61
- runner.use(VerifyPaused());
61
+ runner.use(VerifyStatus({ paused: false }));
62
62
  runner.use((context, next) => {
63
63
  const { itx, rollupState } = context;
64
64
  if (rollupState.tokenAddress !== itx.token.address) {
@@ -12,8 +12,8 @@ const debug = require('debug')(`${require('../../../package.json').name}:withdra
12
12
 
13
13
  const EnsureTxGas = require('../../pipes/ensure-gas');
14
14
  const EnsureTxCost = require('../../pipes/ensure-cost');
15
+ const VerifyStatus = require('../rollup/pipes/verify-status');
15
16
 
16
- const VerifyPaused = require('../rollup/pipes/verify-paused');
17
17
  const { applyTokenUpdates, getTxFee, getBNSum, getRewardLocker, applyTokenChange } = require('../../util');
18
18
 
19
19
  const verifyMultiSigV2 = pipes.VerifyMultiSigV2({ signersKey: 'signers' });
@@ -46,7 +46,7 @@ runner.use(({ tx, itx }, next) => {
46
46
 
47
47
  // 2. verify rollup against itx
48
48
  runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
49
- runner.use(VerifyPaused());
49
+ runner.use(VerifyStatus({ paused: false }));
50
50
  runner.use((context, next) => {
51
51
  const { itx, rollupState } = context;
52
52
  if (rollupState.tokenAddress !== itx.token.address) {
@@ -24,7 +24,7 @@ const exchangeInfoSchema = Joi.object({
24
24
  assetsList: Joi.array().items(Joi.DID().prefix().role('ROLE_ASSET')).default([]),
25
25
  });
26
26
  const schema = Joi.object({
27
- to: Joi.DID().prefix().role('ROLE_ACCOUNT').required(),
27
+ to: schemas.tokenHolder.required(),
28
28
  sender: exchangeInfoSchema.required(),
29
29
  receiver: exchangeInfoSchema.required(),
30
30
  data: Joi.any().optional(),
@@ -20,7 +20,7 @@ runner.use(pipes.VerifyMultiSig(0));
20
20
 
21
21
  // 1. verify itx
22
22
  const schema = Joi.object({
23
- to: Joi.DID().prefix().role('ROLE_ACCOUNT').required(),
23
+ to: schemas.tokenHolder.required(),
24
24
  value: Joi.any().optional(),
25
25
  tokensList: Joi.array().items(schemas.tokenInput).default([]),
26
26
  assetsList: Joi.array().items(Joi.DID().prefix().role('ROLE_ASSET')).default([]),
package/lib/util.js CHANGED
@@ -184,6 +184,7 @@ const splitTxFee = ({ total, shares = {}, stringify = true }) => {
184
184
 
185
185
  const getRewardLocker = (rollupAddress) => toStakeAddress(rollupAddress, rollupAddress);
186
186
  const getBNSum = (...args) => flattenDeep(args).reduce((sum, x) => sum.add(new BN(x)), new BN(0)).toString(10); // prettier-ignore
187
+ const isGovernanceTx = (x) => ['pause_rollup', 'resume_rollup', 'migrate_rollup'].includes(x.type);
187
188
  const isFixedFee = (x) => !x.tx.itxJson.maxFee || new BN(x.tx.itxJson.maxFee).isZero();
188
189
 
189
190
  const ensureBlockReward = (rollupState, minReward, txStates) => {
@@ -207,10 +208,10 @@ const ensureBlockReward = (rollupState, minReward, txStates) => {
207
208
  }
208
209
 
209
210
  // 1. find dynamic reward tx
210
- const dynamicFeeTxs = txStates.filter((x) => isFixedFee(x) === false);
211
+ const dynamicFeeTxs = txStates.filter((x) => isGovernanceTx(x) === false && isFixedFee(x) === false);
211
212
  const totalDynamicFee = dynamicFeeTxs.reduce((sum, x) => sum.add(new BN(x.tx.itxJson.maxFee)), new BN(0));
212
213
 
213
- const fixedFeeTxs = txStates.filter((x) => isFixedFee(x));
214
+ const fixedFeeTxs = txStates.filter((x) => isGovernanceTx(x) === false && isFixedFee(x));
214
215
  const totalFixedFee = fixedFeeTxs.reduce((sum, x) => sum.add(new BN(x.tx.itxJson.actualFee)), new BN(0));
215
216
 
216
217
  const totalMissingFee = minRequiredReward.sub(totalFixedFee);
@@ -270,33 +271,35 @@ const ensureBlockReward = (rollupState, minReward, txStates) => {
270
271
  });
271
272
 
272
273
  // 3. bur/mint tokens, update stakes
273
- txStates.forEach((x) => {
274
- const user = x.tx.itxJson.token.value;
275
- const fee = x.tx.itxJson.actualFee;
276
- const total = getBNSum(user, fee);
277
-
278
- if (x.type === 'deposit_token_v2') {
279
- result.rewardAmount = result.rewardAmount.add(new BN(fee));
280
- result.mintedAmount = result.mintedAmount.add(new BN(total));
281
-
282
- // mint tokens for deposit proposer
283
- changes.stake.push({
284
- address: toStakeAddress(x.tx.itxJson.proposer, address),
285
- delta: total,
286
- action: 'mint',
287
- });
288
- } else if (x.type === 'withdraw_token_v2') {
289
- result.rewardAmount = result.rewardAmount.add(new BN(fee));
290
- result.burnedAmount = result.burnedAmount.add(new BN(user));
291
-
292
- // burn tokens from locked withdraws: user amount
293
- changes.stake.push({
294
- address: toStakeAddress(x.tx.from, address),
295
- delta: `-${user}`,
296
- action: 'burn',
297
- });
298
- }
299
- });
274
+ txStates
275
+ .filter((x) => isGovernanceTx(x) === false)
276
+ .forEach((x) => {
277
+ const user = x.tx.itxJson.token.value;
278
+ const fee = x.tx.itxJson.actualFee;
279
+ const total = getBNSum(user, fee);
280
+
281
+ if (x.type === 'deposit_token_v2') {
282
+ result.rewardAmount = result.rewardAmount.add(new BN(fee));
283
+ result.mintedAmount = result.mintedAmount.add(new BN(total));
284
+
285
+ // mint tokens for deposit proposer
286
+ changes.stake.push({
287
+ address: toStakeAddress(x.tx.itxJson.proposer, address),
288
+ delta: total,
289
+ action: 'mint',
290
+ });
291
+ } else if (x.type === 'withdraw_token_v2') {
292
+ result.rewardAmount = result.rewardAmount.add(new BN(fee));
293
+ result.burnedAmount = result.burnedAmount.add(new BN(user));
294
+
295
+ // burn tokens from locked withdraws: user amount
296
+ changes.stake.push({
297
+ address: toStakeAddress(x.tx.from, address),
298
+ delta: `-${user}`,
299
+ action: 'burn',
300
+ });
301
+ }
302
+ });
300
303
 
301
304
  const grouped = {
302
305
  stake: groupBy(changes.stake, 'address'),
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.18.33",
6
+ "version": "1.18.35",
7
7
  "description": "Predefined tx pipeline sets to execute certain type of transactions",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -21,18 +21,18 @@
21
21
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "@arcblock/did": "1.18.33",
25
- "@arcblock/did-util": "1.18.33",
26
- "@arcblock/jwt": "1.18.33",
27
- "@arcblock/validator": "1.18.33",
28
- "@ocap/asset": "1.18.33",
29
- "@ocap/mcrypto": "1.18.33",
30
- "@ocap/merkle-tree": "1.18.33",
31
- "@ocap/message": "1.18.33",
32
- "@ocap/state": "1.18.33",
33
- "@ocap/tx-pipeline": "1.18.33",
34
- "@ocap/util": "1.18.33",
35
- "@ocap/wallet": "1.18.33",
24
+ "@arcblock/did": "1.18.35",
25
+ "@arcblock/did-util": "1.18.35",
26
+ "@arcblock/jwt": "1.18.35",
27
+ "@arcblock/validator": "1.18.35",
28
+ "@ocap/asset": "1.18.35",
29
+ "@ocap/mcrypto": "1.18.35",
30
+ "@ocap/merkle-tree": "1.18.35",
31
+ "@ocap/message": "1.18.35",
32
+ "@ocap/state": "1.18.35",
33
+ "@ocap/tx-pipeline": "1.18.35",
34
+ "@ocap/util": "1.18.35",
35
+ "@ocap/wallet": "1.18.35",
36
36
  "debug": "^4.3.4",
37
37
  "deep-diff": "^1.0.2",
38
38
  "empty-value": "^1.0.1",
@@ -47,5 +47,5 @@
47
47
  "jest": "^27.5.1",
48
48
  "start-server-and-test": "^1.14.0"
49
49
  },
50
- "gitHead": "0d03089c6651d92a3451e9bbbc865c18c2f5d8ad"
50
+ "gitHead": "21178573bd44a1fb3a3a71f21bb8e81433322a69"
51
51
  }
@@ -1,10 +0,0 @@
1
- const { pipes } = require('@ocap/tx-pipeline');
2
-
3
- module.exports = () =>
4
- pipes.VerifyInfo([
5
- {
6
- error: 'INVALID_TX',
7
- message: 'The rollup is paused',
8
- fn: (context) => context.rollupState.paused === false,
9
- },
10
- ]);