Skip to content

Commit

Permalink
add configurable UI fee
Browse files Browse the repository at this point in the history
  • Loading branch information
capt-nemo429 committed Jun 24, 2023
1 parent 40a23b2 commit 5293925
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 20 deletions.
60 changes: 60 additions & 0 deletions plugins/ageusd/src/ageUsdBank.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { percent } from "@fleet-sdk/common";
import { Amount, Box, RECOMMENDED_MIN_FEE_VALUE, SBool, SConstant, SParse } from "@fleet-sdk/core";
import { describe, expect, it, test } from "vitest";
import { mockBankBox, mockOracleBox } from "./_tests/mocking";
Expand All @@ -22,6 +23,59 @@ describe("Bank construction", () => {
expect(bank.stableCoin).to.be.deep.equal(_bankBox.assets[0]);
expect(bank.reserveCoin).to.be.deep.equal(_bankBox.assets[1]);
expect(bank.nft).to.be.deep.equal(_bankBox.assets[2]);

expect(bank.getUIFee(100n)).to.be.equal(0n); // no configured UI fee, should be zero
});

it("Should set percentage based UI fee parameters", () => {
const bank = new AgeUSDBank(_bankBox, _oracleBox, SIGMA_USD_PARAMETERS);
bank.setUIFee({
percentage: 20n,
precision: 4n,
address: "9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin"
});

expect(bank.uiFeeAddress).to.be.equal("9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin");
expect(bank.getUIFee(1500n)).to.be.equal(3n);

bank.setUIFee({
percentage: 20n,
address: "9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin"
});

expect(bank.uiFeeAddress).to.be.equal("9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin");
expect(bank.getUIFee(1500n)).to.be.equal(30n);

bank.setUIFee({
percentage: 0n,
address: "9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin"
});

expect(bank.uiFeeAddress).to.be.equal("9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin");
expect(bank.getUIFee(1501232310n)).to.be.equal(0n);
});

it("Should set callback based UI fee parameters", () => {
const bank = new AgeUSDBank(_bankBox, _oracleBox, SIGMA_USD_PARAMETERS);
// percentage
bank.setUIFee({
callback: (amount) => percent(amount, 2n),
address: "9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin"
});

expect(bank.uiFeeAddress).to.be.equal("9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin");
expect(bank.getUIFee(1500n)).to.be.equal(30n);

// flat fee
bank.setUIFee({
callback: () => 100n,
address: "9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin"
});

expect(bank.uiFeeAddress).to.be.equal("9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin");
expect(bank.getUIFee(1500n)).to.be.equal(100n);
expect(bank.getUIFee(1n)).to.be.equal(100n);
expect(bank.getUIFee(1123n)).to.be.equal(100n);
});

it("Should fail with incorrect bank parameters", () => {
Expand Down Expand Up @@ -211,6 +265,12 @@ describe("Bank calculations", () => {
const oracleBox = mockOracleBox(SParse("05ccadf6ee05"));
const bank = new AgeUSDBank(bankBox, oracleBox, SIGMA_USD_PARAMETERS);

bank.setUIFee({
percentage: 2n,
precision: 3n,
address: "9i3g6d958MpZAqWn9hrTHcqbBiY5VPYBBY6vRDszZn4koqnahin"
});

expect(bank.stableCoinNominalPrice).to.be.equal(7_874_015n);
expect(bank.reserveCoinNominalPrice).to.be.equal(422_904n);
expect(bank.reserveRatio).to.be.equal(341n);
Expand Down
90 changes: 70 additions & 20 deletions plugins/ageusd/src/ageUsdBank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,50 @@ import {
Amount,
Box,
ensureBigInt,
hasKey,
isDefined,
max,
min,
percent,
TokenAmount
} from "@fleet-sdk/common";
import { OnlyR4Register, R4ToR5Registers, SParse } from "@fleet-sdk/core";
import { AgeUSDBankParameters } from "./sigmaUsdParameters";

const _2n = BigInt(2);
const _3n = BigInt(3);
const _100n = BigInt(100);
const _1000n = BigInt(1000);
const _1000000000n = BigInt(1000000000);

export type AgeUSDBankBox<T extends Amount = Amount> = Box<T, R4ToR5Registers>;
export type OracleBox = Box<Amount, OnlyR4Register>;
export type UIFeeCallback = (amount: bigint) => bigint;

export type UIFeeCallbackParams = {
callback: UIFeeCallback;
address: string;
};

export type UIFeePercentageParams = {
percentage: bigint;
precision?: bigint;
address: string;
};

type UIFeeParams = UIFeeCallbackParams | UIFeePercentageParams;

function isUIFeeCallbackParams(params: UIFeeParams): params is UIFeeCallbackParams {
return hasKey(params, "callback");
}

export class AgeUSDBank {
private readonly _bankBox: AgeUSDBankBox;
private readonly _oracleBox: OracleBox;
private readonly _oracleRate: bigint;
private readonly _params: AgeUSDBankParameters;

private _uiFeeParams?: UIFeeCallbackParams;

constructor(bankBox: AgeUSDBankBox, oracleBox: OracleBox, params: AgeUSDBankParameters) {
if (!this.validateBankBox(bankBox, params)) {
throw new Error("Invalid bank box.");
Expand Down Expand Up @@ -197,6 +219,10 @@ export class AgeUSDBank {
return _1000000000n / this.reserveCoinNominalPrice;
}

get uiFeeAddress(): string | undefined {
return this._uiFeeParams?.address;
}

protected validateBankBox(bankBox: AgeUSDBankBox, params: AgeUSDBankParameters): boolean {
return (
bankBox.assets.length === 3 &&
Expand All @@ -215,12 +241,36 @@ export class AgeUSDBank {
);
}

protected getImplementorFee(amount: bigint): bigint {
return (amount * _2n) / _1000n;
setUIFee(params: UIFeeCallbackParams): AgeUSDBank;
setUIFee(params: UIFeePercentageParams): AgeUSDBank;
setUIFee(params: UIFeeParams): AgeUSDBank {
if (isUIFeeCallbackParams(params)) {
this._uiFeeParams = params;

return this;
}

this._uiFeeParams = {
address: params.address,
callback:
params.percentage > _0n
? (amount) => percent(amount, params.percentage, params.precision || _3n)
: () => _0n
};

return this;
}

getUIFee(amount: bigint): bigint {
if (!this._uiFeeParams || !this._uiFeeParams.callback) {
return _0n;
}

return this._uiFeeParams.callback(amount);
}

protected getProtocolFee(amount: bigint): bigint {
return (amount * _2n) / _100n;
getProtocolFee(amount: bigint): bigint {
return percent(amount, _2n);
}

protected getReserveRatio(
Expand Down Expand Up @@ -293,37 +343,37 @@ export class AgeUSDBank {
const baseCost = this.getStableCoinMintingBaseCost(amount);
const minBoxValue = this._params.minBoxValue;

return baseCost + transactionFee + minBoxValue * _2n + this.getImplementorFee(baseCost);
return baseCost + transactionFee + minBoxValue * _2n + this.getUIFee(baseCost);
}

getStableCoinMintingFees(amount: bigint, transactionFee: bigint): bigint {
const baseAmount = this.stableCoinNominalPrice * amount;
const protocolFee = this.getProtocolFee(baseAmount);
const implementorFee = this.getImplementorFee(baseAmount + protocolFee);
const uiFee = this.getUIFee(baseAmount + protocolFee);

return transactionFee + protocolFee + implementorFee;
return transactionFee + protocolFee + uiFee;
}

getStableCoinMintingBaseCost(amount: bigint): bigint {
const feeLessAmount = this.stableCoinNominalPrice * amount;
const protocolFee = this.getProtocolFee(feeLessAmount);
const baseAmount = this.stableCoinNominalPrice * amount;
const protocolFee = this.getProtocolFee(baseAmount);

return feeLessAmount + protocolFee;
return baseAmount + protocolFee;
}

getTotalReserveCoinMintingCost(amount: bigint, transactionFee: bigint): bigint {
const baseCost = this.getReserveCoinMintingBaseCost(amount);
const minBoxValue = this._params.minBoxValue;

return baseCost + transactionFee + minBoxValue * _2n + this.getImplementorFee(baseCost);
return baseCost + transactionFee + minBoxValue * _2n + this.getUIFee(baseCost);
}

getReserveCoinMintingFees(amount: bigint, transactionFee: bigint): bigint {
const baseAmount = this.reserveCoinNominalPrice * amount;
const protocolFee = this.getProtocolFee(baseAmount);
const implementorFee = this.getImplementorFee(baseAmount + protocolFee);
const uiFee = this.getUIFee(baseAmount + protocolFee);

return transactionFee + protocolFee + implementorFee;
return transactionFee + protocolFee + uiFee;
}

getReserveCoinMintingBaseCost(amount: bigint): bigint {
Expand All @@ -335,17 +385,17 @@ export class AgeUSDBank {

getTotalReserveCoinRedeemingAmount(amount: bigint, transactionFee: bigint): bigint {
const baseAmount = this.getReserveCoinRedeemingBaseAmount(amount);
const fees = transactionFee + this.getImplementorFee(baseAmount);
const fees = transactionFee + this.getUIFee(baseAmount);

return max(baseAmount - fees, _0n);
}

getReserveCoinRedeemingFees(amount: bigint, transactionFee: bigint): bigint {
const baseAmount = this.reserveCoinNominalPrice * amount;
const protocolFee = this.getProtocolFee(baseAmount);
const implementorFee = this.getImplementorFee(this.getReserveCoinRedeemingBaseAmount(amount));
const uiFee = this.getUIFee(this.getReserveCoinRedeemingBaseAmount(amount));

return transactionFee + protocolFee + implementorFee;
return transactionFee + protocolFee + uiFee;
}

getReserveCoinRedeemingBaseAmount(amount: bigint): bigint {
Expand All @@ -357,17 +407,17 @@ export class AgeUSDBank {

getTotalStableCoinRedeemingAmount(amount: bigint, transactionFee: bigint): bigint {
const baseAmount = this.getStableCoinRedeemingBaseAmount(amount);
const fees = transactionFee + this.getImplementorFee(baseAmount);
const fees = transactionFee + this.getUIFee(baseAmount);

return max(baseAmount - fees, _0n);
}

getRedeemingStableCoinFees(amount: bigint, transactionFee: bigint): bigint {
const baseAmount = this.stableCoinNominalPrice * amount;
const protocolFee = this.getProtocolFee(baseAmount);
const implementorFee = this.getImplementorFee(baseAmount + protocolFee);
const uiFee = this.getUIFee(baseAmount + protocolFee);

return protocolFee + transactionFee + implementorFee;
return protocolFee + transactionFee + uiFee;
}

getStableCoinRedeemingBaseAmount(amount: bigint): bigint {
Expand Down

0 comments on commit 5293925

Please sign in to comment.