Skip to content

Commit

Permalink
closes AST-2635, AST-2717, AST-2716, AST-2715, AST-2707, AST-2710, AS…
Browse files Browse the repository at this point in the history
…T-2711, AST-2709, AST-2713
  • Loading branch information
androolloyd committed Dec 29, 2023
1 parent e9ddced commit fec4acd
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 33 deletions.
3 changes: 2 additions & 1 deletion ffi-scripts/test-origination-hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@ const types = {


const domain = (verifyingContract: Address, chainId: number) => ({
name: "Starport",
version: "0",
chainId: chainId,
verifyingContract
});

type caveatType = [`0x${string}`, `0x${string}`];

const typeDataMessage = (account: Address, accountNonce: string, singleUse: number, salt: Hex, deadline: string, caveats: any) => ({
const typeDataMessage = (account: Address, accountNonce: string, singleUse: boolean, salt: Hex, deadline: string, caveats: any) => ({
account: account, accountNonce: parseInt(accountNonce), singleUse: singleUse, salt: salt, deadline: deadline, caveats: caveats[0]
});

Expand Down
1 change: 1 addition & 0 deletions ffi-scripts/test-sign-typed-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const types = {


const domain = (verifyingContract: Address, chainId: number) => ({
name: "Starport",
version: "0",
chainId: chainId,
verifyingContract
Expand Down
35 changes: 21 additions & 14 deletions src/Custodian.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ contract Custodian is ERC721, ContractOffererInterface {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

error CustodianCannotBeAuthorized();
error ImplementInChild();
error InvalidAction();
error InvalidFulfiller();
Expand Down Expand Up @@ -162,12 +162,7 @@ contract Custodian is ERC721, ContractOffererInterface {
* @param loan The loan to mint a custody token for
*/
function mint(Starport.Loan calldata loan) external {
bytes memory encodedLoan = abi.encode(loan);
uint256 loanId = uint256(keccak256(encodedLoan));
if (loan.custodian != address(this) || SP.closed(loanId)) {
revert InvalidLoan();
}
_safeMint(loan.borrower, loanId, encodedLoan);
_validateAndMint(loan);
}

/**
Expand All @@ -176,16 +171,22 @@ contract Custodian is ERC721, ContractOffererInterface {
* @param approvedTo The address with pre approvals set
*/
function mintWithApprovalSet(Starport.Loan calldata loan, address approvedTo) external {
bytes memory encodedLoan = abi.encode(loan);
uint256 loanId = uint256(keccak256(encodedLoan));
if (loan.custodian != address(this) || SP.closed(loanId)) {
revert InvalidLoan();
}
if (msg.sender != loan.borrower) {
revert NotAuthorized();
}
_safeMint(loan.borrower, loanId, encodedLoan);
_approve(loan.borrower, approvedTo, loanId);
_approve(loan.borrower, approvedTo, _validateAndMint(loan));
}

/**
* @dev internal helper that validates and mints a custody token for a loan.
* @param loan The loan to mint a custody token for
*/
function _validateAndMint(Starport.Loan calldata loan) internal returns (uint256 loanId) {
loanId = loan.getId();
if (loan.custodian != address(this) || SP.closed(loanId)) {
revert InvalidLoan();
}
_safeMint(loan.borrower, loanId);
}

/**
Expand Down Expand Up @@ -244,6 +245,9 @@ contract Custodian is ERC721, ContractOffererInterface {
address authorized;
_beforeGetSettlementConsideration(loan);
(consideration, authorized) = Settlement(loan.terms.settlement).getSettlementConsideration(loan);
if (authorized == address(this)) {
revert CustodianCannotBeAuthorized();
}
consideration = StarportLib.removeZeroAmountItems(consideration);
_afterGetSettlementConsideration(loan);
if (authorized == address(0) || fulfiller == authorized) {
Expand Down Expand Up @@ -334,6 +338,9 @@ contract Custodian is ERC721, ContractOffererInterface {
} else if (close.action == Actions.Settlement && !loanActive) {
address authorized;
(consideration, authorized) = Settlement(loan.terms.settlement).getSettlementConsideration(loan);
if (authorized == address(this)) {
revert CustodianCannotBeAuthorized();
}
consideration = StarportLib.removeZeroAmountItems(consideration);
if (authorized == address(0) || fulfiller == authorized) {
offer = loan.collateral;
Expand Down
21 changes: 14 additions & 7 deletions src/Starport.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,16 @@ contract Starport is PausableNonReentrant {
bytes32 private constant _LOAN_EXISTS = 0x14ec57fc00000000000000000000000000000000000000000000000000000000;

Stargate public immutable SG;
uint256 public immutable chainId;
address public immutable defaultCustodian;
bytes32 public immutable DEFAULT_CUSTODIAN_CODE_HASH;
bytes32 public immutable CACHED_DOMAIN_SEPARATOR;

// Define the EIP712 domain and typeHash constants for generating signatures
bytes32 public constant EIP_DOMAIN =
keccak256("EIP712Domain(" "string version," "uint256 chainId," "address verifyingContract" ")");
keccak256("EIP712Domain(" "string name," "string version," "uint256 chainId," "address verifyingContract" ")");
bytes32 public constant VERSION = keccak256(bytes("0"));
bytes32 public constant NAME = keccak256(bytes("Starport"));

bytes32 public constant INTENT_ORIGINATION_TYPEHASH = keccak256(
"Origination(" "address account," "uint256 accountNonce," "bool singleUse," "bytes32 salt," "uint256 deadline,"
Expand Down Expand Up @@ -165,19 +168,25 @@ contract Starport is PausableNonReentrant {

constructor(address seaport_, Stargate stargate_) {
SG = stargate_;
chainId = block.chainid;
CACHED_DOMAIN_SEPARATOR = keccak256(abi.encode(EIP_DOMAIN, NAME, VERSION, block.chainid, address(this)));
address custodian = address(new Custodian(this, seaport_));

bytes32 defaultCustodianCodeHash;
assembly ("memory-safe") {
defaultCustodianCodeHash := extcodehash(custodian)
}
defaultCustodian = payable(custodian);
defaultCustodian = custodian;
DEFAULT_CUSTODIAN_CODE_HASH = defaultCustodianCodeHash;
_initializeOwner(msg.sender);
}

function domainSeparator() public view returns (bytes32) {
return keccak256(abi.encode(EIP_DOMAIN, VERSION, block.chainid, address(this)));
//return the cached domain separator if the chainId is the same
if (chainId == block.chainid) {
return CACHED_DOMAIN_SEPARATOR;
}
return keccak256(abi.encode(EIP_DOMAIN, NAME, VERSION, block.chainid, address(this)));
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
Expand Down Expand Up @@ -502,10 +511,8 @@ contract Starport is PausableNonReentrant {
assembly ("memory-safe") {
codeHash := extcodehash(custodian)
}
if (
codeHash != DEFAULT_CUSTODIAN_CODE_HASH
&& Custodian(payable(custodian)).custody(loan) != Custodian.custody.selector
) {
if (codeHash != DEFAULT_CUSTODIAN_CODE_HASH && Custodian(custodian).custody(loan) != Custodian.custody.selector)
{
revert InvalidCustodian();
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib/RefStarportLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ library RefStarportLib {

function validateSalt(
mapping(address => mapping(bytes32 => bool)) storage usedSalts,
address borrower,
address validator,
bytes32 salt
) internal {
if (usedSalts[borrower][salt]) {
if (usedSalts[validator][salt]) {
revert InvalidSalt();
}
usedSalts[borrower][salt] = true;
usedSalts[validator][salt] = true;
}
}
4 changes: 2 additions & 2 deletions src/lib/StarportLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ library StarportLib {

function validateSalt(
mapping(address => mapping(bytes32 => bool)) storage usedSalts,
address borrower,
address validator,
bytes32 salt
) internal {
assembly ("memory-safe") {
mstore(0x0, borrower)
mstore(0x0, validator)
mstore(0x20, usedSalts.slot)

// usedSalts[borrower]
Expand Down
1 change: 0 additions & 1 deletion src/pricing/SimpleInterestPricing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ contract SimpleInterestPricing is BasePricing {

if (oldDetails.decimals == newDetails.decimals && (newDetails.rate < oldDetails.rate)) {
(repayConsideration, carryConsideration) = getPaymentConsideration(loan);
additionalConsideration = new AdditionalTransfer[](0);
} else {
revert InvalidRefinance();
}
Expand Down
7 changes: 5 additions & 2 deletions src/settlement/DutchAuctionSettlement.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ abstract contract DutchAuctionSettlement is Settlement, AmountDeriver {
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

error InvalidAmount();
error AuctionNotStarted();

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTRUCTOR */
Expand Down Expand Up @@ -116,6 +116,9 @@ abstract contract DutchAuctionSettlement is Settlement, AmountDeriver {

uint256 start = getAuctionStart(loan);

if (start > block.timestamp) {
revert AuctionNotStarted();
}
// DutchAuction has failed, allow lender to redeem
if (start + details.window < block.timestamp) {
return (new ReceivedItem[](0), loan.issuer);
Expand All @@ -134,7 +137,7 @@ abstract contract DutchAuctionSettlement is Settlement, AmountDeriver {
loan, pricingDetails.rate, loan.start, block.timestamp, 0, pricingDetails.decimals
);

uint256 carry = interest.mulWad(pricingDetails.carryRate);
uint256 carry = interest * pricingDetails.carryRate / 10 ** pricingDetails.decimals;

if (carry > 0 && loan.debt[0].amount + interest - carry < settlementPrice) {
consideration = new ReceivedItem[](2);
Expand Down
2 changes: 1 addition & 1 deletion src/status/FixedTermStatus.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ contract FixedTermStatus is Status {
// @inheritdoc Status
function isActive(Starport.Loan calldata loan, bytes calldata extraData) external view override returns (bool) {
Details memory details = abi.decode(loan.terms.statusData, (Details));
return loan.start + details.loanDuration > block.timestamp;
return loan.start + details.loanDuration >= block.timestamp;
}

function validate(Starport.Loan calldata loan) external view override returns (bytes4) {
Expand Down
2 changes: 1 addition & 1 deletion test/integration-testing/TestNewLoan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ contract TestNewLoan is StarportTest {
// //default is 14 day term
Starport.Loan memory activeLoan = testNewLoanERC721CollateralDefaultTerms2();

skip(14 days);
skip(14 days + 1);

minimumReceived.push(
SpentItem({itemType: ItemType.ERC20, token: address(erc20s[0]), amount: 600 ether, identifier: 0})
Expand Down
2 changes: 1 addition & 1 deletion test/integration-testing/TestRepayLoan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ contract TestRepayLoan is StarportTest {
_createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: borrowAmount, terms: terms});

vm.startPrank(borrower.addr);
skip(14 days);
skip(14 days + 1);
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, details.decimals
Expand Down
15 changes: 15 additions & 0 deletions test/unit-testing/ModuleTesting.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ contract ModuleTesting is StarportTest, DeepEq {
);
}

function testFixedTermDutchAuctionSettlementAuctionNotStarted() public {
Starport.Terms memory terms = Starport.Terms({
status: address(fixedTermStatus),
settlement: address(dutchAuctionSettlement), //fixed term dutch auction
pricing: address(simpleInterestPricing),
pricingData: defaultPricingData,
settlementData: defaultSettlementData,
statusData: defaultStatusData
});
Starport.Loan memory loan =
_createLoan721Collateral20Debt({lender: lender.addr, borrowAmount: 100, terms: terms});
vm.expectRevert(DutchAuctionSettlement.AuctionNotStarted.selector);
FixedTermDutchAuctionSettlement(loan.terms.settlement).getSettlementConsideration(loan);
}

function testFixedTermDutchAuctionSettlementNotValid() public {
DutchAuctionSettlement.Details memory details =
DutchAuctionSettlement.Details({startingPrice: 1 ether, endingPrice: 10 ether, window: 7 days});
Expand Down
22 changes: 22 additions & 0 deletions test/unit-testing/TestCustodian.sol
Original file line number Diff line number Diff line change
Expand Up @@ -613,4 +613,26 @@ contract TestCustodian is StarportTest, DeepEq, MockCall {
seaportAddr, alice, new SpentItem[](0), activeDebt, abi.encode(Actions.Nothing, activeLoan)
);
}

function testCustodianCannotBeAuthorized() public {
mockStatusCall(activeLoan.terms.status, false);
mockSettlementCall(activeLoan.terms.settlement, new ReceivedItem[](0), address(custodian));

vm.expectRevert(abi.encodeWithSelector(Custodian.CustodianCannotBeAuthorized.selector));
custodian.previewOrder(
seaportAddr,
alice,
new SpentItem[](0),
activeDebt,
abi.encode(Custodian.Command(Actions.Settlement, activeLoan, ""))
);

mockStatusCall(activeLoan.terms.status, false);
mockSettlementCall(activeLoan.terms.settlement, new ReceivedItem[](0), address(custodian));
vm.prank(seaportAddr);
vm.expectRevert(abi.encodeWithSelector(Custodian.CustodianCannotBeAuthorized.selector));
custodian.generateOrder(
alice, new SpentItem[](0), activeDebt, abi.encode(Custodian.Command(Actions.Settlement, activeLoan, ""))
);
}
}

0 comments on commit fec4acd

Please sign in to comment.