Giter VIP home page Giter VIP logo

enable-contracts's Introduction

Enable Stablecoin Loan Kit

Enable is a open source stablecoin loan kit that enables anyone to deploy a fullly functional peer-to-peer stablecoin loan with the following features:

  1. Immutable record of loan agreement and automatic tracking of repayments and defaults
  2. Out-of-the-box handling of crowdfunding and fractional ownership through loan shares
  3. Automatic routing of repayments to fractional owners

We built Enable with the vision to expand opportunity to emerging market borrowers through access to credit, to fund value-creating activities like education and starting a business.

Design Philosophy

The Enable stablecoin loan kit is standalone, and designed with minimum viable complexity in mind.

It is heavily inspired by the OpenZeppelin Crowdfund contracts and Dharma's loan contracts.

Components

The Crowdloan functionality has been decomposed into the following categories:

  • Crowdloan: Track state of crowdfund, collect funds from lenders, and issue debt tokens. Once the funding is complete the borrower can withdraw funds. If the loan fails to get fully funded, lenders can withdraw their contribution.

  • RepaymentRouter: Handle repayments, and track withdrawal allowances for debt token holders.

  • TermsContract: Get information about the terms of the loan and it's current status.

Future Plans

We believe parts of this project could morph into generic standards useful to theEthereum community. We'll be expanding, modularizing, and genercizing as appropriate when the initial implementation is finished.

Developer Instructions

CI Pipeline

https://circleci.com/gh/enabledao/enable-contracts

zos workflow for local development

We use ZeppelinOS to develop, deploy and operate the Enable loan kit packages. The ZeppelinOS Documentation is a good start.

Setup

  1. Run npm install to install all zeppelinOS related dependencies
  2. Run ganache-cli (or ganache-cli --deterministic) to run a local blockchain
  3. Create your own .env file based on .env.sample. These are the process.env variables that will be used for deployment / application. As of Aug 2019 this is the Infura API key and mnemonic"

Deploy to ganache development network

For background: read Publishing an EVM package.

  1. zos publish --network development. This publishes the project's app, package and provider. This updates the zos config file with "app.address" field that is needed for tests to run.
  2. zos push --network development. This deploys the contracts in the project. This has the same effect as running zos create on every contract. See Quickstart for context.

Deploy to ethereum and development networks mainnet, ropsten, kovan, ganche

  1. Run npm run deploy:contracts -- --network kovan from bash; change the network to the desired network

Deploy Miscellaneous contracts to any truffle configures network mainnet, ropsten, kovan, ganche

Run npx truffle exec scripts/deploy/paymentToken-deploy.js --network development --{args} from node/terminal; change the network to the desired network

Avaialble scripts

  1. scripts/deploy/paymentToken-deploy.js: Arguments --name, --symbol, --decimals.

npx truffle exec scripts/deploy/paymentToken-deploy.js --network development --name 'EnableDao Dai' --symbol EDAI --decimals 18

  1. scripts/deploy/tokenFaucet-deploy.js: No arguments.

npx truffle exec scripts/deploy/tokenFaucet-deploy.js --network development

Running tests

  1. npm run test. This also runs zos push, which updates the contracts with the latest vrsions

Upgrading contracts

For background: read Upgrading contracts

  1. zos upgrade <contract name> or zos upgrade --all based on contract changed. This should upgrade the contracts.

Editor setup

We use ESLint and Prettier to format our code. Please make sure you have the following setting turned on in VSCode (or equivalent editor).

editor.formatOnSave: true

Test solidity coverage

We use Solidity Coverage.

$(npm bin)/solidity-coverage

Troubleshooting

Common errors

Cannot read property address

// Example
> npm t
TypeError: Cannot read property 'address' of undefined

This happens because zos needs contracts to be published. To resolve, run:

zos publish
zos publish --network development

"Appears to be git repo or submodule" during npm install

This is usually because of the websocket module which web3 depends on. Remove all .git submodules using the command below

# Finds all instances of .git folders in node_modules
find ./node_modules -name ".git"

# Deletes them
find ./node_modules -name ".git" -delete

Terminology

Persons

  1. Lender: lends to a loan
  2. Borrower: person who loan is disbursed to

Nouns

  1. Loan Shares: fractional ownership in a loan
  2. Funding Goal: this is same as principalRequested from the borrower's point of view
  3. Total Crowdfunded: this is the amount raised in the crowdfund
  4. Principal Requested: the loan amount the borrower is requesting for
  5. Principal Disbursed: the amount
  6. Donations: "unauthorized" native ERC-20 transfers to smart contract

Actions

  1. Fund: lenders fund a loan

Stages and Outcomes

We need a set of (Mutually Exclusive, Collectively Exhaustive)[https://www.caseinterview.com/mece] stages and outcomes, that are used for our require checks.

Some possible scenarios we will need to 'describe':

Scenario 1: Crowdfund has ended, but borrower does not withdraw funds. If they do not start loan within a certain period of time (e.g. 48 hours), lenders should be able to refund.

Stage: crowdfund ended Crowdfund Outcome: no outcome yet -> crowdfund refunded

Scenario 2: Crowdfund has ended, and borrower decides they do not want the loan and wants to refund the money.

Stage: crowdfund ended Crowdfund Outcome: crowdfund refunded

Scenario 3: During crowdfund, borrower decides to pause the crowdfund. The crowdloan:fund should be suspended.

Stage: crowdfund started Crowdfund Outcome: no outcome yet -> crowdfund paused

Stages

These steps are sequential. require statements can use < of <= to test for stages

  1. crowdfund notStarted
  2. crowdfund started
  3. crowdfund ended (either early end by borrower, or hit goal)
  4. loan started and in repayment cycle (this can mean that it's in default)
  5. completed (i.e. either fully paid back or written off)

Crowdfund Outcomes (pertains to outcome of crowdfund)

  1. no outcome yet (in progress, or pending acceptance)
  2. crowdfund paused (not implemented)
  3. crowdfund ended
  4. crowdfund refunded (i.e. borrower rejects the crowdfund, returns money)
  5. crowdfund accepted (i.e. borrower starts the loan)

Loan Outcomes (pertains to outcome of loan)

  1. On time (not fully paid back yet)
  2. Late 30, 60, 90, 180 (number of days it's behind in loans)
  3. Fully paid back
  4. Written off (default)

enable-contracts's People

Contributors

adibas03 avatar dan-homebrew avatar dependabot[bot] avatar onggunhao avatar tspoff avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

adibas03 seichris

enable-contracts's Issues

RepaymentManager - Missing functions

Track missing features from components

  • Trigger TermsContract to do a loanStatus update after each payment/ withdrawal.
  • Optimize payee variable within the contract: Either remove completely or optimize to be usable.

Housekeeping on enable-contracts repo

Why

  • Our coding styles are very different
  • We have a lot of "hackathon" code (i.e. abandoned imports, unused variables)

Clean up

  • Install codebase-wide checker?

Javascript

  • Install eslint
  • Install prettier / formatter

Solidity

Monitoring

  • Setup Discord channel for tracking CI failures

Git Hooks?

  • Git hook to run linter prior to commit?

Kovan Testnet Deployment

We hope to deploy the contracts to the kovan testnet, by the 15 - 16 July 2019.
To begin actual network testnets. Will also be good to deploy to Ropsten , as it is the closest testnet to the mainnet

Potential Issue: Borrower / Lender loses private keys during loan term

  • Simple: We can leave the repayment function open such that anyone can repay (and they can continue in case of lost keys)

  • We could suggest the borrower set up a multisig and use that for their borrower address, with a 1 of n scheme such that multiple accounts they own can act as athe borrower.

  • The borrower could also set up a system such as this once it's ready IdentityContract

Ultimately, we don't want to get into the key / identity management business - but we want to make it easy for users to use better practices. Key management and opsec is on the user.

User error in sending funds

This is related to the inherent issue of ERC20 tokens that there is no way to know if someone has sent them to a contract on-chain (no hooks) and no way to react when this happens.

(Aside: Maybe some sort of event log + proof could exist to solve this... Hopefully tokens with hooks become the standard)

Situation 1: Borrower sends payment tokens natively to RepaymentRouter

  • We don't want to allow the user to withdraw tokens because this contract stores tokens for others! Otherwise we could have an onlyBorrower() method that allows approval + transfer from the contract.

Situation 2: Borrower sends payment tokens natively to incorrect contract

  • If they use the UI, this won't happen

Situation 3: Lenders make native token transfers to Crowdsale

  • If they use the UI, this won't happen
  • Funds are often lost this way IRL

Situation 4: Someone sends Eth somewhere

  • All contracts will have fallback functions that revert() to prevent accidental Eth sends

Implement additional Solidity CI and gas testing packages

Remove ability to ask for refunds

High-level spec:

  • To simplify the smart contracts, we are not going to allow for refunds (at least for Ines.fund) during crowdfunding phase
  • We can explore starting loan status once contract is deployed, to reduce the attack surface there as well
  • We may still need refunds for the scenario where funding is done, but borrower wants to cancel the loan

Repayment Allocation Tracking

Issue

Tracking what lenders are entitled to withdraw what repayments at what time requires overhead. We want to minimize cost and keep interfaces as familiar as possible.

Solutions

MiniMe Style ERC-20

[+] Can handle ERC-20 payment tokens if sent though a function, like repay()

[-] O(logn) loops on transfer and withdraw
[-] Large storage overhead

Conclusion: Likely won't use due to gas costs

ERC-721

[+] Can handle ERC-20 payment tokens if sent though a function, like repay()
[+] No loops, withdraws simple to calculate

[-] Can't split or combine stake without extra logic
[-] Can't withdraw multiple stakes at once without a batch function (which we could totally do)

ERC-1843: Claims Token Standard

[+] Can keep ERC-20 ownership tokens, looks efficient (still evaluating)

[-] Requires payment token to be ERC-223 compatible for the tokenFallback hook.
* We could potentially "wrap" the payment token before sending, it'd be the same number
of calls as approve() + function() anyways.

ERC-1726: Dividend-Paying Token Standard

[-] Only supports Ether payments

Conclusion: Won't use as ERC-20 payments are critical. Can learn from though.

Consider UI exploits

  • Spoofed site at another domain
  • Domain hack

Should we have an on-chain registry of all Enable "approved" crowdloans (e.g. deployed with an official factory)?

Potential issue: Overfunded Loan

With the implementation of the ERC20 stable coin,
more than the principal can be sent to the smart contracts as described in #32 ,
This becomes a problem if a user natively sends tokens to the contract address during the fund process, which means the user can send any amount, and will not be recorded as a contributor to the crowdloan, and will get not repayment.
The issue to the borrower can be removed, by removing limitation of the amount of principalDisbursed been worked on by @onggunhao , so any amount can be disbured, and that would be used in calculating the repayment.
The problem to the sender of the said funds, is that they can not get the funds back, and it would be sent to the contributors as part of the repayment, which the sender will not partake of.

[$100 - 4 hours, Dan] Term Contracts should store loan params and generate payment tables, with test suite

3. LoanManager

Conceptually, this keeps track of loan terms, and takes care of accounting and tracking whether the loan has been paid on time. In "real-life" terms this is the loan operations and accounting department, a back-office function.

  1. Record loan terms: Principal, interest, term, etc
  2. Keep track of Amortization Schedule
    1. Pre-loan disbursement: just shows schedule in terms of generic months (e.g. "Month 1", "Month 2")
    2. Post-loan disbursement: Amortization schedule will have repayment timestamps associated with each repayment tranche
  3. Keeps track of repayments
    1. Keeps track of repayments (array)
    2. Keeps track of whether loan is in default or current

Improve Factory tests with Automated deploy / file reading script

This could be strait-forward, but there is no modern example.

Problem

We want to deploy a Factory which references the existing App contract for the current network, to find out where the implementations are so we can deploy proxy instances for them.

Solution

A 'deploy' script which will run in front of tests

The script will:

  • Figure out what network we're on
  • If there is no deployment (e.g. new local blockchain), create one (zos publish zos push)
  • Allow the tests to get the current deployed addresses
  • Create a factory instance using zos js (Creating a new logic contract + init is not the same test imo)
  • Run all tests as per normal

I prefer the zos artifacts to the truffle ones, and will be using those in my tests. This is because you can use the zos cli 'create' command via js to deploy instances. The most notable difference is they use web3-style commands to send() and call().

Feature Backlog from Version 1

This is a collection of Github comments + discussions that we KIV-ed:

Usability

  • Understand ERC20 approve step, can it be done as part of "pay"? (for both lenders and borrowers)

Loan Statuses

  • Should refactor loan statuses to have different concepts for stage and outcomes
  • (LoanStatusUpdated should have timestamp)[https://github.com//pull/54#discussion_r307490843]
  • Remove LogAccess event
  • Remove REPAYMENT_COMPLETE from loanStatuses

Crowdloan

  • Should crowdfundStart and crowdfundEnd be params, instead of a struct (which cannot be changed by zos?
  • fund should not allow funding beyond crowdfundEndTimestamp
  • Should have an onlyBorrower modifier (for loan disbursement)
  • Is there a way trackCrowdfundStatus can avoid calling updateCrowdfund multiple times?
  • contributor should be a lender
  • Can we have roles defined? e.g. borrower, lender
  • expect statements should not have the unspecified flag
  • Should have an onlyLender modifier (for refund)
  • isBelowMaxSupply should be belowCrowdfundingGoal

Naming

  • Rename interestRate to monthlyInterestRate
  • controllerRole should be something other than minters
  • Should have a different word for borrower who is having funds disbursed by the smart contract, and lender who is withdrawing repayments from the smart contract

RepaymentManager

  • getRepaymentStatus should have both a now() version and a `(timestamp) version
  • Event LenderWithdrawal should be PaymentReleased
  • Payees should be lenders
  • "Pay" should be "Repay"
  • Remove the whole updateRepaymentStatus?
  • Remove the pay function and just use ERC20 transfers to track off-chain for the status of the loan?
  • Refactor borrower mint and approve function (I can't remember what I was thinking when I wrote this down)

Overall

  • Implement loanStatusUpdated messages throughout the smart contract

Testing

  • Implement more realistic test scenario generation
  • Use a variable number of lenders, refactor split_MAX_CROWDFUND

Crowdloan contract - Missing functions

Track missing functions in crowdloan contract

  • Implement crowdloanEnd (Time trigger to end crowdfund period)
  • Set time to wait between crowdfund completion and withdrawal, after which lenders can refund their loans if the borrower did not start the loan (Prevents bad borrower holding lenders funds hostage, might be pointless)

Finalize specification for enable-contracts architecture

Overview

@tspoff @adibas03

With T-9 days to Ines' fundraise, I'd like us to have a really productive weekend, and I've taken some time to spelunk through our codebase and write up clearer specifications for business logic.

I'll be focusing full-time and finally understand enough Solidity et al to be useful. I understand if you guys have other commitments, but I need your advice + experience to code review and point me in the right direction. I'm fairly new to this but learning very fast, and really appreciate your help.

I think we can break the work into 3 workstreams, and stub dependencies so we can work quickly in parallel. I've drawn it out below.

Questions I've had going through our codebase

Sorry if they're off-point or just a work-in-progress! A few things jumped out at me:

  1. DebtManager vs DebtToken: After looking at our contracts, a lot of our logic on Debt is spread across debtToken, debtManager. Can we combine them?

  2. DebtTokenFactory: Why don't we just create a DebtToken in the constructor of CrowdloanFactory?

  3. DebtManager re-implementing functionality that is already available in the ERC721 library: DebtManager current implements addDebtValue. Is it possible that we just have a simple mapping(uint256 tokenId => uint256 debtValue) that we write to while extending the mint() from ERC721?

  4. Which ERC721 libraries should we inherit from?: ERC721MetadataMintable instead of ERC721Mintable and ERC721Metadata?

Specifications

From the above questions, I thought we could re-organize our workstreams into the following 3 contracts:

(This might change, e.g. if @tspoff finds the PaymentSplitter is the easiest way for us to route repayments)

1. DaiCrowdsale

We should have a standalone DaiCrowdsale that has been extensively tested for the following functionality. We might consider inheriting the ERC20FundedCrowdsale that we found in the OpenZeppelin repo.

Conceptually, this functions very much like an ERC20Crowdsale, just that instead of minting a ERC20 token we replace it with calls to the DebtManager:mint().

  1. Dai Contract integration: The Dai contracts should be deployed as part of our Truffle migrations, and we should also get it working on Kovan which is the Dai testnet
  2. Hold "Crowdsale" Parameters: i.e. the Cap amount, start timestamp, end timestamp
  3. Accept Dai Funding: (a.k.a. "makeLoan")
    1. Checks to make sure funding amount doesn't put us over the Cap amount (may have to use a stubbed method from DebtManager)
    2. Once amount has been transferred, call the stubbed method in DebtManager to mint a new debt token that represents the funding amount
  4. Disburse Loan:
    1. The borrower will "start" the loan by disbursing the amount from the crowdsale to her address
    2. This will trigger the TermsContract method to write timestamps to the amortization table, officially starting the loan

2. DebtManager

Conceptually, this keeps track of the lenders, and takes care of withdrawals. In "real-life" terms this is the back-office executive who specializes in dealing with fund suppliers.

  1. Keeps track of debt: i.e. the fundraising goal, the current total amount raised
  2. Minting debt tokens: internal method to mint a debt token
    1. This is Potential Security Break Point 1 as minting unlimited debt tokens would break the entire repayments system
  3. Keeping track of owner balances: each debt token would have a loan amount associated with it, and repayments are calculated accordingly
  4. Withdrawal Manager:
    1. Tracks the amount the each lender address or debt token has already withdrawn
      1. Implementation method 1: withdraw by owner
      2. Implementation method 2: withdraw by debt token (i.e. if you've funded it 15 times, you have to withdraw it 15 times). I like this method because of its simplicity
    2. Calculate the amount available for withdrawal, by getting LoanManager:getTotalRepaid() and then dividing it by the fractionalShare that belongs to the lender or debt token. (we can use a stub for this method first)

3. Terms Contract

Conceptually, this keeps track of loan terms, and takes care of accounting and tracking whether the loan has been paid on time. In "real-life" terms this is the loan operations and accounting department, a back-office function.

  1. Record loan terms: Principal, interest, term, etc
  2. Keep track of Amortization Schedule
    1. Pre-loan disbursement: just shows schedule in terms of generic months (e.g. "Month 1", "Month 2")
    2. Post-loan disbursement: Amortization schedule will have repayment timestamps associated with each repayment tranche
  3. Keeps track of repayments
    1. Keeps track of repayments (array)
    2. Keeps track of whether loan is in default or current

Allow for partial fundraises to proceed on a reduced loan amount

Right now, our terms contract assumes that the full amount is raised.

I'll be adding:

  • principalRequested (initial funding goal)
  • principalDisbursed (the actual amount that is disbursed, will be used to calculate repayments)

This will affect Terms Contract only.

Closed in PR #51

Discuss:: Set Minimum contribution based on expected Repayment

While setting up the e2e tests, I realized, that based on the size of the contribution to the loan, some contributors might not have a repayment due every month, but instead, every second month or more.
For instance: A contribution of 6 units out of 60,000 units (0.01%), will not be due for any repayment until 10,000 units have been repaid.
Do we leave this as is and educate, or do we set a minimum contribution, based on the expected repayment.
I think education might be sufficient to allow for smaller contribution, especially since the project is P2P and less corporate.
Note* Implementation of any decision should be against the next version

@onggunhao @tspoff thoughts

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.