Skip to content

Commit

Permalink
feat: implement decimal support (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
dangerousfood authored Nov 13, 2023
1 parent 044dae5 commit 012be7a
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 184 deletions.
228 changes: 108 additions & 120 deletions .gas-snapshot

Large diffs are not rendered by default.

21 changes: 7 additions & 14 deletions src/lib/StarportLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -277,20 +277,13 @@ library StarportLib {
}
}

function calculateCompoundInterest(
uint256 delta_t,
uint256 amount,
uint256 rate // expressed as SPR seconds per rate
) public pure returns (uint256) {
return amount.mulWad(uint256(int256(rate * delta_t).expWad())) - amount;
}

function calculateSimpleInterest(
uint256 delta_t,
uint256 amount,
uint256 rate // expressed as SPR seconds per rate
) public pure returns (uint256) {
return (delta_t * rate).mulWad(amount);
function calculateSimpleInterest(uint256 delta_t, uint256 amount, uint256 rate, uint256 decimals)
public
pure
returns (uint256)
{
rate /= 365 days;
return ((delta_t * rate) * amount) / 10 ** decimals;
}

function _transferItem(
Expand Down
38 changes: 23 additions & 15 deletions src/pricing/BasePricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ abstract contract BasePricing is Pricing {
struct Details {
uint256 rate;
uint256 carryRate;
uint256 decimals;
}

function getPaymentConsideration(Starport.Loan memory loan)
Expand All @@ -46,7 +47,7 @@ abstract contract BasePricing is Pricing {
returns (SpentItem[] memory repayConsideration, SpentItem[] memory carryConsideration)
{
Details memory details = abi.decode(loan.terms.pricingData, (Details));
if (details.carryRate > 0) {
if (details.carryRate > 0 && loan.issuer != loan.originator) {
carryConsideration = new SpentItem[](loan.debt.length);
} else {
carryConsideration = new SpentItem[](0);
Expand All @@ -55,13 +56,13 @@ abstract contract BasePricing is Pricing {

uint256 i = 0;
for (; i < loan.debt.length;) {
uint256 interest = getInterest(loan, details.rate, loan.start, block.timestamp, i);
uint256 interest = getInterest(loan, details.rate, loan.start, block.timestamp, i, details.decimals);

if (details.carryRate > 0) {
if (carryConsideration.length > 0) {
carryConsideration[i] = SpentItem({
itemType: loan.debt[i].itemType,
identifier: loan.debt[i].identifier,
amount: interest.mulWad(details.carryRate),
amount: (interest * details.carryRate) / 10 ** details.decimals,
token: loan.debt[i].token
});
repayConsideration[i] = SpentItem({
Expand All @@ -84,18 +85,25 @@ abstract contract BasePricing is Pricing {
}
}

function getInterest(Starport.Loan memory loan, uint256 rate, uint256 start, uint256 end, uint256 index)
public
pure
returns (uint256)
{
function validate(Starport.Loan calldata loan) external pure virtual override returns (bytes4) {
return Pricing.validate.selector;
}

function getInterest(
Starport.Loan memory loan,
uint256 rate,
uint256 start,
uint256 end,
uint256 index,
uint256 decimals
) public pure returns (uint256) {
uint256 delta_t = end - start;
return calculateInterest(delta_t, loan.debt[index].amount, rate);
return calculateInterest(delta_t, loan.debt[index].amount, rate, decimals);
}

function calculateInterest(
uint256 delta_t,
uint256 amount,
uint256 rate // expressed as SPR seconds per rate
) public pure virtual returns (uint256);
function calculateInterest(uint256 delta_t, uint256 amount, uint256 rate, uint256 decimals)
public
pure
virtual
returns (uint256);
}
2 changes: 2 additions & 0 deletions src/pricing/Pricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ abstract contract Pricing {
view
virtual
returns (SpentItem[] memory, SpentItem[] memory, AdditionalTransfer[] memory);

function validate(Starport.Loan calldata loan) external pure virtual returns (bytes4);
}
13 changes: 7 additions & 6 deletions src/pricing/SimpleInterestPricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ contract SimpleInterestPricing is BasePricing {

constructor(Starport SP_) Pricing(SP_) {}

function calculateInterest(
uint256 delta_t,
uint256 amount,
uint256 rate // expressed as SPR seconds per rate
) public pure override returns (uint256) {
return StarportLib.calculateSimpleInterest(delta_t, amount, rate);
function calculateInterest(uint256 delta_t, uint256 amount, uint256 rate, uint256 decimals)
public
pure
override
returns (uint256)
{
return StarportLib.calculateSimpleInterest(delta_t, amount, rate, decimals);
}

function getRefinanceConsideration(Starport.Loan memory loan, bytes memory newPricingData, address fulfiller)
Expand Down
5 changes: 3 additions & 2 deletions src/settlement/DutchAuctionSettlement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ abstract contract DutchAuctionSettlement is Settlement, AmountDeriver {
});

BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details));
uint256 interest =
BasePricing(loan.terms.pricing).getInterest(loan, pricingDetails.rate, loan.start, block.timestamp, 0);
uint256 interest = BasePricing(loan.terms.pricing).getInterest(
loan, pricingDetails.rate, loan.start, block.timestamp, 0, pricingDetails.decimals
);

uint256 carry = interest.mulWad(pricingDetails.carryRate);

Expand Down
7 changes: 4 additions & 3 deletions test/StarportTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ contract StarportTest is BaseOrderTest {

// 1% interest rate per second
bytes defaultPricingData =
abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: (uint256(1e16) * 150) / (365 * 1 days)}));
abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: uint256(1e16) * 150, decimals: 18}));

bytes defaultSettlementData = abi.encode(
DutchAuctionSettlement.Details({startingPrice: uint256(500 ether), endingPrice: 100 wei, window: 7 days})
Expand Down Expand Up @@ -633,8 +633,9 @@ contract StarportTest is BaseOrderTest {
uint256 originatorBefore = erc20s[0].balanceOf(loan.originator);

BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details));
uint256 interest =
SimpleInterestPricing(loan.terms.pricing).calculateInterest(10 days, loan.debt[0].amount, details.rate);
uint256 interest = SimpleInterestPricing(loan.terms.pricing).calculateInterest(
10 days, loan.debt[0].amount, details.rate, details.decimals
);
uint256 carry = interest.mulWad(1e17);

_executeRepayLoan(loan, fulfiller);
Expand Down
7 changes: 4 additions & 3 deletions test/fuzz-testing/TestFuzzStarport.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ contract TestFuzzStarport is StarportTest, Bound {

function boundPricingData(uint256 min) internal view returns (bytes memory pricingData) {
BasePricing.Details memory details = BasePricing.Details({
rate: _boundMax(min, (uint256(1e16) * 150) / (365 * 1 days)),
carryRate: _boundMax(0, uint256((1e16 * 100)))
rate: _boundMax(min, (uint256(1e16) * 150)),
carryRate: _boundMax(0, uint256((1e16 * 100))),
decimals: 18
});
pricingData = abi.encode(details);
}
Expand Down Expand Up @@ -392,7 +393,7 @@ contract TestFuzzStarport is StarportTest, Bound {
uint256 oldRate = abi.decode(goodLoan.terms.pricingData, (BasePricing.Details)).rate;

uint256 newRate = _boundMax(oldRate - 1, (uint256(1e16) * 1000) / (365 * 1 days));
BasePricing.Details memory newPricingDetails = BasePricing.Details({rate: newRate, carryRate: 0});
BasePricing.Details memory newPricingDetails = BasePricing.Details({rate: newRate, carryRate: 0, decimals: 18});
Account memory account = makeAndAllocateAccount(params.refiKey);

address refiFulfiller;
Expand Down
25 changes: 15 additions & 10 deletions test/integration-testing/TestRepayLoan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ contract TestRepayLoan is StarportTest {
vm.startPrank(borrower.addr);
skip(10 days);
BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details));
uint256 interest =
SimpleInterestPricing(loan.terms.pricing).calculateInterest(10 days, loan.debt[0].amount, details.rate);
uint256 interest = SimpleInterestPricing(loan.terms.pricing).calculateInterest(
10 days, loan.debt[0].amount, details.rate, details.decimals
);
erc20s[0].approve(address(SP.seaport()), loan.debt[0].amount + interest);
vm.stopPrank();

Expand All @@ -51,8 +52,9 @@ contract TestRepayLoan is StarportTest {
{
skip(10 days);
BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details));
uint256 interest =
SimpleInterestPricing(loan.terms.pricing).calculateInterest(10 days, loan.debt[0].amount, details.rate);
uint256 interest = SimpleInterestPricing(loan.terms.pricing).calculateInterest(
10 days, loan.debt[0].amount, details.rate, details.decimals
);
erc20s[0].approve(address(SP.seaport()), loan.debt[0].amount + interest);

uint256 balance = erc20s[0].balanceOf(address(this));
Expand Down Expand Up @@ -122,8 +124,9 @@ contract TestRepayLoan is StarportTest {
vm.startPrank(borrower.addr);
skip(10 days);
BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details));
uint256 interest =
SimpleInterestPricing(loan.terms.pricing).calculateInterest(10 days, loan.debt[0].amount, details.rate);
uint256 interest = SimpleInterestPricing(loan.terms.pricing).calculateInterest(
10 days, loan.debt[0].amount, details.rate, details.decimals
);
erc20s[0].approve(address(SP.seaport()), loan.debt[0].amount + interest);
custodian.mintWithApprovalSet(loan, address(this));
vm.stopPrank();
Expand Down Expand Up @@ -175,8 +178,9 @@ contract TestRepayLoan is StarportTest {
vm.startPrank(borrower.addr);
skip(14 days);
BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details));
uint256 interest =
SimpleInterestPricing(loan.terms.pricing).calculateInterest(10 days, loan.debt[0].amount, details.rate);
uint256 interest = SimpleInterestPricing(loan.terms.pricing).calculateInterest(
10 days, loan.debt[0].amount, details.rate, details.decimals
);
erc20s[0].approve(address(SP.seaport()), loan.debt[0].amount + interest);
vm.stopPrank();

Expand Down Expand Up @@ -243,8 +247,9 @@ contract TestRepayLoan is StarportTest {
vm.startPrank(borrower.addr);
skip(10 days);
BasePricing.Details memory details = abi.decode(loan.terms.pricingData, (BasePricing.Details));
uint256 interest =
SimpleInterestPricing(loan.terms.pricing).calculateInterest(10 days, loan.debt[0].amount, details.rate);
uint256 interest = SimpleInterestPricing(loan.terms.pricing).calculateInterest(
10 days, loan.debt[0].amount, details.rate, details.decimals
);
erc20s[0].approve(address(SP.seaport()), loan.debt[0].amount + interest);
vm.stopPrank();

Expand Down
22 changes: 11 additions & 11 deletions test/unit-testing/TestPricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ contract TestSimpleInterestPricing is StarportTest, DeepEq {
settlement: address(settlement),
pricing: address(pricing),
pricingData: abi.encode(
BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: (uint256(1e16) * 150) / (365 * 1 days)})
BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: (uint256(1e16) * 150), decimals: 18})
),
settlementData: abi.encode(
DutchAuctionSettlement.Details({startingPrice: uint256(500 ether), endingPrice: 100 wei, window: 7 days})
Expand Down Expand Up @@ -93,39 +93,39 @@ contract TestSimpleInterestPricing is StarportTest, DeepEq {
uint256 time = 15 days;
uint256 expectedInterest = 6;

assertEq(simplePricing.calculateInterest(time, amount, rate), expectedInterest);
assertEq(simplePricing.calculateInterest(time, amount, rate, 18), expectedInterest);

// TODO: should this be fuzz tested?
assertEq(simplePricing.calculateInterest(time, amount, rate * 2), expectedInterest * 2);
assertEq(simplePricing.calculateInterest(time, amount * 2, rate), expectedInterest * 2);
assertEq(simplePricing.calculateInterest(time * 2, amount, rate), expectedInterest * 2);
assertEq(simplePricing.calculateInterest(time, amount, rate * 2, 18), expectedInterest * 2);
assertEq(simplePricing.calculateInterest(time, amount * 2, rate, 18), expectedInterest * 2);
assertEq(simplePricing.calculateInterest(time * 2, amount, rate, 18), expectedInterest * 2);

vm.expectRevert(stdError.arithmeticError);
simplePricing.calculateInterest(time - (time * 2), amount, rate);
simplePricing.calculateInterest(time - (time * 2), amount, rate, 18);

vm.expectRevert();
simplePricing.calculateInterest(time, amount - (amount * 2), rate);
simplePricing.calculateInterest(time, amount - (amount * 2), rate, 18);

vm.expectRevert();
simplePricing.calculateInterest(time, amount, rate - (rate * 2));
simplePricing.calculateInterest(time, amount, rate - (rate * 2), 18);
}

function test_getRefinanceConsideration() public {
SimpleInterestPricing simplePricing = new SimpleInterestPricing(SP);

uint256 baseRate = (uint256(1e16) * 150) / (365 * 1 days);
uint256 baseRate = (uint256(1e16) * 150);

simplePricing.getRefinanceConsideration(
targetLoan,
abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: baseRate / 2})),
abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: baseRate / 2, decimals: 18})),
address(0)
);

vm.expectRevert(bytes4(keccak256("InvalidRefinance()")));

simplePricing.getRefinanceConsideration(
targetLoan,
abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: baseRate * 2})),
abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: baseRate * 2, decimals: 18})),
address(0)
);
}
Expand Down

0 comments on commit 012be7a

Please sign in to comment.