Created
November 19, 2019 16:57
-
-
Save JesusTheHun/563206150ea9d9e59e2cd057904ae68c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
BaseTransaction, | |
TransactionError, | |
utils, | |
} = require('@liskhq/lisk-transactions'); | |
const _ = require('lodash'); | |
/** | |
* type asset { | |
* recipients: Recipient[] | |
* } | |
* type Recipient { | |
* address: <lisk_address>, | |
* amount: BigNum string, | |
* } | |
*/ | |
class MultiTransfer extends BaseTransaction { | |
static get TYPE() { | |
return 42; | |
} | |
static get FEE() { | |
return `${10 ** 8}`; | |
}; | |
validateAsset() { | |
const transaction = this; | |
const errors = []; | |
const minimumRecipientsCount = 2; | |
const maximumRecipientsCount = 10; | |
const errorRecipientMustBeBlankAddress = new TransactionError("The recipient must be 0L for this transaction", this.id, ".recipientId" , this.recipientId, "0L"); | |
const errorNotAnArray = new TransactionError("The asset must be an array of Recipients", this.id, ".asset.recipients" , this.asset.recipients, "an array of recipients"); | |
const errorTooFewRecipients = new TransactionError( | |
`You must provide at least ${minimumRecipientsCount} recipients`, this.id, ".asset.recipients" , this.asset.recipients, `at least ${minimumRecipientsCount} recipients`); | |
const errorTooManyRecipients = new TransactionError( | |
`You must provide at most ${maximumRecipientsCount} recipients`, this.id, ".asset.recipients" , this.asset.recipients, `at most ${maximumRecipientsCount} recipients`); | |
const errorDuplicates = new TransactionError("Each recipient must appear at most once", this.id, ".asset.recipients" , this.asset.recipients, "a set of unique recipients"); | |
const errorSelfRecipient = new TransactionError("The sender cannot be one of the recipients", this.id, ".asset.recipients" , this.asset.recipients); | |
const errorInvalidAddressBuilder = (recipientIndex, givenAddress, validationError) => new TransactionError(`Invalid address of recipient #${recipientIndex} : ${validationError}`, this.id, ".asset.recipients[*].address", givenAddress, "a valid Lisk address"); | |
const errorInvalidAmountBuilder = (recipientIndex, givenAmount) => new TransactionError(`Invalid transfer amount for recipient #${recipientIndex}`, this.id, ".asset.recipients[*].amount", givenAmount, "a valid BigNumber.toString() amount"); | |
if (this.recipientId !== '0L') { | |
errors.push(errorRecipientMustBeBlankAddress); | |
} | |
if (!_.isArray(this.asset.recipients)) { | |
errors.push(errorNotAnArray); | |
return errors; | |
} | |
// From here this.asset.recipients is an actual array | |
if (this.asset.recipients.length < minimumRecipientsCount) { | |
errors.push(errorTooFewRecipients); | |
} | |
if (this.asset.recipients.length > maximumRecipientsCount) { | |
errors.push(errorTooManyRecipients); | |
} | |
const uniqueRecipients = _.uniqBy(this.asset.recipients, 'address'); | |
if (uniqueRecipients.length !== this.asset.recipients.length) { | |
errors.push(errorDuplicates); | |
} | |
// Check the format of each recipient | |
this.asset.recipients.forEach((recipient, index) => { | |
// Validate the address | |
try { | |
utils.validateAddress(recipient.address); | |
} catch(e) { | |
errors.push(errorInvalidAddressBuilder(index, recipient.address, e.message)); | |
} | |
// Validate the amount is a valid BigNum | |
try { | |
new utils.BigNum(recipient.amount); | |
if (!utils.validateTransferAmount(recipient.amount)) { | |
throw new Error(); | |
} | |
} catch (e) { | |
errors.push(errorInvalidAmountBuilder(index, recipient.amount)); | |
} | |
}); | |
const selfRecipient = this.asset.recipients.find(r => r.address === transaction.senderId); | |
if (selfRecipient) { | |
errors.push(errorSelfRecipient); | |
} | |
return errors; | |
} | |
async prepare(store) { | |
const accountsToFetch = this.asset.recipients.map(r => ({address: r.address})); | |
accountsToFetch.push({address: this.senderId}); | |
await store.account.cache(accountsToFetch); | |
} | |
applyAsset(store) { | |
const errors = []; | |
// Let's validate the sender is rich enough | |
const sender = store.account.get(this.senderId); | |
const totalAmountToTransfer = this.asset.recipients.reduce((total, r) => { | |
return total.add(r.amount); | |
}, new utils.BigNum(0)); | |
const senderBalanceValidationError = utils.verifyAmountBalance(this.id, sender, totalAmountToTransfer, this.fee); | |
if (senderBalanceValidationError) { | |
errors.push(senderBalanceValidationError); | |
} | |
if (errors.length > 0) { | |
return errors; | |
} | |
this.asset.recipients.forEach(r => { | |
const { address, amount } = r; | |
const recipient = store.account.get(address); | |
const bigNumAmount = new utils.BigNum(amount); | |
recipient.balance = new utils.BigNum(recipient.balance).add(bigNumAmount).toString(); | |
store.account.set(address, recipient); | |
}); | |
sender.balance = new utils.BigNum(sender.balance).sub(totalAmountToTransfer).toString(); | |
store.account.set(sender.address, sender); | |
return errors; | |
} | |
undoAsset(store) { | |
const errors = []; | |
// Here in undoAsset() I assume every account has a sufficient balance since it's a rollback | |
const sender = store.account.get(this.senderId); | |
const totalAmountToTransfer = this.asset.recipients.reduce((total, r) => { | |
return total.add(r.amount); | |
}, new utils.BigNum(0)); | |
this.asset.recipients.forEach(r => { | |
const { address, amount } = r; | |
const recipient = store.account.get(address); | |
const bigNumAmount = new utils.BigNum(amount); | |
recipient.balance = new utils.BigNum(recipient.balance).sub(bigNumAmount).toString(); | |
store.account.set(address, recipient); | |
}); | |
sender.balance = new utils.BigNum(sender.balance).add(totalAmountToTransfer).toString(); | |
store.account.set(sender.address, sender); | |
return errors; | |
} | |
} | |
module.exports = MultiTransfer; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment