@ocap/tx-protocols 1.17.23 → 1.18.1
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/pipes/ensure-cost.js +140 -0
- package/lib/pipes/ensure-gas.js +53 -0
- package/lib/protocols/account/delegate.js +30 -3
- package/lib/protocols/account/migrate.js +15 -1
- package/lib/protocols/account/revoke-delegate.js +14 -2
- package/lib/protocols/asset/acquire-v2.js +29 -2
- package/lib/protocols/asset/acquire-v3.js +34 -2
- package/lib/protocols/asset/create.js +9 -10
- package/lib/protocols/asset/mint.js +28 -2
- package/lib/protocols/asset/pipes/exec-mint-hook.js +1 -1
- package/lib/protocols/asset/update.js +11 -2
- package/lib/protocols/factory/create.js +8 -9
- package/lib/protocols/governance/claim-stake.js +41 -4
- package/lib/protocols/governance/revoke-stake.js +14 -3
- package/lib/protocols/governance/stake.js +87 -5
- package/lib/protocols/rollup/claim-reward.js +46 -16
- package/lib/protocols/rollup/create-block.js +35 -3
- package/lib/protocols/rollup/create.js +8 -18
- package/lib/protocols/rollup/join.js +25 -2
- package/lib/protocols/rollup/leave.js +15 -2
- package/lib/protocols/rollup/migrate-contract.js +13 -2
- package/lib/protocols/rollup/migrate-token.js +13 -2
- package/lib/protocols/rollup/pause.js +13 -2
- package/lib/protocols/rollup/resume.js +13 -2
- package/lib/protocols/rollup/update.js +13 -2
- package/lib/protocols/token/create.js +19 -9
- package/lib/protocols/token/deposit-v2.js +46 -6
- package/lib/protocols/token/withdraw-v2.js +45 -6
- package/lib/protocols/trade/exchange-v2.js +38 -3
- package/lib/protocols/trade/transfer-v2.js +29 -2
- package/lib/protocols/trade/transfer-v3.js +34 -1
- package/lib/util.js +58 -8
- package/package.json +14 -13
- package/lib/protocols/rollup/pipes/ensure-service-fee.js +0 -37
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const noop = require('lodash/noop');
|
|
2
|
+
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
3
|
+
const { fromTokenToUnit, BN } = require('@ocap/util');
|
|
4
|
+
const JWT = require('@arcblock/jwt');
|
|
5
|
+
const { account } = require('@ocap/state');
|
|
6
|
+
const { toAddress } = require('@arcblock/did');
|
|
7
|
+
const { toStakeAddress } = require('@arcblock/did-util');
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line global-require
|
|
10
|
+
const debug = require('debug')(`${require('../../package.json').name}:pipes:ensure-cost`);
|
|
11
|
+
|
|
12
|
+
const { applyTokenUpdates, isGasStakeValid } = require('../util');
|
|
13
|
+
|
|
14
|
+
// We have a layered transaction cost design
|
|
15
|
+
// - gas: charged for every tx for creating/updating states on the ledger
|
|
16
|
+
// - service fee: charged for creating tokens/assets/factories/rollups
|
|
17
|
+
// - protocol fee: charged for moving tokens across the bridge
|
|
18
|
+
module.exports = function CreateEnsureTxCostPipe({ attachSenderChanges = true } = {}) {
|
|
19
|
+
return async function EnsureTxCost(context, next) {
|
|
20
|
+
// TODO: we are using the sender as gas payer, this may change in future
|
|
21
|
+
const { config, statedb, txType, senderState, gasEstimate, gasVaultState, totalGas } = context;
|
|
22
|
+
const feeVaultState = await statedb.account.get(config.vaults.txFee, context);
|
|
23
|
+
|
|
24
|
+
// Get service fee and gas fee
|
|
25
|
+
const changes = {};
|
|
26
|
+
let txCost = new BN(0);
|
|
27
|
+
const txFee = config.transaction.txFee[txType];
|
|
28
|
+
if (txFee) {
|
|
29
|
+
const totalFee = fromTokenToUnit(txFee, config.token.decimal);
|
|
30
|
+
txCost = txCost.add(totalFee);
|
|
31
|
+
changes.fee = { address: config.token.address, value: totalFee.toString(10) };
|
|
32
|
+
context.feeVaultChange = changes.fee;
|
|
33
|
+
}
|
|
34
|
+
if (totalGas.gt(new BN(0))) {
|
|
35
|
+
txCost = txCost.add(totalGas);
|
|
36
|
+
changes.gas = { address: config.token.address, value: totalGas.toString(10) };
|
|
37
|
+
context.gasVaultChange = changes.gas;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// verify gas staking headers
|
|
41
|
+
const { tx, gasStakeHeaders = {} } = context;
|
|
42
|
+
const { token, pk } = gasStakeHeaders || {};
|
|
43
|
+
const gasStake = {};
|
|
44
|
+
if (token && pk) {
|
|
45
|
+
if (JWT.verify(token, pk) === false) {
|
|
46
|
+
return next(new Error('INVALID_GAS_HEADER', 'Gas headers can not be verified'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const decoded = JWT.decode(token);
|
|
50
|
+
gasStake.owner = toAddress(decoded.iss);
|
|
51
|
+
gasStake.stakeId = toStakeAddress(gasStake.owner, gasStake.owner);
|
|
52
|
+
} else {
|
|
53
|
+
gasStake.owner = tx.from;
|
|
54
|
+
gasStake.stakeId = toStakeAddress(gasStake.owner, gasStake.owner);
|
|
55
|
+
}
|
|
56
|
+
gasStake.state = await statedb.stake.get(gasStake.stakeId, context);
|
|
57
|
+
gasStake.valid = isGasStakeValid(gasStake.state, config);
|
|
58
|
+
context.gasStake = gasStake;
|
|
59
|
+
|
|
60
|
+
// If we have someone to pay for this tx
|
|
61
|
+
if (senderState && !gasStake.valid) {
|
|
62
|
+
const expected = new BN(gasEstimate.payment || 0).add(txCost);
|
|
63
|
+
const actual = new BN(senderState.tokens[config.token.address] || 0);
|
|
64
|
+
if (actual.lt(expected)) {
|
|
65
|
+
return next(
|
|
66
|
+
new Error(
|
|
67
|
+
'INSUFFICIENT_FUND',
|
|
68
|
+
`Insufficient fund to pay for tx cost from ${senderState.address}, expected ${expected}, got ${actual}`
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// to be merged into later pipe
|
|
74
|
+
context.senderUpdates = applyTokenUpdates(
|
|
75
|
+
[{ address: config.token.address, value: txCost.toString(10) }],
|
|
76
|
+
senderState,
|
|
77
|
+
'sub'
|
|
78
|
+
);
|
|
79
|
+
context.senderChange = {
|
|
80
|
+
address: senderState.address,
|
|
81
|
+
token: config.token.address,
|
|
82
|
+
delta: `-${txCost.toString(10)}`,
|
|
83
|
+
action: 'fee',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
debug({ changes, senderUpdates: context.senderUpdates });
|
|
87
|
+
|
|
88
|
+
// to be called in later pipes
|
|
89
|
+
context.updateVaults = async function updateVaults() {
|
|
90
|
+
const [newFeeVaultState, newGasVaultState] = await Promise.all([
|
|
91
|
+
changes.fee
|
|
92
|
+
? statedb.account.update(
|
|
93
|
+
feeVaultState.address,
|
|
94
|
+
account.update(feeVaultState, applyTokenUpdates([changes.fee], feeVaultState, 'add'), context),
|
|
95
|
+
context
|
|
96
|
+
)
|
|
97
|
+
: Promise.resolve(feeVaultState),
|
|
98
|
+
changes.gas
|
|
99
|
+
? statedb.account.update(
|
|
100
|
+
gasVaultState.address,
|
|
101
|
+
account.update(gasVaultState, applyTokenUpdates([changes.gas], gasVaultState, 'add'), context),
|
|
102
|
+
context
|
|
103
|
+
)
|
|
104
|
+
: Promise.resolve(gasVaultState),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
context.feeVaultState = newFeeVaultState;
|
|
108
|
+
context.gasVaultState = newGasVaultState;
|
|
109
|
+
|
|
110
|
+
context.updatedAccounts = context.updatedAccounts || [];
|
|
111
|
+
if (changes.fee) {
|
|
112
|
+
context.updatedAccounts.push({
|
|
113
|
+
address: feeVaultState.address,
|
|
114
|
+
token: config.token.address,
|
|
115
|
+
delta: changes.fee.value,
|
|
116
|
+
action: 'fee',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (changes.gas) {
|
|
120
|
+
context.updatedAccounts.push({
|
|
121
|
+
address: gasVaultState.address,
|
|
122
|
+
token: config.token.address,
|
|
123
|
+
delta: changes.gas.value,
|
|
124
|
+
action: 'gas',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (attachSenderChanges) {
|
|
128
|
+
context.updatedAccounts.push(context.senderChange);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
} else {
|
|
132
|
+
context.senderUpdates = {};
|
|
133
|
+
context.senderChange = null;
|
|
134
|
+
context.updateVaults = noop;
|
|
135
|
+
context.updatedAccounts = [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return next();
|
|
139
|
+
};
|
|
140
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
2
|
+
const { BN, hexToNumber } = require('@ocap/util');
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line global-require
|
|
5
|
+
const debug = require('debug')(`${require('../../package.json').name}:pipes:estimate-gas`);
|
|
6
|
+
|
|
7
|
+
// Must be called after all state ready
|
|
8
|
+
// Must be called before creating/updating any state
|
|
9
|
+
// estimateTxGas should be a function that returns the following data:
|
|
10
|
+
// - create: state creating ops
|
|
11
|
+
// - update: state updating ops
|
|
12
|
+
// - payment: sender payment amount of the gas token
|
|
13
|
+
module.exports = function CreateGasEnsureFn(estimateTxGas) {
|
|
14
|
+
return async function EnsureTxGas(context, next) {
|
|
15
|
+
const { txHash, statedb, config } = context;
|
|
16
|
+
|
|
17
|
+
// total gas in native token
|
|
18
|
+
const { txGas } = config.transaction;
|
|
19
|
+
let totalGas = new BN(txGas.price * txGas.dataStorage * context.txSize);
|
|
20
|
+
const estimate = estimateTxGas(context);
|
|
21
|
+
totalGas = totalGas.add(new BN(txGas.price * txGas.createState * (estimate.create + 1))); // 1 = tx insert
|
|
22
|
+
totalGas = totalGas.add(new BN(txGas.price * txGas.updateState * estimate.update));
|
|
23
|
+
|
|
24
|
+
// gas receiver address
|
|
25
|
+
const { txGas: gasVaults } = config.vaults;
|
|
26
|
+
const index = hexToNumber(txHash.slice(txHash.length - 4)) % gasVaults.length;
|
|
27
|
+
const gasVault = gasVaults[index];
|
|
28
|
+
|
|
29
|
+
// extract gasReceiver
|
|
30
|
+
if (gasVault) {
|
|
31
|
+
const gasVaultState = await statedb.account.get(gasVault, context);
|
|
32
|
+
if (!gasVaultState) {
|
|
33
|
+
return next(new Error('INVALID_GAS_VAULT', `Can not find gas vault state: ${gasVault}`));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
context.gasVaultState = gasVaultState;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
debug({
|
|
40
|
+
txType: context.txType,
|
|
41
|
+
txHash: context.txHash,
|
|
42
|
+
txSize: context.txSize,
|
|
43
|
+
totalGas: totalGas.toString(10),
|
|
44
|
+
gasVault,
|
|
45
|
+
estimate,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
context.totalGas = totalGas;
|
|
49
|
+
context.gasEstimate = estimate;
|
|
50
|
+
|
|
51
|
+
return next();
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -10,6 +10,9 @@ const { toDelegateAddress } = require('@arcblock/did-util');
|
|
|
10
10
|
// eslint-disable-next-line global-require
|
|
11
11
|
const debug = require('debug')(`${require('../../../package.json').name}:delegate`);
|
|
12
12
|
|
|
13
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
14
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
15
|
+
|
|
13
16
|
const runner = new Runner();
|
|
14
17
|
|
|
15
18
|
runner.use(pipes.VerifyMultiSig(0));
|
|
@@ -62,17 +65,39 @@ runner.use(
|
|
|
62
65
|
);
|
|
63
66
|
|
|
64
67
|
// Ensure sender/receiver exist
|
|
65
|
-
runner.use(
|
|
68
|
+
runner.use(
|
|
69
|
+
pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })
|
|
70
|
+
);
|
|
66
71
|
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
|
|
67
72
|
runner.use(pipes.ExtractState({ from: 'itx.to', to: 'receiverState', status: 'OK', table: 'account' }));
|
|
68
73
|
|
|
69
74
|
// Extract delegation
|
|
70
75
|
runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', table: 'delegation', status: 'OK' }));
|
|
71
76
|
|
|
77
|
+
// Ensure tx fee and gas
|
|
78
|
+
runner.use(
|
|
79
|
+
EnsureTxGas((context) => {
|
|
80
|
+
const result = { create: 0, update: 1, payment: 0 };
|
|
81
|
+
if (context.receiverState) {
|
|
82
|
+
result.update += 1;
|
|
83
|
+
} else {
|
|
84
|
+
result.create += 1;
|
|
85
|
+
}
|
|
86
|
+
if (context.delegationState) {
|
|
87
|
+
result.update += 1;
|
|
88
|
+
} else {
|
|
89
|
+
result.create += 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return result;
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
96
|
+
|
|
72
97
|
// Create delegation state
|
|
73
98
|
runner.use(
|
|
74
99
|
async (context, next) => {
|
|
75
|
-
const { tx, itx, ops, statedb, senderState, receiverState, delegationState } = context;
|
|
100
|
+
const { tx, itx, ops, statedb, senderState, receiverState, delegationState, senderUpdates, updateVaults } = context;
|
|
76
101
|
|
|
77
102
|
const opsObj = ops.reduce(
|
|
78
103
|
(acc, x) => {
|
|
@@ -94,7 +119,7 @@ runner.use(
|
|
|
94
119
|
// update sender
|
|
95
120
|
statedb.account.updateOrCreate(
|
|
96
121
|
senderState,
|
|
97
|
-
account.updateOrCreate(senderState, { address: sender, nonce: tx.nonce, pk: tx.pk }, context),
|
|
122
|
+
account.updateOrCreate(senderState, { ...senderUpdates, address: sender, nonce: tx.nonce, pk: tx.pk }, context),
|
|
98
123
|
context
|
|
99
124
|
),
|
|
100
125
|
|
|
@@ -107,6 +132,8 @@ runner.use(
|
|
|
107
132
|
delegationState
|
|
108
133
|
? statedb.delegation.update(itx.address, delegation.update(delegationState, { ...itx, ops: opsObj }, context), context) // prettier-ignore
|
|
109
134
|
: statedb.delegation.create(itx.address, delegation.create({ ...itx, ops: opsObj }, context), context),
|
|
135
|
+
|
|
136
|
+
updateVaults(),
|
|
110
137
|
]);
|
|
111
138
|
|
|
112
139
|
debug(delegationState ? 'update' : 'create', newDelegationState);
|
|
@@ -8,6 +8,9 @@ const { account } = require('@ocap/state');
|
|
|
8
8
|
// eslint-disable-next-line global-require
|
|
9
9
|
const debug = require('debug')(`${require('../../../package.json').name}:migrate`);
|
|
10
10
|
|
|
11
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
12
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
13
|
+
|
|
11
14
|
const runner = new Runner();
|
|
12
15
|
|
|
13
16
|
runner.use(pipes.VerifyMultiSig(0));
|
|
@@ -39,10 +42,18 @@ runner.use(
|
|
|
39
42
|
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
40
43
|
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
|
|
41
44
|
|
|
45
|
+
// Ensure tx fee and gas
|
|
46
|
+
runner.use(
|
|
47
|
+
EnsureTxGas((context) => {
|
|
48
|
+
return { create: 1, update: getRelatedAddresses(context.senderState).length, payment: 0 };
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
52
|
+
|
|
42
53
|
// Create account state, and update old accounts
|
|
43
54
|
runner.use(
|
|
44
55
|
async (context, next) => {
|
|
45
|
-
const { tx, itx, statedb } = context;
|
|
56
|
+
const { tx, itx, statedb, senderUpdates, updateVaults } = context;
|
|
46
57
|
|
|
47
58
|
const sender = context.senderState;
|
|
48
59
|
const receiver = await statedb.account.get(itx.address, context);
|
|
@@ -58,6 +69,7 @@ runner.use(
|
|
|
58
69
|
account.create(
|
|
59
70
|
{
|
|
60
71
|
...sender, // copy all old account data to new account
|
|
72
|
+
...senderUpdates,
|
|
61
73
|
address: itx.address,
|
|
62
74
|
pk: itx.pk,
|
|
63
75
|
migratedFrom: [sender.address],
|
|
@@ -86,6 +98,8 @@ runner.use(
|
|
|
86
98
|
);
|
|
87
99
|
debug('old accounts', states);
|
|
88
100
|
|
|
101
|
+
await updateVaults();
|
|
102
|
+
|
|
89
103
|
// Update context
|
|
90
104
|
context.receiverState = newAccount;
|
|
91
105
|
context.senderState = states.find((x) => x.address === sender.from);
|
|
@@ -10,6 +10,9 @@ const { toDelegateAddress } = require('@arcblock/did-util');
|
|
|
10
10
|
// eslint-disable-next-line global-require
|
|
11
11
|
const debug = require('debug')(`${require('../../../package.json').name}:delegate`);
|
|
12
12
|
|
|
13
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
14
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
15
|
+
|
|
13
16
|
const runner = new Runner();
|
|
14
17
|
|
|
15
18
|
runner.use(pipes.VerifyMultiSig(0));
|
|
@@ -59,10 +62,14 @@ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', stat
|
|
|
59
62
|
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
60
63
|
runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
|
|
61
64
|
|
|
65
|
+
// Ensure tx fee and gas
|
|
66
|
+
runner.use(EnsureTxGas(() => ({ create: 0, update: 2, payment: 0 })));
|
|
67
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
68
|
+
|
|
62
69
|
// Update delegation state
|
|
63
70
|
runner.use(
|
|
64
71
|
async (context, next) => {
|
|
65
|
-
const { tx, itx, typeUrls, statedb, senderState, delegationState } = context;
|
|
72
|
+
const { tx, itx, typeUrls, statedb, senderState, senderUpdates, delegationState, updateVaults } = context;
|
|
66
73
|
|
|
67
74
|
debug('update', { address: itx.address, typeUrls });
|
|
68
75
|
|
|
@@ -70,12 +77,17 @@ runner.use(
|
|
|
70
77
|
typeUrls.forEach((x) => delete newOps[x]);
|
|
71
78
|
|
|
72
79
|
const [newSenderState, newDelegationState] = await Promise.all([
|
|
73
|
-
statedb.account.update(
|
|
80
|
+
statedb.account.update(
|
|
81
|
+
senderState.address,
|
|
82
|
+
account.update(senderState, { ...senderUpdates, nonce: tx.nonce }, context),
|
|
83
|
+
context
|
|
84
|
+
),
|
|
74
85
|
statedb.delegation.update(
|
|
75
86
|
itx.address,
|
|
76
87
|
delegation.update(context.delegationState, { ops: newOps }, context),
|
|
77
88
|
context
|
|
78
89
|
),
|
|
90
|
+
updateVaults(),
|
|
79
91
|
]);
|
|
80
92
|
|
|
81
93
|
// Update context
|
|
@@ -15,7 +15,10 @@ const verifyItxAddress = require('./pipes/verify-itx-address');
|
|
|
15
15
|
const execMintHook = require('./pipes/exec-mint-hook');
|
|
16
16
|
const extractFactoryTokens = require('./pipes/extract-factory-tokens');
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
19
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
20
|
+
|
|
21
|
+
const { applyTokenUpdates, applyTokenChange } = require('../../util');
|
|
19
22
|
|
|
20
23
|
const verifyTokenBalance = promisify(pipes.VerifyTokenBalance({ ownerKey: 'senders', conditionKey: 'conditions' }));
|
|
21
24
|
|
|
@@ -79,6 +82,22 @@ runner.use(async (context, next) => {
|
|
|
79
82
|
runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'assetOwner' }));
|
|
80
83
|
runner.use(verifyItxAssets);
|
|
81
84
|
|
|
85
|
+
// Ensure tx fee and gas
|
|
86
|
+
runner.use(
|
|
87
|
+
EnsureTxGas((context) => {
|
|
88
|
+
// FIXME: payment check
|
|
89
|
+
const result = { create: 1, update: 2, payment: 0 };
|
|
90
|
+
if (context.assetStates) {
|
|
91
|
+
result.update += context.assetStates.length;
|
|
92
|
+
}
|
|
93
|
+
if (context.delegatorState) {
|
|
94
|
+
result.update += 1;
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
100
|
+
|
|
82
101
|
// update statedb
|
|
83
102
|
runner.use(
|
|
84
103
|
async (context, next) => {
|
|
@@ -93,6 +112,8 @@ runner.use(
|
|
|
93
112
|
factoryTokens,
|
|
94
113
|
mintedAsset,
|
|
95
114
|
mintedAddress,
|
|
115
|
+
senderChange,
|
|
116
|
+
updateVaults,
|
|
96
117
|
assetStates = [],
|
|
97
118
|
} = context;
|
|
98
119
|
|
|
@@ -100,7 +121,11 @@ runner.use(
|
|
|
100
121
|
const senderUpdates = {
|
|
101
122
|
nonce: tx.nonce,
|
|
102
123
|
pk: tx.pk,
|
|
103
|
-
...applyTokenUpdates(
|
|
124
|
+
...applyTokenUpdates(
|
|
125
|
+
factoryTokens,
|
|
126
|
+
senderChange ? applyTokenChange(senderState, senderChange) : senderState,
|
|
127
|
+
'sub'
|
|
128
|
+
),
|
|
104
129
|
};
|
|
105
130
|
|
|
106
131
|
// Owner updates
|
|
@@ -139,6 +164,8 @@ runner.use(
|
|
|
139
164
|
context.delegatorState
|
|
140
165
|
? statedb.account.update(assetOwner.address, account.update(assetOwner, ownerUpdates, context), context)
|
|
141
166
|
: null,
|
|
167
|
+
|
|
168
|
+
updateVaults(),
|
|
142
169
|
]);
|
|
143
170
|
|
|
144
171
|
context.senderState = newSenderState;
|
|
@@ -10,7 +10,7 @@ const { account, asset, factory } = require('@ocap/state');
|
|
|
10
10
|
// eslint-disable-next-line global-require
|
|
11
11
|
const debug = require('debug')(`${require('../../../package.json').name}:acquire-asset-v3`);
|
|
12
12
|
|
|
13
|
-
const { applyTokenUpdates } = require('../../util');
|
|
13
|
+
const { applyTokenUpdates, applyTokenChange } = require('../../util');
|
|
14
14
|
|
|
15
15
|
// custom pipes
|
|
16
16
|
const verifyAcquireParams = require('./pipes/verify-acquire-params');
|
|
@@ -21,6 +21,9 @@ const verifyItxAddress = require('./pipes/verify-itx-address');
|
|
|
21
21
|
const execMintHook = require('./pipes/exec-mint-hook');
|
|
22
22
|
const extractFactoryTokens = require('./pipes/extract-factory-tokens');
|
|
23
23
|
|
|
24
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
25
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
26
|
+
|
|
24
27
|
const verifyAssetOwner = promisify(pipes.VerifyUpdater({ assetKey: 'assets', ownerKey: 'owner' }));
|
|
25
28
|
|
|
26
29
|
const runner = new Runner();
|
|
@@ -136,6 +139,25 @@ runner.use(
|
|
|
136
139
|
runner.use(verifyItxAddress('acquire-v3'));
|
|
137
140
|
runner.use(verifyItxAssets);
|
|
138
141
|
|
|
142
|
+
// Ensure tx fee and gas
|
|
143
|
+
runner.use(
|
|
144
|
+
EnsureTxGas((context) => {
|
|
145
|
+
// FIXME: payment check
|
|
146
|
+
const result = { create: 1, update: 2, payment: 0 };
|
|
147
|
+
if (context.assetStates) {
|
|
148
|
+
result.update += context.assetStates.length;
|
|
149
|
+
}
|
|
150
|
+
if (context.signerStates) {
|
|
151
|
+
result.update += context.signerStates.length;
|
|
152
|
+
}
|
|
153
|
+
if (!context.ownerState) {
|
|
154
|
+
result.create += 1;
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
160
|
+
|
|
139
161
|
// update statedb
|
|
140
162
|
runner.use(
|
|
141
163
|
async (context, next) => {
|
|
@@ -152,6 +174,8 @@ runner.use(
|
|
|
152
174
|
mintedAsset,
|
|
153
175
|
mintedAddress,
|
|
154
176
|
signerStates,
|
|
177
|
+
senderChange,
|
|
178
|
+
updateVaults,
|
|
155
179
|
assetStates = [],
|
|
156
180
|
} = context;
|
|
157
181
|
|
|
@@ -163,6 +187,9 @@ runner.use(
|
|
|
163
187
|
signerStates.find((s) => s.address === owner),
|
|
164
188
|
'sub'
|
|
165
189
|
);
|
|
190
|
+
if (senderChange && owner === senderChange.address) {
|
|
191
|
+
signerUpdates[owner] = applyTokenChange(signerUpdates[owner], senderChange);
|
|
192
|
+
}
|
|
166
193
|
});
|
|
167
194
|
|
|
168
195
|
const isAlsoSigner = !!signerUpdates[tx.from];
|
|
@@ -184,7 +211,10 @@ runner.use(
|
|
|
184
211
|
senderState,
|
|
185
212
|
account.updateOrCreate(
|
|
186
213
|
senderState,
|
|
187
|
-
Object.assign(
|
|
214
|
+
Object.assign(
|
|
215
|
+
{ address: tx.from, nonce: tx.nonce, pk: tx.pk },
|
|
216
|
+
signerUpdates[tx.from] || (senderChange ? applyTokenChange(senderState, senderChange) : {})
|
|
217
|
+
),
|
|
188
218
|
context
|
|
189
219
|
),
|
|
190
220
|
context
|
|
@@ -220,6 +250,8 @@ runner.use(
|
|
|
220
250
|
statedb.asset.update(x.address, asset.update(x, { consumedTime: txTime }, context), context)
|
|
221
251
|
)
|
|
222
252
|
),
|
|
253
|
+
|
|
254
|
+
updateVaults(),
|
|
223
255
|
]);
|
|
224
256
|
|
|
225
257
|
context.senderState = newSenderState;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* eslint-disable indent */
|
|
2
2
|
const { CustomError: Error } = require('@ocap/util/lib/error');
|
|
3
|
-
const isEmpty = require('lodash/isEmpty');
|
|
4
3
|
const cloneDeep = require('lodash/cloneDeep');
|
|
5
4
|
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
6
5
|
const { account, asset } = require('@ocap/state');
|
|
@@ -11,7 +10,9 @@ const { toAssetAddress } = require('@arcblock/did-util');
|
|
|
11
10
|
const debug = require('debug')(`${require('../../../package.json').name}:create-asset`);
|
|
12
11
|
|
|
13
12
|
const { decodeAnySafe } = require('../../util');
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
15
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
15
16
|
|
|
16
17
|
const runner = new Runner();
|
|
17
18
|
|
|
@@ -89,16 +90,17 @@ runner.use(
|
|
|
89
90
|
// Ensure parent exist
|
|
90
91
|
runner.use(pipes.ExtractState({ from: 'itx.parent', to: 'parentAsset', status: 'INVALID_ASSET', table: 'asset' }));
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
// Ensure tx fee and gas
|
|
94
|
+
runner.use(EnsureTxGas(() => ({ create: 1, update: 1, payment: 0 })));
|
|
95
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
93
96
|
|
|
94
97
|
// Update asset state
|
|
95
98
|
runner.use(
|
|
96
99
|
async (context, next) => {
|
|
97
|
-
const { tx, itx, assetData, statedb, senderState, delegatorState, senderUpdates,
|
|
98
|
-
context;
|
|
100
|
+
const { tx, itx, assetData, statedb, senderState, delegatorState, senderUpdates, updateVaults } = context;
|
|
99
101
|
const owner = delegatorState ? delegatorState.address : senderState.address;
|
|
100
102
|
|
|
101
|
-
const [newSenderState, assetState
|
|
103
|
+
const [newSenderState, assetState] = await Promise.all([
|
|
102
104
|
statedb.account.update(
|
|
103
105
|
senderState.address,
|
|
104
106
|
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
@@ -111,14 +113,11 @@ runner.use(
|
|
|
111
113
|
context
|
|
112
114
|
),
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
? vaultState
|
|
116
|
-
: statedb.account.update(vaultState.address, account.update(vaultState, vaultUpdates, context), context),
|
|
116
|
+
updateVaults(),
|
|
117
117
|
]);
|
|
118
118
|
|
|
119
119
|
context.senderState = newSenderState;
|
|
120
120
|
context.assetState = assetState;
|
|
121
|
-
context.vaultState = newVaultState;
|
|
122
121
|
|
|
123
122
|
debug('createAsset', assetState);
|
|
124
123
|
|
|
@@ -13,6 +13,9 @@ const verifyItxAddress = require('./pipes/verify-itx-address');
|
|
|
13
13
|
|
|
14
14
|
const runner = new Runner();
|
|
15
15
|
|
|
16
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
17
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
18
|
+
|
|
16
19
|
runner.use(pipes.VerifyMultiSig(0));
|
|
17
20
|
runner.use(verifyAcquireParams('mint'));
|
|
18
21
|
|
|
@@ -69,6 +72,22 @@ runner.use(verifyItxAddress('mint'));
|
|
|
69
72
|
runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'ownerState' }));
|
|
70
73
|
runner.use(verifyItxAssets);
|
|
71
74
|
|
|
75
|
+
// Ensure tx fee and gas
|
|
76
|
+
runner.use(
|
|
77
|
+
EnsureTxGas((context) => {
|
|
78
|
+
const result = { create: 1, update: 2, payment: 0 };
|
|
79
|
+
if (!context.ownerState) {
|
|
80
|
+
result.create += 1;
|
|
81
|
+
}
|
|
82
|
+
if (context.assetStates) {
|
|
83
|
+
result.update += context.assetStates.length;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
90
|
+
|
|
72
91
|
// update statedb
|
|
73
92
|
runner.use(
|
|
74
93
|
async (context, next) => {
|
|
@@ -82,17 +101,22 @@ runner.use(
|
|
|
82
101
|
mintedAsset,
|
|
83
102
|
mintedAddress,
|
|
84
103
|
ownerState,
|
|
104
|
+
senderUpdates,
|
|
85
105
|
assetStates = [],
|
|
106
|
+
updateVaults,
|
|
86
107
|
} = context;
|
|
87
108
|
|
|
88
109
|
const owner = ownerState ? ownerState.address : itx.owner;
|
|
89
110
|
|
|
90
|
-
const senderUpdates = { nonce: tx.nonce, pk: tx.pk };
|
|
91
111
|
const factoryUpdates = { numMinted: factoryState.numMinted + 1 };
|
|
92
112
|
|
|
93
113
|
const [newSenderState, newReceiverState, assetState, newFactoryState, newAssetStates] = await Promise.all([
|
|
94
114
|
// update sender
|
|
95
|
-
statedb.account.update(
|
|
115
|
+
statedb.account.update(
|
|
116
|
+
senderState.address,
|
|
117
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
118
|
+
context
|
|
119
|
+
),
|
|
96
120
|
|
|
97
121
|
// create receiver if not exist: asset owner
|
|
98
122
|
ownerState
|
|
@@ -115,6 +139,8 @@ runner.use(
|
|
|
115
139
|
statedb.asset.update(x.address, asset.update(x, { consumedTime: txTime }, context), context)
|
|
116
140
|
)
|
|
117
141
|
),
|
|
142
|
+
|
|
143
|
+
updateVaults(),
|
|
118
144
|
]);
|
|
119
145
|
|
|
120
146
|
context.senderState = newSenderState;
|
|
@@ -15,7 +15,7 @@ module.exports = async (context, next) => {
|
|
|
15
15
|
if (factoryState.settlement === 'instant') {
|
|
16
16
|
const mintHook = factoryState.hooks.find((x) => x.name === 'mint');
|
|
17
17
|
if (mintHook && mintHook.type === 'contract') {
|
|
18
|
-
context.updatedAccounts = [];
|
|
18
|
+
context.updatedAccounts = context.updatedAccounts || [];
|
|
19
19
|
|
|
20
20
|
// These calls must be run sequentially, because different calls may update same account
|
|
21
21
|
const clone = cloneDeep(mintHook.compiled);
|
|
@@ -9,6 +9,9 @@ const debug = require('debug')(`${require('../../../package.json').name}:update-
|
|
|
9
9
|
|
|
10
10
|
const { decodeAnySafe } = require('../../util');
|
|
11
11
|
|
|
12
|
+
const EnsureTxGas = require('../../pipes/ensure-gas');
|
|
13
|
+
const EnsureTxCost = require('../../pipes/ensure-cost');
|
|
14
|
+
|
|
12
15
|
const runner = new Runner();
|
|
13
16
|
|
|
14
17
|
runner.use(pipes.VerifyMultiSig(0));
|
|
@@ -74,16 +77,20 @@ runner.use(async (context, next) => {
|
|
|
74
77
|
return next();
|
|
75
78
|
});
|
|
76
79
|
|
|
80
|
+
// Ensure tx fee and gas
|
|
81
|
+
runner.use(EnsureTxGas(() => ({ create: 0, update: 2, payment: 0 })));
|
|
82
|
+
runner.use(EnsureTxCost({ attachSenderChanges: true }));
|
|
83
|
+
|
|
77
84
|
// Update asset state
|
|
78
85
|
runner.use(
|
|
79
86
|
async (context, next) => {
|
|
80
|
-
const { tx, itx, newData, statedb, senderState, assetState } = context;
|
|
87
|
+
const { tx, itx, newData, statedb, senderState, senderUpdates, assetState, updateVaults } = context;
|
|
81
88
|
|
|
82
89
|
const [newSenderState, newAssetState] = await Promise.all([
|
|
83
90
|
// update owner state
|
|
84
91
|
statedb.account.update(
|
|
85
92
|
senderState.address,
|
|
86
|
-
account.update(senderState, { nonce: tx.nonce, pk: tx.pk }, context),
|
|
93
|
+
account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
|
|
87
94
|
context
|
|
88
95
|
),
|
|
89
96
|
|
|
@@ -97,6 +104,8 @@ runner.use(
|
|
|
97
104
|
),
|
|
98
105
|
context
|
|
99
106
|
),
|
|
107
|
+
|
|
108
|
+
updateVaults(),
|
|
100
109
|
]);
|
|
101
110
|
|
|
102
111
|
context.senderState = newSenderState;
|