@ocap/tx-pipeline 1.27.7 → 1.27.8
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 +2 -0
- package/lib/pipes/verify-token-access.js +99 -0
- package/lib/pipes/verify-tx-input.js +3 -2
- package/lib/runner.js +10 -0
- package/package.json +11 -11
package/lib/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const VerifyTxSize = require('./pipes/verify-tx-size');
|
|
|
23
23
|
const VerifyTxInput = require('./pipes/verify-tx-input');
|
|
24
24
|
const VerifyUpdater = require('./pipes/verify-updater');
|
|
25
25
|
const VerifyTokenBalance = require('./pipes/verify-token-balance');
|
|
26
|
+
const verifyTokenAccess = require('./pipes/verify-token-access');
|
|
26
27
|
const VerifyStateDiff = require('./pipes/verify-state-diff');
|
|
27
28
|
const TakeStateSnapshot = require('./pipes/take-state-snapshot');
|
|
28
29
|
|
|
@@ -53,6 +54,7 @@ module.exports = {
|
|
|
53
54
|
VerifyTxSize,
|
|
54
55
|
VerifyTxInput,
|
|
55
56
|
VerifyTokenBalance,
|
|
57
|
+
verifyTokenAccess,
|
|
56
58
|
VerifyStateDiff,
|
|
57
59
|
TakeStateSnapshot,
|
|
58
60
|
},
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* eslint-disable no-restricted-syntax */
|
|
2
|
+
const get = require('lodash/get');
|
|
3
|
+
const isEmpty = require('empty-value');
|
|
4
|
+
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
5
|
+
const { getRelatedAddresses } = require('@ocap/util/lib/get-related-addr');
|
|
6
|
+
const { toAddress } = require('@ocap/util');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Verify that accounts are in a specified list field of token/tokenFactory states
|
|
10
|
+
* This is a generic pipe that can be used for various restriction checks (spenders, minters, etc.)
|
|
11
|
+
*
|
|
12
|
+
* @param {object} params
|
|
13
|
+
* @param {string} params.statesKey - context key for token/tokenFactory states array, defaults to 'tokenStates'
|
|
14
|
+
* @param {string} params.listFieldKey - field name in token state to check (e.g., 'spenders', 'minters')
|
|
15
|
+
* @param {string[]} params.accountKeys - array of context keys, each value can be account state object or plain address string
|
|
16
|
+
* @param {string} params.errorMessage - custom error message template, use {address} and {tokenAddress} as placeholders
|
|
17
|
+
* @returns {Function} pipe function
|
|
18
|
+
*/
|
|
19
|
+
module.exports = function CreateVerifyTokenAccessPipe({
|
|
20
|
+
statesKey = 'tokenStates',
|
|
21
|
+
listFieldKey = 'spenders',
|
|
22
|
+
accountKeys = [],
|
|
23
|
+
errorMessage,
|
|
24
|
+
}) {
|
|
25
|
+
if (!Array.isArray(accountKeys) || accountKeys.length === 0) {
|
|
26
|
+
throw new Error('INTERNAL', 'verify-token-access: accountKeys must be a non-empty array');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!listFieldKey) {
|
|
30
|
+
throw new Error('INTERNAL', 'verify-token-access: listFieldKey is required');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const defaultErrorMessage =
|
|
34
|
+
errorMessage ||
|
|
35
|
+
`Account {address} is not allowed to interact with token {tokenAddress} (${listFieldKey} restriction)`;
|
|
36
|
+
|
|
37
|
+
return function VerifyAccountInList(context, next) {
|
|
38
|
+
const states = get(context, statesKey);
|
|
39
|
+
if (isEmpty(states)) {
|
|
40
|
+
return next();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const statesList = Array.isArray(states) ? states : [states];
|
|
44
|
+
const addressCache = new Map();
|
|
45
|
+
|
|
46
|
+
function getCachedAddresses(acct) {
|
|
47
|
+
if (typeof acct === 'string') return [acct];
|
|
48
|
+
|
|
49
|
+
if (!addressCache.has(acct.address)) {
|
|
50
|
+
addressCache.set(acct.address, getRelatedAddresses(acct));
|
|
51
|
+
}
|
|
52
|
+
return addressCache.get(acct.address);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const state of statesList) {
|
|
56
|
+
// Skip if state doesn't have the list field or it's empty
|
|
57
|
+
const list = state[listFieldKey];
|
|
58
|
+
if (!list) continue;
|
|
59
|
+
|
|
60
|
+
// Normalize addresses to lowercase for case-insensitive comparison (important for ETH addresses)
|
|
61
|
+
const listSet = new Set(list.map((addr) => toAddress(addr).toLowerCase()));
|
|
62
|
+
|
|
63
|
+
// Verify each account in accountKeys
|
|
64
|
+
for (const accountKey of accountKeys) {
|
|
65
|
+
const accountState = get(context, accountKey);
|
|
66
|
+
|
|
67
|
+
// Skip if account state doesn't exist
|
|
68
|
+
if (!accountState) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle array of account states
|
|
73
|
+
const accountStates = Array.isArray(accountState) ? accountState : [accountState];
|
|
74
|
+
|
|
75
|
+
for (const acct of accountStates) {
|
|
76
|
+
// Skip if acct is null/undefined
|
|
77
|
+
if (!acct) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const addresses = getCachedAddresses(acct);
|
|
82
|
+
|
|
83
|
+
// Check if any address is in the list (case-insensitive)
|
|
84
|
+
const isAllowed = addresses.some((addr) => listSet.has(toAddress(addr).toLowerCase()));
|
|
85
|
+
|
|
86
|
+
if (!isAllowed) {
|
|
87
|
+
const displayAddress = typeof acct === 'string' ? acct : acct.address;
|
|
88
|
+
const message = defaultErrorMessage
|
|
89
|
+
.replace('{address}', displayAddress)
|
|
90
|
+
.replace('{tokenAddress}', state.address);
|
|
91
|
+
return next(new Error('FORBIDDEN', `verify-token-access: ${message}`, { persist: true }));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
next();
|
|
98
|
+
};
|
|
99
|
+
};
|
|
@@ -14,6 +14,7 @@ module.exports = function CreateVerifyTxInputPipe({
|
|
|
14
14
|
sendersKey = 'senders',
|
|
15
15
|
tokensKey = 'tokens',
|
|
16
16
|
assetsKey = 'assets',
|
|
17
|
+
allowEmpty = false,
|
|
17
18
|
}) {
|
|
18
19
|
return function VerifyTxInput(context, next) {
|
|
19
20
|
const inputs = getListField(context, fieldKey);
|
|
@@ -38,7 +39,7 @@ module.exports = function CreateVerifyTxInputPipe({
|
|
|
38
39
|
{
|
|
39
40
|
error: 'INSUFFICIENT_DATA',
|
|
40
41
|
message: `${fieldKey} should not be empty`,
|
|
41
|
-
fn: () => !isEmpty(inputs),
|
|
42
|
+
fn: () => allowEmpty || !isEmpty(inputs),
|
|
42
43
|
},
|
|
43
44
|
{
|
|
44
45
|
error: 'INVALID_TX',
|
|
@@ -59,7 +60,7 @@ module.exports = function CreateVerifyTxInputPipe({
|
|
|
59
60
|
{
|
|
60
61
|
error: 'INVALID_TX',
|
|
61
62
|
message: `Token amount must be greater than 0 in each ${fieldKey} item`,
|
|
62
|
-
fn: () => inputs.every((x) => getTokens(x).every(({ value }) => new BN(value).gt(ZERO))),
|
|
63
|
+
fn: () => allowEmpty || inputs.every((x) => getTokens(x).every(({ value }) => new BN(value).gt(ZERO))),
|
|
63
64
|
},
|
|
64
65
|
{
|
|
65
66
|
error: 'INVALID_TX',
|
package/lib/runner.js
CHANGED
|
@@ -37,7 +37,17 @@ class PipelineRunner extends EventEmitter {
|
|
|
37
37
|
await fn(err, context, next);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
|
+
|
|
40
41
|
if (!hasError && paramCount < 3) {
|
|
42
|
+
// Check if this pipe should be skipped
|
|
43
|
+
if (opts && opts.shouldSkip) {
|
|
44
|
+
const skip = typeof opts.shouldSkip === 'function' ? opts.shouldSkip(context) : opts.shouldSkip;
|
|
45
|
+
if (skip) {
|
|
46
|
+
next();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
await fn(context, (e) => {
|
|
42
52
|
if (e) {
|
|
43
53
|
attachErrorProps(e, opts);
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.27.
|
|
6
|
+
"version": "1.27.8",
|
|
7
7
|
"description": "Pipeline runner and common pipelines to process transactions",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"jest": "^29.7.0",
|
|
17
17
|
"start-server-and-test": "^1.14.0",
|
|
18
|
-
"@ocap/e2e-test": "1.27.
|
|
19
|
-
"@ocap/statedb-memory": "1.27.
|
|
18
|
+
"@ocap/e2e-test": "1.27.8",
|
|
19
|
+
"@ocap/statedb-memory": "1.27.8"
|
|
20
20
|
},
|
|
21
21
|
"resolutions": {
|
|
22
22
|
"bn.js": "5.2.2",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"debug": "^4.3.6",
|
|
27
27
|
"empty-value": "^1.0.1",
|
|
28
28
|
"lodash": "^4.17.21",
|
|
29
|
-
"@arcblock/did": "1.27.
|
|
30
|
-
"@arcblock/did-util": "1.27.
|
|
31
|
-
"@ocap/client": "1.27.
|
|
32
|
-
"@ocap/
|
|
33
|
-
"@ocap/
|
|
34
|
-
"@ocap/
|
|
35
|
-
"@ocap/
|
|
36
|
-
"@ocap/
|
|
29
|
+
"@arcblock/did": "1.27.8",
|
|
30
|
+
"@arcblock/did-util": "1.27.8",
|
|
31
|
+
"@ocap/client": "1.27.8",
|
|
32
|
+
"@ocap/message": "1.27.8",
|
|
33
|
+
"@ocap/mcrypto": "1.27.8",
|
|
34
|
+
"@ocap/wallet": "1.27.8",
|
|
35
|
+
"@ocap/state": "1.27.8",
|
|
36
|
+
"@ocap/util": "1.27.8"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"lint": "eslint tests lib",
|