caver-js
Version:
caver-js is a JavaScript API library that allows developers to interact with a Kaia node
248 lines (212 loc) • 9.27 kB
JavaScript
/*
Copyright 2018 The caver-js Authors
This file is part of the caver-js library.
The caver-js library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The caver-js library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the caver-js. If not, see <http://www.gnu.org/licenses/>.
*/
const _ = require('lodash')
const RLP = require('eth-lib/lib/rlp')
const Bytes = require('eth-lib/lib/bytes')
const utils = require('../../../../caver-utils')
const helpers = require('../../../../caver-core-helpers')
const {
ACCOUNT_UPDATE_TYPE_TAG,
ACCOUNT_KEY_NIL_TAG,
ACCOUNT_KEY_LEGACY_TAG,
ACCOUNT_KEY_PUBLIC_TAG,
ACCOUNT_KEY_FAIL_TAG,
ACCOUNT_KEY_WEIGHTED_MULTISIG_TAG,
ACCOUNT_KEY_ROLE_BASED_TAG,
FEE_DELEGATED_ACCOUNT_UPDATE_TYPE_TAG,
FEE_DELEGATED_ACCOUNT_UPDATE_WITH_RATIO_TYPE_TAG,
} = helpers.constants
function rlpEncodeForAccountUpdate(transaction) {
const accountKey = resolveRawKeyToAccountKey(transaction)
return RLP.encode([
RLP.encode([
ACCOUNT_UPDATE_TYPE_TAG,
Bytes.fromNat(transaction.nonce),
Bytes.fromNat(transaction.gasPrice),
Bytes.fromNat(transaction.gas),
transaction.from.toLowerCase(),
accountKey,
]),
Bytes.fromNat(transaction.chainId || '0x1'),
'0x',
'0x',
])
}
function rlpEncodeForFeeDelegatedAccountUpdate(transaction) {
if (transaction.senderRawTransaction) {
const typeDetacehdRawTransaction = `0x${transaction.senderRawTransaction.slice(4)}`
// eslint-disable-next-line no-unused-vars
const [nonce, gasPrice, gas, from, accountKey, [[v, r, s]]] = utils.rlpDecode(typeDetacehdRawTransaction)
return RLP.encode([
RLP.encode([
FEE_DELEGATED_ACCOUNT_UPDATE_TYPE_TAG,
Bytes.fromNat(nonce),
Bytes.fromNat(gasPrice),
Bytes.fromNat(gas),
from.toLowerCase(),
accountKey,
]),
transaction.feePayer.toLowerCase(),
Bytes.fromNat(transaction.chainId || '0x1'),
'0x',
'0x',
])
}
const accountKey = resolveRawKeyToAccountKey(transaction)
return RLP.encode([
RLP.encode([
FEE_DELEGATED_ACCOUNT_UPDATE_TYPE_TAG,
Bytes.fromNat(transaction.nonce),
Bytes.fromNat(transaction.gasPrice),
Bytes.fromNat(transaction.gas),
transaction.from.toLowerCase(),
accountKey,
]),
Bytes.fromNat(transaction.chainId || '0x1'),
'0x',
'0x',
])
}
function rlpEncodeForFeeDelegatedAccountUpdateWithRatio(transaction) {
if (transaction.senderRawTransaction) {
const typeDetacehdRawTransaction = `0x${transaction.senderRawTransaction.slice(4)}`
// eslint-disable-next-line no-unused-vars
const [nonce, gasPrice, gas, from, accountKey, feeRatio, [[v, r, s]]] = utils.rlpDecode(typeDetacehdRawTransaction)
return RLP.encode([
RLP.encode([
FEE_DELEGATED_ACCOUNT_UPDATE_WITH_RATIO_TYPE_TAG,
Bytes.fromNat(nonce),
Bytes.fromNat(gasPrice),
Bytes.fromNat(gas),
from.toLowerCase(),
accountKey,
Bytes.fromNat(feeRatio),
]),
transaction.feePayer.toLowerCase(),
Bytes.fromNat(transaction.chainId || '0x1'),
'0x',
'0x',
])
}
const accountKey = resolveRawKeyToAccountKey(transaction)
return RLP.encode([
RLP.encode([
FEE_DELEGATED_ACCOUNT_UPDATE_WITH_RATIO_TYPE_TAG,
Bytes.fromNat(transaction.nonce),
Bytes.fromNat(transaction.gasPrice),
Bytes.fromNat(transaction.gas),
transaction.from.toLowerCase(),
accountKey,
Bytes.fromNat(transaction.feeRatio),
]),
Bytes.fromNat(transaction.chainId || '0x1'),
'0x',
'0x',
])
}
function resolveRawKeyToAccountKey(transaction) {
// Handles the case where AccountForUpdate is set in key field in transaction object to update account.
if (transaction.key) {
// If the key field is a string,
// it means that the already encoded Account Key is passed as a parameter.
if (_.isString(transaction.key)) {
return transaction.key
}
if (transaction.from && transaction.from.toLowerCase() !== transaction.key.address.toLowerCase()) {
throw new Error('The value of the from field of the transaction does not match the address of AccountForUpdate.')
}
transaction.key.fillUpdateObject(transaction)
}
if (transaction.legacyKey) return ACCOUNT_KEY_LEGACY_TAG
if (transaction.failKey) return ACCOUNT_KEY_FAIL_TAG
if (transaction.multisig) {
const { threshold, keys } = transaction.multisig
const encodedMultisigPublicKeys = keys.map(({ weight, publicKey }) => {
if (!weight) throw new Error('weight should be specified for multisig account')
if (!publicKey) throw new Error('publicKey should be specified for multisig account')
const compressedPublicKey = utils.compressPublicKey(publicKey)
return [Bytes.fromNat(utils.numberToHex(weight)), compressedPublicKey]
})
return (
ACCOUNT_KEY_WEIGHTED_MULTISIG_TAG +
RLP.encode([Bytes.fromNat(utils.numberToHex(threshold)), encodedMultisigPublicKeys]).slice(2)
)
}
if (transaction.publicKey) {
const compressedPublicKey = utils.compressPublicKey(transaction.publicKey)
return ACCOUNT_KEY_PUBLIC_TAG + RLP.encode(compressedPublicKey).slice(2)
}
if (transaction.roleTransactionKey || transaction.roleAccountUpdateKey || transaction.roleFeePayerKey) {
// Create a new object so as not to damage the input transaction object.
const roleBasedObject = {}
roleBasedObject.roleTransactionKey = transaction.roleTransactionKey
? resolveRawKeyToAccountKey(transaction.roleTransactionKey)
: ACCOUNT_KEY_NIL_TAG
roleBasedObject.roleAccountUpdateKey = transaction.roleAccountUpdateKey
? resolveRawKeyToAccountKey(transaction.roleAccountUpdateKey)
: ACCOUNT_KEY_NIL_TAG
roleBasedObject.roleFeePayerKey = transaction.roleFeePayerKey
? resolveRawKeyToAccountKey(transaction.roleFeePayerKey)
: ACCOUNT_KEY_NIL_TAG
const keys = [roleBasedObject.roleTransactionKey, roleBasedObject.roleAccountUpdateKey, roleBasedObject.roleFeePayerKey]
return ACCOUNT_KEY_ROLE_BASED_TAG + RLP.encode(keys).slice(2)
}
return ACCOUNT_KEY_NIL_TAG
}
function parseAccountKey(transaction) {
const key = transaction.accountKey
delete transaction.accountKey
if (key.startsWith(ACCOUNT_KEY_LEGACY_TAG)) {
transaction.legacyKey = true
} else if (key.startsWith(ACCOUNT_KEY_FAIL_TAG)) {
transaction.failKey = true
} else if (key.startsWith(ACCOUNT_KEY_PUBLIC_TAG)) {
transaction.publicKey = RLP.decode(`0x${key.slice(ACCOUNT_KEY_PUBLIC_TAG.length)}`)
} else if (key.startsWith(ACCOUNT_KEY_WEIGHTED_MULTISIG_TAG)) {
let [threshold, multiSigkeys] = RLP.decode(`0x${key.slice(ACCOUNT_KEY_WEIGHTED_MULTISIG_TAG.length)}`)
multiSigkeys = multiSigkeys.map(k => {
return { weight: utils.hexToNumber(k[0]), publicKey: k[1] }
})
transaction.multisig = {
threshold: utils.hexToNumber(threshold),
keys: multiSigkeys,
}
} else if (key.startsWith(ACCOUNT_KEY_ROLE_BASED_TAG)) {
const keys = RLP.decode(`0x${key.slice(ACCOUNT_KEY_ROLE_BASED_TAG.length)}`)
keys.map(k => {
if (k.startsWith(ACCOUNT_KEY_ROLE_BASED_TAG)) throw new Error('Nested role based key.')
})
if (keys.length > 0 && !keys[0].startsWith(ACCOUNT_KEY_NIL_TAG))
transaction.roleTransactionKey = parseAccountKey({
accountKey: keys[0],
})
if (keys.length > 1 && !keys[1].startsWith(ACCOUNT_KEY_NIL_TAG))
transaction.roleAccountUpdateKey = parseAccountKey({
accountKey: keys[1],
})
if (keys.length > 2 && !keys[2].startsWith(ACCOUNT_KEY_NIL_TAG))
transaction.roleFeePayerKey = parseAccountKey({
accountKey: keys[2],
})
}
return transaction
}
module.exports = {
rlpEncodeForAccountUpdate,
rlpEncodeForFeeDelegatedAccountUpdate,
rlpEncodeForFeeDelegatedAccountUpdateWithRatio,
parseAccountKey,
resolveRawKeyToAccountKey,
}