@ocap/tx-protocols 1.6.3 → 1.6.10
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/README.md +36 -0
- package/lib/execute.js +97 -30
- package/lib/index.js +56 -14
- package/lib/protocols/account/declare.js +36 -30
- package/lib/protocols/account/delegate.js +78 -40
- package/lib/protocols/account/migrate.js +65 -49
- package/lib/protocols/account/revoke-delegate.js +39 -23
- package/lib/protocols/asset/acquire-v2.js +159 -0
- package/lib/protocols/asset/acquire-v3.js +242 -0
- package/lib/protocols/asset/calls/README.md +5 -0
- package/lib/protocols/asset/calls/transfer-token.js +37 -0
- package/lib/protocols/asset/calls/transfer.js +29 -0
- package/lib/protocols/asset/create.js +85 -36
- package/lib/protocols/asset/mint.js +133 -0
- package/lib/protocols/asset/pipes/exec-mint-hook.js +59 -0
- package/lib/protocols/asset/pipes/extract-factory-tokens.js +18 -0
- package/lib/protocols/asset/pipes/verify-acquire-params.js +30 -0
- package/lib/protocols/asset/pipes/verify-itx-address.js +41 -0
- package/lib/protocols/asset/pipes/verify-itx-assets.js +49 -0
- package/lib/protocols/asset/pipes/verify-itx-variables.js +26 -0
- package/lib/protocols/asset/pipes/verify-mint-limit.js +13 -0
- package/lib/protocols/asset/update.js +76 -44
- package/lib/protocols/factory/create.js +146 -0
- package/lib/protocols/governance/claim-stake.js +219 -0
- package/lib/protocols/governance/revoke-stake.js +136 -0
- package/lib/protocols/governance/stake.js +176 -0
- package/lib/protocols/rollup/claim-reward.js +283 -0
- package/lib/protocols/rollup/create-block.js +333 -0
- package/lib/protocols/rollup/create.js +169 -0
- package/lib/protocols/rollup/join.js +156 -0
- package/lib/protocols/rollup/leave.js +127 -0
- package/lib/protocols/rollup/migrate-contract.js +53 -0
- package/lib/protocols/rollup/migrate-token.js +66 -0
- package/lib/protocols/rollup/pause.js +52 -0
- package/lib/protocols/rollup/pipes/ensure-service-fee.js +37 -0
- package/lib/protocols/rollup/pipes/ensure-validator.js +10 -0
- package/lib/protocols/rollup/pipes/verify-evidence.js +37 -0
- package/lib/protocols/rollup/pipes/verify-paused.js +10 -0
- package/lib/protocols/rollup/pipes/verify-signers.js +88 -0
- package/lib/protocols/rollup/resume.js +52 -0
- package/lib/protocols/rollup/update.js +98 -0
- package/lib/protocols/token/create.js +150 -0
- package/lib/protocols/token/deposit-v2.js +241 -0
- package/lib/protocols/token/withdraw-v2.js +255 -0
- package/lib/protocols/trade/exchange-v2.js +179 -0
- package/lib/protocols/trade/transfer-v2.js +136 -0
- package/lib/protocols/trade/transfer-v3.js +241 -0
- package/lib/util.js +325 -2
- package/package.json +23 -16
- package/lib/protocols/misc/poke.js +0 -106
- package/lib/protocols/trade/exchange.js +0 -139
- package/lib/protocols/trade/transfer.js +0 -101
|
@@ -1,44 +1,49 @@
|
|
|
1
|
+
/* eslint-disable indent */
|
|
2
|
+
const Error = require('@ocap/util/lib/error');
|
|
3
|
+
const isEmpty = require('lodash/isEmpty');
|
|
1
4
|
const cloneDeep = require('lodash/cloneDeep');
|
|
2
|
-
const isEmpty = require('empty-value');
|
|
3
|
-
const { toAssetAddress } = require('@arcblock/did-util');
|
|
4
5
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
5
6
|
const { account, asset } = require('@ocap/state');
|
|
7
|
+
const { toAssetAddress } = require('@arcblock/did-util');
|
|
6
8
|
|
|
7
9
|
// eslint-disable-next-line global-require
|
|
8
10
|
const debug = require('debug')(`${require('../../../package.json').name}:create-asset`);
|
|
9
11
|
|
|
10
|
-
const {
|
|
12
|
+
const { decodeAnySafe } = require('../../util');
|
|
13
|
+
const ensureServiceFee = require('../rollup/pipes/ensure-service-fee');
|
|
11
14
|
|
|
12
15
|
const runner = new Runner();
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
// Verify itx
|
|
18
|
+
runner.use(({ itx }, next) => {
|
|
19
|
+
const { error } = asset.schema.validate(itx);
|
|
20
|
+
if (error) {
|
|
21
|
+
return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
|
|
22
|
+
}
|
|
23
|
+
return next();
|
|
24
|
+
});
|
|
16
25
|
runner.use(
|
|
17
26
|
pipes.VerifyInfo([
|
|
18
|
-
{
|
|
19
|
-
error: 'INSUFFICIENT_DATA',
|
|
20
|
-
message: 'itx.data or itx.address must not be empty',
|
|
21
|
-
fn: ({ itx }) => itx.address && itx.data,
|
|
22
|
-
},
|
|
23
27
|
{
|
|
24
28
|
error: 'INVALID_ASSET',
|
|
25
29
|
message: 'Asset address is not valid',
|
|
26
30
|
fn: (context) => {
|
|
27
31
|
const itx = cloneDeep(context.itx);
|
|
28
|
-
itx.data =
|
|
29
|
-
|
|
32
|
+
itx.data = decodeAnySafe(itx.data);
|
|
33
|
+
itx.address = '';
|
|
34
|
+
|
|
35
|
+
// Save for later use
|
|
36
|
+
context.assetData = itx.data;
|
|
37
|
+
context.signers = [itx.issuer].filter(Boolean);
|
|
38
|
+
|
|
30
39
|
return toAssetAddress(itx) === context.itx.address;
|
|
31
40
|
},
|
|
32
41
|
},
|
|
33
42
|
])
|
|
34
43
|
);
|
|
35
44
|
|
|
36
|
-
// Ensure
|
|
37
|
-
runner.use(pipes.
|
|
38
|
-
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
|
|
39
|
-
|
|
40
|
-
// Ensure parent exist
|
|
41
|
-
runner.use(pipes.ExtractState({ from: 'itx.parent', to: 'parentAsset', status: 'INVALID_ASSET' }));
|
|
45
|
+
// Ensure issuer signed
|
|
46
|
+
runner.use(pipes.VerifyMultiSigV2({ signersKey: 'signers' }));
|
|
42
47
|
|
|
43
48
|
// Ensure asset not exist
|
|
44
49
|
runner.use(pipes.ExtractState({ from: 'itx.address', to: 'assetState', status: 'OK' }));
|
|
@@ -47,33 +52,77 @@ runner.use(
|
|
|
47
52
|
{
|
|
48
53
|
error: 'INVALID_ASSET',
|
|
49
54
|
message: 'This asset already exist on chain',
|
|
50
|
-
fn: (context) =>
|
|
55
|
+
fn: (context) => !context.assetState,
|
|
51
56
|
},
|
|
52
57
|
])
|
|
53
58
|
);
|
|
54
59
|
|
|
60
|
+
// Ensure issuer exist
|
|
61
|
+
runner.use(pipes.ExtractState({ from: 'itx.issuer', to: 'issuerState', status: 'INVALID_ISSUER_STATE', table: 'account' })); // prettier-ignore
|
|
62
|
+
runner.use(pipes.VerifyAccountMigration({ signerKey: 'issuerState' }));
|
|
63
|
+
|
|
64
|
+
// Ensure sender exist
|
|
65
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
66
|
+
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
|
|
67
|
+
|
|
68
|
+
// Ensure delegation
|
|
69
|
+
runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
|
|
70
|
+
runner.use(pipes.VerifyDelegation({ type: 'signature', signerKey: 'senderState', delegatorKey: 'delegatorState' }));
|
|
71
|
+
|
|
72
|
+
// Check if parent is a factory
|
|
73
|
+
runner.use(pipes.ExtractState({ from: 'itx.parent', to: 'factoryState', status: 'OK', table: 'factory' }));
|
|
74
|
+
runner.use(
|
|
75
|
+
pipes.VerifyInfo([
|
|
76
|
+
// For security consideration, user can not create fake assets from a factory
|
|
77
|
+
// https://github.com/ArcBlock/asset-chain/issues/97
|
|
78
|
+
{
|
|
79
|
+
error: 'CREATE_FROM_FACTORY',
|
|
80
|
+
message: 'You can only get asset from factory from acquire or mint',
|
|
81
|
+
fn: ({ itx, factoryState }) => !(itx.parent && factoryState),
|
|
82
|
+
persist: true,
|
|
83
|
+
},
|
|
84
|
+
])
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Ensure parent exist
|
|
88
|
+
runner.use(pipes.ExtractState({ from: 'itx.parent', to: 'parentAsset', status: 'INVALID_ASSET', table: 'asset' }));
|
|
89
|
+
|
|
90
|
+
runner.use(ensureServiceFee);
|
|
91
|
+
|
|
55
92
|
// Update asset state
|
|
56
|
-
runner.use(
|
|
57
|
-
|
|
58
|
-
|
|
93
|
+
runner.use(
|
|
94
|
+
async (context, next) => {
|
|
95
|
+
const { tx, itx, assetData, statedb, senderState, delegatorState, senderUpdates, vaultState, vaultUpdates } =
|
|
96
|
+
context;
|
|
97
|
+
const owner = delegatorState ? delegatorState.address : senderState.address;
|
|
59
98
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
99
|
+
const [newSenderState, assetState, newVaultState] = await Promise.all([
|
|
100
|
+
statedb.account.update(
|
|
101
|
+
senderState.address,
|
|
102
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
103
|
+
context
|
|
104
|
+
),
|
|
66
105
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
106
|
+
statedb.asset.create(
|
|
107
|
+
itx.address,
|
|
108
|
+
asset.create({ ...itx, tags: itx.tagsList, data: assetData, owner }, context),
|
|
109
|
+
context
|
|
110
|
+
),
|
|
70
111
|
|
|
71
|
-
|
|
72
|
-
|
|
112
|
+
isEmpty(vaultUpdates)
|
|
113
|
+
? vaultState
|
|
114
|
+
: statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
|
|
115
|
+
]);
|
|
73
116
|
|
|
74
|
-
|
|
117
|
+
context.senderState = newSenderState;
|
|
118
|
+
context.assetState = assetState;
|
|
119
|
+
context.vaultState = newVaultState;
|
|
75
120
|
|
|
76
|
-
|
|
77
|
-
|
|
121
|
+
debug('createAsset', assetState);
|
|
122
|
+
|
|
123
|
+
next();
|
|
124
|
+
},
|
|
125
|
+
{ persistError: true }
|
|
126
|
+
);
|
|
78
127
|
|
|
79
128
|
module.exports = runner;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* eslint-disable function-paren-newline */
|
|
2
|
+
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
3
|
+
const { account, asset, factory } = require('@ocap/state');
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line global-require
|
|
6
|
+
const debug = require('debug')(`${require('../../../package.json').name}:acquire-asset`);
|
|
7
|
+
|
|
8
|
+
const verifyAcquireParams = require('./pipes/verify-acquire-params');
|
|
9
|
+
const verifyMintLimit = require('./pipes/verify-mint-limit');
|
|
10
|
+
const verifyItxVariables = require('./pipes/verify-itx-variables');
|
|
11
|
+
const verifyItxAssets = require('./pipes/verify-itx-assets');
|
|
12
|
+
const verifyItxAddress = require('./pipes/verify-itx-address');
|
|
13
|
+
|
|
14
|
+
const runner = new Runner();
|
|
15
|
+
|
|
16
|
+
runner.use(pipes.VerifyMultiSig(0));
|
|
17
|
+
runner.use(verifyAcquireParams('mint'));
|
|
18
|
+
|
|
19
|
+
// ensure asset not exist
|
|
20
|
+
runner.use(pipes.ExtractState({ from: 'itx.address', to: 'duplicateAsset', status: 'OK', table: 'asset' }));
|
|
21
|
+
runner.use(
|
|
22
|
+
pipes.VerifyInfo([
|
|
23
|
+
{
|
|
24
|
+
error: 'FORBIDDEN',
|
|
25
|
+
message: 'Asset with same address already exists',
|
|
26
|
+
fn: ({ duplicateAsset }) => !duplicateAsset,
|
|
27
|
+
persist: true,
|
|
28
|
+
},
|
|
29
|
+
])
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Ensure sender exist
|
|
33
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
34
|
+
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
|
|
35
|
+
|
|
36
|
+
// Ensure factory exist
|
|
37
|
+
runner.use(pipes.ExtractState({ from: 'itx.factory', to: 'factoryState', status: 'INVALID_FACTORY_STATE', table: 'factory' })); // prettier-ignore
|
|
38
|
+
|
|
39
|
+
// Used to assemble issuer object
|
|
40
|
+
runner.use(pipes.ExtractState({ from: 'factoryState.owner', to: 'factoryOwnerState', status: 'INVALID_OWNER', table: 'account' })); // prettier-ignore
|
|
41
|
+
|
|
42
|
+
// Ensure factory owner exist and equal to sender
|
|
43
|
+
runner.use(pipes.ExtractState({ from: 'itx.owner', to: 'ownerState', status: 'OK', table: 'account' }));
|
|
44
|
+
runner.use(
|
|
45
|
+
pipes.VerifyInfo([
|
|
46
|
+
{
|
|
47
|
+
error: 'OWNER_ONLY_OPERATION',
|
|
48
|
+
message: 'Only factory owner is allowed to mint',
|
|
49
|
+
fn: ({ factoryState, senderState }) => factoryState.owner === senderState.address,
|
|
50
|
+
persist: true,
|
|
51
|
+
},
|
|
52
|
+
])
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Prepare for itx.assets verify
|
|
56
|
+
runner.use(pipes.ExtractState({ from: 'itx.assetsList', to: 'assetStates', status: 'OK', table: 'asset' }));
|
|
57
|
+
runner.use(
|
|
58
|
+
pipes.ExtractState({
|
|
59
|
+
from: 'factoryState.input.assets',
|
|
60
|
+
to: 'expectedFactoryStates',
|
|
61
|
+
status: 'OK',
|
|
62
|
+
table: 'factory',
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
runner.use(verifyMintLimit);
|
|
67
|
+
runner.use(verifyItxVariables);
|
|
68
|
+
runner.use(verifyItxAddress('mint'));
|
|
69
|
+
runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'ownerState' }));
|
|
70
|
+
runner.use(verifyItxAssets);
|
|
71
|
+
|
|
72
|
+
// update statedb
|
|
73
|
+
runner.use(
|
|
74
|
+
async (context, next) => {
|
|
75
|
+
const {
|
|
76
|
+
tx,
|
|
77
|
+
itx,
|
|
78
|
+
txTime,
|
|
79
|
+
statedb,
|
|
80
|
+
senderState,
|
|
81
|
+
factoryState,
|
|
82
|
+
mintedAsset,
|
|
83
|
+
mintedAddress,
|
|
84
|
+
ownerState,
|
|
85
|
+
assetStates = [],
|
|
86
|
+
} = context;
|
|
87
|
+
|
|
88
|
+
const owner = ownerState ? ownerState.address : itx.owner;
|
|
89
|
+
|
|
90
|
+
const senderUpdates = { nonce: tx.nonce, pk: tx.pk };
|
|
91
|
+
const factoryUpdates = { numMinted: factoryState.numMinted + 1 };
|
|
92
|
+
|
|
93
|
+
const [newSenderState, newReceiverState, assetState, newFactoryState, newAssetStates] = await Promise.all([
|
|
94
|
+
// update sender
|
|
95
|
+
statedb.account.update(senderState.address, account.update(senderState, senderUpdates, context), context),
|
|
96
|
+
|
|
97
|
+
// create receiver if not exist: asset owner
|
|
98
|
+
ownerState
|
|
99
|
+
? Promise.resolve(ownerState)
|
|
100
|
+
: statedb.account.create(owner, account.create({ address: owner }, context), context),
|
|
101
|
+
|
|
102
|
+
// create asset
|
|
103
|
+
statedb.asset.create(
|
|
104
|
+
itx.address,
|
|
105
|
+
asset.create({ ...mintedAsset, owner, address: mintedAddress }, context),
|
|
106
|
+
context
|
|
107
|
+
),
|
|
108
|
+
|
|
109
|
+
// update factory
|
|
110
|
+
statedb.factory.update(factoryState.address, factory.update(factoryState, factoryUpdates, context), context),
|
|
111
|
+
|
|
112
|
+
// mark input assets as consumed
|
|
113
|
+
Promise.all(
|
|
114
|
+
assetStates.map((x) =>
|
|
115
|
+
statedb.asset.update(x.address, asset.update(x, { consumedTime: txTime }, context), context)
|
|
116
|
+
)
|
|
117
|
+
),
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
context.senderState = newSenderState;
|
|
121
|
+
context.receiverState = newReceiverState;
|
|
122
|
+
context.assetState = assetState;
|
|
123
|
+
context.factoryState = newFactoryState;
|
|
124
|
+
context.assetStates = newAssetStates;
|
|
125
|
+
|
|
126
|
+
debug('mint', assetState);
|
|
127
|
+
|
|
128
|
+
next();
|
|
129
|
+
},
|
|
130
|
+
{ persistError: true }
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
module.exports = runner;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
// contract calls
|
|
3
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
4
|
+
const transferCall = require('../calls/transfer');
|
|
5
|
+
const transferTokenCall = require('../calls/transfer-token');
|
|
6
|
+
|
|
7
|
+
const contractCalls = {
|
|
8
|
+
transfer: transferCall,
|
|
9
|
+
transferToken: transferTokenCall,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports = async (context, next) => {
|
|
13
|
+
const { statedb, factoryState, tokenUpdates, config } = context;
|
|
14
|
+
|
|
15
|
+
if (factoryState.settlement === 'instant') {
|
|
16
|
+
const mintHook = factoryState.hooks.find((x) => x.name === 'mint');
|
|
17
|
+
if (mintHook && mintHook.type === 'contract') {
|
|
18
|
+
context.updatedAccounts = [];
|
|
19
|
+
|
|
20
|
+
// These calls must be run sequentially, because different calls may update same account
|
|
21
|
+
const clone = cloneDeep(mintHook.compiled);
|
|
22
|
+
for (const x of clone) {
|
|
23
|
+
// Convert transfer to transferToken call for backwards compatibility
|
|
24
|
+
if (x.call === 'transfer') {
|
|
25
|
+
x.call = 'transferToken';
|
|
26
|
+
x.args.tokenAddress = config.token.address;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const pipeline = contractCalls[x.call];
|
|
30
|
+
const { updateSet } = await pipeline.runAsync({
|
|
31
|
+
states: context.states,
|
|
32
|
+
statedb,
|
|
33
|
+
txn: context.txn,
|
|
34
|
+
txTime: context.txTime,
|
|
35
|
+
txHash: context.txHash,
|
|
36
|
+
args: x.args,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await statedb.account.update(updateSet.address, updateSet.updates, context);
|
|
40
|
+
context.updatedAccounts.push(updateSet);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
context.receipts = [
|
|
45
|
+
{
|
|
46
|
+
address: factoryState.address,
|
|
47
|
+
changes: Object.keys(tokenUpdates.tokens)
|
|
48
|
+
.map((x) => ({
|
|
49
|
+
target: x,
|
|
50
|
+
action: 'transfer',
|
|
51
|
+
value: tokenUpdates.tokens[x],
|
|
52
|
+
}))
|
|
53
|
+
.filter((x) => x.value > 0),
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
next();
|
|
59
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const uniqBy = require('lodash/uniqBy');
|
|
2
|
+
const { BN } = require('@ocap/util');
|
|
3
|
+
const createSortedList = require('@ocap/util/lib/create-sorted-list');
|
|
4
|
+
|
|
5
|
+
// Create token requirements from factoryState
|
|
6
|
+
module.exports = (context, next) => {
|
|
7
|
+
const { factoryState, config } = context;
|
|
8
|
+
context.factoryTokens = [...factoryState.input.tokens];
|
|
9
|
+
|
|
10
|
+
const exist = context.factoryTokens.find((x) => x.address === config.token.address);
|
|
11
|
+
if (!exist && new BN(factoryState.input.value).gt(new BN(0))) {
|
|
12
|
+
context.factoryTokens.push({ address: config.token.address, value: factoryState.input.value });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
context.factoryTokens = uniqBy(context.factoryTokens, 'address');
|
|
16
|
+
context.tokenAddress = createSortedList(context.factoryTokens.map((x) => x.address));
|
|
17
|
+
next();
|
|
18
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const Error = require('@ocap/util/lib/error');
|
|
2
|
+
const getListField = require('@ocap/util/lib/get-list-field');
|
|
3
|
+
|
|
4
|
+
module.exports =
|
|
5
|
+
(mode) =>
|
|
6
|
+
({ itx }, next) => {
|
|
7
|
+
if (!itx.factory) {
|
|
8
|
+
return next(new Error('INSUFFICIENT_DATA', 'itx.factory must not be empty'));
|
|
9
|
+
}
|
|
10
|
+
if (!itx.address) {
|
|
11
|
+
return next(new Error('INSUFFICIENT_DATA', 'itx.address must not be empty'));
|
|
12
|
+
}
|
|
13
|
+
if (mode === 'acquire-v2') {
|
|
14
|
+
if (!itx.issuer) {
|
|
15
|
+
return next(new Error('INSUFFICIENT_DATA', 'itx.issuer must not be empty'));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (['mint', 'acquire-v3'].includes(mode)) {
|
|
19
|
+
if (!itx.owner) {
|
|
20
|
+
return next(new Error('INSUFFICIENT_DATA', 'itx.owner must not be empty'));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const variables = getListField(itx, 'variables');
|
|
25
|
+
if (!Array.isArray(variables) && !variables.length) {
|
|
26
|
+
return next(new Error('INSUFFICIENT_DATA', 'itx.variables must not be empty'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return next();
|
|
30
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const Error = require('@ocap/util/lib/error');
|
|
2
|
+
const { mintFromFactory } = require('@ocap/asset');
|
|
3
|
+
|
|
4
|
+
module.exports = (mode) => (context, next) => {
|
|
5
|
+
const { itx, senderState, ownerState, factoryState, delegatorState, factoryOwnerState, factoryInputs } = context;
|
|
6
|
+
|
|
7
|
+
let issuer = null;
|
|
8
|
+
let owner = null;
|
|
9
|
+
|
|
10
|
+
if (mode === 'acquire-v2') {
|
|
11
|
+
owner = delegatorState || senderState;
|
|
12
|
+
issuer = itx.issuer;
|
|
13
|
+
context.assetOwner = owner;
|
|
14
|
+
} else if (mode === 'acquire-v3') {
|
|
15
|
+
owner = ownerState || { address: itx.owner };
|
|
16
|
+
issuer = itx.issuer;
|
|
17
|
+
} else if (mode === 'mint') {
|
|
18
|
+
owner = ownerState || { address: itx.owner };
|
|
19
|
+
issuer = {
|
|
20
|
+
id: factoryOwnerState.address,
|
|
21
|
+
pk: factoryOwnerState.pk,
|
|
22
|
+
name: factoryOwnerState.moniker,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const minted = mintFromFactory({
|
|
27
|
+
factory: factoryState,
|
|
28
|
+
inputs: factoryInputs,
|
|
29
|
+
owner: owner.address,
|
|
30
|
+
issuer,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (minted.address !== itx.address) {
|
|
34
|
+
return next(new Error('INVALID_ASSET', 'Invalid itx.address: does not match with minted asset address'));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
context.mintedAsset = minted.asset;
|
|
38
|
+
context.mintedAddress = minted.address;
|
|
39
|
+
|
|
40
|
+
return next();
|
|
41
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const Error = require('@ocap/util/lib/error');
|
|
2
|
+
const getListField = require('@ocap/util/lib/get-list-field');
|
|
3
|
+
|
|
4
|
+
module.exports = (context, next) => {
|
|
5
|
+
const { factoryState, expectedFactoryStates = [], assetStates: actualAssetStates = [], itx } = context;
|
|
6
|
+
const { assets: expectedAssets } = factoryState.input;
|
|
7
|
+
|
|
8
|
+
if (expectedAssets.length === 0) {
|
|
9
|
+
return next();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const inputAssets = getListField(itx, 'assets');
|
|
13
|
+
|
|
14
|
+
// Are all input assets found on chain?
|
|
15
|
+
if (inputAssets.length > actualAssetStates.length) {
|
|
16
|
+
return next(new Error('INVALID_ASSET', 'Input asset does not exist on chain'));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// https://github.com/ArcBlock/asset-chain/issues/96
|
|
20
|
+
// If factory.input.assets are all factory
|
|
21
|
+
if (expectedFactoryStates.length === expectedAssets.length) {
|
|
22
|
+
// Just ensure that input assets are minted from any of the factory
|
|
23
|
+
// They do not need to match on number
|
|
24
|
+
if (actualAssetStates.every((x) => x.parent && expectedAssets.includes(x.parent) && !x.consumedTime)) {
|
|
25
|
+
return next();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return next(new Error('INVALID_ASSET', 'Input asset not minted from any of factory.input.assets'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// For specific assets, they must not be consumed
|
|
32
|
+
// For factory, they must exist on chain
|
|
33
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
34
|
+
for (const address of expectedAssets) {
|
|
35
|
+
const plainAsset = actualAssetStates.find((x) => x.address === address);
|
|
36
|
+
if (plainAsset) {
|
|
37
|
+
if (plainAsset.consumedTime) {
|
|
38
|
+
return next(new Error('INVALID_ASSET', `Input asset ${address} already consumed`));
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
const childAsset = actualAssetStates.find((x) => x.parent === address);
|
|
42
|
+
if (!childAsset) {
|
|
43
|
+
return next(new Error('INVALID_ASSET', `Input asset not found ${address}`));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return next();
|
|
49
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const Error = require('@ocap/util/lib/error');
|
|
2
|
+
const getListField = require('@ocap/util/lib/get-list-field');
|
|
3
|
+
|
|
4
|
+
module.exports = (context, next) => {
|
|
5
|
+
const { factoryState, itx } = context;
|
|
6
|
+
const { variables } = factoryState.input;
|
|
7
|
+
|
|
8
|
+
// validate input variables
|
|
9
|
+
const factoryInputs = getListField(itx, 'variables').reduce((acc, x) => {
|
|
10
|
+
acc[x.name] = x.value;
|
|
11
|
+
return acc;
|
|
12
|
+
}, {});
|
|
13
|
+
const missingVariables = variables.filter((x) => x.required && !factoryInputs[x.name]);
|
|
14
|
+
if (missingVariables.length) {
|
|
15
|
+
return next(
|
|
16
|
+
new Error(
|
|
17
|
+
'INVALID_FACTORY_INPUT',
|
|
18
|
+
`Invalid itx.variables: missing required input variable: ${missingVariables.map((x) => x.name).join(', ')}`
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
context.factoryInputs = factoryInputs;
|
|
24
|
+
|
|
25
|
+
return next();
|
|
26
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const Error = require('@ocap/util/lib/error');
|
|
2
|
+
|
|
3
|
+
module.exports = ({ factoryState }, next) => {
|
|
4
|
+
if (factoryState.limit === 0) {
|
|
5
|
+
return next();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (factoryState.numMinted < factoryState.limit) {
|
|
9
|
+
return next();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return next(new Error('EXCEED_MINT_LIMIT', 'This request will exceed factory mint limit'));
|
|
13
|
+
};
|