Skip to content

Commit

Permalink
Merge branch 'develop' into add-max-lender-check-at-addApprovedLender
Browse files Browse the repository at this point in the history
  • Loading branch information
bin-57blocks authored Dec 28, 2023
2 parents 278262d + c36cfbd commit c8405f2
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 71 deletions.
6 changes: 3 additions & 3 deletions contracts/PoolSafe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ contract PoolSafe is PoolConfigCache, IPoolSafe {

/// @inheritdoc IPoolSafe
function deposit(address from, uint256 amount) external virtual {
_onlyCustodian(msg.sender);
_onlySystemMoneyMover(msg.sender);

underlyingToken.safeTransferFrom(from, address(this), amount);
}

/// @inheritdoc IPoolSafe
function withdraw(address to, uint256 amount) external virtual {
if (to == address(0)) revert Errors.zeroAddressProvided();
_onlyCustodian(msg.sender);
_onlySystemMoneyMover(msg.sender);

underlyingToken.safeTransfer(to, amount);
}
Expand Down Expand Up @@ -100,7 +100,7 @@ contract PoolSafe is PoolConfigCache, IPoolSafe {
availableBalance = balance > reserved ? balance - reserved : 0;
}

function _onlyCustodian(address account) internal view {
function _onlySystemMoneyMover(address account) internal view {
if (
account != poolConfig.seniorTranche() &&
account != poolConfig.juniorTranche() &&
Expand Down
1 change: 0 additions & 1 deletion contracts/credit/CreditLine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ contract CreditLine is Credit, ICreditLine {
function drawdown(address borrower, uint256 borrowAmount) external virtual override {
poolConfig.onlyProtocolAndPoolOn();
if (borrower != msg.sender) revert Errors.notBorrower();
if (borrowAmount == 0) revert Errors.zeroAmountProvided();

bytes32 creditHash = getCreditHash(borrower);
creditManager.onlyCreditBorrower(creditHash, borrower);
Expand Down
42 changes: 38 additions & 4 deletions contracts/credit/Receivable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/t
import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import {ERC721BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {CountersUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Errors} from "../Errors.sol";
Expand All @@ -21,7 +20,6 @@ import {ReceivableInfo, ReceivableState} from "./CreditStructs.sol";
contract Receivable is
IReceivable,
ReceivableStorage,
Initializable,
ERC721Upgradeable,
ERC721EnumerableUpgradeable,
ERC721URIStorageUpgradeable,
Expand Down Expand Up @@ -61,6 +59,20 @@ contract Receivable is
uint16 currencyCode
);

/**
* @dev Emitted when a receivable metadata URI is updated
* @param owner The address of the owner of the receivable
* @param tokenId The ID of the newly created receivable update token
* @param oldTokenURI The old metadata URI of the receivable
* @param newTokenURI The new metadata URI of the receivable
*/
event ReceivableMetadataUpdated(
address indexed owner,
uint256 indexed tokenId,
string oldTokenURI,
string newTokenURI
);

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
// _disableInitializers();
Expand Down Expand Up @@ -90,8 +102,7 @@ contract Receivable is
uint64 maturityDate,
string memory uri
) public onlyRole(MINTER_ROLE) returns (uint256 tokenId) {
tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
tokenId = _getNewTokenId();
_safeMint(msg.sender, tokenId);

receivableInfoMap[tokenId] = ReceivableInfo(
Expand Down Expand Up @@ -128,6 +139,22 @@ contract Receivable is
emit PaymentDeclared(msg.sender, tokenId, receivableInfo.currencyCode, paymentAmount);
}

/**
* @notice Updates the metadata URI of a receivable
* @custom:access Only the owner or the original creator of the token can update the metadata URI
* @param tokenId The ID of the receivable token
* @param uri The new metadata URI of the receivable
*/
function updateReceivableMetadata(uint256 tokenId, string memory uri) external {
if (msg.sender != ownerOf(tokenId) && msg.sender != creators[tokenId])
revert Errors.notReceivableOwnerOrCreator();

string memory oldTokenURI = tokenURI(tokenId);
_setTokenURI(tokenId, uri);

emit ReceivableMetadataUpdated(msg.sender, tokenId, oldTokenURI, uri);
}

/// @inheritdoc IReceivable
function getReceivable(
uint256 tokenId
Expand Down Expand Up @@ -161,6 +188,13 @@ contract Receivable is
super._burn(tokenId);
}

function _getNewTokenId() internal returns (uint256) {
// Increment the counter first before assigning a new ID so that the ID starts at 1
// instead of 0.
_tokenIdCounter.increment();
return _tokenIdCounter.current();
}

function tokenURI(
uint256 tokenId
)
Expand Down
13 changes: 10 additions & 3 deletions contracts/credit/ReceivableBackedCreditLine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,18 @@ contract ReceivableBackedCreditLine is Credit, IERC721Receiver {

bytes32 creditHash = getCreditHash(borrower);
creditManager.onlyCreditBorrower(creditHash, borrower);
if (getCreditRecord(creditHash).state != CreditState.GoodStanding)
CreditRecord memory cr = getCreditRecord(creditHash);
if (cr.state != CreditState.GoodStanding)
revert Errors.creditLineNotInStateForMakingPrincipalPayment();

if (drawdownAmount == 0 || paymentAmount == 0) revert Errors.zeroAmountProvided();

uint256 principalOutstanding = cr.unbilledPrincipal + cr.nextDue - cr.yieldDue;
if (principalOutstanding == 0) {
// No principal payment is needed when there is no principal outstanding.
return (0, false);
}

IERC721 receivableAsset = IERC721(poolConfig.receivableAsset());
_prepareForPayment(borrower, receivableAsset, paymentReceivableId);
_prepareForDrawdown(
Expand All @@ -152,7 +159,6 @@ contract ReceivableBackedCreditLine is Credit, IERC721Receiver {
drawdownAmount
);

// TODO(jiatu): What if there is no principal in the first place?
if (paymentAmount == drawdownAmount) {
poolSafe.deposit(msg.sender, paymentAmount);
poolSafe.withdraw(borrower, paymentAmount);
Expand Down Expand Up @@ -216,9 +222,10 @@ contract ReceivableBackedCreditLine is Credit, IERC721Receiver {
ReceivableInput memory receivableInput,
uint256 amount
) internal {
// TODO: Check amount < receivable amount?
if (receivableInput.receivableAmount == 0) revert Errors.zeroAmountProvided();
if (receivableInput.receivableId == 0) revert Errors.zeroReceivableIdProvided();
if (amount > receivableInput.receivableAmount)
revert Errors.insufficientReceivableAmount();
if (receivableAsset.ownerOf(receivableInput.receivableId) != borrower)
revert Errors.notReceivableOwner();

Expand Down
10 changes: 5 additions & 5 deletions contracts/credit/ReceivableFactoringCredit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ contract ReceivableFactoringCredit is

event ExtraFundsDispersed(address indexed receiver, uint256 amount);

event DrawdownWithReceivableMade(
event DrawdownMadeWithReceivable(
address indexed borrower,
uint256 indexed receivableId,
uint256 amount,
address by
);

event PaymentWithReceivableMade(
event PaymentMadeWithReceivable(
address indexed borrower,
uint256 indexed receivableId,
uint256 amount,
Expand Down Expand Up @@ -73,7 +73,7 @@ contract ReceivableFactoringCredit is

_drawdown(borrower, creditHash, amount);

emit DrawdownWithReceivableMade(borrower, receivableId, amount, msg.sender);
emit DrawdownMadeWithReceivable(borrower, receivableId, amount, msg.sender);
}

/// @inheritdoc IReceivableFactoringCredit
Expand All @@ -94,7 +94,7 @@ contract ReceivableFactoringCredit is
revert Errors.notReceivableOwner();

(amountPaid, paidoff) = _makePaymentWithReceivable(borrower, creditHash, amount);
emit PaymentWithReceivableMade(borrower, receivableId, amount, msg.sender);
emit PaymentMadeWithReceivable(borrower, receivableId, amount, msg.sender);
}

/// TODO(jiatu): rename this?
Expand All @@ -118,7 +118,7 @@ contract ReceivableFactoringCredit is
);

(amountPaid, paidoff) = _makePaymentWithReceivable(borrower, creditHash, amount);
emit PaymentWithReceivableMade(borrower, receivableId, amount, msg.sender);
emit PaymentMadeWithReceivable(borrower, receivableId, amount, msg.sender);
}

function _makePaymentWithReceivable(
Expand Down
5 changes: 1 addition & 4 deletions contracts/credit/utils/CreditDueManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contract CreditDueManager is PoolConfigCache, ICreditDueManager {

function _updatePoolConfigData(PoolConfig _poolConfig) internal virtual override {
address addr = _poolConfig.calendar();
if (addr == address(0)) revert Errors.zeroAddressProvided();
assert(addr != address(0));
calendar = ICalendar(addr);
}

Expand Down Expand Up @@ -261,9 +261,6 @@ contract CreditDueManager is PoolConfigCache, ICreditDueManager {
uint256 principal,
uint256 daysPassed
) internal pure returns (uint96 accrued, uint96 committed) {
if (daysPassed == 0) {
return (0, 0);
}
accrued = computeYieldDue(principal, cc.yieldInBps, daysPassed);
committed = computeYieldDue(cc.committedAmount, cc.yieldInBps, daysPassed);
return (accrued, committed);
Expand Down
16 changes: 11 additions & 5 deletions test/credit/CreditLineTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7609,7 +7609,7 @@ describe("CreditLine Test", function () {
});
});

it("Should not allow payment when the protocol is paused or pool is not on", async function () {
it("Should not allow payment when the protocol is paused or the pool is not on", async function () {
await humaConfigContract.connect(protocolOwner).pause();
await expect(
creditContract.makePayment(borrower.getAddress(), toToken(1)),
Expand Down Expand Up @@ -8951,18 +8951,21 @@ describe("CreditLine Test", function () {
});

it("Should not allow the borrower to close a credit that has outstanding unbilled principal", async function () {
// Close the approved credit then open a new one with a different committed amount.
await creditManagerContract.connect(borrower).closeCredit(borrower.getAddress());
const amount = toToken(1_000);
await approveCredit(3, toToken(100_000));
await creditContract.connect(borrower).drawdown(borrower.getAddress(), amount);
// Only pay back the yield next due and have principal due outstanding.
const oldCR = await creditContract.getCreditRecord(creditHash);
await creditContract
.connect(borrower)
.makePayment(borrower.getAddress(), oldCR.yieldDue);
.makePayment(borrower.getAddress(), oldCR.nextDue);

const newCR = await creditContract.getCreditRecord(creditHash);
expect(newCR.nextDue.sub(oldCR.yieldDue)).to.be.gt(0);
expect(newCR.nextDue).to.equal(0);
expect(newCR.totalPastDue).to.equal(0);
expect(newCR.unbilledPrincipal).to.equal(0);
expect(newCR.unbilledPrincipal).to.be.gt(0);
await testCloseCreditReversion(borrower, "creditLineHasOutstandingBalance");
});

Expand Down Expand Up @@ -9186,9 +9189,12 @@ describe("CreditLine Test", function () {
it("Should not allow extension on a credit line that becomes delayed after refresh", async function () {
await creditContract.connect(borrower).drawdown(borrower.address, toToken(5_000));
const oldCR = await creditContract.getCreditRecord(creditHash);
// All principal and yield is due in the first period since there is only 1 period,
// so pay slightly less than the amount next due so that the bill can become past due
// when refreshed.
await creditContract
.connect(borrower)
.makePayment(borrower.getAddress(), oldCR.nextDue);
.makePayment(borrower.getAddress(), oldCR.nextDue.sub(toToken(1)));

const extensionDate =
oldCR.nextDueDate.toNumber() +
Expand Down
Loading

0 comments on commit c8405f2

Please sign in to comment.