Skip to content

Commit

Permalink
feat: add minSats to the validateOutputAmount function. Fix missing p…
Browse files Browse the repository at this point in the history
…ackage
  • Loading branch information
Shadouts authored and bucko13 committed Apr 6, 2023
1 parent 5578572 commit 52b3c60
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 83 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 36 additions & 29 deletions src/outputs.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
/**
/**
* This module provides functions for validating transaction
* output and amounts.
*
*
* @module outputs
*/

import BigNumber from 'bignumber.js';
import BigNumber from "bignumber.js";

import {ZERO} from "./utils";
import {
validateAddress,
} from "./addresses";
import { ZERO } from "./utils";
import { validateAddress } from "./addresses";

/**
/**
* Represents an output in a transaction.
*
* @typedef module:outputs.TransactionOutput
* @type {Object}
* @property {string} address - the output address
* @property {string|number|BigNumber} amountSats - output amount in Satoshis
* @property {Multisig} [multisig] - output multisig for a change address
*
*
*/

/**
Expand All @@ -32,14 +30,18 @@ import {
* @param {module:outputs.TransactionOutput[]} outputs - outputs to validate
* @param {string|number|BigNumber} [inputsTotalSats] - (optional) the total input amount in Satoshis
* @returns {string} empty if valid or corresponding validation message if not
*
*
*/
export function validateOutputs(network, outputs, inputsTotalSats) {
if (!outputs || outputs.length === 0) { return "At least one output is required."; }
if (!outputs || outputs.length === 0) {
return "At least one output is required.";
}
for (let outputIndex = 0; outputIndex < outputs.length; outputIndex++) {
const output = outputs[outputIndex];
const error = validateOutput(network, output, inputsTotalSats);
if (error) { return error; }
if (error) {
return error;
}
}
return "";
}
Expand All @@ -48,7 +50,7 @@ export function validateOutputs(network, outputs, inputsTotalSats) {
* Validate the given transaction output.
*
* - Validates the presence and value of `address`.
*
*
* - Validates the presence and value of `amountSats`. If `inputsTotalSats`
* is also passed, this will be taken into account when validating the
* amount.
Expand All @@ -64,58 +66,63 @@ export function validateOutputs(network, outputs, inputsTotalSats) {
* console.log(validateOutput(MAINNET, {amountSats: 100000, address: "3..."}, 10000)); // "Amount is too large."
*/
export function validateOutput(network, output, inputsTotalSats) {
if (output.amountSats !== 0 && (!output.amountSats)) {
if (output.amountSats !== 0 && !output.amountSats) {
return `Does not have an 'amountSats' property.`;
}
let error = validateOutputAmount(output.amountSats, inputsTotalSats);
if (error) { return error; }
if (error) {
return error;
}
if (!output.address) {
return `Does not have an 'address' property.`;
}
error = validateAddress(output.address, network);
if (error) {
return `Has an invalid 'address' property: ${error}.`;
}
return '';
return "";
}

/**
* Lowest acceptable output amount in Satoshis.
*
*
* @constant
* @type {BigNumber}
* @default 546 Satoshis
*
*
*/
const DUST_LIMIT_SATS = BigNumber(546);

/**
* Validate the given output amount (in Satoshis).
*
*
* - Must be a parseable as a number.
*
* - Cannot be negative (zero is OK).
*
* - Cannot be smaller than the limit set by `DUST_LIMIT_SATS`.
*
*
* - Cannot exceed the total input amount (this check is only run if `inputsTotalSats` is passed.
*
*
* @param {string|number|BigNumber} amountSats - output amount in Satoshis
* @param {string|number|BigNumber} inputsTotalSats - (optional) total input amount in Satoshis
* @param {string|number|BigNumber} maxSats - (optional) maximum amount in Satoshis
* @param {string|number|BigNumber} minSats - (optional) minimum acceptable
* amount in Satoshis
* @returns {string} empty if valid or corresponding validation message if not
* @example
* import {validateOutputAmount} from "unchained-bitcoin";
* console.log(validateOutputAmount(-100, 1000000) // "Output amount must be positive."
* console.log(validateOutputAmount(0, 1000000) // "Output amount must be positive."
* console.log(validateOutputAmount(10, 1000000) // "Output amount is too small."
* * console.log(validateOutputAmount(800, 1000000, 1000) // "Output amount is too small."
* console.log(validateOutputAmount(1000000, 100000) // "Output amount is too large."
* console.log(validateOutputAmount(100000, 1000000) // ""
*/
export function validateOutputAmount(amountSats, inputsTotalSats) {
export function validateOutputAmount(amountSats, maxSats, minSats) {
let a, its;
try {
a = BigNumber(amountSats);
} catch(e) {
} catch (e) {
return "Invalid output amount.";
}
if (!a.isFinite()) {
Expand All @@ -124,13 +131,13 @@ export function validateOutputAmount(amountSats, inputsTotalSats) {
if (a.isLessThanOrEqualTo(ZERO)) {
return "Output amount must be positive.";
}
if (a.isLessThanOrEqualTo(DUST_LIMIT_SATS)) {
if (a.isLessThanOrEqualTo(minSats || DUST_LIMIT_SATS)) {
return "Output amount is too small.";
}
if (inputsTotalSats !== undefined) {
if (maxSats !== undefined) {
try {
its = BigNumber(inputsTotalSats);
} catch(e) {
its = BigNumber(maxSats);
} catch (e) {
return "Invalid total input amount.";
}
if (!its.isFinite()) {
Expand All @@ -143,5 +150,5 @@ export function validateOutputAmount(amountSats, inputsTotalSats) {
return "Output amount is too large.";
}
}
return '';
return "";
}
124 changes: 70 additions & 54 deletions src/outputs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,149 +2,165 @@ import {
validateOutputs,
validateOutput,
validateOutputAmount,
} from './outputs';
import {TESTNET} from "./networks";
import BigNumber from 'bignumber.js';
} from "./outputs";
import { TESTNET } from "./networks";
import BigNumber from "bignumber.js";

describe("outputs", () => {

const validAddress = "2NE1LH35XT4YrdnEebk5oKMmRpGiYcUvpNR";

describe('validateOutputs', () => {

describe("validateOutputs", () => {
it("should return an error message if no outputs", () => {
expect(
validateOutputs(
TESTNET,
[]
)
).toMatch(/At least one output is required/i);
expect(validateOutputs(TESTNET, [])).toMatch(
/At least one output is required/i
);
});

it("should return an error message if one of the outputs is invalid", () => {
expect(
validateOutputs(
TESTNET,
[
{address: validAddress, amountSats: 1000},
{address: "foo", amountSats: 1000},
]
)
validateOutputs(TESTNET, [
{ address: validAddress, amountSats: 1000 },
{ address: "foo", amountSats: 1000 },
])
).toMatch(/invalid.+address/i);
});

it("should return an empty string if all outputs are valid", () => {
expect(
validateOutputs(
TESTNET,
[
{address: validAddress, amountSats: 1000},
]
)
validateOutputs(TESTNET, [{ address: validAddress, amountSats: 1000 }])
).toEqual("");
});

});

describe('validateOutput', () => {


describe("validateOutput", () => {
it("should return an error message for a missing amount", () => {
expect(validateOutput(TESTNET, {address: validAddress})).toMatch(/does not have.+amountSats/i);
expect(validateOutput(TESTNET, { address: validAddress })).toMatch(
/does not have.+amountSats/i
);
});

it("should return an error message for an invalid amount", () => {
BigNumber.DEBUG = true;
expect(validateOutput(TESTNET, {address: validAddress, amountSats: "foo"})).toMatch(/invalid output amount/i);
expect(
validateOutput(TESTNET, { address: validAddress, amountSats: "foo" })
).toMatch(/invalid output amount/i);
BigNumber.DEBUG = false;
});

it("should return an error message for an invalid amount", () => {
expect(validateOutput(TESTNET, {address: validAddress, amountSats: "foo"})).toMatch(/invalid output amount/i);
expect(
validateOutput(TESTNET, { address: validAddress, amountSats: "foo" })
).toMatch(/invalid output amount/i);
});

it("should return an error message for a missing address", () => {
expect(validateOutput(TESTNET, {amountSats: 10000})).toMatch(/does not have.+address/i);
expect(validateOutput(TESTNET, { amountSats: 10000 })).toMatch(
/does not have.+address/i
);
});

it("should return an error message for an invalid address", () => {
expect(validateOutput(TESTNET, {amountSats: 10000, address: "foo"})).toMatch(/invalid.+address/i);
expect(
validateOutput(TESTNET, { amountSats: 10000, address: "foo" })
).toMatch(/invalid.+address/i);
});

it("returns an empty string on a valid output", () => {
expect(validateOutput(TESTNET, {amountSats: 10000, address: validAddress})).toEqual("");
expect(
validateOutput(TESTNET, { amountSats: 10000, address: validAddress })
).toEqual("");
});

});

describe('validateOutputAmount', () => {

describe("validateOutputAmount", () => {
it("should return an error message for an unparseable output amount", () => {
expect(validateOutputAmount('foo')).toMatch(/invalid output amount/i);
expect(validateOutputAmount("foo")).toMatch(/invalid output amount/i);
});

it("should return an error message for a negative output amount", () => {
expect(validateOutputAmount(-10000)).toMatch(/output amount must be positive/i);
expect(validateOutputAmount(-10000)).toMatch(
/output amount must be positive/i
);
});

it("should return an error message for a zero output amount", () => {
expect(validateOutputAmount(0)).toMatch(/output amount must be positive/i);
expect(validateOutputAmount(0)).toMatch(
/output amount must be positive/i
);
});

it("should return an error message when the output is too small", () => {
expect(validateOutputAmount(100)).toMatch(/output amount is too small/i);
expect(validateOutputAmount(800, 10000, 1000)).toMatch(
/output amount is too small/i
);
});

it("should return an empty string on an acceptable amount", () => {
expect(validateOutputAmount(100000)).toBe("");
});

describe("when also passing `inputTotalSats`", () => {

it("should return an error message for an unparseable inputTotalSats amount", () => {
BigNumber.DEBUG = true;
expect(validateOutputAmount(1000, 'foo')).toMatch(/invalid total input amount/i);
expect(validateOutputAmount(1000, "foo")).toMatch(
/invalid total input amount/i
);

BigNumber.DEBUG = false;
});

it("should return an error message for an unparseable output amount", () => {
expect(validateOutputAmount('foo', 100000)).toMatch(/invalid output amount/i);
expect(validateOutputAmount("foo", 100000)).toMatch(
/invalid output amount/i
);
});

it("should return an error message for a negative output amount", () => {
expect(validateOutputAmount(-10000, 100000)).toMatch(/output amount must be positive/i);
expect(validateOutputAmount(-10000, 100000)).toMatch(
/output amount must be positive/i
);
});

it("should return an error message for a zero output amount", () => {
expect(validateOutputAmount(0, 100000)).toMatch(/output amount must be positive/i);
expect(validateOutputAmount(0, 100000)).toMatch(
/output amount must be positive/i
);
});

it("should return an error message when the output is too small", () => {
expect(validateOutputAmount(100, 100000)).toMatch(/output amount is too small/i);
expect(validateOutputAmount(100, 100000)).toMatch(
/output amount is too small/i
);
});

it("should return an error message for an unparseable total input amount", () => {
expect(validateOutputAmount(100000, 'foo')).toMatch(/invalid total input amount/i);
expect(validateOutputAmount(100000, "foo")).toMatch(
/invalid total input amount/i
);
});

it("should return an error message for a negative total input amount", () => {
expect(validateOutputAmount(100000, -1000000)).toMatch(/total input amount must be positive/i);
expect(validateOutputAmount(100000, -1000000)).toMatch(
/total input amount must be positive/i
);
});

it("should return an error message for a zero total input amount", () => {
expect(validateOutputAmount(100000, 0)).toMatch(/total input amount must be positive/i);
expect(validateOutputAmount(100000, 0)).toMatch(
/total input amount must be positive/i
);
});

it("should return an error message when the output is larger than the total input amount", () => {
expect(validateOutputAmount(100001, 100000)).toMatch(/output amount is too large/i);
expect(validateOutputAmount(100001, 100000)).toMatch(
/output amount is too large/i
);
});

it("should return an empty string on an acceptable amount", () => {
expect(validateOutputAmount(100000, 1000000)).toBe("");
});
});

});

});

0 comments on commit 52b3c60

Please sign in to comment.