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

Partial crowdfund support #51

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
10e179a
Move monthly interest rate into a calculated rather than stored field
dan-menlo Jul 19, 2019
1510404
Add principalDisbursed as a field
dan-menlo Jul 19, 2019
8b56f7f
Add principalDisbursed
dan-menlo Jul 19, 2019
a3bc525
Lint solidity{
dan-menlo Jul 19, 2019
b1a306a
Revert getExpectedRepayment to previous
dan-menlo Jul 19, 2019
1bc2535
Add getLoanPeriod() as a method
dan-menlo Jul 20, 2019
d30a0b7
Add overloaded getExpectedRepaymentValue
dan-menlo Jul 20, 2019
b4738bc
refactor onlyStatus into modifier functions, use principalDisbursed
dan-menlo Jul 20, 2019
24224db
Allow for partial principalDisbursed, tests"
dan-menlo Jul 20, 2019
ff9b344
Lint contracts
dan-menlo Jul 20, 2019
5686a29
Remove ScheduledPayments and paymentTable
dan-menlo Jul 20, 2019
da435b4
Add getRequestedScheduledPayment and getScheduledPayment
dan-menlo Jul 21, 2019
ebdb38e
Add comments
dan-menlo Jul 21, 2019
d95d6e2
Add comments
dan-menlo Jul 21, 2019
73f7a5e
Errors in Crowdloan due to vm reverts, will do deep dive tomorrow
dan-menlo Jul 21, 2019
9d12f5b
Implement partial crowdfunding, partial principalDisbursed. Still pen…
dan-menlo Jul 21, 2019
ad5a0ee
Lint
dan-menlo Jul 21, 2019
0941954
Refactor crowdloan tests to test for partial crowdloan and partial pr…
dan-menlo Jul 22, 2019
3b96275
Finish test suite for partialCrowdfund, test events and access control
dan-menlo Jul 22, 2019
f1aba44
Remove totalCrowdfunded variable for getBalance. Implement overfunded…
dan-menlo Jul 22, 2019
6b586e0
Linting
dan-menlo Jul 22, 2019
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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,32 @@ We use [Solidity Coverage](https://github.com/sc-forks/solidity-coverage).
```
$(npm bin)/solidity-coverage
```

# Draft Loan Statuses

**crowdfundStatus** (pertains to _stage_ of crowdfund)

1. notStarted
2. started
3. paused
4. ended (either early end by borrower, or hit goal)

**crowdfundSuccess** (pertains to _outcome_ of crowdfund)

1. pending (i.e. during phase)
2. accepted (i.e. borrower starts the loan)
3. refunded (i.e. borrower rejects the crowdfund, returns money)

**loanStatus** (pertains to _stage_ of loan)

1. Crowdfunding
2. Disbursed
3. RepaymentCycle (ie. >30 days after disbursement, also covers loans that are in default). Can also rename this to "in progress",
4. Completed (i.e. either fully paid back or written off)

**loanOutcome** (pertains to _outcome_ of loan)

1. on time (not fully paid back yet)
2. late 30, 60, 90
3. fully paid back
4. written off (default)
65 changes: 33 additions & 32 deletions contracts/crowdloan/Crowdloan.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,42 @@ contract Crowdloan is Initializable, ICrowdloan, ReentrancyGuard {
crowdfundParams = CrowdfundParams(_crowdfundLength, _crowdfundStart);
}

function getCrowdfundParams() public view returns (uint256, uint256) {
return (crowdfundParams.crowdfundLength, crowdfundParams.crowdfundStart);
}

function getCrowdfundEnd() public view returns (uint256) {
return (crowdfundParams.crowdfundStart.add(crowdfundParams.crowdfundLength));
}

function getTotalCrowdfunded() public view returns (uint256 total) {
/** Note: this may return more than principalRequested because of native ERC20 transfers */
total = _getPrincipalToken().balanceOf(address(this));
}

function getBorrower() public view returns (address) {
return termsContract.borrower();
}

// @notice additional payment does not exceed the pricipal Amount
function _isBelowMaxSupply(uint256 amount) internal view returns (bool) {
uint256 principal = termsContract.getPrincipal();
return repaymentManager.totalShares().add(amount) <= principal;
uint256 principalRequested = termsContract.getPrincipalRequested();
return repaymentManager.totalShares().add(amount) <= principalRequested;
}

// @notice reconcile the loans funding status
function _updateCrowdfundStatus() internal {
uint256 principal = termsContract.getPrincipal();
uint256 principalRequested = termsContract.getPrincipalRequested();
uint256 totalShares = repaymentManager.totalShares();
uint256 totalPaid = repaymentManager.totalPaid();

if (
totalShares > 0 &&
totalShares < principal &&
totalShares < principalRequested &&
termsContract.getLoanStatus() < TermsContractLib.LoanStatus.FUNDING_FAILED
) {
termsContract.setLoanStatus(TermsContractLib.LoanStatus.FUNDING_STARTED);
} else if (totalShares >= principal && totalPaid == 0) {
} else if (totalShares >= principalRequested && totalPaid == 0) {
termsContract.setLoanStatus(TermsContractLib.LoanStatus.FUNDING_COMPLETE);
}
}
Expand Down Expand Up @@ -111,7 +128,7 @@ contract Crowdloan is Initializable, ICrowdloan, ReentrancyGuard {
_validatedERC20Transfer(_getPrincipalToken(), msg.sender, address(this), amount);
//Mint new debt token and transfer to sender
repaymentManager.increaseShares(msg.sender, amount);
emit Fund(msg.sender, amount); // TODO(Dan): Remove comments once IClaimsToken is implemented
emit Fund(msg.sender, amount);
}

/// @notice Get a refund for a debt token owned by the sender
Expand All @@ -125,48 +142,32 @@ contract Crowdloan is Initializable, ICrowdloan, ReentrancyGuard {

repaymentManager.decreaseShares(msg.sender, amount);
_validatedERC20Transfer(_getPrincipalToken(), address(this), msg.sender, amount);

emit Refund(msg.sender, amount);
}

/**
* @notice Withdraw method
*/
function withdraw() public {
withdraw(_getPrincipalToken().balanceOf(address(this)));
}

// @notice Withdraw loan
function withdraw(uint256 amount) public {
require(
termsContract.getLoanStatus() > TermsContractLib.LoanStatus.FUNDING_FAILED,
"Crowdfund not completed"
);
require(msg.sender == termsContract.borrower(), "Withdrawal only allowed for Borrower");
require(
_getPrincipalToken().balanceOf(address(this)) >= amount,
"Amount exceeds available balance"
);

uint256 totalCrowdfunded = _getPrincipalToken().balanceOf(address(this));
require(amount <= totalCrowdfunded, "Amount exceeds available balance");
if (termsContract.getLoanStatus() < TermsContractLib.LoanStatus.REPAYMENT_CYCLE) {
termsContract.startLoan();
termsContract.startRepaymentCycle(totalCrowdfunded); // TODO(Dan): change this to be the amount actually raised
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be
termsContract.startRepaymentCycle(_getPrincipalToken().balanceOf(address(this))),
which simply takes the balance at this point in time of the crowdloan contract.
This also removes the need to track totalCrowdfunded within the fund and refund functions.

Copy link
Contributor Author

@onggunhao onggunhao Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adibas03 Ah yes I thought about that. That approach might create a few problems:

  1. Someone does a native ERC20 transfer to the smart contract, such that _getPrincipalToken().balanceOf(address(this)) is > than principalRequested

  2. In TermsContract, we have a require that checks that principalDisbursed is not more than principalRequested

  3. If that happens, the funds are locked and can't be withdrawn

I think I can resolve it by removing totalCrowdfunded, and instead just passing in the smaller of balanceOf(address(this)) and principalRequested into the startRepaymentCycle function

Copy link
Contributor Author

@onggunhao onggunhao Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes made in f1aba44

Tied to issue #52

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are to use a ceiling, it means we have to implement a cap for the withdrawal function as well, which makes withdrawing in bits more annoying, and would be better we have a simple withdraw function that takes the lesser of principalRequested and balanceOf(address(this)) and transfers the said amount, after which it locks withdrawal

Copy link
Contributor Author

@onggunhao onggunhao Jul 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah just saw your message in the other post - makes sense. I'll implement the donations functionality today

#52

}

_validatedERC20Transfer(_getPrincipalToken(), address(this), msg.sender, amount);

emit ReleaseFunds(msg.sender, amount);
}

// @notice Withdraw loan
function withdraw() public {
withdraw(_getPrincipalToken().balanceOf(address(this)));
}

function getCrowdfundParams() public view returns (uint256, uint256) {
return (crowdfundParams.crowdfundLength, crowdfundParams.crowdfundStart);
}

function getCrowdfundEnd() public view returns (uint256) {
return (crowdfundParams.crowdfundStart.add(crowdfundParams.crowdfundLength));
}

function getBorrower() public view returns (address) {
return termsContract.borrower();
}

/**
* @dev fallback function ***DO NOT OVERRIDE***
* Revert all native Ether payments
Expand Down
Loading