@ocap/tx-protocols 1.18.124 → 1.18.126

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
@@ -17,6 +17,7 @@ const mintAsset = require('./protocols/asset/mint');
17
17
  const createToken = require('./protocols/token/create');
18
18
  const stake = require('./protocols/governance/stake');
19
19
  const revokeStake = require('./protocols/governance/revoke-stake');
20
+ const returnStake = require('./protocols/governance/return-stake');
20
21
  const claimStake = require('./protocols/governance/claim-stake');
21
22
  const slashStake = require('./protocols/governance/slash-stake');
22
23
  const depositTokenV2 = require('./protocols/token/deposit-v2');
@@ -69,6 +70,7 @@ const createExecutor = ({ filter, runAsLambda }) => {
69
70
  revokeStake,
70
71
  claimStake,
71
72
  slashStake,
73
+ returnStake,
72
74
 
73
75
  // rollup
74
76
  createRollup,
@@ -0,0 +1,237 @@
1
+ const isEmpty = require('empty-value');
2
+ const { CustomError: Error } = require('@ocap/util/lib/error');
3
+ const { BN } = require('@ocap/util');
4
+ const { Joi, schemas } = require('@arcblock/validator');
5
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
6
+ const { account, stake, asset } = require('@ocap/state');
7
+ const { getRelatedAddresses } = require('@ocap/util/lib/get-related-addr');
8
+
9
+ // eslint-disable-next-line global-require
10
+ const debug = require('debug')(`${require('../../../package.json').name}:return-stake`);
11
+
12
+ const { applyTokenUpdates, applyTokenChange } = require('../../util');
13
+ const EnsureTxGas = require('../../pipes/ensure-gas');
14
+ const EnsureTxCost = require('../../pipes/ensure-cost');
15
+
16
+ const runner = new Runner();
17
+
18
+ runner.use(pipes.VerifyMultiSig(0));
19
+
20
+ // 0. verify itx
21
+ const schema = Joi.object({
22
+ address: Joi.DID().prefix().role('ROLE_STAKE').required(),
23
+ message: Joi.string().trim().min(1).max(256).required(),
24
+ outputsList: schemas.multiInput.min(1).required(),
25
+ data: Joi.any().optional(),
26
+ }).options({ stripUnknown: true, noDefaults: false });
27
+ runner.use(({ itx }, next) => {
28
+ const { error } = schema.validate(itx);
29
+ return next(error ? new Error('INVALID_TX', `Invalid itx: ${error.message}`) : null);
30
+ });
31
+
32
+ // 1. verify itx.outputs
33
+ runner.use(
34
+ pipes.VerifyTxInput({
35
+ fieldKey: 'itx.outputs',
36
+ inputsKey: 'outputs',
37
+ sendersKey: 'receivers',
38
+ tokensKey: 'tokens',
39
+ assetsKey: 'assets',
40
+ })
41
+ );
42
+
43
+ // 2. verify output
44
+ runner.use(
45
+ pipes.VerifyInfo([
46
+ {
47
+ error: 'INSUFFICIENT_DATA',
48
+ message: 'Can not return stake without any output',
49
+ fn: ({ assets, tokens }) => !(isEmpty(tokens) && isEmpty(assets)),
50
+ },
51
+ ])
52
+ );
53
+
54
+ // 3. verify itx size: set hard limit here because more output leads to longer tx execute time
55
+ runner.use(pipes.VerifyListSize({ listKey: ['outputs', 'receivers', 'tokens', 'assets'] }));
56
+
57
+ // 4. verify sender & receiver
58
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
59
+ runner.use(pipes.ExtractState({ from: 'receivers', to: 'receiverStates', status: 'INVALID_RECEIVER_STATE', table: 'account' })); // prettier-ignore
60
+ runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
61
+
62
+ // 5. verify stake state
63
+ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'stakeState', status: 'INVALID_STAKE_STATE', table: 'stake' })); // prettier-ignore
64
+ runner.use(
65
+ pipes.VerifyInfo([
66
+ {
67
+ error: 'INVALID_TX',
68
+ message: 'Can only return from none-self staking',
69
+ fn: ({ stakeState }) => stakeState.sender !== stakeState.receiver,
70
+ },
71
+ {
72
+ error: 'INVALID_TX',
73
+ message: 'Can only return stake from receiver',
74
+ fn: ({ senderState, stakeState }) => {
75
+ return getRelatedAddresses(senderState).includes(stakeState.receiver);
76
+ },
77
+ },
78
+ {
79
+ error: 'INVALID_TX',
80
+ message: 'Can only return stake to sender',
81
+ fn: ({ receiverStates, stakeState }) =>
82
+ receiverStates.every((x) => getRelatedAddresses(x).includes(stakeState.sender)),
83
+ },
84
+ ])
85
+ );
86
+
87
+ // 6. verify token state and balance from staked
88
+ runner.use(pipes.ExtractState({ from: 'tokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
89
+ runner.use((context, next) => {
90
+ const { outputs, stakeState } = context;
91
+ const tokens = {};
92
+ outputs.forEach(({ tokensList }) => {
93
+ tokensList.forEach(({ address, value }) => {
94
+ if (typeof tokens[address] === 'undefined') {
95
+ tokens[address] = new BN(0);
96
+ }
97
+ tokens[address] = tokens[address].add(new BN(value));
98
+ });
99
+ });
100
+
101
+ const lockedState = {
102
+ address: stakeState.address,
103
+ tokens: { ...stakeState.tokens },
104
+ assets: [...stakeState.assets],
105
+ };
106
+
107
+ context.lockedState = lockedState;
108
+ context.tokenCondition = {
109
+ owner: stakeState.address,
110
+ tokens: Object.keys(tokens).map((address) => ({ address, value: tokens[address] })),
111
+ };
112
+
113
+ next();
114
+ });
115
+ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'lockedState', conditionKey: 'tokenCondition' }));
116
+ runner.use(
117
+ pipes.VerifyInfo([
118
+ {
119
+ error: 'INVALID_TX',
120
+ message: 'Can not return assets that are not locked in the stake',
121
+ fn: ({ assets, lockedState }) => assets.every((x) => lockedState.assets.includes(x)),
122
+ },
123
+ ])
124
+ );
125
+
126
+ // 7. verify asset state and ownership
127
+ runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'INVALID_ASSET_STATE', table: 'asset' }));
128
+ runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
129
+ runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'stakeState' }));
130
+
131
+ // ensure tx cost and gas
132
+ runner.use(
133
+ EnsureTxGas((context) => {
134
+ const result = { create: 0, update: 2, payment: 0 };
135
+ if (context.receiverStates) {
136
+ result.update += context.receiverStates.length;
137
+ }
138
+ if (context.assetStates) {
139
+ result.update += context.assetStates.length;
140
+ }
141
+
142
+ return result;
143
+ })
144
+ );
145
+ runner.use(EnsureTxCost({ attachSenderChanges: true }));
146
+
147
+ // 8. update statedb
148
+ runner.use(
149
+ async (context, next) => {
150
+ const {
151
+ tx,
152
+ itx,
153
+ outputs,
154
+ statedb,
155
+ senderState,
156
+ receiverStates = [],
157
+ assetStates = [],
158
+ stakeState,
159
+ updateVaults,
160
+ senderChange,
161
+ } = context;
162
+
163
+ const receiverUpdates = {};
164
+ const assetUpdates = {};
165
+ const stakeUpdates = {
166
+ tokens: stakeState.tokens || {},
167
+ assets: stakeState.assets || [],
168
+ };
169
+
170
+ outputs.forEach((x) => {
171
+ const { owner, tokensList, assetsList } = x;
172
+
173
+ // move assets to output
174
+ stakeUpdates.assets = stakeUpdates.assets.filter((a) => assetsList.includes(a) === false);
175
+
176
+ // move tokens and assets from staked to output
177
+ const newTokens = applyTokenUpdates(tokensList, { tokens: stakeUpdates.tokens }, 'sub');
178
+ stakeUpdates.tokens = newTokens.tokens;
179
+
180
+ // Update receiver balance
181
+ const ownerState = receiverStates.find((s) => getRelatedAddresses(s).includes(owner));
182
+ receiverUpdates[ownerState.address] = applyTokenUpdates(tokensList, ownerState, 'add');
183
+ if (senderChange && ownerState.address === senderChange.address) {
184
+ receiverUpdates[ownerState.address] = applyTokenChange(receiverUpdates[ownerState.address], senderChange);
185
+ }
186
+
187
+ // Update asset owner
188
+ assetsList.forEach((a) => {
189
+ assetUpdates[a] = { owner: ownerState.address };
190
+ });
191
+ });
192
+
193
+ const [newSenderState, newReceiverStates, newStakeState, newAssetStates] = await Promise.all([
194
+ // Update sender state
195
+ statedb.account.update(
196
+ senderState.address,
197
+ account.update(
198
+ senderState,
199
+ Object.assign({ nonce: tx.nonce }, senderChange ? applyTokenChange(senderState, senderChange) : {}),
200
+ context
201
+ ),
202
+ context
203
+ ),
204
+
205
+ // Update receiver states
206
+ Promise.all(
207
+ receiverStates.map((x) =>
208
+ statedb.account.update(x.address, account.update(x, receiverUpdates[x.address], context), context)
209
+ )
210
+ ),
211
+
212
+ // Update stake state
213
+ statedb.stake.update(stakeState.address, stake.update(stakeState, stakeUpdates, context), context),
214
+
215
+ // Transfer assets to output account
216
+ Promise.all(
217
+ assetStates.map((x) =>
218
+ statedb.asset.update(x.address, asset.update(x, assetUpdates[x.address], context), context)
219
+ )
220
+ ),
221
+
222
+ updateVaults(),
223
+ ]);
224
+
225
+ context.senderState = newSenderState;
226
+ context.receiverStates = newReceiverStates;
227
+ context.stakeState = newStakeState;
228
+ context.assetStates = newAssetStates;
229
+
230
+ debug('return-stake', { address: itx.address, stakeUpdates, assetUpdates, senderChange, ...receiverUpdates });
231
+
232
+ next();
233
+ },
234
+ { persistError: true }
235
+ );
236
+
237
+ module.exports = runner;
@@ -74,12 +74,9 @@ runner.use(
74
74
  },
75
75
  {
76
76
  error: 'INVALID_TX',
77
- message: 'Can only send slashed token/assets to vault, sender, or specified slasher',
78
- fn: ({ config, receivers, stakeState, slasherStates }) =>
79
- receivers.every(
80
- (x) =>
81
- x === stakeState.sender || x === config.vaults.slashedStake || slasherStates.some((s) => s.address === x)
82
- ),
77
+ message: 'Can only send slashed token/assets to vault or specified slasher',
78
+ fn: ({ config, receivers, slasherStates }) =>
79
+ receivers.every((x) => x === config.vaults.slashedStake || slasherStates.some((s) => s.address === x)),
83
80
  },
84
81
  ])
85
82
  );
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.18.124",
6
+ "version": "1.18.126",
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.124",
25
- "@arcblock/did-util": "1.18.124",
26
- "@arcblock/jwt": "1.18.124",
27
- "@arcblock/validator": "1.18.124",
28
- "@ocap/asset": "1.18.124",
29
- "@ocap/mcrypto": "1.18.124",
30
- "@ocap/merkle-tree": "1.18.124",
31
- "@ocap/message": "1.18.124",
32
- "@ocap/state": "1.18.124",
33
- "@ocap/tx-pipeline": "1.18.124",
34
- "@ocap/util": "1.18.124",
35
- "@ocap/wallet": "1.18.124",
24
+ "@arcblock/did": "1.18.126",
25
+ "@arcblock/did-util": "1.18.126",
26
+ "@arcblock/jwt": "1.18.126",
27
+ "@arcblock/validator": "1.18.126",
28
+ "@ocap/asset": "1.18.126",
29
+ "@ocap/mcrypto": "1.18.126",
30
+ "@ocap/merkle-tree": "1.18.126",
31
+ "@ocap/message": "1.18.126",
32
+ "@ocap/state": "1.18.126",
33
+ "@ocap/tx-pipeline": "1.18.126",
34
+ "@ocap/util": "1.18.126",
35
+ "@ocap/wallet": "1.18.126",
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": "c48ab6ae29affa47c479c77333ce3a4dc40ba46a"
50
+ "gitHead": "f25463b1fb916131d6cca7077cc12badb4abd52e"
51
51
  }