@ocap/tx-protocols 1.13.58 → 1.13.62

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/util.js CHANGED
@@ -1,9 +1,13 @@
1
+ const groupBy = require('lodash/groupBy');
2
+ const flattenDeep = require('lodash/flattenDeep');
3
+ const Error = require('@ocap/util/lib/error');
1
4
  const { BN } = require('@ocap/util');
2
5
  const { decodeAny } = require('@ocap/message');
3
6
  const { toStakeAddress } = require('@arcblock/did-util');
4
7
  const cloneDeep = require('lodash/cloneDeep');
5
8
 
6
9
  const ZERO = new BN(0);
10
+ const RATE_BASE = new BN(10000);
7
11
 
8
12
  const decodeAnyNested = (encoded) => {
9
13
  if (!encoded) {
@@ -50,7 +54,7 @@ const decodeAnySafe = (encoded) => {
50
54
 
51
55
  const applyTokenUpdates = (tokens, state, operator) => {
52
56
  if (['add', 'sub'].includes(operator) === false) {
53
- throw new Error(`Invalid operator when applyTokenUpdates: ${operator}`);
57
+ throw new Error('FORBIDDEN', `Invalid operator when applyTokenUpdates: ${operator}`);
54
58
  }
55
59
 
56
60
  if (!state) {
@@ -65,7 +69,7 @@ const applyTokenUpdates = (tokens, state, operator) => {
65
69
  const balance = new BN(oldTokens[address] || 0);
66
70
  const newBalance = balance[operator](requirement);
67
71
  if (newBalance.lt(ZERO)) {
68
- throw new Error(`Negative token balance when applyTokenUpdates for ${address}`);
72
+ throw new Error('FORBIDDEN', `Negative token balance when applyTokenUpdates for ${address}`);
69
73
  }
70
74
  newTokens[address] = newBalance.toString(10);
71
75
  }
@@ -75,10 +79,11 @@ const applyTokenUpdates = (tokens, state, operator) => {
75
79
  };
76
80
  };
77
81
  const applyTokenChange = (state, change) => {
78
- if (change.delta.gt(ZERO)) {
79
- return applyTokenUpdates([{ address: change.token, value: change.delta.toString(10) }], state, 'add');
82
+ const delta = typeof change.delta === 'string' ? new BN(change.delta) : change.delta;
83
+ if (delta.gt(ZERO)) {
84
+ return applyTokenUpdates([{ address: change.token, value: delta.toString(10) }], state, 'add');
80
85
  }
81
- return applyTokenUpdates([{ address: change.token, value: change.delta.abs().toString(10) }], state, 'sub');
86
+ return applyTokenUpdates([{ address: change.token, value: delta.abs().toString(10) }], state, 'sub');
82
87
  };
83
88
 
84
89
  const fixTokenInput = (input, config) => {
@@ -91,54 +96,221 @@ const fixTokenInput = (input, config) => {
91
96
  return input;
92
97
  };
93
98
 
94
- const RATE_BASE = new BN(10000);
95
- const splitBlockReward = ({ total, feeRate, maxFee, minFee, shares = {}, stringify = true }) => {
99
+ const getTxFee = ({ amount, feeRate, maxFee, minFee, stringify = true }) => {
100
+ const userAmount = new BN(amount);
96
101
  const maxFeeAmount = new BN(maxFee);
97
102
  const minFeeAmount = new BN(minFee);
98
- const totalAmount = new BN(total);
103
+
104
+ if (feeRate < 0) {
105
+ throw new Error('FORBIDDEN', 'Unexpected negative feeRate when getTxFee, abort!');
106
+ }
107
+ if (userAmount.lt(ZERO)) {
108
+ throw new Error('FORBIDDEN', 'Unexpected negative amount when getTxFee, abort!');
109
+ }
110
+ if (maxFeeAmount.lt(ZERO)) {
111
+ throw new Error('FORBIDDEN', 'Unexpected negative maxFee when getTxFee, abort!');
112
+ }
113
+ if (minFeeAmount.lt(ZERO)) {
114
+ throw new Error('FORBIDDEN', 'Unexpected negative minFee when getTxFee, abort!');
115
+ }
99
116
 
100
117
  // total fee
101
- let feeAmount = totalAmount.mul(new BN(feeRate)).div(RATE_BASE);
102
- if (feeAmount.lt(minFeeAmount)) {
103
- feeAmount = minFeeAmount;
118
+ let rewardAmount = userAmount.mul(new BN(feeRate)).div(RATE_BASE);
119
+ if (rewardAmount.lt(minFeeAmount)) {
120
+ rewardAmount = minFeeAmount;
104
121
  }
105
- if (feeAmount.gt(maxFeeAmount)) {
106
- feeAmount = maxFeeAmount;
122
+ if (rewardAmount.gt(maxFeeAmount)) {
123
+ rewardAmount = maxFeeAmount;
107
124
  }
108
125
 
109
- // fee shares
110
- const feeShares = Object.keys(shares).reduce((acc, x) => {
111
- acc[x] = feeAmount.mul(new BN(shares[x])).div(RATE_BASE);
112
- return acc;
113
- }, {});
126
+ // total amount
127
+ const totalAmount = userAmount.add(rewardAmount);
114
128
 
115
129
  if (stringify) {
116
130
  return {
117
131
  total: totalAmount.toString(10),
118
- fee: feeAmount.toString(10),
119
- user: totalAmount.sub(feeAmount).toString(10),
120
- feeShares: Object.keys(feeShares).reduce((acc, x) => {
121
- acc[x] = feeShares[x].toString(10);
122
- return acc;
123
- }, {}),
132
+ user: userAmount.toString(10),
133
+ reward: rewardAmount.toString(10),
124
134
  };
125
135
  }
126
136
 
127
137
  return {
128
138
  total: totalAmount,
129
- fee: feeAmount,
130
- user: totalAmount.sub(feeAmount),
131
- feeShares,
139
+ user: userAmount,
140
+ reward: rewardAmount,
132
141
  };
133
142
  };
134
143
 
135
- const getBlockRewardLocker = (rollupAddress) => toStakeAddress(rollupAddress, rollupAddress);
144
+ const splitTxFee = ({ total, shares = {}, stringify = true }) => {
145
+ const totalAmount = new BN(total);
146
+ if (totalAmount.lt(ZERO)) {
147
+ throw new Error('FORBIDDEN', 'Unexpected negative total when splitTxFee, abort!');
148
+ }
149
+ Object.keys(shares).forEach((key) => {
150
+ if (shares[key] < 0) {
151
+ throw new Error('FORBIDDEN', `Unexpected negative shares[${key}] when splitTxFee, abort!`);
152
+ }
153
+ });
154
+
155
+ const rewardShares = Object.keys(shares).reduce((acc, x) => {
156
+ acc[x] = totalAmount.mul(new BN(shares[x])).div(RATE_BASE);
157
+ return acc;
158
+ }, {});
159
+
160
+ return Object.keys(rewardShares).reduce((acc, x) => {
161
+ acc[x] = stringify ? rewardShares[x].toString(10) : rewardShares[x];
162
+ return acc;
163
+ }, {});
164
+ };
165
+
166
+ const getRewardLocker = (rollupAddress) => toStakeAddress(rollupAddress, rollupAddress);
167
+ const getBNSum = (...args) => flattenDeep(args).reduce((sum, x) => sum.add(new BN(x)), new BN(0)).toString(10); // prettier-ignore
168
+ const isFixedFee = (x) => new BN(x.tx.itxJson.maxFee).isZero();
169
+
170
+ const ensureBlockReward = (rollupState, minReward, txStates) => {
171
+ const { address, withdrawFeeRate, minWithdrawFee, maxWithdrawFee, tokenAddress } = rollupState;
172
+ const locker = getRewardLocker(address);
173
+
174
+ const result = {
175
+ mintedAmount: new BN(0),
176
+ burnedAmount: new BN(0),
177
+ rewardAmount: new BN(0),
178
+ };
179
+
180
+ // 0. ensure reward requirement
181
+ const maxPossibleReward = txStates.reduce(
182
+ (sum, x) => sum.add(new BN(isFixedFee(x) ? x.tx.itxJson.actualFee : x.tx.itxJson.maxFee)),
183
+ new BN(0)
184
+ );
185
+ const minRequiredReward = new BN(minReward);
186
+ if (maxPossibleReward.lt(minRequiredReward)) {
187
+ throw new Error('INVALID_BLOCK', 'Block reward does not match minReward requirement');
188
+ }
189
+
190
+ // 1. find dynamic reward tx
191
+ const dynamicFeeTxs = txStates.filter((x) => isFixedFee(x) === false);
192
+ const totalDynamicFee = dynamicFeeTxs.reduce((sum, x) => sum.add(new BN(x.tx.itxJson.maxFee)), new BN(0));
193
+
194
+ const fixedFeeTxs = txStates.filter((x) => isFixedFee(x));
195
+ const totalFixedFee = fixedFeeTxs.reduce((sum, x) => sum.add(new BN(x.tx.itxJson.actualFee)), new BN(0));
196
+
197
+ const totalMissingFee = minRequiredReward.sub(totalFixedFee);
198
+
199
+ // 2. calculate actual reward for each dynamic reward tx, mark tx to be updated
200
+ const changes = { stake: [], account: [] };
201
+ dynamicFeeTxs.forEach((x) => {
202
+ const maxFee = new BN(x.tx.itxJson.maxFee);
203
+ let actualFee = new BN(0);
204
+ // If totalMissingFee is less than 0, then the tx will be charged for fixedFee
205
+ if (totalMissingFee.lt(ZERO)) {
206
+ const fee = getTxFee({
207
+ amount: x.tx.itxJson.token.value,
208
+ feeRate: withdrawFeeRate,
209
+ maxFee: maxWithdrawFee,
210
+ minFee: minWithdrawFee,
211
+ stringify: false,
212
+ });
213
+ actualFee = fee.reward;
214
+ } else {
215
+ // Else the tx is charged for a portion of totalMissingFee
216
+ actualFee = totalMissingFee.mul(maxFee).div(totalDynamicFee);
217
+ }
218
+
219
+ if (actualFee.lt(ZERO)) {
220
+ throw new Error('FORBIDDEN', 'Got negative actualFee for tx, abort!');
221
+ }
222
+
223
+ // If the actualFee is less than the maxFee, user will have a refund
224
+ if (actualFee.lt(maxFee)) {
225
+ const refundFee = maxFee.sub(actualFee).toString(10);
226
+ changes.account.push({
227
+ address: x.tx.from,
228
+ delta: refundFee,
229
+ action: 'refund',
230
+ });
231
+ changes.stake.push({
232
+ address: locker,
233
+ delta: `-${refundFee}`,
234
+ action: 'refund',
235
+ });
236
+ }
237
+
238
+ x.tx.itxJson.actualFee = actualFee.toString(10);
239
+ });
240
+
241
+ // 3. bur/mint tokens, update stakes
242
+ txStates.forEach((x) => {
243
+ const user = x.tx.itxJson.token.value;
244
+ const fee = x.tx.itxJson.actualFee;
245
+ const total = getBNSum(user, fee);
246
+
247
+ if (x.type === 'deposit_token_v2') {
248
+ result.rewardAmount = result.rewardAmount.add(new BN(fee));
249
+ result.mintedAmount = result.mintedAmount.add(new BN(total));
250
+
251
+ // mint tokens for deposit proposer
252
+ changes.stake.push({
253
+ address: toStakeAddress(x.tx.itxJson.proposer, address),
254
+ delta: total,
255
+ action: 'mint',
256
+ });
257
+ } else if (x.type === 'withdraw_token_v2') {
258
+ result.rewardAmount = result.rewardAmount.add(new BN(fee));
259
+ result.burnedAmount = result.burnedAmount.add(new BN(user));
260
+
261
+ // burn tokens from locked withdraws: user amount
262
+ changes.stake.push({
263
+ address: toStakeAddress(x.tx.from, address),
264
+ delta: `-${user}`,
265
+ action: 'burn',
266
+ });
267
+ }
268
+ });
269
+
270
+ const grouped = {
271
+ stake: groupBy(changes.stake, 'address'),
272
+ account: groupBy(changes.account, 'address'),
273
+ };
274
+
275
+ result.stakeUpdates = Object.keys(grouped.stake).reduce((acc, x) => {
276
+ acc[x] = {
277
+ address: x,
278
+ token: tokenAddress,
279
+ delta: getBNSum(...grouped.stake[x].map((c) => c.delta)),
280
+ action: grouped.stake[x][grouped.stake[x].length - 1].action,
281
+ };
282
+
283
+ return acc;
284
+ }, {});
285
+
286
+ result.accountUpdates = Object.keys(grouped.account).reduce((acc, x) => {
287
+ acc[x] = {
288
+ address: x,
289
+ token: tokenAddress,
290
+ delta: getBNSum(...grouped.account[x].map((c) => c.delta)),
291
+ action: grouped.account[x][grouped.account[x].length - 1].action,
292
+ };
293
+
294
+ return acc;
295
+ }, {});
296
+
297
+ result.mintedAmount = result.mintedAmount.toString(10);
298
+ result.burnedAmount = result.burnedAmount.toString(10);
299
+ result.rewardAmount = result.rewardAmount.toString(10);
300
+
301
+ return result;
302
+ };
136
303
 
137
304
  module.exports = {
138
305
  decodeAnySafe,
139
306
  applyTokenUpdates,
140
307
  applyTokenChange,
141
308
  fixTokenInput,
142
- splitBlockReward,
143
- getBlockRewardLocker,
309
+ getTxFee,
310
+ splitTxFee,
311
+ getRewardLocker,
312
+ ensureBlockReward,
313
+ getBNSum,
314
+ isFixedFee,
315
+ RATE_BASE,
144
316
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.13.58",
6
+ "version": "1.13.62",
7
7
  "description": "Predefined tx pipeline sets to execute certain type of transactions",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -19,16 +19,16 @@
19
19
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@arcblock/did": "1.13.58",
23
- "@arcblock/did-util": "1.13.58",
24
- "@ocap/asset": "1.13.58",
25
- "@ocap/mcrypto": "1.13.58",
26
- "@ocap/merkle-tree": "1.13.58",
27
- "@ocap/message": "1.13.58",
28
- "@ocap/state": "1.13.58",
29
- "@ocap/tx-pipeline": "1.13.58",
30
- "@ocap/util": "1.13.58",
31
- "@ocap/wallet": "1.13.58",
22
+ "@arcblock/did": "1.13.62",
23
+ "@arcblock/did-util": "1.13.62",
24
+ "@ocap/asset": "1.13.62",
25
+ "@ocap/mcrypto": "1.13.62",
26
+ "@ocap/merkle-tree": "1.13.62",
27
+ "@ocap/message": "1.13.62",
28
+ "@ocap/state": "1.13.62",
29
+ "@ocap/tx-pipeline": "1.13.62",
30
+ "@ocap/util": "1.13.62",
31
+ "@ocap/wallet": "1.13.62",
32
32
  "debug": "^4.3.2",
33
33
  "empty-value": "^1.0.1",
34
34
  "lodash": "^4.17.21",
@@ -41,5 +41,5 @@
41
41
  "devDependencies": {
42
42
  "jest": "^27.3.1"
43
43
  },
44
- "gitHead": "5eabfe8405725ee0193f76a9f7cd895d8c763e52"
44
+ "gitHead": "a14f8d2ade8dc0c58fd9fbf331a4eb9b842de6ed"
45
45
  }