Skip to content

Commit

Permalink
update fuzzers with random erc20 and decimals, update applyRefinanceC…
Browse files Browse the repository at this point in the history
…onsideration to just override the debt based on incoming payment data from getRefinanceConsideration
  • Loading branch information
androolloyd committed Nov 20, 2023
1 parent d77352e commit 37acaad
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 64 deletions.
30 changes: 17 additions & 13 deletions src/Starport.sol
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ contract Starport is PausableNonReentrant {

_settle(loan);
_postRepaymentExecute(loan, msg.sender);
loan = applyRefinanceConsiderationToLoan(loan, considerationPayment, carryPayment, pricingData);
loan.debt = applyRefinanceConsiderationToLoan(considerationPayment, carryPayment);
loan.terms.pricingData = pricingData;

StarportLib.transferSpentItems(considerationPayment, lender, loan.issuer, false);
if (carryPayment.length > 0) {
Expand Down Expand Up @@ -246,39 +247,42 @@ contract Starport is PausableNonReentrant {
}
}

function applyRefinanceConsiderationToLoan(
Starport.Loan memory loan,
SpentItem[] memory considerationPayment,
SpentItem[] memory carryPayment,
bytes calldata pricingData
) public pure returns (Starport.Loan memory) {
function applyRefinanceConsiderationToLoan(SpentItem[] memory considerationPayment, SpentItem[] memory carryPayment)
public
pure
returns (SpentItem[] memory newDebt)
{
if (
considerationPayment.length == 0
|| (carryPayment.length != 0 && considerationPayment.length != carryPayment.length)
|| considerationPayment.length != loan.debt.length
) {
revert MalformedRefinance();
}
newDebt = new SpentItem[](considerationPayment.length);

uint256 i = 0;
if (carryPayment.length > 0) {
for (; i < considerationPayment.length;) {
loan.debt[i].amount = considerationPayment[i].amount + carryPayment[i].amount;

newDebt[i] = considerationPayment[i];
newDebt[i].amount += carryPayment[i].amount;
if (newDebt[i].itemType == ItemType.ERC721 && newDebt[i].amount > 1) {
revert MalformedRefinance();
}
unchecked {
++i;
}
}
} else {
for (; i < considerationPayment.length;) {
loan.debt[i].amount = considerationPayment[i].amount;
newDebt[i] = considerationPayment[i];
if (newDebt[i].itemType == ItemType.ERC721 && newDebt[i].amount > 1) {
revert MalformedRefinance();
}
unchecked {
++i;
}
}
}
loan.terms.pricingData = pricingData;
return loan;
}

/**
Expand Down
10 changes: 0 additions & 10 deletions test/StarportTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -482,16 +482,6 @@ contract StarportTest is BaseOrderTest {
return refinanceLoan(loan, newPricingData, asWho, lenderCaveat, lender, "");
}

function getRefinanceCaveat(Starport.Loan memory loan, bytes memory pricingData, address fulfiller)
public
view
returns (Starport.Loan memory)
{
(SpentItem[] memory considerationPayment, SpentItem[] memory carryPayment,) =
Pricing(loan.terms.pricing).getRefinanceConsideration(loan, pricingData, fulfiller);
return SP.applyRefinanceConsiderationToLoan(loan, considerationPayment, carryPayment, pricingData);
}

function refinanceLoan(
Starport.Loan memory loan,
bytes memory pricingData,
Expand Down
144 changes: 117 additions & 27 deletions test/fuzz-testing/TestFuzzStarport.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,63 @@ import "starport-test/utils/Bound.sol";
import {FixedPointMathLib} from "solady/src/utils/FixedPointMathLib.sol";
import {DeepEq} from "../utils/DeepEq.sol";
import {StarportLib} from "starport-core/lib/StarportLib.sol";
import {ERC20 as RariERC20} from "@rari-capital/solmate/src/tokens/ERC20.sol";

contract TestDebt is RariERC20 {
bool public blocked;

bool public noReturnData;

constructor(uint8 decimals) RariERC20("Test20", "TST20", decimals) {
blocked = false;
noReturnData = false;
}

function blockTransfer(bool blocking) external {
blocked = blocking;
}

function setNoReturnData(bool noReturn) external {
noReturnData = noReturn;
}

function mint(address to, uint256 amount) external returns (bool) {
_mint(to, amount);
return true;
}

function transferFrom(address from, address to, uint256 amount) public override returns (bool ok) {
if (blocked) {
return false;
}

uint256 allowed = allowance[from][msg.sender];

if (amount > allowed) {
revert("NOT_AUTHORIZED");
}

super.transferFrom(from, to, amount);

if (noReturnData) {
assembly {
return(0, 0)
}
}

ok = true;
}

function increaseAllowance(address spender, uint256 amount) external returns (bool) {
uint256 current = allowance[msg.sender][spender];
uint256 remaining = type(uint256).max - current;
if (amount > remaining) {
amount = remaining;
}
approve(spender, current + amount);
return true;
}
}

contract TestFuzzStarport is StarportTest, Bound, DeepEq {
using FixedPointMathLib for uint256;
Expand Down Expand Up @@ -54,7 +111,7 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
BasePricing.Details memory details = BasePricing.Details({
rate: _boundMax(min, (uint256(1e16) * 150)),
carryRate: _boundMax(0, uint256((1e16 * 100))),
decimals: 18
decimals: _boundMax(0, 18)
});
pricingData = abi.encode(details);
}
Expand Down Expand Up @@ -133,12 +190,24 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
}
loan.collateral = ret;
SpentItem[] memory debt = new SpentItem[](1);
debt[0] = SpentItem({
itemType: ItemType.ERC20,
identifier: 0,
amount: _boundMax(params.debtAmount, type(uint128).max),
token: address(erc20s[1])
});
BasePricing.Details memory pricingDetails = abi.decode(loan.terms.pricingData, (BasePricing.Details));
if (pricingDetails.decimals == 18) {
debt[0] = SpentItem({
itemType: ItemType.ERC20,
identifier: 0,
amount: _boundMax(params.debtAmount, type(uint128).max),
token: address(erc20s[1])
});
} else {
TestDebt newDebt = new TestDebt(uint8(pricingDetails.decimals));
debt[0] = SpentItem({
itemType: ItemType.ERC20,
identifier: 0,
amount: _boundMax(params.debtAmount, type(uint128).max),
token: address(newDebt)
});
}

loan.debt = debt;
loan.borrower = borrower.addr;
loan.custodian = SP.defaultCustodian();
Expand Down Expand Up @@ -203,17 +272,19 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
} else {
fulfiller = _toAddress(_boundMin(_toUint(params.fulfiller), 100));
}
uint256 borrowerDebtBalanceBefore = erc20s[1].balanceOf(loan.borrower);
uint256 borrowerDebtBalanceBefore = ERC20(loan.debt[0].token).balanceOf(loan.borrower);

goodLoan = newLoan(loan, borrowerSalt, lenderSalt, fulfiller);

if (params.feesOn) {
assert(
erc20s[1].balanceOf(loan.borrower)
ERC20(loan.debt[0].token).balanceOf(loan.borrower)
== (borrowerDebtBalanceBefore + (loan.debt[0].amount - loan.debt[0].amount.mulWad(feeRake)))
);
} else {
assert(erc20s[1].balanceOf(loan.borrower) == (borrowerDebtBalanceBefore + loan.debt[0].amount));
assert(
ERC20(loan.debt[0].token).balanceOf(loan.borrower) == (borrowerDebtBalanceBefore + loan.debt[0].amount)
);
}
}

Expand Down Expand Up @@ -342,16 +413,22 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
extraData: abi.encode(Custodian.Command(Actions.Repayment, badLoan, ""))
});

vm.startPrank(badLoan.borrower);
for (uint256 i = 0; i < paymentConsideration.length; i++) {
TestDebt token = TestDebt(paymentConsideration[i].token);
token.mint(goodLoan.borrower, paymentConsideration[i].amount);
token.approve(address(consideration), type(uint256).max);
}
if (keccak256(abi.encode(goodLoan)) != keccak256(abi.encode(badLoan))) {
vm.expectRevert();
}
vm.prank(badLoan.borrower);
consideration.fulfillAdvancedOrder({
advancedOrder: x,
criteriaResolvers: new CriteriaResolver[](0),
fulfillerConduitKey: bytes32(0),
recipient: address(badLoan.borrower)
});
vm.stopPrank();
}

function testFuzzRepaymentSuccess(FuzzRepaymentLoan memory params) public {
Expand All @@ -366,9 +443,6 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
new SpentItem[](0),
abi.encode(Custodian.Command(Actions.Repayment, goodLoan, ""))
);
for (uint256 i = 0; i < paymentConsideration.length; i++) {
erc20s[0].mint(goodLoan.borrower, paymentConsideration[i].amount);
}

OrderParameters memory op = _buildContractOrder(
address(goodLoan.custodian), _SpentItemsToOfferItems(offer), _toConsiderationItems(paymentConsideration)
Expand All @@ -382,7 +456,11 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
});

vm.startPrank(goodLoan.borrower);
erc20s[0].approve(address(consideration), type(uint256).max);
for (uint256 i = 0; i < paymentConsideration.length; i++) {
TestDebt token = TestDebt(paymentConsideration[i].token);
token.mint(goodLoan.borrower, paymentConsideration[i].amount);
token.approve(address(consideration), type(uint256).max);
}
consideration.fulfillAdvancedOrder({
advancedOrder: x,
criteriaResolvers: new CriteriaResolver[](0),
Expand Down Expand Up @@ -428,16 +506,22 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
extraData: abi.encode(Actions.Settlement, badLoan)
});

vm.startPrank(badLoan.borrower);
for (uint256 i = 0; i < paymentConsideration.length; i++) {
TestDebt token = TestDebt(paymentConsideration[i].token);
token.mint(goodLoan.borrower, paymentConsideration[i].amount);
token.approve(address(consideration), type(uint256).max);
}
if (keccak256(abi.encode(goodLoan)) != keccak256(abi.encode(badLoan))) {
vm.expectRevert();
}
vm.prank(badLoan.borrower);
consideration.fulfillAdvancedOrder({
advancedOrder: x,
criteriaResolvers: new CriteriaResolver[](0),
fulfillerConduitKey: bytes32(0),
recipient: address(badLoan.borrower)
});
vm.stopPrank();
}

function testFuzzSettlementSuccess(FuzzSettleLoan memory params) public {
Expand Down Expand Up @@ -478,7 +562,11 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
});

vm.startPrank(filler);
erc20s[1].approve(address(consideration), type(uint256).max);
for (uint256 i = 0; i < paymentConsideration.length; i++) {
TestDebt token = TestDebt(paymentConsideration[i].token);
token.mint(filler, paymentConsideration[i].amount);
token.approve(address(consideration), type(uint256).max);
}
consideration.fulfillAdvancedOrder({
advancedOrder: x,
criteriaResolvers: new CriteriaResolver[](0),
Expand All @@ -491,11 +579,14 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
function testFuzzRefinance(FuzzRefinanceLoan memory params) public {
Starport.Loan memory goodLoan = fuzzNewLoanOrigination(params.origination, LoanBounds(1));

uint256 oldRate = abi.decode(goodLoan.terms.pricingData, (BasePricing.Details)).rate;
BasePricing.Details memory oldDetails = abi.decode(goodLoan.terms.pricingData, (BasePricing.Details));

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

address refiFulfiller;
Expand All @@ -516,11 +607,10 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
refiFulfiller = _toAddress(_boundMin(params.skipTime, 100));
}
Starport.Loan memory goodLoan2 = goodLoan;
LenderEnforcer.Details memory details = LenderEnforcer.Details({
loan: SP.applyRefinanceConsiderationToLoan(
goodLoan2, considerationPayment, carryPayment, abi.encode(newPricingDetails)
)
});
Starport.Loan memory refiLoan = loanCopy(goodLoan);
refiLoan.terms.pricingData = abi.encode(newPricingDetails);
refiLoan.debt = SP.applyRefinanceConsiderationToLoan(considerationPayment, carryPayment);
LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan});
_issueAndApproveTarget(details.loan.debt, account.addr, address(SP));

details.loan.issuer = account.addr;
Expand All @@ -534,7 +624,7 @@ contract TestFuzzStarport is StarportTest, Bound, DeepEq {
enforcer: address(lenderEnforcer)
});
{
if (newRate > oldRate) {
if (newRate > oldDetails.rate) {
vm.expectRevert();
}
vm.prank(refiFulfiller);
Expand Down
14 changes: 7 additions & 7 deletions test/integration-testing/TestCaveats.sol
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,9 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {

function testRefinanceWCaveatsInvalidSalt() public {
Starport.Loan memory loan = newLoanWithDefaultTerms();

LenderEnforcer.Details memory details = LenderEnforcer.Details({
loan: SP.applyRefinanceConsiderationToLoan(loan, loan.debt, new SpentItem[](0), defaultPricingData)
});
Starport.Loan memory refiLoan = loanCopy(loan);
refiLoan.debt = SP.applyRefinanceConsiderationToLoan(loan.debt, new SpentItem[](0));
LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan});

details.loan.issuer = lender.addr;
details.loan.originator = address(0);
Expand Down Expand Up @@ -233,9 +232,10 @@ contract IntegrationTestCaveats is StarportTest, DeepEq, MockCall {

function testRefinanceUnapprovedFulfiller() public {
Starport.Loan memory loan = newLoanWithDefaultTerms();
LenderEnforcer.Details memory details = LenderEnforcer.Details({
loan: SP.applyRefinanceConsiderationToLoan(loan, loan.debt, new SpentItem[](0), defaultPricingData)
});
Starport.Loan memory refiLoan = loanCopy(loan);

refiLoan.debt = SP.applyRefinanceConsiderationToLoan(loan.debt, new SpentItem[](0));
LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan});

details.loan.issuer = lender.addr;
details.loan.originator = address(0);
Expand Down
7 changes: 4 additions & 3 deletions test/integration-testing/TestNewLoan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,12 @@ contract TestNewLoan is StarportTest {

function testNewLoanRefinance() public {
Starport.Loan memory loan = testNewLoanERC721CollateralDefaultTerms2();
Starport.Loan memory refiLoan = loanCopy(loan);
bytes memory newPricingData =
abi.encode(BasePricing.Details({carryRate: (uint256(1e16) * 10), rate: uint256(1e16) * 100, decimals: 18}));
LenderEnforcer.Details memory details = LenderEnforcer.Details({
loan: SP.applyRefinanceConsiderationToLoan(loan, loan.debt, new SpentItem[](0), newPricingData)
});
refiLoan.terms.pricingData = newPricingData;
refiLoan.debt = SP.applyRefinanceConsiderationToLoan(loan.debt, new SpentItem[](0));
LenderEnforcer.Details memory details = LenderEnforcer.Details({loan: refiLoan});

details.loan.issuer = refinancer.addr;
details.loan.originator = address(0);
Expand Down
Loading

0 comments on commit 37acaad

Please sign in to comment.