mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 13:47:12 +00:00
Merge pull request #1135 from cosmos/decimal-ceil
Let calculateFee handle fee amounts that exceed the safe integer range
This commit is contained in:
commit
7c82def979
@ -6,6 +6,15 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- @cosmjs/math: Add `Decimal.floor` and `Decimal.ceil`.
|
||||
|
||||
### Changed
|
||||
|
||||
- @cosmjs/stargate: Let `calculateFee` handle fee amounts that exceed the safe
|
||||
integer range.
|
||||
|
||||
## [0.28.4] - 2022-04-15
|
||||
|
||||
### Added
|
||||
|
@ -177,6 +177,42 @@ describe("Decimal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("floor", () => {
|
||||
it("works", () => {
|
||||
// whole numbers
|
||||
expect(Decimal.fromUserInput("0", 0).floor().toString()).toEqual("0");
|
||||
expect(Decimal.fromUserInput("1", 0).floor().toString()).toEqual("1");
|
||||
expect(Decimal.fromUserInput("44", 0).floor().toString()).toEqual("44");
|
||||
expect(Decimal.fromUserInput("0", 3).floor().toString()).toEqual("0");
|
||||
expect(Decimal.fromUserInput("1", 3).floor().toString()).toEqual("1");
|
||||
expect(Decimal.fromUserInput("44", 3).floor().toString()).toEqual("44");
|
||||
|
||||
// with fractional part
|
||||
expect(Decimal.fromUserInput("0.001", 3).floor().toString()).toEqual("0");
|
||||
expect(Decimal.fromUserInput("1.999", 3).floor().toString()).toEqual("1");
|
||||
expect(Decimal.fromUserInput("0.000000000000000001", 18).floor().toString()).toEqual("0");
|
||||
expect(Decimal.fromUserInput("1.999999999999999999", 18).floor().toString()).toEqual("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ceil", () => {
|
||||
it("works", () => {
|
||||
// whole numbers
|
||||
expect(Decimal.fromUserInput("0", 0).ceil().toString()).toEqual("0");
|
||||
expect(Decimal.fromUserInput("1", 0).ceil().toString()).toEqual("1");
|
||||
expect(Decimal.fromUserInput("44", 0).ceil().toString()).toEqual("44");
|
||||
expect(Decimal.fromUserInput("0", 3).ceil().toString()).toEqual("0");
|
||||
expect(Decimal.fromUserInput("1", 3).ceil().toString()).toEqual("1");
|
||||
expect(Decimal.fromUserInput("44", 3).ceil().toString()).toEqual("44");
|
||||
|
||||
// with fractional part
|
||||
expect(Decimal.fromUserInput("0.001", 3).ceil().toString()).toEqual("1");
|
||||
expect(Decimal.fromUserInput("1.999", 3).ceil().toString()).toEqual("2");
|
||||
expect(Decimal.fromUserInput("0.000000000000000001", 18).ceil().toString()).toEqual("1");
|
||||
expect(Decimal.fromUserInput("1.999999999999999999", 18).ceil().toString()).toEqual("2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toString", () => {
|
||||
it("displays no decimal point for full numbers", () => {
|
||||
expect(Decimal.fromUserInput("44", 0).toString()).toEqual("44");
|
||||
|
@ -113,6 +113,37 @@ export class Decimal {
|
||||
};
|
||||
}
|
||||
|
||||
/** Creates a new instance with the same value */
|
||||
private clone(): Decimal {
|
||||
return new Decimal(this.atomics, this.fractionalDigits);
|
||||
}
|
||||
|
||||
/** Returns the greatest decimal <= this which has no fractional part (rounding down) */
|
||||
public floor(): Decimal {
|
||||
const factor = new BN(10).pow(new BN(this.data.fractionalDigits));
|
||||
const whole = this.data.atomics.div(factor);
|
||||
const fractional = this.data.atomics.mod(factor);
|
||||
|
||||
if (fractional.isZero()) {
|
||||
return this.clone();
|
||||
} else {
|
||||
return Decimal.fromAtomics(whole.mul(factor).toString(), this.fractionalDigits);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the smallest decimal >= this which has no fractional part (rounding up) */
|
||||
public ceil(): Decimal {
|
||||
const factor = new BN(10).pow(new BN(this.data.fractionalDigits));
|
||||
const whole = this.data.atomics.div(factor);
|
||||
const fractional = this.data.atomics.mod(factor);
|
||||
|
||||
if (fractional.isZero()) {
|
||||
return this.clone();
|
||||
} else {
|
||||
return Decimal.fromAtomics(whole.addn(1).mul(factor).toString(), this.fractionalDigits);
|
||||
}
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
const factor = new BN(10).pow(new BN(this.data.fractionalDigits));
|
||||
const whole = this.data.atomics.div(factor);
|
||||
|
@ -93,4 +93,15 @@ describe("calculateFee", () => {
|
||||
gas: "80000",
|
||||
});
|
||||
});
|
||||
|
||||
it("works with large gas price", () => {
|
||||
// "The default gas price is 5000000000000 (5e^12), as the native coin has 18 decimals it is exceeding the max safe integer"
|
||||
// https://github.com/cosmos/cosmjs/issues/1134
|
||||
const gasPrice = GasPrice.fromString("5000000000000tiny");
|
||||
const fee = calculateFee(500_000, gasPrice);
|
||||
expect(fee).toEqual({
|
||||
amount: [{ amount: "2500000000000000000", denom: "tiny" }],
|
||||
gas: "500000",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -60,7 +60,9 @@ export class GasPrice {
|
||||
export function calculateFee(gasLimit: number, gasPrice: GasPrice | string): StdFee {
|
||||
const processedGasPrice = typeof gasPrice === "string" ? GasPrice.fromString(gasPrice) : gasPrice;
|
||||
const { denom, amount: gasPriceAmount } = processedGasPrice;
|
||||
const amount = Math.ceil(gasPriceAmount.multiply(new Uint53(gasLimit)).toFloatApproximation());
|
||||
// Note: Amount can exceed the safe integer range (https://github.com/cosmos/cosmjs/issues/1134),
|
||||
// which we handle by converting from Decimal to string without going through number.
|
||||
const amount = gasPriceAmount.multiply(new Uint53(gasLimit)).ceil().toString();
|
||||
return {
|
||||
amount: coins(amount, denom),
|
||||
gas: gasLimit.toString(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user