Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update getTargetFeesAndTaxes #63

Merged
merged 3 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions bulla-contracts/src/functions/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,19 +302,17 @@ export const calculateTax = (poolAddress: Address, version: string, amount: BigI
};

export const getTargetFeesAndTaxes = (poolAddress: Address, version: string, invoiceId: BigInt): BigInt[] => {
const upfrontBps = getApprovedInvoiceUpfrontBps(poolAddress, version, invoiceId);

if (version == "v1") {
const targetFees = BullaFactoring.bind(poolAddress).calculateTargetFees(invoiceId, upfrontBps);
const targetInterest = targetFees.getTargetInterest();
const targetTax = calculateTax(poolAddress, version, targetInterest);
return [targetInterest, targetFees.getTargetProtocolFee(), targetFees.getAdminFee(), targetTax];
} else {
const targetFees = BullaFactoringv2.bind(poolAddress).calculateTargetFees(invoiceId, upfrontBps);
const targetInterest = targetFees.getTargetInterest();
const targetTax = calculateTax(poolAddress, version, targetInterest);
return [targetInterest, targetFees.getTargetProtocolFee(), targetFees.getAdminFee(), targetTax];
}
const approvedInvoice = BullaFactoring.bind(poolAddress).approvedInvoices(invoiceId);
bengobeil marked this conversation as resolved.
Show resolved Hide resolved
const grossAmount = approvedInvoice.getFundedAmountGross();
const netAmount = approvedInvoice.getFundedAmountNet();
const targetFees = grossAmount.minus(netAmount);
const adminFee = approvedInvoice.getAdminFee();
const protocolPlusGrossInterest = targetFees.minus(adminFee);
const protocolFeeBps = BigInt.fromI32(BullaFactoring.bind(poolAddress).protocolFeeBps());
const protocolFee = protocolFeeBps.times(protocolPlusGrossInterest).div(BigInt.fromI32(10_000).plus(protocolFeeBps));
const grossInterest = protocolPlusGrossInterest.minus(protocolFee);
const tax = calculateTax(poolAddress, version, grossInterest);
return [grossInterest, protocolFee, adminFee, tax];
};

// For compatibility with InvoiceReconciled for v1 fees
Expand All @@ -323,19 +321,17 @@ export const getTrueFeesAndTaxesV1 = (poolAddress: Address, invoiceId: BigInt):
const adminFee = targetFees[2]; // in v1 realisedAdminFee = targetAdminFee
const paidTax = BullaFactoring.bind(poolAddress).paidInvoiceTax(invoiceId);
const trueInterest = BullaFactoring.bind(poolAddress).paidInvoicesGain(invoiceId);

/* we can't assume no kickback for V1, because they can repay a 100% upfront invoice early and get some of the targetInterest back.
So instead, let's do a rule of three:

targetProtocolFee trueProtocolFee
---------------------- = ---------------------
targetInterest (gross) trueInterestNet + paidTax
*/
const trueProcotolFee =
targetFees[1] // targetProcotolFee
.times((trueInterest.plus(paidTax)))
.div(targetFees[0]) // targetInterest

const trueProcotolFee = targetFees[1] // targetProcotolFee
.times(trueInterest.plus(paidTax))
.div(targetFees[0]); // targetInterest

return [trueInterest, trueProcotolFee, adminFee, paidTax];
};
5 changes: 4 additions & 1 deletion bulla-contracts/src/mappings/BullaFactoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ export function handleInvoicePaid(event: InvoicePaid, version: string): void {
InvoiceReconciledEvent.priceAfterTransaction = latestPrice;
InvoiceReconciledEvent.claim = underlyingClaim.id;

original_creditor.factoringEvents = original_creditor.factoringEvents ? original_creditor.factoringEvents.concat([InvoiceReconciledEvent.id]) : [InvoiceReconciledEvent.id];
original_creditor.factoringEvents = original_creditor.factoringEvents
? original_creditor.factoringEvents.concat([InvoiceReconciledEvent.id])
: [InvoiceReconciledEvent.id];

InvoiceReconciledEvent.save();
original_creditor.save();
Expand Down Expand Up @@ -508,6 +510,7 @@ export function handleInvoiceImpaired(event: InvoiceImpaired, version: string):
InvoiceImpairedEvent.save();
price_per_share.save();
historical_factoring_statistics.save();
pool_pnl.save();
}

export function handleInvoiceImpairedV1(event: InvoiceImpaired): void {
Expand Down
18 changes: 7 additions & 11 deletions bulla-contracts/tests/BullaFactoring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,12 @@ test("it handles InvoicePaid event for v2", () => {
assert.bigIntEquals(trueInterest, pnlHistoryEntry!.pnl);

const invoiceReconciledEventId = getInvoiceReconciledEventId(claimId, invoicePaidEvent);
assert.fieldEquals("InvoicePaidEvent", invoiceReconciledEventId, "invoiceId", invoicePaidEvent.params.invoiceId.toString());
assert.fieldEquals("InvoicePaidEvent", invoiceReconciledEventId, "trueInterest", invoicePaidEvent.params.trueInterest.toString());
assert.fieldEquals("InvoicePaidEvent", invoiceReconciledEventId, "trueAdminFee", invoicePaidEvent.params.adminFee.toString());
assert.fieldEquals("InvoicePaidEvent", invoiceReconciledEventId, "trueProtocolFee", invoicePaidEvent.params.trueProtocolFee.toString());
assert.fieldEquals("InvoicePaidEvent", invoiceReconciledEventId, "poolAddress", MOCK_BULLA_FACTORING_ADDRESS.toHexString());
assert.fieldEquals("InvoicePaidEvent", invoiceReconciledEventId, "claim", claimId.toString());
assert.fieldEquals("InvoiceReconciledEvent", invoiceReconciledEventId, "invoiceId", invoicePaidEvent.params.invoiceId.toString());
assert.fieldEquals("InvoiceReconciledEvent", invoiceReconciledEventId, "trueInterest", invoicePaidEvent.params.trueInterest.toString());
assert.fieldEquals("InvoiceReconciledEvent", invoiceReconciledEventId, "trueAdminFee", invoicePaidEvent.params.adminFee.toString());
assert.fieldEquals("InvoiceReconciledEvent", invoiceReconciledEventId, "trueProtocolFee", invoicePaidEvent.params.trueProtocolFee.toString());
assert.fieldEquals("InvoiceReconciledEvent", invoiceReconciledEventId, "poolAddress", MOCK_BULLA_FACTORING_ADDRESS.toHexString());
assert.fieldEquals("InvoiceReconciledEvent", invoiceReconciledEventId, "claim", claimId.toString());

log.info("✅ should create a InvoicePaid event", []);

Expand Down Expand Up @@ -418,9 +418,5 @@ test("it handles BullaFactoring v2 events and stores price history", () => {
assert.bigIntEquals(BigInt.fromI32(1100000), newPriceHistoryEntry!.price);
});


// exporting for test coverage
export {
handleClaimCreated, handleInvoiceFundedV2, handleInvoiceKickbackAmountSentV2, handleInvoicePaidV2, handleInvoiceUnfactoredV2
};

export { handleClaimCreated, handleInvoiceFundedV2, handleInvoiceKickbackAmountSentV2, handleInvoicePaidV2, handleInvoiceUnfactoredV2 };
64 changes: 64 additions & 0 deletions bulla-contracts/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,70 @@ export const setupContracts = (): void => {
ethereum.Value.fromI32(10000),
ethereum.Value.fromI32(10000)
]);

createMockedFunction(
MOCK_BULLA_FACTORING_ADDRESS,
"approvedInvoices",
"approvedInvoices(uint256):(bool,(uint256,address,address,uint256,address,uint256,bool),uint256,uint256,uint16,uint16,uint256,uint256,uint256,uint16,uint256)"
)
.withArgs([ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1))])
.returns([
ethereum.Value.fromBoolean(true),
ethereum.Value.fromTuple(
changetype<ethereum.Tuple>([
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)),
ethereum.Value.fromAddress(ADDRESS_1),
ethereum.Value.fromAddress(ADDRESS_1),
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)),
ethereum.Value.fromAddress(ADDRESS_1),
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)),
ethereum.Value.fromBoolean(false)
])
),
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // fundedAmountGross
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // fundedAmountNet
ethereum.Value.fromI32(9000), // upfrontBps
ethereum.Value.fromI32(10000), // adminFeeBps
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // adminFee
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // targetInterest
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // targetProtocolFee
ethereum.Value.fromI32(10000), // protocolFeeBps
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)) // timestamp
]);

// Add another mock for invoice ID 2
createMockedFunction(
MOCK_BULLA_FACTORING_ADDRESS,
"approvedInvoices",
"approvedInvoices(uint256):(bool,(uint256,address,address,uint256,address,uint256,bool),uint256,uint256,uint16,uint16,uint256,uint256,uint256,uint16,uint256)"
)
.withArgs([ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(2))])
.returns([
ethereum.Value.fromBoolean(true),
ethereum.Value.fromTuple(
changetype<ethereum.Tuple>([
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)),
ethereum.Value.fromAddress(ADDRESS_1),
ethereum.Value.fromAddress(ADDRESS_1),
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)),
ethereum.Value.fromAddress(ADDRESS_1),
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)),
ethereum.Value.fromBoolean(false)
])
),
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // fundedAmountGross
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // fundedAmountNet
ethereum.Value.fromI32(9000), // upfrontBps
ethereum.Value.fromI32(10000), // adminFeeBps
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // adminFee
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // targetInterest
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)), // targetProtocolFee
ethereum.Value.fromI32(10000), // protocolFeeBps
ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(1000000)) // timestamp
]);

createMockedFunction(MOCK_BULLA_FACTORING_ADDRESS, "protocolFeeBps", "protocolFeeBps():(uint16)").returns([ethereum.Value.fromI32(500)]); // Example: 5% protocol fee (500 basis points)

updateFundInfoMock(BigInt.fromI32(10000), BigInt.fromI32(5000), BigInt.fromI32(15000));
};

Expand Down
Loading