@ocap/tx-protocols 1.13.63 → 1.13.67
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/execute.js +1 -1
- package/lib/protocols/account/declare.js +2 -1
- package/lib/protocols/account/delegate.js +23 -6
- package/lib/protocols/account/migrate.js +4 -3
- package/lib/protocols/account/revoke-delegate.js +4 -4
- package/lib/protocols/asset/acquire-v2.js +3 -2
- package/lib/protocols/asset/acquire-v3.js +47 -37
- package/lib/protocols/asset/create.js +22 -9
- package/lib/protocols/asset/mint.js +20 -9
- package/lib/protocols/asset/pipes/verify-itx-address.js +3 -3
- package/lib/protocols/asset/pipes/verify-mint-limit.js +1 -1
- package/lib/protocols/asset/update.js +7 -2
- package/lib/protocols/factory/create.js +21 -17
- package/lib/protocols/governance/claim-stake.js +6 -5
- package/lib/protocols/governance/revoke-stake.js +2 -2
- package/lib/protocols/governance/stake.js +3 -2
- package/lib/protocols/rollup/claim-reward.js +4 -3
- package/lib/protocols/rollup/create-block.js +8 -7
- package/lib/protocols/rollup/create.js +18 -5
- package/lib/protocols/rollup/join.js +4 -3
- package/lib/protocols/rollup/leave.js +4 -3
- package/lib/protocols/rollup/pipes/ensure-tx-fee.js +38 -0
- package/lib/protocols/rollup/update.js +2 -1
- package/lib/protocols/token/create.js +22 -11
- package/lib/protocols/token/deposit-v2.js +26 -20
- package/lib/protocols/token/withdraw-v2.js +6 -5
- package/lib/protocols/trade/exchange-v2.js +6 -6
- package/lib/protocols/trade/transfer-v2.js +21 -13
- package/lib/protocols/trade/transfer-v3.js +27 -13
- package/lib/util.js +10 -10
- package/package.json +13 -12
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const uniqBy = require('lodash/uniqBy');
|
|
2
2
|
const groupBy = require('lodash/groupBy');
|
|
3
3
|
const cloneDeep = require('lodash/cloneDeep');
|
|
4
|
+
const Joi = require('@ocap/validator');
|
|
4
5
|
const Error = require('@ocap/util/lib/error');
|
|
5
6
|
const { BN } = require('@ocap/util');
|
|
6
7
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
7
|
-
const { account, stake, evidence, rollupBlock
|
|
8
|
+
const { account, stake, evidence, rollupBlock } = require('@ocap/state');
|
|
8
9
|
|
|
9
10
|
// eslint-disable-next-line global-require
|
|
10
11
|
const debug = require('debug')(`${require('../../../package.json').name}:claim-block-reward`);
|
|
@@ -19,9 +20,9 @@ const runner = new Runner();
|
|
|
19
20
|
const schema = Joi.object({
|
|
20
21
|
rollup: Joi.DID().role('ROLE_ROLLUP').required(),
|
|
21
22
|
blockHeight: Joi.number().integer().greater(0).required(),
|
|
22
|
-
blockHash: Joi.string().regex(Joi.
|
|
23
|
+
blockHash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
23
24
|
evidence: Joi.object({
|
|
24
|
-
hash: Joi.string().regex(Joi.
|
|
25
|
+
hash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
25
26
|
}).required(),
|
|
26
27
|
publisher: Joi.DID().wallet('ethereum').required(),
|
|
27
28
|
data: Joi.any().optional(),
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
const pick = require('lodash/pick');
|
|
3
3
|
const Error = require('@ocap/util/lib/error');
|
|
4
4
|
const MerkleTree = require('@ocap/merkle-tree');
|
|
5
|
+
const Joi = require('@ocap/validator');
|
|
5
6
|
const { formatMessage } = require('@ocap/message');
|
|
6
7
|
const { toStakeAddress } = require('@arcblock/did-util');
|
|
7
8
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
8
|
-
const { account, stake, rollup, rollupBlock, tx: Tx
|
|
9
|
+
const { account, stake, rollup, rollupBlock, tx: Tx } = require('@ocap/state');
|
|
9
10
|
|
|
10
11
|
// eslint-disable-next-line global-require
|
|
11
12
|
const debug = require('debug')(`${require('../../../package.json').name}:create-rollup-block`);
|
|
@@ -19,18 +20,18 @@ const runner = new Runner();
|
|
|
19
20
|
|
|
20
21
|
// 1. verify itx
|
|
21
22
|
const schema = Joi.object({
|
|
22
|
-
hash: Joi.string().regex(Joi.
|
|
23
|
+
hash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
23
24
|
height: Joi.number().integer().greater(0).required(),
|
|
24
|
-
merkleRoot: Joi.string().regex(Joi.
|
|
25
|
+
merkleRoot: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
25
26
|
previousHash: Joi.string().when('height', {
|
|
26
27
|
is: 1,
|
|
27
28
|
then: Joi.string().optional().allow(''),
|
|
28
|
-
otherwise: Joi.string().regex(Joi.
|
|
29
|
+
otherwise: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
29
30
|
}),
|
|
30
|
-
txsHash: Joi.string().regex(Joi.
|
|
31
|
-
txsList: Joi.array().items(Joi.string().regex(Joi.
|
|
31
|
+
txsHash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
32
|
+
txsList: Joi.array().items(Joi.string().regex(Joi.patterns.txHash).required()).min(1).unique().required(),
|
|
32
33
|
proposer: Joi.DID().wallet('ethereum').required(),
|
|
33
|
-
signaturesList: Joi.
|
|
34
|
+
signaturesList: Joi.schemas.multiSig.min(1).required(),
|
|
34
35
|
rollup: Joi.DID().role('ROLE_ROLLUP').required(),
|
|
35
36
|
minReward: Joi.BN().min(0).required(),
|
|
36
37
|
data: Joi.any().optional(),
|
|
@@ -12,6 +12,7 @@ const { toRollupAddress } = require('@arcblock/did-util');
|
|
|
12
12
|
const debug = require('debug')(`${require('../../../package.json').name}:create-rollup`);
|
|
13
13
|
|
|
14
14
|
const { decodeAnySafe } = require('../../util');
|
|
15
|
+
const ensureTxFee = require('./pipes/ensure-tx-fee');
|
|
15
16
|
|
|
16
17
|
const runner = new Runner();
|
|
17
18
|
|
|
@@ -87,7 +88,7 @@ runner.use(async (context, next) => {
|
|
|
87
88
|
const { statedb, itx } = context;
|
|
88
89
|
const exist = await statedb.rollup.existByToken(itx.tokenAddress, context);
|
|
89
90
|
if (exist) {
|
|
90
|
-
return next(new Error('
|
|
91
|
+
return next(new Error('DUPLICATE_ROLLUP', 'Only 1 rollup can be created for the token'));
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
return next();
|
|
@@ -98,20 +99,27 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'rollupState', status:
|
|
|
98
99
|
runner.use(
|
|
99
100
|
pipes.VerifyInfo([
|
|
100
101
|
{
|
|
101
|
-
error: '
|
|
102
|
+
error: 'DUPLICATE_ROLLUP',
|
|
102
103
|
message: 'This rollup already exist on chain',
|
|
103
104
|
fn: (context) => isEmpty(context.rollupState),
|
|
104
105
|
},
|
|
105
106
|
])
|
|
106
107
|
);
|
|
107
108
|
|
|
109
|
+
runner.use(ensureTxFee);
|
|
110
|
+
|
|
108
111
|
// 5. create rollup state
|
|
109
112
|
runner.use(
|
|
110
113
|
async (context, next) => {
|
|
111
|
-
const { tx, formattedItx, rollupData, statedb, senderState } = context;
|
|
114
|
+
const { tx, formattedItx, rollupData, statedb, senderState, senderUpdates, vaultState, vaultUpdates } = context;
|
|
115
|
+
|
|
116
|
+
const [newSenderState, rollupState, newVaultState] = await Promise.all([
|
|
117
|
+
statedb.account.update(
|
|
118
|
+
senderState.address,
|
|
119
|
+
account.update(senderState, { nonce: tx.nonce, ...senderUpdates }, context),
|
|
120
|
+
context
|
|
121
|
+
),
|
|
112
122
|
|
|
113
|
-
const [newSenderState, rollupState] = await Promise.all([
|
|
114
|
-
statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
|
|
115
123
|
statedb.rollup.create(
|
|
116
124
|
formattedItx.address,
|
|
117
125
|
rollup.create(
|
|
@@ -128,6 +136,10 @@ runner.use(
|
|
|
128
136
|
),
|
|
129
137
|
context
|
|
130
138
|
),
|
|
139
|
+
|
|
140
|
+
isEmpty(vaultUpdates)
|
|
141
|
+
? vaultState
|
|
142
|
+
: statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
|
|
131
143
|
]);
|
|
132
144
|
|
|
133
145
|
// FIXME: create-rollup 的时候不能校验 stake,还是在这里创建 stake?如果在这里创建 stake,是否需要多签?
|
|
@@ -135,6 +147,7 @@ runner.use(
|
|
|
135
147
|
|
|
136
148
|
context.senderState = newSenderState;
|
|
137
149
|
context.rollupState = rollupState;
|
|
150
|
+
context.vaultState = newVaultState;
|
|
138
151
|
|
|
139
152
|
debug('create-rollup', rollupState);
|
|
140
153
|
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
const MerkleTree = require('@ocap/merkle-tree');
|
|
3
3
|
const joinUrl = require('url-join');
|
|
4
4
|
const Error = require('@ocap/util/lib/error');
|
|
5
|
+
const Joi = require('@ocap/validator');
|
|
5
6
|
const { BN, toHex } = require('@ocap/util');
|
|
6
7
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
7
|
-
const { account, rollup, stake, evidence
|
|
8
|
+
const { account, rollup, stake, evidence } = require('@ocap/state');
|
|
8
9
|
const { toStakeAddress } = require('@arcblock/did-util');
|
|
9
10
|
const { isEthereumDid } = require('@arcblock/did');
|
|
10
11
|
|
|
@@ -24,9 +25,9 @@ const schema = Joi.object({
|
|
|
24
25
|
.uri({ scheme: [/https?/] })
|
|
25
26
|
.required(),
|
|
26
27
|
evidence: Joi.object({
|
|
27
|
-
hash: Joi.string().regex(Joi.
|
|
28
|
+
hash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
28
29
|
}).required(),
|
|
29
|
-
signaturesList: Joi.
|
|
30
|
+
signaturesList: Joi.schemas.multiSig.min(1).required(),
|
|
30
31
|
data: Joi.any().optional(),
|
|
31
32
|
}).options({ stripUnknown: true, noDefaults: false });
|
|
32
33
|
runner.use(({ itx }, next) => {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/* eslint-disable indent */
|
|
2
2
|
const MerkleTree = require('@ocap/merkle-tree');
|
|
3
3
|
const Error = require('@ocap/util/lib/error');
|
|
4
|
+
const Joi = require('@ocap/validator');
|
|
4
5
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
5
|
-
const { account, rollup, stake, evidence
|
|
6
|
+
const { account, rollup, stake, evidence } = require('@ocap/state');
|
|
6
7
|
const { toStakeAddress } = require('@arcblock/did-util');
|
|
7
8
|
|
|
8
9
|
// eslint-disable-next-line global-require
|
|
@@ -18,9 +19,9 @@ const runner = new Runner();
|
|
|
18
19
|
const schema = Joi.object({
|
|
19
20
|
rollup: Joi.DID().role('ROLE_ROLLUP').required(),
|
|
20
21
|
evidence: Joi.object({
|
|
21
|
-
hash: Joi.string().regex(Joi.
|
|
22
|
+
hash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
22
23
|
}).required(),
|
|
23
|
-
signaturesList: Joi.
|
|
24
|
+
signaturesList: Joi.schemas.multiSig.min(1).required(),
|
|
24
25
|
data: Joi.any().optional(),
|
|
25
26
|
}).options({ stripUnknown: true, noDefaults: false });
|
|
26
27
|
runner.use(({ itx }, next) => {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const Error = require('@ocap/util/lib/error');
|
|
2
|
+
const { fromTokenToUnit, BN } = require('@ocap/util');
|
|
3
|
+
|
|
4
|
+
const { applyTokenUpdates } = require('../../../util');
|
|
5
|
+
|
|
6
|
+
// FIXME: There maybe bug for tx that decrease senderState token balance
|
|
7
|
+
module.exports = async (context, next) => {
|
|
8
|
+
const { config, statedb, txType, senderState } = context;
|
|
9
|
+
const txFee = config.transaction.txFee[txType];
|
|
10
|
+
const vaultState = await statedb.account.get(config.vaults.txFee, context);
|
|
11
|
+
|
|
12
|
+
if (!txFee) {
|
|
13
|
+
context.senderUpdates = {};
|
|
14
|
+
context.vaultUpdates = {};
|
|
15
|
+
context.vaultState = vaultState;
|
|
16
|
+
context.updatedAccounts = [];
|
|
17
|
+
return next();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const expected = fromTokenToUnit(txFee, config.token.decimal);
|
|
21
|
+
const actual = new BN(senderState.tokens[config.token.address] || 0);
|
|
22
|
+
if (actual.lt(expected)) {
|
|
23
|
+
return next(new Error('INSUFFICIENT_FUND', `Insufficient fund to pay for tx fee, expected ${txFee}`));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const tokenChange = { address: config.token.address, value: expected.toString(10) };
|
|
27
|
+
|
|
28
|
+
context.senderUpdates = applyTokenUpdates([tokenChange], senderState, 'sub');
|
|
29
|
+
context.vaultUpdates = applyTokenUpdates([tokenChange], vaultState, 'add');
|
|
30
|
+
context.vaultState = vaultState;
|
|
31
|
+
|
|
32
|
+
context.updatedAccounts = [
|
|
33
|
+
{ address: senderState.address, token: config.token.address, delta: `-${tokenChange.value}`, action: 'fee' },
|
|
34
|
+
{ address: vaultState.address, token: config.token.address, delta: tokenChange.value, action: 'fee' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
return next();
|
|
38
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const Error = require('@ocap/util/lib/error');
|
|
2
|
+
const Joi = require('@ocap/validator');
|
|
2
3
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
3
|
-
const { account, rollup
|
|
4
|
+
const { account, rollup } = require('@ocap/state');
|
|
4
5
|
|
|
5
6
|
// eslint-disable-next-line global-require
|
|
6
7
|
const debug = require('debug')(`${require('../../../package.json').name}:update-rollup`);
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
const isEmpty = require('empty-value');
|
|
2
2
|
const cloneDeep = require('lodash/cloneDeep');
|
|
3
|
+
const Joi = require('@ocap/validator');
|
|
3
4
|
const Error = require('@ocap/util/lib/error');
|
|
4
5
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
5
|
-
const { account, token
|
|
6
|
+
const { account, token } = require('@ocap/state');
|
|
6
7
|
const { toTokenAddress } = require('@arcblock/did-util');
|
|
7
8
|
const { fromTokenToUnit } = require('@ocap/util');
|
|
8
9
|
|
|
9
10
|
// eslint-disable-next-line global-require
|
|
10
11
|
const debug = require('debug')(`${require('../../../package.json').name}:create-token`);
|
|
11
12
|
const { decodeAnySafe } = require('../../util');
|
|
13
|
+
const ensureTxFee = require('../rollup/pipes/ensure-tx-fee');
|
|
12
14
|
|
|
13
15
|
const MAX_TOTAL_SUPPLY = fromTokenToUnit(10000 * 100000000, 18); // 32
|
|
14
16
|
|
|
@@ -26,7 +28,7 @@ const schema = Joi.object({
|
|
|
26
28
|
icon: Joi.string().optional().valid(''),
|
|
27
29
|
totalSupply: Joi.BN().greater(0).max(MAX_TOTAL_SUPPLY).required(),
|
|
28
30
|
initialSupply: Joi.BN().greater(0).max(Joi.ref('totalSupply')).required(),
|
|
29
|
-
foreignToken: Joi.
|
|
31
|
+
foreignToken: Joi.schemas.foreignToken.optional().default(null),
|
|
30
32
|
data: Joi.any().optional(),
|
|
31
33
|
}).options({ stripUnknown: true, noDefaults: false });
|
|
32
34
|
|
|
@@ -56,7 +58,7 @@ runner.use(
|
|
|
56
58
|
);
|
|
57
59
|
|
|
58
60
|
// Ensure sender exist
|
|
59
|
-
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
|
|
61
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
60
62
|
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
|
|
61
63
|
|
|
62
64
|
// Ensure token not exist
|
|
@@ -64,7 +66,7 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'tokenState', table: 't
|
|
|
64
66
|
runner.use(
|
|
65
67
|
pipes.VerifyInfo([
|
|
66
68
|
{
|
|
67
|
-
error: '
|
|
69
|
+
error: 'DUPLICATE_TOKEN',
|
|
68
70
|
message: 'Token address already exists on chain',
|
|
69
71
|
fn: (context) => isEmpty(context.tokenState),
|
|
70
72
|
},
|
|
@@ -75,37 +77,46 @@ runner.use(
|
|
|
75
77
|
runner.use(async (context, next) => {
|
|
76
78
|
const { symbol } = context.config.token;
|
|
77
79
|
if (symbol.toLowerCase() === context.itx.symbol.toLowerCase()) {
|
|
78
|
-
return next(new Error('
|
|
80
|
+
return next(new Error('DUPLICATE_SYMBOL', `Token symbol can not be ${symbol}`));
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
const exist = await context.statedb.token.existBySymbol(context.itx.symbol, context);
|
|
82
84
|
if (exist) {
|
|
83
|
-
return next(new Error('
|
|
85
|
+
return next(new Error('DUPLICATE_SYMBOL', 'Token symbol already exists'));
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
return next();
|
|
87
89
|
});
|
|
88
90
|
|
|
91
|
+
runner.use(ensureTxFee);
|
|
92
|
+
|
|
89
93
|
// Update sender state, token state
|
|
90
94
|
runner.use(
|
|
91
95
|
async (context, next) => {
|
|
92
|
-
const { tx, itx, statedb, senderState } = context;
|
|
96
|
+
const { tx, itx, statedb, senderState, senderUpdates, vaultState, vaultUpdates } = context;
|
|
93
97
|
const data = decodeAnySafe(itx.data);
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
// We are definitely creating a different token, so it is safe to set tokens to initial supply
|
|
100
|
+
senderUpdates.tokens = senderUpdates.tokens || {};
|
|
101
|
+
senderUpdates.tokens[itx.address] = itx.initialSupply;
|
|
97
102
|
|
|
98
|
-
const [newSenderState, tokenState] = await Promise.all([
|
|
103
|
+
const [newSenderState, tokenState, newVaultState] = await Promise.all([
|
|
99
104
|
statedb.account.update(
|
|
100
105
|
senderState.address,
|
|
101
|
-
account.update(senderState, {
|
|
106
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
102
107
|
context
|
|
103
108
|
),
|
|
109
|
+
|
|
104
110
|
statedb.token.create(itx.address, token.create({ ...itx, data, issuer: senderState.address }, context), context),
|
|
111
|
+
|
|
112
|
+
isEmpty(vaultUpdates)
|
|
113
|
+
? vaultState
|
|
114
|
+
: statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
|
|
105
115
|
]);
|
|
106
116
|
|
|
107
117
|
context.senderState = newSenderState;
|
|
108
118
|
context.tokenState = tokenState;
|
|
119
|
+
context.vaultState = newVaultState;
|
|
109
120
|
|
|
110
121
|
debug('create token v2', tokenState);
|
|
111
122
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* eslint-disable indent */
|
|
2
2
|
const Error = require('@ocap/util/lib/error');
|
|
3
|
+
const Joi = require('@ocap/validator');
|
|
3
4
|
const getListField = require('@ocap/util/lib/get-list-field');
|
|
4
5
|
const { BN, fromUnitToToken } = require('@ocap/util');
|
|
5
6
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
6
|
-
const { account, stake, evidence
|
|
7
|
+
const { account, stake, evidence } = require('@ocap/state');
|
|
7
8
|
const { toStakeAddress } = require('@arcblock/did-util');
|
|
8
9
|
|
|
9
10
|
// eslint-disable-next-line global-require
|
|
@@ -11,14 +12,14 @@ const debug = require('debug')(`${require('../../../package.json').name}:deposit
|
|
|
11
12
|
|
|
12
13
|
const VerifySigners = require('../rollup/pipes/verify-signers');
|
|
13
14
|
const VerifyPaused = require('../rollup/pipes/verify-paused');
|
|
14
|
-
const { applyTokenUpdates, getTxFee, getRewardLocker } = require('../../util');
|
|
15
|
+
const { applyTokenUpdates, getTxFee, getBNSum, getRewardLocker } = require('../../util');
|
|
15
16
|
|
|
16
17
|
const schema = Joi.object({
|
|
17
|
-
token: Joi.
|
|
18
|
+
token: Joi.schemas.tokenInput.required(),
|
|
18
19
|
to: Joi.DID().wallet('ethereum').required(),
|
|
19
20
|
proposer: Joi.DID().wallet('ethereum').required(),
|
|
20
21
|
evidence: Joi.object({
|
|
21
|
-
hash: Joi.string().regex(Joi.
|
|
22
|
+
hash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
22
23
|
}).required(),
|
|
23
24
|
rollup: Joi.DID().role('ROLE_ROLLUP').required(),
|
|
24
25
|
actualFee: Joi.BN().min(0).required(),
|
|
@@ -138,15 +139,15 @@ runner.use((context, next) => {
|
|
|
138
139
|
const { itx, rollupState, tokenState } = context;
|
|
139
140
|
const { depositFeeRate, maxDepositFee, minDepositFee } = rollupState;
|
|
140
141
|
|
|
141
|
-
|
|
142
|
+
const { reward } = getTxFee({
|
|
142
143
|
amount: itx.token.value,
|
|
143
144
|
feeRate: depositFeeRate,
|
|
144
145
|
maxFee: maxDepositFee,
|
|
145
146
|
minFee: minDepositFee,
|
|
146
147
|
});
|
|
147
148
|
|
|
148
|
-
if (new BN(itx.actualFee).lt(new BN(
|
|
149
|
-
const expected = fromUnitToToken(
|
|
149
|
+
if (new BN(itx.actualFee).lt(new BN(reward))) {
|
|
150
|
+
const expected = fromUnitToToken(reward, tokenState.decimal);
|
|
150
151
|
const actual = fromUnitToToken(itx.actualFee, tokenState.decimal);
|
|
151
152
|
return next(new Error('INVALID_TX', `itx.actualFee too low, expect at least ${expected}, got ${actual}`));
|
|
152
153
|
}
|
|
@@ -155,28 +156,33 @@ runner.use((context, next) => {
|
|
|
155
156
|
});
|
|
156
157
|
|
|
157
158
|
// 7. verify sender and signer states
|
|
158
|
-
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: '
|
|
159
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
|
|
159
160
|
runner.use(pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
|
|
160
161
|
runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
|
|
161
162
|
|
|
162
163
|
// 8. update state: the token minting is done when deposit finalized in rollup-block
|
|
163
164
|
runner.use(
|
|
164
165
|
async (context, next) => {
|
|
165
|
-
const { tx, itx,
|
|
166
|
+
const { tx, itx, statedb, senderState, stakeState, stakeAddress, lockerState, lockerAddress } = context;
|
|
166
167
|
|
|
167
|
-
const
|
|
168
|
-
const
|
|
168
|
+
const user = itx.token.value;
|
|
169
|
+
const fee = itx.actualFee;
|
|
170
|
+
const total = getBNSum(user, fee);
|
|
171
|
+
|
|
172
|
+
const stakeUpdates = applyTokenUpdates([{ address: itx.token.address, value: total }], stakeState, 'sub');
|
|
173
|
+
const senderUpdates = applyTokenUpdates([{ address: itx.token.address, value: user }], senderState || {}, 'add');
|
|
169
174
|
const lockerUpdates = applyTokenUpdates(
|
|
170
|
-
[{ address: itx.token.address, value:
|
|
175
|
+
[{ address: itx.token.address, value: fee }],
|
|
171
176
|
lockerState || { tokens: {} },
|
|
172
177
|
'add'
|
|
173
178
|
);
|
|
174
179
|
|
|
180
|
+
const sender = senderState ? senderState.address : tx.from;
|
|
175
181
|
const [newSenderState, newStakeState, newLockerState, evidenceState] = await Promise.all([
|
|
176
|
-
//
|
|
177
|
-
statedb.account.
|
|
178
|
-
senderState
|
|
179
|
-
account.
|
|
182
|
+
// updateOrCreate user account
|
|
183
|
+
statedb.account.updateOrCreate(
|
|
184
|
+
senderState,
|
|
185
|
+
account.updateOrCreate(senderState, { address: sender, nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
180
186
|
context
|
|
181
187
|
),
|
|
182
188
|
|
|
@@ -218,11 +224,11 @@ runner.use(
|
|
|
218
224
|
|
|
219
225
|
context.updatedAccounts = [
|
|
220
226
|
// stake for tx proposer is decreased
|
|
221
|
-
{ address: stakeAddress, token: itx.token.address, delta: `-${
|
|
227
|
+
{ address: stakeAddress, token: itx.token.address, delta: `-${total}`, action: 'unlock' },
|
|
222
228
|
// mint to depositor from stake
|
|
223
|
-
{ address:
|
|
224
|
-
//
|
|
225
|
-
{ address: lockerAddress, token: itx.token.address, delta:
|
|
229
|
+
{ address: sender, token: itx.token.address, delta: user, action: 'unlock' },
|
|
230
|
+
// tx fee is locked for later claiming
|
|
231
|
+
{ address: lockerAddress, token: itx.token.address, delta: fee, action: 'pending' },
|
|
226
232
|
];
|
|
227
233
|
|
|
228
234
|
debug('deposit-token-v2', itx);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* eslint-disable indent */
|
|
2
2
|
const Error = require('@ocap/util/lib/error');
|
|
3
|
+
const Joi = require('@ocap/validator');
|
|
3
4
|
const getListField = require('@ocap/util/lib/get-list-field');
|
|
4
5
|
const { BN, fromUnitToToken } = require('@ocap/util');
|
|
5
6
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
6
|
-
const { account, stake
|
|
7
|
+
const { account, stake } = require('@ocap/state');
|
|
7
8
|
const { toStakeAddress } = require('@arcblock/did-util');
|
|
8
9
|
|
|
9
10
|
// eslint-disable-next-line global-require
|
|
@@ -15,7 +16,7 @@ const { applyTokenUpdates, getTxFee, getBNSum, getRewardLocker } = require('../.
|
|
|
15
16
|
const verifyMultiSigV2 = pipes.VerifyMultiSigV2({ signersKey: 'signers' });
|
|
16
17
|
|
|
17
18
|
const schema = Joi.object({
|
|
18
|
-
token: Joi.
|
|
19
|
+
token: Joi.schemas.tokenInput.required(),
|
|
19
20
|
to: Joi.DID().wallet('ethereum').required(),
|
|
20
21
|
rollup: Joi.DID().role('ROLE_ROLLUP').required(),
|
|
21
22
|
proposer: Joi.DID().wallet('ethereum').optional().allow('').default(''),
|
|
@@ -99,15 +100,15 @@ runner.use((context, next) => {
|
|
|
99
100
|
|
|
100
101
|
const isFixedFee = new BN(itx.maxFee).isZero();
|
|
101
102
|
if (isFixedFee) {
|
|
102
|
-
|
|
103
|
+
const { reward } = getTxFee({
|
|
103
104
|
amount: itx.token.value,
|
|
104
105
|
feeRate: withdrawFeeRate,
|
|
105
106
|
maxFee: maxWithdrawFee,
|
|
106
107
|
minFee: minWithdrawFee,
|
|
107
108
|
});
|
|
108
109
|
|
|
109
|
-
if (new BN(itx.actualFee).lt(new BN(
|
|
110
|
-
const expected = fromUnitToToken(
|
|
110
|
+
if (new BN(itx.actualFee).lt(new BN(reward))) {
|
|
111
|
+
const expected = fromUnitToToken(reward, tokenState.decimal);
|
|
111
112
|
const actual = fromUnitToToken(itx.actualFee, tokenState.decimal);
|
|
112
113
|
return next(new Error('INVALID_TX', `itx.actualFee too low, expect at least ${expected}, got ${actual}`));
|
|
113
114
|
}
|
|
@@ -81,8 +81,8 @@ runner.use(pipes.VerifyListSize({ listKey: ['senderTokens', 'receiverTokens'] })
|
|
|
81
81
|
|
|
82
82
|
// TODO: anti-replay-exchange-attack
|
|
83
83
|
|
|
84
|
-
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
|
|
85
|
-
runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK' }));
|
|
84
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
85
|
+
runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
|
|
86
86
|
runner.use(pipes.VerifyDelegation({ type: 'signature', signerKey: 'senderState', delegatorKey: 'delegatorState' }));
|
|
87
87
|
runner.use(pipes.ExtractSigner({ signerKey: 'signerStates', delegatorKey: 'delegatorStates' }));
|
|
88
88
|
runner.use(
|
|
@@ -94,9 +94,9 @@ runner.use(
|
|
|
94
94
|
})
|
|
95
95
|
);
|
|
96
96
|
|
|
97
|
-
runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN' }));
|
|
97
|
+
runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
|
|
98
98
|
|
|
99
|
-
runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' }));
|
|
99
|
+
runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE', table: 'account' })); // prettier-ignore
|
|
100
100
|
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState', signerKey: 'signerStates' }));
|
|
101
101
|
runner.use((context, next) => {
|
|
102
102
|
context.senderTokenConditions = {
|
|
@@ -114,11 +114,11 @@ runner.use(pipes.VerifyTokenBalance({ ownerKey: 'receiverState', conditionKey: '
|
|
|
114
114
|
|
|
115
115
|
runner.use(pipes.AntiLandAttack({ senderState: 'senderState', receiverState: 'receiverState' }));
|
|
116
116
|
|
|
117
|
-
runner.use(pipes.ExtractState({ from: 'senderAssets', to: 'priv.senderAssets', status: 'INVALID_ASSET' }));
|
|
117
|
+
runner.use(pipes.ExtractState({ from: 'senderAssets', to: 'priv.senderAssets', status: 'INVALID_ASSET', table: 'asset' })); // prettier-ignore
|
|
118
118
|
runner.use(pipes.VerifyTransferrable({ assets: 'priv.senderAssets' }));
|
|
119
119
|
runner.use(pipes.VerifyUpdater({ assetKey: 'priv.senderAssets', ownerKey: 'senderState' }));
|
|
120
120
|
|
|
121
|
-
runner.use(pipes.ExtractState({ from: 'receiverAssets', to: 'priv.receiverAssets', status: 'INVALID_ASSET' }));
|
|
121
|
+
runner.use(pipes.ExtractState({ from: 'receiverAssets', to: 'priv.receiverAssets', status: 'INVALID_ASSET', table: 'asset' })); // prettier-ignore
|
|
122
122
|
runner.use(pipes.VerifyTransferrable({ assets: 'priv.receiverAssets' }));
|
|
123
123
|
runner.use(pipes.VerifyUpdater({ assetKey: 'priv.receiverAssets', ownerKey: 'receiverState' }));
|
|
124
124
|
|
|
@@ -62,10 +62,10 @@ runner.use((context, next) => {
|
|
|
62
62
|
next();
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
|
|
65
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
66
66
|
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
|
|
67
67
|
|
|
68
|
-
runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN' }));
|
|
68
|
+
runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
|
|
69
69
|
runner.use((context, next) => {
|
|
70
70
|
context.tokenConditions = {
|
|
71
71
|
owner: context.senderState.address,
|
|
@@ -75,24 +75,32 @@ runner.use((context, next) => {
|
|
|
75
75
|
});
|
|
76
76
|
runner.use(pipes.VerifyTokenBalance({ ownerKey: 'senderState', conditionKey: 'tokenConditions' }));
|
|
77
77
|
|
|
78
|
-
runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK' }));
|
|
78
|
+
runner.use(pipes.ExtractState({ from: 'tx.delegator', to: 'delegatorState', status: 'OK', table: 'account' }));
|
|
79
79
|
runner.use(pipes.VerifyDelegation({ type: 'signature', signerKey: 'senderState', delegatorKey: 'delegatorState' }));
|
|
80
80
|
|
|
81
|
-
runner.use(pipes.ExtractReceiver({ from: 'itx.to' }));
|
|
82
|
-
runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: '
|
|
81
|
+
runner.use(pipes.ExtractReceiver({ from: 'itx.to', to: 'receiver' }));
|
|
82
|
+
runner.use(pipes.ExtractState({ from: 'receiver', to: 'receiverState', status: 'OK', table: 'account' }));
|
|
83
83
|
runner.use(pipes.AntiLandAttack({ senderState: 'senderState', receiverState: 'receiverState' }));
|
|
84
84
|
|
|
85
|
-
runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'INVALID_ASSET' }));
|
|
85
|
+
runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'INVALID_ASSET', table: 'asset' }));
|
|
86
86
|
runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
|
|
87
87
|
runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'senderState' }));
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
// transfer assets to new owner
|
|
90
|
+
runner.use((context, next) => {
|
|
91
|
+
const { itx, receiverState } = context;
|
|
92
|
+
context.receiverAddr = receiverState ? receiverState.address : itx.to;
|
|
93
|
+
return next();
|
|
94
|
+
});
|
|
95
|
+
runner.use(pipes.UpdateOwner({ assets: 'assetStates', owner: 'receiverAddr' }));
|
|
96
|
+
|
|
97
|
+
// update statedb: transfer tokens to new owner
|
|
90
98
|
runner.use(
|
|
91
99
|
async (context, next) => {
|
|
92
|
-
const { tx, itx, tokens, senderState, receiverState, statedb } = context;
|
|
100
|
+
const { tx, itx, tokens, senderState, receiverAddr, receiverState, statedb } = context;
|
|
93
101
|
|
|
94
102
|
const { tokens: senderTokens = {} } = senderState;
|
|
95
|
-
const { tokens: receiverTokens = {} } = receiverState;
|
|
103
|
+
const { tokens: receiverTokens = {} } = receiverState || {};
|
|
96
104
|
for (const token of tokens) {
|
|
97
105
|
const delta = new BN(token.value);
|
|
98
106
|
senderTokens[token.address] = new BN(senderTokens[token.address]).sub(delta).toString();
|
|
@@ -103,14 +111,14 @@ runner.use(
|
|
|
103
111
|
// Update sender state
|
|
104
112
|
statedb.account.update(
|
|
105
113
|
senderState.address,
|
|
106
|
-
account.update(senderState, { nonce: tx.nonce, tokens: senderTokens }, context),
|
|
114
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, tokens: senderTokens }, context),
|
|
107
115
|
context
|
|
108
116
|
),
|
|
109
117
|
|
|
110
118
|
// Update receiver state
|
|
111
|
-
statedb.account.
|
|
112
|
-
receiverState
|
|
113
|
-
account.
|
|
119
|
+
statedb.account.updateOrCreate(
|
|
120
|
+
receiverState,
|
|
121
|
+
account.updateOrCreate(receiverState, { address: receiverAddr, tokens: receiverTokens }, context),
|
|
114
122
|
context
|
|
115
123
|
),
|
|
116
124
|
]);
|