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

refactor: remove old names, use new solidity features, add file for poolClosing #567

Merged
merged 22 commits into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,3 @@
[submodule "lib/tinlake-erc20"]
path = lib/tinlake-erc20
url = https://github.com/centrifuge/tinlake-erc20
[submodule "lib/ds-note"]
path = lib/ds-note
url = https://github.com/dapphub/ds-note
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dapp update
## Testing
The tests for Tinlake are written in Solidity. To set up your environment, you should add these variables:
```bash
export DAPP_SOLC_VERSION=0.5.15
export DAPP_SOLC_VERSION=0.6.12
export DAPP_TEST_TIMESTAMP=1234567
```

Expand All @@ -37,4 +37,4 @@ dapp test -m ':ContractName\.'
For deploying the Tinlake contracts to mainnet or a testnet, view our deploy scripts in [Tinlake Deploy](https://github.com/centrifuge/tinlake-deploy).

## Community
Join our public Slack channel: [Centrifuge Slack](http://centrifuge.io/slack).
Join our public Discord: [Centrifuge Discord](https://centrifuge.io/discord/).
Copy link
Member

Choose a reason for hiding this comment

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

good catch

Empty file modified bin/test.sh
100644 → 100755
Empty file.
1 change: 0 additions & 1 deletion lib/ds-note
Submodule ds-note deleted from 93d508
2 changes: 1 addition & 1 deletion lib/tinlake-math
Submodule tinlake-math updated 1 files
+1 −0 .travis.yml
2 changes: 1 addition & 1 deletion lib/tinlake-title
Submodule tinlake-title updated 1 files
+1 −1 src/title.sol
40 changes: 22 additions & 18 deletions src/borrower/collect/collector.sol
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.6.12;

import "ds-note/note.sol";
import "tinlake-auth/auth.sol";

interface NFTLike {
function ownerOf(uint256 tokenId) external view returns (address owner);
function transferFrom(address from, address to, uint256 tokenId) external;
}

interface DistributorLike {
interface ReserveLike {
function balance() external;
}

Expand All @@ -27,12 +26,12 @@ interface ShelfLike {
function recover(uint loan, address usr, uint wad) external;
}

contract Collector is DSNote, Auth {
contract Collector is Auth {

// -- Collectors --
mapping (address => uint) public collectors;
function relyCollector(address usr) public auth note { collectors[usr] = 1; }
function denyCollector(address usr) public auth note { collectors[usr] = 0; }
function relyCollector(address usr) public auth { collectors[usr] = 1; emit RelyCollector(usr); }
function denyCollector(address usr) public auth { collectors[usr] = 0; emit DenyCollector(usr); }
modifier auth_collector { require(collectors[msg.sender] == 1); _; }

// --- Data ---
Expand All @@ -45,27 +44,31 @@ contract Collector is DSNote, Auth {

mapping (uint => Option) public options;

DistributorLike distributor;
ReserveLike reserve;
ShelfLike shelf;
PileLike pile;

event Collect(uint indexed loan, address indexed buyer);
event RelyCollector(address indexed usr);
event DenyCollector(address indexed usr);

constructor (address shelf_, address pile_, address threshold_) public {
shelf = ShelfLike(shelf_);
pile = PileLike(pile_);
threshold = ThresholdRegistryLike(threshold_);
wards[msg.sender] = 1;
}

/// sets the dependency to another contract
// sets the dependency to another contract
function depend(bytes32 contractName, address addr) external auth {
if (contractName == "distributor") distributor = DistributorLike(addr);
if (contractName == "reserve") reserve = ReserveLike(addr);
else if (contractName == "shelf") shelf = ShelfLike(addr);
else if (contractName == "pile") pile = PileLike(addr);
else if (contractName == "threshold") threshold = ThresholdRegistryLike(addr);
else revert();
}

/// sets the liquidation-price of an NFT
// sets the liquidation-price of an NFT
function file(bytes32 what, uint loan, address buyer, uint nftPrice) external auth {
if (what == "loan") {
require(nftPrice > 0, "no-nft-price-defined");
Expand All @@ -75,24 +78,24 @@ contract Collector is DSNote, Auth {
}


/// if the loan debt is above the loan threshold the NFT should be seized,
/// i.e. taken away from the borrower to be sold off at a later stage.
/// therefore the ownership of the nft is transferred to the collector
// if the loan debt is above the loan threshold the NFT should be seized,
// i.e. taken away from the borrower to be sold off at a later stage.
// therefore the ownership of the nft is transferred to the collector
function seize(uint loan) external {
uint debt = pile.debt(loan);
require((threshold.threshold(loan) <= debt), "threshold-not-reached");
shelf.claim(loan, address(this));
}


/// a nft can be collected if the collector is the nft- owner
/// The NFT needs to be `seized` first to transfer ownership to the collector.
/// and then seized by the collector
function collect(uint loan) external auth_collector note {
// a nft can be collected if the collector is the nft- owner
// The NFT needs to be `seized` first to transfer ownership to the collector.
// and then seized by the collector
function collect(uint loan) external auth_collector {
_collect(loan, msg.sender);
}

function collect(uint loan, address buyer) external auth note {
function collect(uint loan, address buyer) external auth {
_collect(loan, buyer);
}

Expand All @@ -102,6 +105,7 @@ contract Collector is DSNote, Auth {
require(options[loan].nftPrice > 0, "no-nft-price-defined");
shelf.recover(loan, buyer, options[loan].nftPrice);
NFTLike(registry).transferFrom(address(this), buyer, nft);
distributor.balance();
reserve.balance();
emit Collect(loan, buyer);
}
}
8 changes: 4 additions & 4 deletions src/borrower/collect/test/collector.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity >=0.6.12;
import "ds-test/test.sol";

import "../../test/mock/shelf.sol";
import "../../test/mock/distributor.sol";
import "../../test/mock/reserve.sol";
import "../../test/mock/nft.sol";
import "../../test/mock/pile.sol";

Expand All @@ -14,7 +14,7 @@ import "../collector.sol";
contract CollectorTest is DSTest {
ShelfMock shelf;
PileMock pile;
DistributorMock distributor;
ReserveMock reserve;
NFTMock nft;

Collector collector;
Expand All @@ -23,10 +23,10 @@ contract CollectorTest is DSTest {
nft = new NFTMock();
shelf = new ShelfMock();
pile = new PileMock();
distributor = new DistributorMock();
reserve = new ReserveMock();

collector = new Collector(address(shelf), address(pile), address(nft));
collector.depend("distributor", address(distributor));
collector.depend("reserve", address(reserve));
}

function collect(uint loan, uint tokenId, uint price) internal {
Expand Down
15 changes: 7 additions & 8 deletions src/borrower/feed/navfeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
pragma solidity >=0.6.12;
pragma experimental ABIEncoderV2;

import "ds-note/note.sol";
import "tinlake-auth/auth.sol";
import "tinlake-math/interest.sol";
import "./nftfeed.sol";
Expand Down Expand Up @@ -107,7 +106,7 @@ contract NAVFeed is BaseNFTFeed, Interest, Buckets, FixedPoint {
ONE // recoveryRatePD: 1.0
);

/// Overdue loans (= loans that were not repaid by the maturityDate) are moved to write Offs
// Overdue loans (= loans that were not repaid by the maturityDate) are moved to write Offs
// 6% interest rate & 60% write off
setWriteOff(0, WRITE_OFF_PHASE_A, uint(1000000674400000000000000000), 6 * 10**26);
// 6% interest rate & 80% write off
Expand All @@ -131,7 +130,7 @@ contract NAVFeed is BaseNFTFeed, Interest, Buckets, FixedPoint {
return (1 days) * (timestamp/(1 days));
}

/// maturityDate is a unix timestamp
// maturityDate is a unix timestamp
function file(bytes32 name, bytes32 nftID_, uint maturityDate_) public auth {
// maturity date only can be changed when there is no debt on the collateral -> futureValue == 0
if (name == "maturityDate") {
Expand Down Expand Up @@ -188,10 +187,10 @@ contract NAVFeed is BaseNFTFeed, Interest, Buckets, FixedPoint {
function calcFutureValue(uint loan, uint amount, uint maturityDate_, uint recoveryRatePD_) public returns(uint) {
// retrieve interest rate from the pile
(, ,uint loanInterestRate, ,) = pile.rates(pile.loanRates(loan));
return rmul(rmul(rpow(loanInterestRate, safeSub(maturityDate_, uniqueDayTimestamp(now)), ONE), amount), recoveryRatePD_);
return rmul(rmul(rpow(loanInterestRate, safeSub(maturityDate_, uniqueDayTimestamp(block.timestamp)), ONE), amount), recoveryRatePD_);
}

/// update the nft value and change the risk group
// update the nft value and change the risk group
function update(bytes32 nftID_, uint value, uint risk_) public override auth {
nftValues[nftID_] = value;

Expand Down Expand Up @@ -281,7 +280,7 @@ contract NAVFeed is BaseNFTFeed, Interest, Buckets, FixedPoint {
}


/// calculates the total discount of all buckets with a timestamp > block.timestamp
// calculates the total discount of all buckets with a timestamp > block.timestamp
function calcTotalDiscount() public view returns(uint) {
uint normalizedBlockTimestamp = uniqueDayTimestamp(block.timestamp);
uint sum = 0;
Expand All @@ -305,7 +304,7 @@ contract NAVFeed is BaseNFTFeed, Interest, Buckets, FixedPoint {
return sum;
}

/// returns the NAV (net asset value) of the pool
// returns the NAV (net asset value) of the pool
function currentNAV() public view returns(uint) {
// calculates the NAV for ongoing loans with a maturityDate date in the future
uint nav_ = calcTotalDiscount();
Expand All @@ -323,7 +322,7 @@ contract NAVFeed is BaseNFTFeed, Interest, Buckets, FixedPoint {
return approximatedNAV;
}

/// workaround for transition phase between V2 & V3
// workaround for transition phase between V2 & V3
function totalValue() public override view returns(uint) {
return currentNAV();
}
Expand Down
11 changes: 5 additions & 6 deletions src/borrower/feed/nftfeed.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.6.12;

import "ds-note/note.sol";
import "tinlake-auth/auth.sol";
import "tinlake-math/math.sol";

Expand All @@ -25,7 +24,7 @@ interface PileLike {
// The NFTFeed stores values and risk group of nfts that are used as collateral in tinlake. A risk group contains: thresholdRatio, ceilingRatio & interstRate.
// The risk groups for a tinlake deployment are defined on contract creation and can not be changed afterwards.
// Loan parameters like interstRate, max borrow amount and liquidation threshold are determined based on the value and risk group of the underlying collateral nft.
contract BaseNFTFeed is DSNote, Auth, Math {
contract BaseNFTFeed is Auth, Math {

// nftID => nftValues
mapping (bytes32 => uint) public nftValues;
Expand Down Expand Up @@ -57,7 +56,7 @@ contract BaseNFTFeed is DSNote, Auth, Math {
// part of Feed interface
function file(bytes32 name, uint value) public virtual auth {}

/// sets the dependency to another contract
// sets the dependency to another contract
function depend(bytes32 contractName, address addr) external auth {
if (contractName == "pile") {pile = PileLike(addr);}
else if (contractName == "shelf") { shelf = ShelfLike(addr); }
Expand Down Expand Up @@ -86,7 +85,7 @@ contract BaseNFTFeed is DSNote, Auth, Math {
} else {revert ("unkown name");}
}

/// -- Oracle Updates --
// -- Oracle Updates --

// The nft value is to be updated by authenticated oracles
function update(bytes32 nftID_, uint value) public auth {
Expand Down Expand Up @@ -139,7 +138,7 @@ contract BaseNFTFeed is DSNote, Auth, Math {
// part of Feed interface
function unlockEvent(uint loan) public auth {}

/// -- Getter methods --
// -- Getter methods --
// returns the ceiling of a loan
// the ceiling defines the maximum amount which can be borrowed
function ceiling(uint loan) public view returns (uint) {
Expand All @@ -161,7 +160,7 @@ contract BaseNFTFeed is DSNote, Auth, Math {
return rmul(nftValues[nftID_], thresholdRatio[risk[nftID_]]);
}

/// implements feed interface and returns poolValue as the total debt of all loans
// implements feed interface and returns poolValue as the total debt of all loans
function totalValue() public virtual view returns (uint) {
return pile.total();
}
Expand Down
50 changes: 25 additions & 25 deletions src/borrower/feed/test/buckets.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,26 @@ contract BucketTest is DSTest, Math {
uint amount = 100 ether;

// add first element
buckets.add(now + 5 days, amount);
buckets.add(block.timestamp + 5 days, amount);
assertEq(buckets.calcSum(), 100 ether);
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(now + 5 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(now + 5 days));
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(block.timestamp + 5 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(block.timestamp + 5 days));

// add second bucket after first
buckets.add(now + 10 days, amount);
buckets.add(block.timestamp + 10 days, amount);
assertEq(buckets.calcSum(), 200 ether);
// still same first bucket
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(now + 5 days));
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(block.timestamp + 5 days));
// new last bucket
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(now + 10 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(block.timestamp + 10 days));

// add before first bucket
buckets.add(now + 3 days, amount);
buckets.add(block.timestamp + 3 days, amount);
assertEq(buckets.calcSum(), 300 ether);
// new first bucket
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(now + 3 days));
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(block.timestamp + 3 days));
// same bucket
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(now + 10 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(block.timestamp + 10 days));
}

function testAddBuckets() public {
Expand All @@ -88,51 +88,51 @@ contract BucketTest is DSTest, Math {
function testRemoveMiddleBucket() public {
addBuckets();
// remove bucket in the middle
buckets.remove(now + 5 days);
buckets.remove(block.timestamp + 5 days);
assertEq(buckets.calcSum(), 200 ether);
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(now + 3 days));
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(block.timestamp + 3 days));
// same bucket
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(now + 10 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(block.timestamp + 10 days));
}

function testRemoveLastBucket() public {
addBuckets();
// remove bucket in the middle
buckets.remove(now + 10 days);
buckets.remove(block.timestamp + 10 days);
assertEq(buckets.calcSum(), 200 ether);
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(now + 3 days));
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(block.timestamp + 3 days));
// same bucket
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(now + 5 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(block.timestamp + 5 days));
}

function testRemoveFirstBucket() public {
addBuckets();
// remove bucket in the middle
buckets.remove(now + 3 days);
buckets.remove(block.timestamp + 3 days);
assertEq(buckets.calcSum(), 200 ether);
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(now + 5 days));
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(block.timestamp + 5 days));
// same bucket
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(now + 10 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(block.timestamp + 10 days));
}

function testRemoveAllBuckets() public {
addBuckets();
buckets.remove(now + 5 days);
buckets.remove(now + 10 days);
buckets.remove(block.timestamp + 5 days);
buckets.remove(block.timestamp + 10 days);
assertEq(buckets.calcSum(), 100 ether);
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(now + 3 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(now + 3 days));
assertEq(buckets.firstBucket(), buckets.uniqueDayTimestamp(block.timestamp + 3 days));
assertEq(buckets.lastBucket(), buckets.uniqueDayTimestamp(block.timestamp + 3 days));

// remove last one
buckets.remove(now + 3 days);
buckets.remove(block.timestamp + 3 days);
assertEq(buckets.firstBucket(), 0);
assertEq(buckets.lastBucket(), 0);

assertEq(buckets.calcSum(), 0);

// add some buckets again to see if everything is still correct
buckets.add(now + 21 days, 3 ether);
buckets.add(now + 24 days, 3 ether);
buckets.add(block.timestamp + 21 days, 3 ether);
buckets.add(block.timestamp + 24 days, 3 ether);
assertEq(buckets.calcSum(), 6 ether);
}
}
Loading