@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
|
|
78
|
-
fn: ({ config, receivers,
|
|
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.
|
|
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.
|
|
25
|
-
"@arcblock/did-util": "1.18.
|
|
26
|
-
"@arcblock/jwt": "1.18.
|
|
27
|
-
"@arcblock/validator": "1.18.
|
|
28
|
-
"@ocap/asset": "1.18.
|
|
29
|
-
"@ocap/mcrypto": "1.18.
|
|
30
|
-
"@ocap/merkle-tree": "1.18.
|
|
31
|
-
"@ocap/message": "1.18.
|
|
32
|
-
"@ocap/state": "1.18.
|
|
33
|
-
"@ocap/tx-pipeline": "1.18.
|
|
34
|
-
"@ocap/util": "1.18.
|
|
35
|
-
"@ocap/wallet": "1.18.
|
|
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": "
|
|
50
|
+
"gitHead": "f25463b1fb916131d6cca7077cc12badb4abd52e"
|
|
51
51
|
}
|