Skip to content

Commit

Permalink
Add fee and hash functions
Browse files Browse the repository at this point in the history
  • Loading branch information
marcusbfs committed Nov 10, 2024
1 parent d35fb29 commit c6229a0
Show file tree
Hide file tree
Showing 8 changed files with 597 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"test": "npm run test-serialization ; npm run test-api",
"test-serialization": "NODE_OPTIONS=--experimental-vm-modules jest serialization",
"test-api": "npm run generate-cdl-definitions ; NODE_OPTIONS=--experimental-vm-modules jest api",
"test-implementation": "NODE_OPTIONS=--experimental-vm-modules jest hash fees",
"codegen": "tsx conway-cddl/codegen/main.ts",
"generate-grammar-bundle": "npx ohm generateBundles -e -t tests/api/grammar.ohm",
"generate-cdl-definitions": "make typedefs",
Expand Down
125 changes: 125 additions & 0 deletions src/fee/fraction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Helper functions to perform arithmetic on fractions

import { BigNum } from "../generated";

export type Fraction = {
numerator: BigNum;
denominator: BigNum;
};

export function add_fractions(f1: Fraction, f2: Fraction): Fraction {
const numerator1 = f1.numerator.checked_mul(f2.denominator);
const numerator2 = f2.numerator.checked_mul(f1.denominator);
const numerator = numerator1.checked_add(numerator2);
const denominator = f1.denominator.checked_mul(f2.denominator);
return { numerator, denominator };
}

export function sub_fractions(f1: Fraction, f2: Fraction): Fraction {
if (f1.numerator.is_zero()) {
return f2;
}

if (f2.numerator.is_zero()) {
return f1;
}

const numerator1 = f1.numerator.checked_mul(f2.denominator);
const numerator2 = f2.numerator.checked_mul(f1.denominator);

const numerator = numerator1.checked_sub(numerator2);
const denominator = f1.denominator.checked_mul(f2.denominator);

return { numerator, denominator };
}

export function mul_fractions(f1: Fraction, f2: Fraction): Fraction {
const numerator = f1.numerator.checked_mul(f2.numerator);
const denominator = f1.denominator.checked_mul(f2.denominator);
return { numerator, denominator };
}

export function div_fractions(f1: Fraction, f2: Fraction): Fraction {
if (f2.numerator.is_zero()) {
throw new Error("Division by zero");
}
const numerator = f1.numerator.checked_mul(f2.denominator);
const denominator = f1.denominator.checked_mul(f2.numerator);
return { numerator, denominator };
}

export function pow_fraction(fraction: Fraction, exponent: number): Fraction {
let resultNumerator = BigNum.one();
let resultDenominator = BigNum.one();
let baseNumerator = fraction.numerator;
let baseDenominator = fraction.denominator;
let exp = exponent;
while (exp > 0) {
if (exp % 2 === 1) {
resultNumerator = resultNumerator.checked_mul(baseNumerator);
resultDenominator = resultDenominator.checked_mul(baseDenominator);
}
exp = Math.floor(exp / 2);
if (exp > 0) {
baseNumerator = baseNumerator.checked_mul(baseNumerator);
baseDenominator = baseDenominator.checked_mul(baseDenominator);
}
}
return { numerator: resultNumerator, denominator: resultDenominator };
}

// Helper function to perform ceiling division of numerator/denominator
// It uses the underlying BigNum implementation of `div_floor` and changes to a ceiling division.
export function ceil_division(f: Fraction): BigNum {
// Step 1: Perform floor division to get the quotient
const quotient = f.numerator.div_floor(f.denominator);

// Step 2: Calculate the product of denominator and quotient
const product = f.denominator.checked_mul(quotient);

// Step 3: Calculate the remainder by subtracting the product from the numerator
const remainder = f.numerator.checked_sub(product);

// Step 4: Check if remainder is zero
if (remainder.is_zero()) {
return quotient;
} else {
// If there is a remainder, increment the quotient by one
return quotient.checked_add(BigNum.one());
}
}

export function is_fraction_negative(f: Fraction): Boolean {
const isNumNegative = f.numerator.compare(BigNum.zero());
const isDenNegative = f.denominator.compare(BigNum.zero());
return isNumNegative !== isDenNegative;
}

export function is_fraction_negative_or_zero(f: Fraction): Boolean {
return f.numerator.compare(BigNum.zero()) < 0 || is_fraction_negative(f);
}

export function fraction_zero(): Fraction {
return {
numerator: BigNum.zero(),
denominator: BigNum.one(),
};
}

export function fraction_one(): Fraction {
return {
numerator: BigNum.one(),
denominator: BigNum.one(),
};
}

export function toBigNumFloor(f: Fraction): BigNum {
const num = f.numerator;
const denum = f.denominator;

if (denum.is_zero()) {
throw new Error("Division by zero");
}

return num.div_floor(denum);
}
166 changes: 166 additions & 0 deletions src/fee/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import {
BigNum,
ExUnitPrices,
ExUnits,
Transaction,
UnitInterval,
} from "../generated";

import {
add_fractions,
ceil_division,
div_fractions,
Fraction,
fraction_one,
fraction_zero,
is_fraction_negative_or_zero,
mul_fractions,
pow_fraction,
sub_fractions,
toBigNumFloor,
} from "./fraction";

export class LinearFee {
private _constant: BigNum;
private _coefficient: BigNum;

constructor(constant: BigNum, coefficient: BigNum) {
this._constant = constant;
this._coefficient = coefficient;
}

static new(constant: BigNum, coefficient: BigNum) {
return new LinearFee(constant, coefficient);
}

constant(): BigNum {
return this._constant.clone();
}

coefficient(): BigNum {
return this._coefficient.clone();
}
}

function min_fee_for_size(size: number, linearFee: LinearFee): BigNum {
const s1 = BigNum._from_number(size).checked_mul(linearFee.coefficient())
return s1.checked_add(linearFee.constant());
}

export function min_fee(tx: Transaction, linearFee: LinearFee): BigNum {
return min_fee_for_size(tx.to_bytes().length, linearFee);
}

// Helper function to multiply a UnitInterval by a BigNum
function multiply_unit_interval_by_bignum(
unitInterval: UnitInterval,
bigNum: BigNum,
): Fraction {
const numerator = unitInterval.numerator().checked_mul(bigNum);
const denominator = unitInterval.denominator().clone();
return { numerator, denominator };
}

export function calculate_exunits_ceil_cost(
exUnits: ExUnits,
exUnitPrices: ExUnitPrices,
): BigNum {
const memPrice: UnitInterval = exUnitPrices.mem_price();
const stepsPrice: UnitInterval = exUnitPrices.step_price();

const memRatio = multiply_unit_interval_by_bignum(memPrice, exUnits.mem());
const stepsRatio = multiply_unit_interval_by_bignum(
stepsPrice,
exUnits.steps(),
);
const totalCostFraction = add_fractions(memRatio, stepsRatio);

return ceil_division(totalCostFraction);
}

export function min_script_fee(
tx: Transaction,
exUnitPrices: ExUnitPrices,
): BigNum {
const redeemers = tx.witness_set().redeemers();
if (redeemers) {
const totalExUnits = redeemers.total_ex_units();
return calculate_exunits_ceil_cost(totalExUnits, exUnitPrices);
}
return BigNum.zero();
}


function tier_ref_script_fee(
multiplier: Fraction,
sizeIncrement: number,
baseFee: Fraction,
totalSize: number,
): BigNum {
if (is_fraction_negative_or_zero(multiplier) || sizeIncrement === 0) {
throw new Error("Size increment and multiplier must be positive");
}
const fullTiers = Math.floor(totalSize / sizeIncrement);
const partialTierSize = totalSize % sizeIncrement;
const tierPrice = mul_fractions(baseFee, {
numerator: BigNum._from_number(sizeIncrement),
denominator: BigNum.one(),
});
let acc: Fraction = fraction_zero();

if (fullTiers > 0) {
const multiplierPowFullTiers = pow_fraction(multiplier, fullTiers);

const progressionEnumerator = sub_fractions(fraction_one(), multiplierPowFullTiers);
const progressionDenominator = sub_fractions(fraction_one(), multiplier);

const tierProgressionSum = div_fractions(
progressionEnumerator,
progressionDenominator,
);

const tierPriceTimesProgressionSum = mul_fractions(
tierPrice,
tierProgressionSum,
);
acc = add_fractions(acc, tierPriceTimesProgressionSum);
}

// Add the partial tier
if (partialTierSize > 0) {
const multiplierPowFullTiers = pow_fraction(multiplier, fullTiers);

const lastTierPrice = mul_fractions(baseFee, multiplierPowFullTiers);

const partialTierFee = mul_fractions(lastTierPrice, {
numerator: BigNum._from_number(partialTierSize),
denominator: BigNum.one(),
});

acc = add_fractions(acc, partialTierFee);
}

return toBigNumFloor(acc);
}

export function min_ref_script_fee(
totalRefScriptsSize: number,
refScriptBigNumsPerByte: UnitInterval,
): BigNum {
const multiplier: Fraction = {
numerator: new BigNum(12n),
denominator: new BigNum(10n),
}; // 1.2
const sizeIncrement = 25_600; // 25KiB

const refMultiplier: Fraction = {
numerator: refScriptBigNumsPerByte.numerator(),
denominator: refScriptBigNumsPerByte.denominator(),
};
return tier_ref_script_fee(
multiplier,
sizeIncrement,
refMultiplier,
totalRefScriptsSize,
);
}
19 changes: 19 additions & 0 deletions src/hash/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { blake2b } from "@noble/hashes/blake2b";
import {
AuxiliaryData,
AuxiliaryDataHash,
DataHash,
PlutusData,
} from "../generated";

export function hash_plutus_data(plutus_data: PlutusData): DataHash {
const bytes = plutus_data.to_bytes();
return DataHash.new(blake2b(bytes, { dkLen: 32 }));
}

export function hash_auxiliary_data(
auxiliary_data: AuxiliaryData,
): AuxiliaryDataHash {
const bytes = auxiliary_data.to_bytes();
return AuxiliaryDataHash.new(blake2b(bytes, { dkLen: 32 }));
}
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./address";
export * from "./generated";
export * from "./fee";
export * from "./hash";
export * from "./min_ada_calculator";
export * from "./generated";
File renamed without changes.
Loading

0 comments on commit c6229a0

Please sign in to comment.