Transaction Hash:
Block:
12493707 at May-24-2021 12:01:28 AM +UTC
Transaction Fee:
0.018261111 ETH
$44.79
Gas Used:
424,677 Gas / 43 Gwei
Emitted Events:
97 |
0xa78c17921e7e060c246ac9575b01ff0fb29bceae.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000e7eb8022cb1040a3fad73886048d8af48be1d1b5, 0x000000000000000000000000c42cfb07bc1140f9a615bd63c4ffae5f8260ab22, 0xf228a8a8c846d97aa0c7c2726ace3761e09aa1857eb9a31d92aeb80272b81445 )
|
98 |
Shelf.0xdd46706400000000000000000000000000000000000000000000000000000000( 0xdd46706400000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000e7eb8022cb1040a3fad73886048d8af48be1d1b5, 0x000000000000000000000000000000000000000000000000000000000000000d, 0x0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000024, dd46706400000000000000000000000000000000000000000000000000000000, 0000000d00000000000000000000000000000000000000000000000000000000 )
|
99 |
Pile.0x46df2ccb00000000000000000000000000000000000000000000000000000000( 0x46df2ccb00000000000000000000000000000000000000000000000000000000, 0x00000000000000000000000000cd3ae59fdbd375a187bf8074db59edaf766c19, 0x000000000000000000000000000000000000000000000000000000000000000d, 0x000000000000000000000000000000000000000000000000000000000000000d, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000044, 46df2ccb00000000000000000000000000000000000000000000000000000000, 0000000d00000000000000000000000000000000000000000000000000000000, 0000000d00000000000000000000000000000000000000000000000000000000 )
|
100 |
Pile.0x071ffb3c00000000000000000000000000000000000000000000000000000000( 0x071ffb3c00000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000c42cfb07bc1140f9a615bd63c4ffae5f8260ab22, 0x000000000000000000000000000000000000000000000000000000000000000d, 0x00000000000000000000000000000000000000000000055e1e2a548a49900000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000044, 071ffb3c00000000000000000000000000000000000000000000000000000000, 0000000d00000000000000000000000000000000000000000000055e1e2a548a, 4990000000000000000000000000000000000000000000000000000000000000 )
|
101 |
Shelf.0x0ecbcdab00000000000000000000000000000000000000000000000000000000( 0x0ecbcdab00000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000e7eb8022cb1040a3fad73886048d8af48be1d1b5, 0x000000000000000000000000000000000000000000000000000000000000000d, 0x00000000000000000000000000000000000000000000055e1e2a548a49900000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000044, 0ecbcdab00000000000000000000000000000000000000000000000000000000, 0000000d00000000000000000000000000000000000000000000055e1e2a548a, 4990000000000000000000000000000000000000000000000000000000000000 )
|
102 |
Dai.Transfer( src=Reserve, dst=Shelf, wad=25348000000000000000000 )
|
103 |
Dai.Transfer( src=Shelf, dst=[Sender] 0xe23d39c932d1cfb8956d0291f4f06e61bb31729e, wad=25348000000000000000000 )
|
104 |
Shelf.0x0ad58d2f00000000000000000000000000000000000000000000000000000000( 0x0ad58d2f00000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000e7eb8022cb1040a3fad73886048d8af48be1d1b5, 0x000000000000000000000000000000000000000000000000000000000000000d, 0x00000000000000000000000000000000000000000000055e1e2a548a49900000, 0000000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000064, 0ad58d2f00000000000000000000000000000000000000000000000000000000, 0000000d00000000000000000000000000000000000000000000055e1e2a548a, 49900000000000000000000000000000e23d39c932d1cfb8956d0291f4f06e61, bb31729e00000000000000000000000000000000000000000000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x00cD3AE5...DAF766C19 | |||||
0x6B175474...495271d0F | |||||
0x729e12cD...32d75AdC5 | |||||
0x95bC79d2...B7A781a96 | |||||
0xa78C1792...Fb29BCeaE | |||||
0xdB07B211...87c9c2aFf | |||||
0xe23d39C9...1Bb31729E |
0.850074354002474621 Eth
Nonce: 57
|
0.831813243002474621 Eth
Nonce: 58
| 0.018261111 | ||
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 1,602.243380951929161693 Eth | 1,602.261642062929161693 Eth | 0.018261111 |
Execution Trace
0xe7eb8022cb1040a3fad73886048d8af48be1d1b5.1cff79cd( )
-
0xc9045c815bf123ad12ea75b9a7c579c1e05051f9.6352211e( )
0x39e9b206dd1e8f9849f11e8ba6bb045e8321a239.1614fa07( )
Shelf.lock( loan=13 )
-
Title.ownerOf( tokenId=13 ) => ( 0xE7eB8022CB1040a3FAd73886048d8AF48Be1D1b5 )
-
NAVFeed.unlockEvent( loan=13 )
-
0xa78c17921e7e060c246ac9575b01ff0fb29bceae.23b872dd( )
-
Shelf.borrow( loan=13, currencyAmount=25348000000000000000000 )
-
Title.ownerOf( tokenId=13 ) => ( 0xE7eB8022CB1040a3FAd73886048d8AF48Be1D1b5 )
-
0xa78c17921e7e060c246ac9575b01ff0fb29bceae.6352211e( )
NAVFeed.borrowEvent( loan=13 )
-
Shelf.shelf( 13 ) => ( registry=0xa78C17921E7E060c246AC9575B01fF0Fb29BCeaE, tokenId=109531547283490506394320449321629014911379087224272814518978146155272055821381 )
-
Pile.loanRates( 13 ) => ( 0 )
-
Pile.setRate( loan=13, rate=13 )
-
-
Pile.accrue( loan=13 )
NAVFeed.borrow( loan=13, amount=25348000000000000000000 ) => ( navIncrease=25294073566662402156298 )
-
Shelf.shelf( 13 ) => ( registry=0xa78C17921E7E060c246AC9575B01fF0Fb29BCeaE, tokenId=109531547283490506394320449321629014911379087224272814518978146155272055821381 )
-
Shelf.shelf( 13 ) => ( registry=0xa78C17921E7E060c246AC9575B01fF0Fb29BCeaE, tokenId=109531547283490506394320449321629014911379087224272814518978146155272055821381 )
-
Shelf.shelf( 13 ) => ( registry=0xa78C17921E7E060c246AC9575B01fF0Fb29BCeaE, tokenId=109531547283490506394320449321629014911379087224272814518978146155272055821381 )
-
Pile.loanRates( 13 ) => ( 13 )
-
Pile.rates( 13 ) => ( pie=37518520242501071069943, chi=1021450326718934255168206307, ratePerSecond=1000000001867706747843734145, lastUpdated=1621814488, fixedRate=0 )
-
Pile.loanRates( 13 ) => ( 13 )
-
Pile.rates( 13 ) => ( pie=37518520242501071069943, chi=1021450326718934255168206307, ratePerSecond=1000000001867706747843734145, lastUpdated=1621814488, fixedRate=0 )
-
-
Pile.incDebt( loan=13, currencyAmount=25348000000000000000000 )
-
Shelf.withdraw( loan=13, currencyAmount=25348000000000000000000, usr=0xe23d39C932D1cfB8956d0291f4F06E61Bb31729E )
-
Title.ownerOf( tokenId=13 ) => ( 0xE7eB8022CB1040a3FAd73886048d8AF48Be1D1b5 )
-
0xa78c17921e7e060c246ac9575b01ff0fb29bceae.6352211e( )
Reserve.CALL( )
Shelf.CALL( )
-
Dai.balanceOf( 0xC42CfB07bC1140f9A615bD63c4fFAE5F8260Ab22 ) => ( 0 )
-
-
Dai.transferFrom( src=0x729e12cDc0190A2e4Ab4401bca4C16132d75AdC5, dst=0xC42CfB07bC1140f9A615bD63c4fFAE5F8260Ab22, wad=25348000000000000000000 ) => ( True )
-
Assessor.borrowUpdate( currencyAmount=25348000000000000000000 )
-
Dai.transferFrom( src=0xC42CfB07bC1140f9A615bD63c4fFAE5F8260Ab22, dst=0xe23d39C932D1cfB8956d0291f4F06E61Bb31729E, wad=25348000000000000000000 ) => ( True )
-
File 1 of 7: Shelf
File 2 of 7: Pile
File 3 of 7: Reserve
File 4 of 7: Dai
File 5 of 7: Title
File 6 of 7: NAVFeed
File 7 of 7: Assessor
// Verified using https://dapp.tools // hevm: flattened sources of src/borrower/shelf.sol pragma solidity >=0.4.23 >=0.5.15 >=0.5.0 <0.6.0 >=0.5.15 <0.6.0; ////// lib/ds-test/src/test.sol // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* pragma solidity >=0.4.23; */ contract DSTest { event eventListener (address target, bool exact); event logs (bytes); event log_bytes32 (bytes32); event log_named_address (bytes32 key, address val); event log_named_bytes32 (bytes32 key, bytes32 val); event log_named_decimal_int (bytes32 key, int val, uint decimals); event log_named_decimal_uint (bytes32 key, uint val, uint decimals); event log_named_int (bytes32 key, int val); event log_named_uint (bytes32 key, uint val); event log_named_string (bytes32 key, string val); bool public IS_TEST; bool public failed; constructor() internal { IS_TEST = true; } function fail() internal { failed = true; } function expectEventsExact(address target) internal { emit eventListener(target, true); } modifier logs_gas() { uint startGas = gasleft(); _; uint endGas = gasleft(); emit log_named_uint("gas", startGas - endGas); } function assertTrue(bool condition) internal { if (!condition) { emit log_bytes32("Assertion failed"); fail(); } } function assertEq(address a, address b) internal { if (a != b) { emit log_bytes32("Error: Wrong `address' value"); emit log_named_address(" Expected", b); emit log_named_address(" Actual", a); fail(); } } function assertEq32(bytes32 a, bytes32 b) internal { assertEq(a, b); } function assertEq(bytes32 a, bytes32 b) internal { if (a != b) { emit log_bytes32("Error: Wrong `bytes32' value"); emit log_named_bytes32(" Expected", b); emit log_named_bytes32(" Actual", a); fail(); } } function assertEqDecimal(int a, int b, uint decimals) internal { if (a != b) { emit log_bytes32("Error: Wrong fixed-point decimal"); emit log_named_decimal_int(" Expected", b, decimals); emit log_named_decimal_int(" Actual", a, decimals); fail(); } } function assertEqDecimal(uint a, uint b, uint decimals) internal { if (a != b) { emit log_bytes32("Error: Wrong fixed-point decimal"); emit log_named_decimal_uint(" Expected", b, decimals); emit log_named_decimal_uint(" Actual", a, decimals); fail(); } } function assertEq(int a, int b) internal { if (a != b) { emit log_bytes32("Error: Wrong `int' value"); emit log_named_int(" Expected", b); emit log_named_int(" Actual", a); fail(); } } function assertEq(uint a, uint b) internal { if (a != b) { emit log_bytes32("Error: Wrong `uint' value"); emit log_named_uint(" Expected", b); emit log_named_uint(" Actual", a); fail(); } } function assertEq(string memory a, string memory b) internal { if (keccak256(abi.encodePacked(a)) != keccak256(abi.encodePacked(b))) { emit log_bytes32("Error: Wrong `string' value"); emit log_named_string(" Expected", b); emit log_named_string(" Actual", a); fail(); } } function assertEq0(bytes memory a, bytes memory b) internal { bool ok = true; if (a.length == b.length) { for (uint i = 0; i < a.length; i++) { if (a[i] != b[i]) { ok = false; } } } else { ok = false; } if (!ok) { emit log_bytes32("Error: Wrong `bytes' value"); emit log_named_bytes32(" Expected", "[cannot show `bytes' value]"); emit log_named_bytes32(" Actual", "[cannot show `bytes' value]"); fail(); } } } ////// lib/tinlake-auth/lib/ds-note/src/note.sol /// note.sol -- the `note' modifier, for logging calls as events // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15; */ contract DSNote { event LogNote( bytes4 indexed sig, address indexed guy, bytes32 indexed foo, bytes32 indexed bar, uint256 wad, bytes fax ) anonymous; modifier note { bytes32 foo; bytes32 bar; uint256 wad; assembly { foo := calldataload(4) bar := calldataload(36) wad := callvalue() } _; emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data); } } ////// lib/tinlake-auth/src/auth.sol // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ contract Auth is DSNote { mapping (address => uint) public wards; function rely(address usr) public auth note { wards[usr] = 1; } function deny(address usr) public auth note { wards[usr] = 0; } modifier auth { require(wards[msg.sender] == 1); _; } } ////// lib/tinlake-math/src/math.sol // Copyright (C) 2018 Rain <[email protected]> // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ contract Math { uint256 constant ONE = 10 ** 27; function safeAdd(uint x, uint y) public pure returns (uint z) { require((z = x + y) >= x, "safe-add-failed"); } function safeSub(uint x, uint y) public pure returns (uint z) { require((z = x - y) <= x, "safe-sub-failed"); } function safeMul(uint x, uint y) public pure returns (uint z) { require(y == 0 || (z = x * y) / y == x, "safe-mul-failed"); } function safeDiv(uint x, uint y) public pure returns (uint z) { z = x / y; } function rmul(uint x, uint y) public pure returns (uint z) { z = safeMul(x, y) / ONE; } function rdiv(uint x, uint y) public pure returns (uint z) { require(y > 0, "division by zero"); z = safeAdd(safeMul(x, ONE), y / 2) / y; } function rdivup(uint x, uint y) internal pure returns (uint z) { require(y > 0, "division by zero"); // always rounds up z = safeAdd(safeMul(x, ONE), safeSub(y, 1)) / y; } } ////// lib/tinlake-title/src/openzeppelin-solidity/math/SafeMath.sol /* pragma solidity ^0.5.0; */ /** * @title SafeMath * @dev Unsigned math operations with safety checks that revert on error. */ library SafeMath { /** * @dev Multiplies two unsigned integers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, "SafeMath: division by zero"); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "SafeMath: subtraction overflow"); uint256 c = a - b; return c; } /** * @dev Adds two unsigned integers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0, "SafeMath: modulo by zero"); return a % b; } } ////// lib/tinlake-title/src/openzeppelin-solidity/drafts/Counters.sol /* pragma solidity ^0.5.0; */ /* import "../math/SafeMath.sol"; */ /** * @title Counters * @author Matt Condon (@shrugs) * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number * of elements in a mapping, issuing ERC721 ids, or counting request ids. * * Include with `using Counters for Counters.Counter;` * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never * directly accessed. */ library Counters { using SafeMath for uint256; struct Counter { // This variable should never be directly accessed by users of the library: interactions must be restricted to // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add // this feature: see https://github.com/ethereum/solidity/issues/4637 uint256 _value; // default: 0 } function current(Counter storage counter) internal view returns (uint256) { return counter._value; } function increment(Counter storage counter) internal { counter._value += 1; } function decrement(Counter storage counter) internal { counter._value = counter._value.sub(1); } } ////// lib/tinlake-title/src/openzeppelin-solidity/introspection/IERC165.sol /* pragma solidity ^0.5.0; */ /** * @title IERC165 * @dev https://eips.ethereum.org/EIPS/eip-165 */ interface IERC165 { /** * @notice Query if a contract implements an interface * @param interfaceId The interface identifier, as specified in ERC-165 * @dev Interface identification is specified in ERC-165. This function * uses less than 30,000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); } ////// lib/tinlake-title/src/openzeppelin-solidity/introspection/ERC165.sol /* pragma solidity ^0.5.0; */ /* import "./IERC165.sol"; */ /** * @title ERC165 * @author Matt Condon (@shrugs) * @dev Implements ERC165 using a lookup table. */ contract ERC165 is IERC165 { /* * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 */ bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; /** * @dev Mapping of interface ids to whether or not it's supported. */ mapping(bytes4 => bool) private _supportedInterfaces; /** * @dev A contract implementing SupportsInterfaceWithLookup * implements ERC165 itself. */ constructor () internal { _registerInterface(_INTERFACE_ID_ERC165); } /** * @dev Implement supportsInterface(bytes4) using a lookup table. */ function supportsInterface(bytes4 interfaceId) external view returns (bool) { return _supportedInterfaces[interfaceId]; } /** * @dev Internal method for registering an interface. */ function _registerInterface(bytes4 interfaceId) internal { require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); _supportedInterfaces[interfaceId] = true; } } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/IERC721.sol /* pragma solidity ^0.5.0; */ /* import "../../introspection/IERC165.sol"; */ /** * @title ERC721 Non-Fungible Token Standard basic interface * @dev see https://eips.ethereum.org/EIPS/eip-721 */ contract IERC721 is IERC165 { event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); function balanceOf(address owner) public view returns (uint256 balance); function ownerOf(uint256 tokenId) public view returns (address owner); function approve(address to, uint256 tokenId) public; function getApproved(uint256 tokenId) public view returns (address operator); function setApprovalForAll(address operator, bool _approved) public; function isApprovedForAll(address owner, address operator) public view returns (bool); function transferFrom(address from, address to, uint256 tokenId) public; function safeTransferFrom(address from, address to, uint256 tokenId) public; function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public; } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/IERC721Receiver.sol /* pragma solidity ^0.5.0; */ /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ contract IERC721Receiver { /** * @notice Handle the receipt of an NFT * @dev The ERC721 smart contract calls this function on the recipient * after a `safeTransfer`. This function MUST return the function selector, * otherwise the caller will revert the transaction. The selector to be * returned can be obtained as `this.onERC721Received.selector`. This * function MAY throw to revert and reject the transfer. * Note: the ERC721 contract address is always the message sender. * @param operator The address which called `safeTransferFrom` function * @param from The address which previously owned the token * @param tokenId The NFT identifier which is being transferred * @param data Additional data with no specified format * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` */ function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4); } ////// lib/tinlake-title/src/openzeppelin-solidity/utils/Address.sol /* pragma solidity ^0.5.0; */ /** * Utility library of inline functions on addresses */ library Address { /** * Returns whether the target address is a contract * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param account address of the account to check * @return whether the target address is a contract */ function isContract(address account) internal view returns (bool) { uint256 size; // XXX Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://ethereum.stackexchange.com/a/14016/36603 // for more details about how this works. // TODO Check this again before the Serenity release, because all addresses will be // contracts then. // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/ERC721.sol /* pragma solidity ^0.5.0; */ /* import "./IERC721.sol"; */ /* import "./IERC721Receiver.sol"; */ /* import "../../math/SafeMath.sol"; */ /* import "../../utils/Address.sol"; */ /* import "../../drafts/Counters.sol"; */ /* import "../../introspection/ERC165.sol"; */ /** * @title ERC721 Non-Fungible Token Standard basic implementation * @dev see https://eips.ethereum.org/EIPS/eip-721 */ contract ERC721 is ERC165, IERC721 { using SafeMath for uint256; using Address for address; using Counters for Counters.Counter; // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector` bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; // Mapping from token ID to owner mapping (uint256 => address) private _tokenOwner; // Mapping from token ID to approved address mapping (uint256 => address) private _tokenApprovals; // Mapping from owner to number of owned token mapping (address => Counters.Counter) private _ownedTokensCount; // Mapping from owner to operator approvals mapping (address => mapping (address => bool)) private _operatorApprovals; /* * bytes4(keccak256('balanceOf(address)')) == 0x70a08231 * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3 * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde * * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ * 0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd */ bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; constructor () public { // register the supported interfaces to conform to ERC721 via ERC165 _registerInterface(_INTERFACE_ID_ERC721); } /** * @dev Gets the balance of the specified address. * @param owner address to query the balance of * @return uint256 representing the amount owned by the passed address */ function balanceOf(address owner) public view returns (uint256) { require(owner != address(0), "ERC721: balance query for the zero address"); return _ownedTokensCount[owner].current(); } /** * @dev Gets the owner of the specified token ID. * @param tokenId uint256 ID of the token to query the owner of * @return address currently marked as the owner of the given token ID */ function ownerOf(uint256 tokenId) public view returns (address) { address owner = _tokenOwner[tokenId]; require(owner != address(0), "ERC721: owner query for nonexistent token"); return owner; } /** * @dev Approves another address to transfer the given token ID * The zero address indicates there is no approved address. * There can only be one approved address per token at a given time. * Can only be called by the token owner or an approved operator. * @param to address to be approved for the given token ID * @param tokenId uint256 ID of the token to be approved */ function approve(address to, uint256 tokenId) public { address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require(msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all" ); _tokenApprovals[tokenId] = to; emit Approval(owner, to, tokenId); } /** * @dev Gets the approved address for a token ID, or zero if no address set * Reverts if the token ID does not exist. * @param tokenId uint256 ID of the token to query the approval of * @return address currently approved for the given token ID */ function getApproved(uint256 tokenId) public view returns (address) { require(_exists(tokenId), "ERC721: approved query for nonexistent token"); return _tokenApprovals[tokenId]; } /** * @dev Sets or unsets the approval of a given operator * An operator is allowed to transfer all tokens of the sender on their behalf. * @param to operator address to set the approval * @param approved representing the status of the approval to be set */ function setApprovalForAll(address to, bool approved) public { require(to != msg.sender, "ERC721: approve to caller"); _operatorApprovals[msg.sender][to] = approved; emit ApprovalForAll(msg.sender, to, approved); } /** * @dev Tells whether an operator is approved by a given owner. * @param owner owner address which you want to query the approval of * @param operator operator address which you want to query the approval of * @return bool whether the given operator is approved by the given owner */ function isApprovedForAll(address owner, address operator) public view returns (bool) { return _operatorApprovals[owner][operator]; } /** * @dev Transfers the ownership of a given token ID to another address. * Usage of this method is discouraged, use `safeTransferFrom` whenever possible. * Requires the msg.sender to be the owner, approved, or operator. * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function transferFrom(address from, address to, uint256 tokenId) public { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _transferFrom(from, to, tokenId); } /** * @dev Safely transfers the ownership of a given token ID to another address * If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, * the transfer is reverted. * Requires the msg.sender to be the owner, approved, or operator * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function safeTransferFrom(address from, address to, uint256 tokenId) public { safeTransferFrom(from, to, tokenId, ""); } /** * @dev Safely transfers the ownership of a given token ID to another address * If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, * the transfer is reverted. * Requires the msg.sender to be the owner, approved, or operator * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred * @param _data bytes data to send along with a safe transfer check */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public { transferFrom(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } /** * @dev Returns whether the specified token exists. * @param tokenId uint256 ID of the token to query the existence of * @return bool whether the token exists */ function _exists(uint256 tokenId) internal view returns (bool) { address owner = _tokenOwner[tokenId]; return owner != address(0); } /** * @dev Returns whether the given spender can transfer a given token ID. * @param spender address of the spender to query * @param tokenId uint256 ID of the token to be transferred * @return bool whether the msg.sender is approved for the given token ID, * is an operator of the owner, or is the owner of the token */ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { require(_exists(tokenId), "ERC721: operator query for nonexistent token"); address owner = ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } /** * @dev Internal function to mint a new token. * Reverts if the given token ID already exists. * @param to The address that will own the minted token * @param tokenId uint256 ID of the token to be minted */ function _mint(address to, uint256 tokenId) internal { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _tokenOwner[tokenId] = to; _ownedTokensCount[to].increment(); emit Transfer(address(0), to, tokenId); } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. * Deprecated, use _burn(uint256) instead. * @param owner owner of the token to burn * @param tokenId uint256 ID of the token being burned */ function _burn(address owner, uint256 tokenId) internal { require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own"); _clearApproval(tokenId); _ownedTokensCount[owner].decrement(); _tokenOwner[tokenId] = address(0); emit Transfer(owner, address(0), tokenId); } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. * @param tokenId uint256 ID of the token being burned */ function _burn(uint256 tokenId) internal { _burn(ownerOf(tokenId), tokenId); } /** * @dev Internal function to transfer ownership of a given token ID to another address. * As opposed to transferFrom, this imposes no restrictions on msg.sender. * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function _transferFrom(address from, address to, uint256 tokenId) internal { require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); require(to != address(0), "ERC721: transfer to the zero address"); _clearApproval(tokenId); _ownedTokensCount[from].decrement(); _ownedTokensCount[to].increment(); _tokenOwner[tokenId] = to; emit Transfer(from, to, tokenId); } /** * @dev Internal function to invoke `onERC721Received` on a target address. * The call is not executed if the target address is not a contract. * @param from address representing the previous owner of the given token ID * @param to target address that will receive the tokens * @param tokenId uint256 ID of the token to be transferred * @param _data bytes optional data to send along with the call * @return bool whether the call correctly returned the expected magic value */ function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) internal returns (bool) { if (!to.isContract()) { return true; } bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data); return (retval == _ERC721_RECEIVED); } /** * @dev Private function to clear current approval of a given token ID. * @param tokenId uint256 ID of the token to be transferred */ function _clearApproval(uint256 tokenId) private { if (_tokenApprovals[tokenId] != address(0)) { _tokenApprovals[tokenId] = address(0); } } } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/IERC721Metadata.sol /* pragma solidity ^0.5.0; */ /* import "./IERC721.sol"; */ /** * @title ERC-721 Non-Fungible Token Standard, optional metadata extension * @dev See https://eips.ethereum.org/EIPS/eip-721 */ contract IERC721Metadata is IERC721 { function name() external view returns (string memory); function symbol() external view returns (string memory); function tokenURI(uint256 tokenId) external view returns (string memory); } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/ERC721Metadata.sol /* pragma solidity ^0.5.0; */ /* import "./ERC721.sol"; */ /* import "./IERC721Metadata.sol"; */ /* import "../../introspection/ERC165.sol"; */ contract ERC721Metadata is ERC165, ERC721, IERC721Metadata { // Token name string private _name; // Token symbol string private _symbol; // Optional mapping for token URIs mapping(uint256 => string) private _tokenURIs; /* * bytes4(keccak256('name()')) == 0x06fdde03 * bytes4(keccak256('symbol()')) == 0x95d89b41 * bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd * * => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f */ bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f; /** * @dev Constructor function */ constructor (string memory name, string memory symbol) public { _name = name; _symbol = symbol; // register the supported interfaces to conform to ERC721 via ERC165 _registerInterface(_INTERFACE_ID_ERC721_METADATA); } /** * @dev Gets the token name. * @return string representing the token name */ function name() external view returns (string memory) { return _name; } /** * @dev Gets the token symbol. * @return string representing the token symbol */ function symbol() external view returns (string memory) { return _symbol; } /** * @dev Returns an URI for a given token ID. * Throws if the token ID does not exist. May return an empty string. * @param tokenId uint256 ID of the token to query */ function tokenURI(uint256 tokenId) external view returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); return _tokenURIs[tokenId]; } /** * @dev Internal function to set the token URI for a given token. * Reverts if the token ID does not exist. * @param tokenId uint256 ID of the token to set its URI * @param uri string URI to assign */ function _setTokenURI(uint256 tokenId, string memory uri) internal { require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token"); _tokenURIs[tokenId] = uri; } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. * Deprecated, use _burn(uint256) instead. * @param owner owner of the token to burn * @param tokenId uint256 ID of the token being burned by the msg.sender */ function _burn(address owner, uint256 tokenId) internal { super._burn(owner, tokenId); // Clear metadata (if any) if (bytes(_tokenURIs[tokenId]).length != 0) { delete _tokenURIs[tokenId]; } } } ////// lib/tinlake-title/src/title.sol // title.sol -- NFT to manage access rights to contracts // Copyright (C) 2019 lucasvo // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import { ERC721Metadata } from "./openzeppelin-solidity/token/ERC721/ERC721Metadata.sol"; */ /* import { Auth } from "tinlake-auth/auth.sol"; */ contract Title is Auth, ERC721Metadata { // --- Data --- uint public count; string public uri; constructor (string memory name, string memory symbol) ERC721Metadata(name, symbol) public { wards[msg.sender] = 1; count = 1; } // --- Title --- function issue (address usr) public auth returns (uint) { return _issue(usr); } function _issue (address usr) internal returns (uint) { _mint(usr, count); count += 1; // can't overflow, not enough gas in the world to pay for 2**256 nfts. return count-1; } function close (uint tkn) public auth { _burn(tkn); } } contract TitleLike_1 { function ownerOf (uint) public returns (address); } contract TitleOwned { TitleLike_1 title; constructor (address title_) public { title = TitleLike_1(title_); } modifier owner (uint loan) { require(title.ownerOf(loan) == msg.sender); _; } } ////// src/borrower/shelf.sol // shelf.sol -- keeps track and owns NFTs // Copyright (C) 2019 lucasvo // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ /* import "tinlake-math/math.sol"; */ /* import "tinlake-auth/auth.sol"; */ /* import "ds-test/test.sol"; */ /* import { TitleOwned } from "tinlake-title/title.sol"; */ contract NFTLike_2 { function ownerOf(uint256 tokenId) public view returns (address owner); function transferFrom(address from, address to, uint256 tokenId) public; } contract TitleLike_2 { function issue(address) public returns (uint); function close(uint) public; function ownerOf (uint) public returns (address); } contract TokenLike_1 { uint public totalSupply; function balanceOf(address) public view returns (uint); function transferFrom(address,address,uint) public returns (bool); function approve(address, uint) public; } contract PileLike_3 { uint public total; function debt(uint) public returns (uint); function accrue(uint) public; function incDebt(uint, uint) public; function decDebt(uint, uint) public; } contract CeilingLike { function borrow(uint loan, uint currencyAmount) public; function repay(uint loan, uint currencyAmount) public; } contract DistributorLike_2 { function balance() public; } contract SubscriberLike { function borrowEvent(uint loan) public; function unlockEvent(uint loan) public; } contract Shelf is DSNote, Auth, TitleOwned, Math { // --- Data --- TitleLike_2 public title; CeilingLike public ceiling; PileLike_3 public pile; TokenLike_1 public currency; DistributorLike_2 public distributor; SubscriberLike public subscriber; struct Loan { address registry; uint256 tokenId; } mapping (uint => uint) public balances; mapping (uint => Loan) public shelf; mapping (bytes32 => uint) public nftlookup; uint public balance; address public lender; constructor(address currency_, address title_, address pile_, address ceiling_) TitleOwned(title_) public { wards[msg.sender] = 1; currency = TokenLike_1(currency_); title = TitleLike_2(title_); pile = PileLike_3(pile_); ceiling = CeilingLike(ceiling_); } /// sets the dependency to another contract function depend(bytes32 contractName, address addr) external auth { if (contractName == "lender") { currency.approve(lender, uint(0)); currency.approve(addr, uint(-1)); lender = addr; } else if (contractName == "token") { currency = TokenLike_1(addr); } else if (contractName == "title") { title = TitleLike_2(addr); } else if (contractName == "pile") { pile = PileLike_3(addr); } else if (contractName == "ceiling") { ceiling = CeilingLike(addr); } else if (contractName == "distributor") { distributor = DistributorLike_2(addr);} else if (contractName == "subscriber") { subscriber = SubscriberLike(addr);} else revert(); } function token(uint loan) public view returns (address registry, uint nft) { return (shelf[loan].registry, shelf[loan].tokenId); } /// issues a new loan in Tinlake - it requires the ownership of an nft /// first step in the loan process - everyone could add an nft function issue(address registry_, uint token_) external note returns (uint) { require(NFTLike_2(registry_).ownerOf(token_) == msg.sender, "nft-not-owned"); bytes32 nft = keccak256(abi.encodePacked(registry_, token_)); require(nftlookup[nft] == 0, "nft-in-use"); uint loan = title.issue(msg.sender); nftlookup[nft] = loan; shelf[loan].registry = registry_; shelf[loan].tokenId = token_; return loan; } function close(uint loan) external note{ require(pile.debt(loan) == 0, "loan-has-outstanding-debt"); require(!nftLocked(loan), "nft-not-locked"); (address registry, uint tokenId) = token(loan); require(title.ownerOf(loan) == msg.sender || NFTLike_2(registry).ownerOf(tokenId) == msg.sender, "not-loan-or-nft-owner"); title.close(loan); bytes32 nft = keccak256(abi.encodePacked(shelf[loan].registry, shelf[loan].tokenId)); nftlookup[nft] = 0; resetLoanBalance(loan); } /// used by the lender contracts to know if currency is needed or currency can be taken function balanceRequest() external view returns (bool, uint) { uint currencyBalance = currency.balanceOf(address(this)); if (balance > currencyBalance) { return (true, safeSub(balance, currencyBalance)); } else { return (false, safeSub(currencyBalance, balance)); } } /// starts the borrow process of a loan /// informs the system of the requested currencyAmount /// interest accumulation starts with this method /// the method can only be called if the nft is locked /// a max ceiling needs to be defined by an oracle function borrow(uint loan, uint currencyAmount) external owner(loan) note { require(nftLocked(loan), "nft-not-locked"); if(address(subscriber) != address(0)) { subscriber.borrowEvent(loan); } pile.accrue(loan); ceiling.borrow(loan, currencyAmount); pile.incDebt(loan, currencyAmount); balances[loan] = safeAdd(balances[loan], currencyAmount); balance = safeAdd(balance, currencyAmount); } /// transfers the requested currencyAmount to the address of the loan owner /// the method triggers the distributor to ensure the shelf has enough currency function withdraw(uint loan, uint currencyAmount, address usr) external owner(loan) note { require(nftLocked(loan), "nft-not-locked"); require(currencyAmount <= balances[loan], "withdraw-amount-too-high"); distributor.balance(); balances[loan] = safeSub(balances[loan], currencyAmount); balance = safeSub(balance, currencyAmount); require(currency.transferFrom(address(this), usr, currencyAmount), "currency-transfer-failed"); } /// repays the entire or partial debt of a loan function repay(uint loan, uint currencyAmount) external owner(loan) note { require(nftLocked(loan), "nft-not-locked"); require(balances[loan] == 0, "withdraw-required-before-repay"); _repay(loan, msg.sender, currencyAmount); } /// a collector can recover defaulted loans /// it is not required to recover the entire loan debt function recover(uint loan, address usr, uint currencyAmount) external auth note { pile.accrue(loan); uint loanDebt = pile.debt(loan); require(currency.transferFrom(usr, address(this), currencyAmount), "currency-transfer-failed"); ceiling.repay(loan, loanDebt); // sets loan debt to 0 pile.decDebt(loan, loanDebt); resetLoanBalance(loan); distributor.balance(); } function _repay(uint loan, address usr, uint currencyAmount) internal { pile.accrue(loan); uint loanDebt = pile.debt(loan); // only repay max loan debt if (currencyAmount > loanDebt) { currencyAmount = loanDebt; } require(currency.transferFrom(usr, address(this), currencyAmount), "currency-transfer-failed"); ceiling.repay(loan, currencyAmount); pile.decDebt(loan, currencyAmount); distributor.balance(); } /// locks an nft in the shelf /// requires an issued loan function lock(uint loan) external owner(loan) note { if(address(subscriber) != address(0)) { subscriber.unlockEvent(loan); } NFTLike_2(shelf[loan].registry).transferFrom(msg.sender, address(this), shelf[loan].tokenId); } /// unlocks an nft in the shelf /// requires zero debt function unlock(uint loan) external owner(loan) note { require(pile.debt(loan) == 0, "loan-has-outstanding-debt"); NFTLike_2(shelf[loan].registry).transferFrom(address(this), msg.sender, shelf[loan].tokenId); } function nftLocked(uint loan) public view returns (bool) { return NFTLike_2(shelf[loan].registry).ownerOf(shelf[loan].tokenId) == address(this); } /// a loan can be claimed by a collector if the loan debt is above the loan threshold /// transfers the nft to the collector function claim(uint loan, address usr) public auth note { NFTLike_2(shelf[loan].registry).transferFrom(address(this), usr, shelf[loan].tokenId); } function resetLoanBalance(uint loan) internal { uint loanBalance = balances[loan]; if (loanBalance > 0) { balances[loan] = 0; balance = safeSub(balance, loanBalance); } } }
File 2 of 7: Pile
// Verified using https://dapp.tools // hevm: flattened sources of src/borrower/pile.sol pragma solidity >=0.5.15 >=0.5.15 <0.6.0; ////// lib/tinlake-auth/lib/ds-note/src/note.sol /// note.sol -- the `note' modifier, for logging calls as events // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15; */ contract DSNote { event LogNote( bytes4 indexed sig, address indexed guy, bytes32 indexed foo, bytes32 indexed bar, uint256 wad, bytes fax ) anonymous; modifier note { bytes32 foo; bytes32 bar; uint256 wad; assembly { foo := calldataload(4) bar := calldataload(36) wad := callvalue() } _; emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data); } } ////// lib/tinlake-auth/src/auth.sol // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ contract Auth is DSNote { mapping (address => uint) public wards; function rely(address usr) public auth note { wards[usr] = 1; } function deny(address usr) public auth note { wards[usr] = 0; } modifier auth { require(wards[msg.sender] == 1); _; } } ////// lib/tinlake-math/src/math.sol // Copyright (C) 2018 Rain <[email protected]> // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ contract Math { uint256 constant ONE = 10 ** 27; function safeAdd(uint x, uint y) public pure returns (uint z) { require((z = x + y) >= x, "safe-add-failed"); } function safeSub(uint x, uint y) public pure returns (uint z) { require((z = x - y) <= x, "safe-sub-failed"); } function safeMul(uint x, uint y) public pure returns (uint z) { require(y == 0 || (z = x * y) / y == x, "safe-mul-failed"); } function safeDiv(uint x, uint y) public pure returns (uint z) { z = x / y; } function rmul(uint x, uint y) public pure returns (uint z) { z = safeMul(x, y) / ONE; } function rdiv(uint x, uint y) public pure returns (uint z) { require(y > 0, "division by zero"); z = safeAdd(safeMul(x, ONE), y / 2) / y; } function rdivup(uint x, uint y) internal pure returns (uint z) { require(y > 0, "division by zero"); // always rounds up z = safeAdd(safeMul(x, ONE), safeSub(y, 1)) / y; } } ////// lib/tinlake-math/src/interest.sol // Copyright (C) 2018 Rain <[email protected]> and Centrifuge, referencing MakerDAO dss => https://github.com/makerdao/dss/blob/master/src/pot.sol // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "./math.sol"; */ contract Interest is Math { // @notice This function provides compounding in seconds // @param chi Accumulated interest rate over time // @param ratePerSecond Interest rate accumulation per second in RAD(10ˆ27) // @param lastUpdated When the interest rate was last updated // @param pie Total sum of all amounts accumulating under one interest rate, divided by that rate // @return The new accumulated rate, as well as the difference between the debt calculated with the old and new accumulated rates. function compounding(uint chi, uint ratePerSecond, uint lastUpdated, uint pie) public view returns (uint, uint) { require(block.timestamp >= lastUpdated, "tinlake-math/invalid-timestamp"); require(chi != 0); // instead of a interestBearingAmount we use a accumulated interest rate index (chi) uint updatedChi = _chargeInterest(chi ,ratePerSecond, lastUpdated, block.timestamp); return (updatedChi, safeSub(rmul(updatedChi, pie), rmul(chi, pie))); } // @notice This function charge interest on a interestBearingAmount // @param interestBearingAmount is the interest bearing amount // @param ratePerSecond Interest rate accumulation per second in RAD(10ˆ27) // @param lastUpdated last time the interest has been charged // @return interestBearingAmount + interest function chargeInterest(uint interestBearingAmount, uint ratePerSecond, uint lastUpdated) public view returns (uint) { if (block.timestamp >= lastUpdated) { interestBearingAmount = _chargeInterest(interestBearingAmount, ratePerSecond, lastUpdated, block.timestamp); } return interestBearingAmount; } function _chargeInterest(uint interestBearingAmount, uint ratePerSecond, uint lastUpdated, uint current) internal pure returns (uint) { return rmul(rpow(ratePerSecond, current - lastUpdated, ONE), interestBearingAmount); } // convert pie to debt/savings amount function toAmount(uint chi, uint pie) public pure returns (uint) { return rmul(pie, chi); } // convert debt/savings amount to pie function toPie(uint chi, uint amount) public pure returns (uint) { return rdivup(amount, chi); } function rpow(uint x, uint n, uint base) public pure returns (uint z) { assembly { switch x case 0 {switch n case 0 {z := base} default {z := 0}} default { switch mod(n, 2) case 0 { z := base } default { z := x } let half := div(base, 2) // for rounding. for { n := div(n, 2) } n { n := div(n,2) } { let xx := mul(x, x) if iszero(eq(div(xx, x), x)) { revert(0,0) } let xxRound := add(xx, half) if lt(xxRound, xx) { revert(0,0) } x := div(xxRound, base) if mod(n,2) { let zx := mul(z, x) if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } let zxRound := add(zx, half) if lt(zxRound, zx) { revert(0,0) } z := div(zxRound, base) } } } } } } ////// src/borrower/pile.sol // Copyright (C) 2018 Rain <[email protected]>, Centrifuge // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ /* import "tinlake-math/interest.sol"; */ /* import "tinlake-auth/auth.sol"; */ // ## Interest Group based Pile // The following is one implementation of a debt module. It keeps track of different buckets of interest rates and is optimized for many loans per interest bucket. It keeps track of interest // rate accumulators (chi values) for all interest rate categories. It calculates debt each // loan according to its interest rate category and pie value. contract Pile is DSNote, Auth, Interest { // --- Data --- /// stores all needed information of an interest rate group struct Rate { uint pie; // Total debt of all loans with this rate uint chi; // Accumulated rates uint ratePerSecond; // Accumulation per second uint48 lastUpdated; // Last time the rate was accumulated uint fixedRate; // fixed rate applied to each loan of the group } /// Interest Rate Groups are identified by a `uint` and stored in a mapping mapping (uint => Rate) public rates; /// mapping of all loan debts /// the debt is stored as pie /// pie is defined as pie = debt/chi therefore debt = pie * chi /// where chi is the accumulated interest rate index over time mapping (uint => uint) public pie; /// loan => rate mapping (uint => uint) public loanRates; /// total debt of all ongoing loans uint public total; constructor() public { wards[msg.sender] = 1; /// pre-definition for loans without interest rates rates[0].chi = ONE; rates[0].ratePerSecond = ONE; } // --- Public Debt Methods --- /// increases the debt of a loan by a currencyAmount /// a change of the loan debt updates the rate debt and total debt function incDebt(uint loan, uint currencyAmount) external auth note { uint rate = loanRates[loan]; require(now == rates[rate].lastUpdated, "rate-group-not-updated"); currencyAmount = safeAdd(currencyAmount, rmul(currencyAmount, rates[rate].fixedRate)); uint pieAmount = toPie(rates[rate].chi, currencyAmount); pie[loan] = safeAdd(pie[loan], pieAmount); rates[rate].pie = safeAdd(rates[rate].pie, pieAmount); total = safeAdd(total, currencyAmount); } /// decrease the loan's debt by a currencyAmount /// a change of the loan debt updates the rate debt and total debt function decDebt(uint loan, uint currencyAmount) external auth note { uint rate = loanRates[loan]; require(now == rates[rate].lastUpdated, "rate-group-not-updated"); uint pieAmount = toPie(rates[rate].chi, currencyAmount); pie[loan] = safeSub(pie[loan], pieAmount); rates[rate].pie = safeSub(rates[rate].pie, pieAmount); if (currencyAmount > total) { total = 0; return; } total = safeSub(total, currencyAmount); } /// returns the current debt based on actual block.timestamp (now) function debt(uint loan) external view returns (uint) { uint rate_ = loanRates[loan]; uint chi_ = rates[rate_].chi; if (now >= rates[rate_].lastUpdated) { chi_ = chargeInterest(rates[rate_].chi, rates[rate_].ratePerSecond, rates[rate_].lastUpdated); } return toAmount(chi_, pie[loan]); } /// returns the total debt of a interest rate group function rateDebt(uint rate) external view returns (uint) { uint chi_ = rates[rate].chi; uint pie_ = rates[rate].pie; if (now >= rates[rate].lastUpdated) { chi_ = chargeInterest(rates[rate].chi, rates[rate].ratePerSecond, rates[rate].lastUpdated); } return toAmount(chi_, pie_); } // --- Interest Rate Group Implementation --- // set rate loanRates for a loan function setRate(uint loan, uint rate) external auth note { require(pie[loan] == 0, "non-zero-debt"); // rate category has to be initiated require(rates[rate].chi != 0, "rate-group-not-set"); loanRates[loan] = rate; } // change rate loanRates for a loan function changeRate(uint loan, uint newRate) external auth note { require(rates[newRate].chi != 0, "rate-group-not-set"); uint currentRate = loanRates[loan]; drip(currentRate); drip(newRate); uint pie_ = pie[loan]; uint debt_ = toAmount(rates[currentRate].chi, pie_); rates[currentRate].pie = safeSub(rates[currentRate].pie, pie_); pie[loan] = toPie(rates[newRate].chi, debt_); rates[newRate].pie = safeAdd(rates[newRate].pie, pie[loan]); loanRates[loan] = newRate; } // set/change the interest rate of a rate category function file(bytes32 what, uint rate, uint value) external auth note { if (what == "rate") { require(value != 0, "rate-per-second-can-not-be-0"); if (rates[rate].chi == 0) { rates[rate].chi = ONE; rates[rate].lastUpdated = uint48(now); } else { drip(rate); } rates[rate].ratePerSecond = value; } else if (what == "fixedRate") { rates[rate].fixedRate = value; } else revert("unknown parameter"); } // accrue needs to be called before any debt amounts are modified by an external component function accrue(uint loan) external { drip(loanRates[loan]); } // drip updates the chi of the rate category by compounding the interest and // updates the total debt function drip(uint rate) public { if (now >= rates[rate].lastUpdated) { (uint chi, uint deltaInterest) = compounding(rates[rate].chi, rates[rate].ratePerSecond, rates[rate].lastUpdated, rates[rate].pie); rates[rate].chi = chi; rates[rate].lastUpdated = uint48(now); total = safeAdd(total, deltaInterest); } } }
File 3 of 7: Reserve
// Verified using https://dapp.tools // hevm: flattened sources of src/lender/reserve.sol pragma solidity >=0.5.15 >=0.5.15 <0.6.0; ////// lib/tinlake-auth/lib/ds-note/src/note.sol /// note.sol -- the `note' modifier, for logging calls as events // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15; */ contract DSNote { event LogNote( bytes4 indexed sig, address indexed guy, bytes32 indexed foo, bytes32 indexed bar, uint256 wad, bytes fax ) anonymous; modifier note { bytes32 foo; bytes32 bar; uint256 wad; assembly { foo := calldataload(4) bar := calldataload(36) wad := callvalue() } _; emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data); } } ////// lib/tinlake-auth/src/auth.sol // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ contract Auth is DSNote { mapping (address => uint) public wards; function rely(address usr) public auth note { wards[usr] = 1; } function deny(address usr) public auth note { wards[usr] = 0; } modifier auth { require(wards[msg.sender] == 1); _; } } ////// lib/tinlake-math/src/math.sol // Copyright (C) 2018 Rain <[email protected]> // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ contract Math { uint256 constant ONE = 10 ** 27; function safeAdd(uint x, uint y) public pure returns (uint z) { require((z = x + y) >= x, "safe-add-failed"); } function safeSub(uint x, uint y) public pure returns (uint z) { require((z = x - y) <= x, "safe-sub-failed"); } function safeMul(uint x, uint y) public pure returns (uint z) { require(y == 0 || (z = x * y) / y == x, "safe-mul-failed"); } function safeDiv(uint x, uint y) public pure returns (uint z) { z = x / y; } function rmul(uint x, uint y) public pure returns (uint z) { z = safeMul(x, y) / ONE; } function rdiv(uint x, uint y) public pure returns (uint z) { require(y > 0, "division by zero"); z = safeAdd(safeMul(x, ONE), y / 2) / y; } function rdivup(uint x, uint y) internal pure returns (uint z) { require(y > 0, "division by zero"); // always rounds up z = safeAdd(safeMul(x, ONE), safeSub(y, 1)) / y; } } ////// src/lender/reserve.sol /* pragma solidity >=0.5.15 <0.6.0; */ /* import "tinlake-math/math.sol"; */ /* import "tinlake-auth/auth.sol"; */ contract ERC20Like_2 { function balanceOf(address) public view returns (uint256); function transferFrom( address, address, uint256 ) public returns (bool); function mint(address, uint256) public; function burn(address, uint256) public; function totalSupply() public view returns (uint256); } contract ShelfLike_3 { function balanceRequest() public returns (bool requestWant, uint256 amount); } contract AssessorLike_4 { function repaymentUpdate(uint amount) public; function borrowUpdate(uint amount) public; } // The reserve keeps track of the currency and the bookkeeping // of the total balance contract Reserve is Math, Auth { ERC20Like_2 public currency; ShelfLike_3 public shelf; AssessorLike_4 public assessor; // currency available for borrowing new loans uint256 public currencyAvailable; // address or contract which holds the currency // by default it is address(this) address pot; // total currency in the reserve uint public balance_; constructor(address currency_) public { wards[msg.sender] = 1; currency = ERC20Like_2(currency_); pot = address(this); } function file(bytes32 what, uint amount) public auth { if (what == "currencyAvailable") { currencyAvailable = amount; } else revert(); } function depend(bytes32 contractName, address addr) public auth { if (contractName == "shelf") { shelf = ShelfLike_3(addr); } else if (contractName == "currency") { currency = ERC20Like_2(addr); } else if (contractName == "assessor") { assessor = AssessorLike_4(addr); } else if (contractName == "pot") { pot = addr; } else revert(); } function totalBalance() public view returns (uint) { return balance_; } // deposits currency in the the reserve function deposit(uint currencyAmount) public auth { _deposit(msg.sender, currencyAmount); } function _deposit(address usr, uint currencyAmount) internal { require(currency.transferFrom(usr, pot, currencyAmount), "reserve-deposit-failed"); balance_ = safeAdd(balance_, currencyAmount); } // remove currency from the reserve function payout(uint currencyAmount) public auth { _payout(msg.sender, currencyAmount); } function _payout(address usr, uint currencyAmount) internal { require(currency.transferFrom(pot, usr, currencyAmount), "reserve-payout-failed"); balance_ = safeSub(balance_, currencyAmount); } // balance handles currency requests from the borrower side // currency is moved between shelf and reserve if needed function balance() public { (bool requestWant, uint256 currencyAmount) = shelf.balanceRequest(); if (requestWant) { require( currencyAvailable >= currencyAmount, "not-enough-currency-reserve" ); currencyAvailable = safeSub(currencyAvailable, currencyAmount); _payout(address(shelf), currencyAmount); assessor.borrowUpdate(currencyAmount); return; } _deposit(address(shelf), currencyAmount); assessor.repaymentUpdate(currencyAmount); } }
File 4 of 7: Dai
// hevm: flattened sources of /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol pragma solidity =0.5.12; ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/lib.sol // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* pragma solidity 0.5.12; */ contract LibNote { event LogNote( bytes4 indexed sig, address indexed usr, bytes32 indexed arg1, bytes32 indexed arg2, bytes data ) anonymous; modifier note { _; assembly { // log an 'anonymous' event with a constant 6 words of calldata // and four indexed topics: selector, caller, arg1 and arg2 let mark := msize // end of memory ensures zero mstore(0x40, add(mark, 288)) // update free memory pointer mstore(mark, 0x20) // bytes type data offset mstore(add(mark, 0x20), 224) // bytes size (padded) calldatacopy(add(mark, 0x40), 0, 224) // bytes payload log4(mark, 288, // calldata shl(224, shr(224, calldataload(0))), // msg.sig caller, // msg.sender calldataload(4), // arg1 calldataload(36) // arg2 ) } } } ////// /nix/store/8xb41r4qd0cjb63wcrxf1qmfg88p0961-dss-6fd7de0/src/dai.sol // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity 0.5.12; */ /* import "./lib.sol"; */ contract Dai is LibNote { // --- Auth --- mapping (address => uint) public wards; function rely(address guy) external note auth { wards[guy] = 1; } function deny(address guy) external note auth { wards[guy] = 0; } modifier auth { require(wards[msg.sender] == 1, "Dai/not-authorized"); _; } // --- ERC20 Data --- string public constant name = "Dai Stablecoin"; string public constant symbol = "DAI"; string public constant version = "1"; uint8 public constant decimals = 18; uint256 public totalSupply; mapping (address => uint) public balanceOf; mapping (address => mapping (address => uint)) public allowance; mapping (address => uint) public nonces; event Approval(address indexed src, address indexed guy, uint wad); event Transfer(address indexed src, address indexed dst, uint wad); // --- Math --- function add(uint x, uint y) internal pure returns (uint z) { require((z = x + y) >= x); } function sub(uint x, uint y) internal pure returns (uint z) { require((z = x - y) <= x); } // --- EIP712 niceties --- bytes32 public DOMAIN_SEPARATOR; // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; constructor(uint256 chainId_) public { wards[msg.sender] = 1; DOMAIN_SEPARATOR = keccak256(abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256(bytes(version)), chainId_, address(this) )); } // --- Token --- function transfer(address dst, uint wad) external returns (bool) { return transferFrom(msg.sender, dst, wad); } function transferFrom(address src, address dst, uint wad) public returns (bool) { require(balanceOf[src] >= wad, "Dai/insufficient-balance"); if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance"); allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad); } balanceOf[src] = sub(balanceOf[src], wad); balanceOf[dst] = add(balanceOf[dst], wad); emit Transfer(src, dst, wad); return true; } function mint(address usr, uint wad) external auth { balanceOf[usr] = add(balanceOf[usr], wad); totalSupply = add(totalSupply, wad); emit Transfer(address(0), usr, wad); } function burn(address usr, uint wad) external { require(balanceOf[usr] >= wad, "Dai/insufficient-balance"); if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) { require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance"); allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad); } balanceOf[usr] = sub(balanceOf[usr], wad); totalSupply = sub(totalSupply, wad); emit Transfer(usr, address(0), wad); } function approve(address usr, uint wad) external returns (bool) { allowance[msg.sender][usr] = wad; emit Approval(msg.sender, usr, wad); return true; } // --- Alias --- function push(address usr, uint wad) external { transferFrom(msg.sender, usr, wad); } function pull(address usr, uint wad) external { transferFrom(usr, msg.sender, wad); } function move(address src, address dst, uint wad) external { transferFrom(src, dst, wad); } // --- Approve by signature --- function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external { bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode(PERMIT_TYPEHASH, holder, spender, nonce, expiry, allowed)) )); require(holder != address(0), "Dai/invalid-address-0"); require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit"); require(expiry == 0 || now <= expiry, "Dai/permit-expired"); require(nonce == nonces[holder]++, "Dai/invalid-nonce"); uint wad = allowed ? uint(-1) : 0; allowance[holder][spender] = wad; emit Approval(holder, spender, wad); } }
File 5 of 7: Title
// Verified using https://dapp.tools // hevm: flattened sources of lib/tinlake-title/src/title.sol pragma solidity >=0.5.15 >=0.5.0 <0.6.0 >=0.5.15 <0.6.0; ////// lib/tinlake-auth/lib/ds-note/src/note.sol /// note.sol -- the `note' modifier, for logging calls as events // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15; */ contract DSNote { event LogNote( bytes4 indexed sig, address indexed guy, bytes32 indexed foo, bytes32 indexed bar, uint256 wad, bytes fax ) anonymous; modifier note { bytes32 foo; bytes32 bar; uint256 wad; assembly { foo := calldataload(4) bar := calldataload(36) wad := callvalue() } _; emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data); } } ////// lib/tinlake-auth/src/auth.sol // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ contract Auth is DSNote { mapping (address => uint) public wards; function rely(address usr) public auth note { wards[usr] = 1; } function deny(address usr) public auth note { wards[usr] = 0; } modifier auth { require(wards[msg.sender] == 1); _; } } ////// lib/tinlake-title/src/openzeppelin-solidity/math/SafeMath.sol /* pragma solidity ^0.5.0; */ /** * @title SafeMath * @dev Unsigned math operations with safety checks that revert on error. */ library SafeMath { /** * @dev Multiplies two unsigned integers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0, "SafeMath: division by zero"); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "SafeMath: subtraction overflow"); uint256 c = a - b; return c; } /** * @dev Adds two unsigned integers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0, "SafeMath: modulo by zero"); return a % b; } } ////// lib/tinlake-title/src/openzeppelin-solidity/drafts/Counters.sol /* pragma solidity ^0.5.0; */ /* import "../math/SafeMath.sol"; */ /** * @title Counters * @author Matt Condon (@shrugs) * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number * of elements in a mapping, issuing ERC721 ids, or counting request ids. * * Include with `using Counters for Counters.Counter;` * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never * directly accessed. */ library Counters { using SafeMath for uint256; struct Counter { // This variable should never be directly accessed by users of the library: interactions must be restricted to // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add // this feature: see https://github.com/ethereum/solidity/issues/4637 uint256 _value; // default: 0 } function current(Counter storage counter) internal view returns (uint256) { return counter._value; } function increment(Counter storage counter) internal { counter._value += 1; } function decrement(Counter storage counter) internal { counter._value = counter._value.sub(1); } } ////// lib/tinlake-title/src/openzeppelin-solidity/introspection/IERC165.sol /* pragma solidity ^0.5.0; */ /** * @title IERC165 * @dev https://eips.ethereum.org/EIPS/eip-165 */ interface IERC165 { /** * @notice Query if a contract implements an interface * @param interfaceId The interface identifier, as specified in ERC-165 * @dev Interface identification is specified in ERC-165. This function * uses less than 30,000 gas. */ function supportsInterface(bytes4 interfaceId) external view returns (bool); } ////// lib/tinlake-title/src/openzeppelin-solidity/introspection/ERC165.sol /* pragma solidity ^0.5.0; */ /* import "./IERC165.sol"; */ /** * @title ERC165 * @author Matt Condon (@shrugs) * @dev Implements ERC165 using a lookup table. */ contract ERC165 is IERC165 { /* * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 */ bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; /** * @dev Mapping of interface ids to whether or not it's supported. */ mapping(bytes4 => bool) private _supportedInterfaces; /** * @dev A contract implementing SupportsInterfaceWithLookup * implements ERC165 itself. */ constructor () internal { _registerInterface(_INTERFACE_ID_ERC165); } /** * @dev Implement supportsInterface(bytes4) using a lookup table. */ function supportsInterface(bytes4 interfaceId) external view returns (bool) { return _supportedInterfaces[interfaceId]; } /** * @dev Internal method for registering an interface. */ function _registerInterface(bytes4 interfaceId) internal { require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); _supportedInterfaces[interfaceId] = true; } } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/IERC721.sol /* pragma solidity ^0.5.0; */ /* import "../../introspection/IERC165.sol"; */ /** * @title ERC721 Non-Fungible Token Standard basic interface * @dev see https://eips.ethereum.org/EIPS/eip-721 */ contract IERC721 is IERC165 { event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved); function balanceOf(address owner) public view returns (uint256 balance); function ownerOf(uint256 tokenId) public view returns (address owner); function approve(address to, uint256 tokenId) public; function getApproved(uint256 tokenId) public view returns (address operator); function setApprovalForAll(address operator, bool _approved) public; function isApprovedForAll(address owner, address operator) public view returns (bool); function transferFrom(address from, address to, uint256 tokenId) public; function safeTransferFrom(address from, address to, uint256 tokenId) public; function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public; } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/IERC721Receiver.sol /* pragma solidity ^0.5.0; */ /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ contract IERC721Receiver { /** * @notice Handle the receipt of an NFT * @dev The ERC721 smart contract calls this function on the recipient * after a `safeTransfer`. This function MUST return the function selector, * otherwise the caller will revert the transaction. The selector to be * returned can be obtained as `this.onERC721Received.selector`. This * function MAY throw to revert and reject the transfer. * Note: the ERC721 contract address is always the message sender. * @param operator The address which called `safeTransferFrom` function * @param from The address which previously owned the token * @param tokenId The NFT identifier which is being transferred * @param data Additional data with no specified format * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` */ function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data) public returns (bytes4); } ////// lib/tinlake-title/src/openzeppelin-solidity/utils/Address.sol /* pragma solidity ^0.5.0; */ /** * Utility library of inline functions on addresses */ library Address { /** * Returns whether the target address is a contract * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param account address of the account to check * @return whether the target address is a contract */ function isContract(address account) internal view returns (bool) { uint256 size; // XXX Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://ethereum.stackexchange.com/a/14016/36603 // for more details about how this works. // TODO Check this again before the Serenity release, because all addresses will be // contracts then. // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/ERC721.sol /* pragma solidity ^0.5.0; */ /* import "./IERC721.sol"; */ /* import "./IERC721Receiver.sol"; */ /* import "../../math/SafeMath.sol"; */ /* import "../../utils/Address.sol"; */ /* import "../../drafts/Counters.sol"; */ /* import "../../introspection/ERC165.sol"; */ /** * @title ERC721 Non-Fungible Token Standard basic implementation * @dev see https://eips.ethereum.org/EIPS/eip-721 */ contract ERC721 is ERC165, IERC721 { using SafeMath for uint256; using Address for address; using Counters for Counters.Counter; // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector` bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; // Mapping from token ID to owner mapping (uint256 => address) private _tokenOwner; // Mapping from token ID to approved address mapping (uint256 => address) private _tokenApprovals; // Mapping from owner to number of owned token mapping (address => Counters.Counter) private _ownedTokensCount; // Mapping from owner to operator approvals mapping (address => mapping (address => bool)) private _operatorApprovals; /* * bytes4(keccak256('balanceOf(address)')) == 0x70a08231 * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3 * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde * * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ * 0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd */ bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; constructor () public { // register the supported interfaces to conform to ERC721 via ERC165 _registerInterface(_INTERFACE_ID_ERC721); } /** * @dev Gets the balance of the specified address. * @param owner address to query the balance of * @return uint256 representing the amount owned by the passed address */ function balanceOf(address owner) public view returns (uint256) { require(owner != address(0), "ERC721: balance query for the zero address"); return _ownedTokensCount[owner].current(); } /** * @dev Gets the owner of the specified token ID. * @param tokenId uint256 ID of the token to query the owner of * @return address currently marked as the owner of the given token ID */ function ownerOf(uint256 tokenId) public view returns (address) { address owner = _tokenOwner[tokenId]; require(owner != address(0), "ERC721: owner query for nonexistent token"); return owner; } /** * @dev Approves another address to transfer the given token ID * The zero address indicates there is no approved address. * There can only be one approved address per token at a given time. * Can only be called by the token owner or an approved operator. * @param to address to be approved for the given token ID * @param tokenId uint256 ID of the token to be approved */ function approve(address to, uint256 tokenId) public { address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require(msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all" ); _tokenApprovals[tokenId] = to; emit Approval(owner, to, tokenId); } /** * @dev Gets the approved address for a token ID, or zero if no address set * Reverts if the token ID does not exist. * @param tokenId uint256 ID of the token to query the approval of * @return address currently approved for the given token ID */ function getApproved(uint256 tokenId) public view returns (address) { require(_exists(tokenId), "ERC721: approved query for nonexistent token"); return _tokenApprovals[tokenId]; } /** * @dev Sets or unsets the approval of a given operator * An operator is allowed to transfer all tokens of the sender on their behalf. * @param to operator address to set the approval * @param approved representing the status of the approval to be set */ function setApprovalForAll(address to, bool approved) public { require(to != msg.sender, "ERC721: approve to caller"); _operatorApprovals[msg.sender][to] = approved; emit ApprovalForAll(msg.sender, to, approved); } /** * @dev Tells whether an operator is approved by a given owner. * @param owner owner address which you want to query the approval of * @param operator operator address which you want to query the approval of * @return bool whether the given operator is approved by the given owner */ function isApprovedForAll(address owner, address operator) public view returns (bool) { return _operatorApprovals[owner][operator]; } /** * @dev Transfers the ownership of a given token ID to another address. * Usage of this method is discouraged, use `safeTransferFrom` whenever possible. * Requires the msg.sender to be the owner, approved, or operator. * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function transferFrom(address from, address to, uint256 tokenId) public { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _transferFrom(from, to, tokenId); } /** * @dev Safely transfers the ownership of a given token ID to another address * If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, * the transfer is reverted. * Requires the msg.sender to be the owner, approved, or operator * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function safeTransferFrom(address from, address to, uint256 tokenId) public { safeTransferFrom(from, to, tokenId, ""); } /** * @dev Safely transfers the ownership of a given token ID to another address * If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, * the transfer is reverted. * Requires the msg.sender to be the owner, approved, or operator * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred * @param _data bytes data to send along with a safe transfer check */ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public { transferFrom(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } /** * @dev Returns whether the specified token exists. * @param tokenId uint256 ID of the token to query the existence of * @return bool whether the token exists */ function _exists(uint256 tokenId) internal view returns (bool) { address owner = _tokenOwner[tokenId]; return owner != address(0); } /** * @dev Returns whether the given spender can transfer a given token ID. * @param spender address of the spender to query * @param tokenId uint256 ID of the token to be transferred * @return bool whether the msg.sender is approved for the given token ID, * is an operator of the owner, or is the owner of the token */ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { require(_exists(tokenId), "ERC721: operator query for nonexistent token"); address owner = ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } /** * @dev Internal function to mint a new token. * Reverts if the given token ID already exists. * @param to The address that will own the minted token * @param tokenId uint256 ID of the token to be minted */ function _mint(address to, uint256 tokenId) internal { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _tokenOwner[tokenId] = to; _ownedTokensCount[to].increment(); emit Transfer(address(0), to, tokenId); } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. * Deprecated, use _burn(uint256) instead. * @param owner owner of the token to burn * @param tokenId uint256 ID of the token being burned */ function _burn(address owner, uint256 tokenId) internal { require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own"); _clearApproval(tokenId); _ownedTokensCount[owner].decrement(); _tokenOwner[tokenId] = address(0); emit Transfer(owner, address(0), tokenId); } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. * @param tokenId uint256 ID of the token being burned */ function _burn(uint256 tokenId) internal { _burn(ownerOf(tokenId), tokenId); } /** * @dev Internal function to transfer ownership of a given token ID to another address. * As opposed to transferFrom, this imposes no restrictions on msg.sender. * @param from current owner of the token * @param to address to receive the ownership of the given token ID * @param tokenId uint256 ID of the token to be transferred */ function _transferFrom(address from, address to, uint256 tokenId) internal { require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); require(to != address(0), "ERC721: transfer to the zero address"); _clearApproval(tokenId); _ownedTokensCount[from].decrement(); _ownedTokensCount[to].increment(); _tokenOwner[tokenId] = to; emit Transfer(from, to, tokenId); } /** * @dev Internal function to invoke `onERC721Received` on a target address. * The call is not executed if the target address is not a contract. * @param from address representing the previous owner of the given token ID * @param to target address that will receive the tokens * @param tokenId uint256 ID of the token to be transferred * @param _data bytes optional data to send along with the call * @return bool whether the call correctly returned the expected magic value */ function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) internal returns (bool) { if (!to.isContract()) { return true; } bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data); return (retval == _ERC721_RECEIVED); } /** * @dev Private function to clear current approval of a given token ID. * @param tokenId uint256 ID of the token to be transferred */ function _clearApproval(uint256 tokenId) private { if (_tokenApprovals[tokenId] != address(0)) { _tokenApprovals[tokenId] = address(0); } } } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/IERC721Metadata.sol /* pragma solidity ^0.5.0; */ /* import "./IERC721.sol"; */ /** * @title ERC-721 Non-Fungible Token Standard, optional metadata extension * @dev See https://eips.ethereum.org/EIPS/eip-721 */ contract IERC721Metadata is IERC721 { function name() external view returns (string memory); function symbol() external view returns (string memory); function tokenURI(uint256 tokenId) external view returns (string memory); } ////// lib/tinlake-title/src/openzeppelin-solidity/token/ERC721/ERC721Metadata.sol /* pragma solidity ^0.5.0; */ /* import "./ERC721.sol"; */ /* import "./IERC721Metadata.sol"; */ /* import "../../introspection/ERC165.sol"; */ contract ERC721Metadata is ERC165, ERC721, IERC721Metadata { // Token name string private _name; // Token symbol string private _symbol; // Optional mapping for token URIs mapping(uint256 => string) private _tokenURIs; /* * bytes4(keccak256('name()')) == 0x06fdde03 * bytes4(keccak256('symbol()')) == 0x95d89b41 * bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd * * => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f */ bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f; /** * @dev Constructor function */ constructor (string memory name, string memory symbol) public { _name = name; _symbol = symbol; // register the supported interfaces to conform to ERC721 via ERC165 _registerInterface(_INTERFACE_ID_ERC721_METADATA); } /** * @dev Gets the token name. * @return string representing the token name */ function name() external view returns (string memory) { return _name; } /** * @dev Gets the token symbol. * @return string representing the token symbol */ function symbol() external view returns (string memory) { return _symbol; } /** * @dev Returns an URI for a given token ID. * Throws if the token ID does not exist. May return an empty string. * @param tokenId uint256 ID of the token to query */ function tokenURI(uint256 tokenId) external view returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); return _tokenURIs[tokenId]; } /** * @dev Internal function to set the token URI for a given token. * Reverts if the token ID does not exist. * @param tokenId uint256 ID of the token to set its URI * @param uri string URI to assign */ function _setTokenURI(uint256 tokenId, string memory uri) internal { require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token"); _tokenURIs[tokenId] = uri; } /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. * Deprecated, use _burn(uint256) instead. * @param owner owner of the token to burn * @param tokenId uint256 ID of the token being burned by the msg.sender */ function _burn(address owner, uint256 tokenId) internal { super._burn(owner, tokenId); // Clear metadata (if any) if (bytes(_tokenURIs[tokenId]).length != 0) { delete _tokenURIs[tokenId]; } } } ////// lib/tinlake-title/src/title.sol // title.sol -- NFT to manage access rights to contracts // Copyright (C) 2019 lucasvo // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import { ERC721Metadata } from "./openzeppelin-solidity/token/ERC721/ERC721Metadata.sol"; */ /* import { Auth } from "tinlake-auth/auth.sol"; */ contract Title is Auth, ERC721Metadata { // --- Data --- uint public count; string public uri; constructor (string memory name, string memory symbol) ERC721Metadata(name, symbol) public { wards[msg.sender] = 1; count = 1; } // --- Title --- function issue (address usr) public auth returns (uint) { return _issue(usr); } function _issue (address usr) internal returns (uint) { _mint(usr, count); count += 1; // can't overflow, not enough gas in the world to pay for 2**256 nfts. return count-1; } function close (uint tkn) public auth { _burn(tkn); } } contract TitleLike_1 { function ownerOf (uint) public returns (address); } contract TitleOwned { TitleLike_1 title; constructor (address title_) public { title = TitleLike_1(title_); } modifier owner (uint loan) { require(title.ownerOf(loan) == msg.sender); _; } }
File 6 of 7: NAVFeed
// Verified using https://dapp.tools // hevm: flattened sources of src/borrower/feed/navfeed.sol pragma solidity >=0.5.15 >=0.5.15 <0.6.0; pragma experimental ABIEncoderV2; ////// lib/tinlake-auth/lib/ds-note/src/note.sol /// note.sol -- the `note' modifier, for logging calls as events // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15; */ contract DSNote { event LogNote( bytes4 indexed sig, address indexed guy, bytes32 indexed foo, bytes32 indexed bar, uint256 wad, bytes fax ) anonymous; modifier note { bytes32 foo; bytes32 bar; uint256 wad; assembly { foo := calldataload(4) bar := calldataload(36) wad := callvalue() } _; emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data); } } ////// lib/tinlake-auth/src/auth.sol // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ contract Auth is DSNote { mapping (address => uint) public wards; function rely(address usr) public auth note { wards[usr] = 1; } function deny(address usr) public auth note { wards[usr] = 0; } modifier auth { require(wards[msg.sender] == 1); _; } } ////// lib/tinlake-math/src/math.sol // Copyright (C) 2018 Rain <[email protected]> // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ contract Math { uint256 constant ONE = 10 ** 27; function safeAdd(uint x, uint y) public pure returns (uint z) { require((z = x + y) >= x, "safe-add-failed"); } function safeSub(uint x, uint y) public pure returns (uint z) { require((z = x - y) <= x, "safe-sub-failed"); } function safeMul(uint x, uint y) public pure returns (uint z) { require(y == 0 || (z = x * y) / y == x, "safe-mul-failed"); } function safeDiv(uint x, uint y) public pure returns (uint z) { z = x / y; } function rmul(uint x, uint y) public pure returns (uint z) { z = safeMul(x, y) / ONE; } function rdiv(uint x, uint y) public pure returns (uint z) { require(y > 0, "division by zero"); z = safeAdd(safeMul(x, ONE), y / 2) / y; } function rdivup(uint x, uint y) internal pure returns (uint z) { require(y > 0, "division by zero"); // always rounds up z = safeAdd(safeMul(x, ONE), safeSub(y, 1)) / y; } } ////// lib/tinlake-math/src/interest.sol // Copyright (C) 2018 Rain <[email protected]> and Centrifuge, referencing MakerDAO dss => https://github.com/makerdao/dss/blob/master/src/pot.sol // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "./math.sol"; */ contract Interest is Math { // @notice This function provides compounding in seconds // @param chi Accumulated interest rate over time // @param ratePerSecond Interest rate accumulation per second in RAD(10ˆ27) // @param lastUpdated When the interest rate was last updated // @param pie Total sum of all amounts accumulating under one interest rate, divided by that rate // @return The new accumulated rate, as well as the difference between the debt calculated with the old and new accumulated rates. function compounding(uint chi, uint ratePerSecond, uint lastUpdated, uint pie) public view returns (uint, uint) { require(block.timestamp >= lastUpdated, "tinlake-math/invalid-timestamp"); require(chi != 0); // instead of a interestBearingAmount we use a accumulated interest rate index (chi) uint updatedChi = _chargeInterest(chi ,ratePerSecond, lastUpdated, block.timestamp); return (updatedChi, safeSub(rmul(updatedChi, pie), rmul(chi, pie))); } // @notice This function charge interest on a interestBearingAmount // @param interestBearingAmount is the interest bearing amount // @param ratePerSecond Interest rate accumulation per second in RAD(10ˆ27) // @param lastUpdated last time the interest has been charged // @return interestBearingAmount + interest function chargeInterest(uint interestBearingAmount, uint ratePerSecond, uint lastUpdated) public view returns (uint) { if (block.timestamp >= lastUpdated) { interestBearingAmount = _chargeInterest(interestBearingAmount, ratePerSecond, lastUpdated, block.timestamp); } return interestBearingAmount; } function _chargeInterest(uint interestBearingAmount, uint ratePerSecond, uint lastUpdated, uint current) internal pure returns (uint) { return rmul(rpow(ratePerSecond, current - lastUpdated, ONE), interestBearingAmount); } // convert pie to debt/savings amount function toAmount(uint chi, uint pie) public pure returns (uint) { return rmul(pie, chi); } // convert debt/savings amount to pie function toPie(uint chi, uint amount) public pure returns (uint) { return rdivup(amount, chi); } function rpow(uint x, uint n, uint base) public pure returns (uint z) { assembly { switch x case 0 {switch n case 0 {z := base} default {z := 0}} default { switch mod(n, 2) case 0 { z := base } default { z := x } let half := div(base, 2) // for rounding. for { n := div(n, 2) } n { n := div(n,2) } { let xx := mul(x, x) if iszero(eq(div(xx, x), x)) { revert(0,0) } let xxRound := add(xx, half) if lt(xxRound, xx) { revert(0,0) } x := div(xxRound, base) if mod(n,2) { let zx := mul(z, x) if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } let zxRound := add(zx, half) if lt(zxRound, zx) { revert(0,0) } z := div(zxRound, base) } } } } } } ////// src/borrower/feed/buckets.sol // Copyright (C) 2020 Centrifuge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15; */ // the buckets contract stores values in a map using a timestamp as a key // each value store a pointer the next value in a linked list // to improve performance/gas efficiency while iterating over all values in a timespan contract Buckets { // abstract contract constructor() internal {} struct Bucket { uint value; uint next; } // timestamp => bucket mapping (uint => Bucket) public buckets; // pointer to the first bucket and last bucket uint public firstBucket; uint public lastBucket; uint constant public NullDate = 1; function addBucket(uint timestamp, uint value) internal { buckets[timestamp].value = value; if (firstBucket == 0) { firstBucket = timestamp; buckets[timestamp].next = NullDate; lastBucket = firstBucket; return; } // new bucket before first one if (timestamp < firstBucket) { buckets[timestamp].next = firstBucket; firstBucket = timestamp; return; } // find predecessor bucket by going back in time // instead of iterating the linked list from the first bucket // assuming its more gas efficient to iterate over time instead of iterating the list from the beginning // not true if buckets are only sparsely populated over long periods of time uint prev = timestamp; while(buckets[prev].next == 0) {prev = prev - 1 days;} if (buckets[prev].next == NullDate) { lastBucket = timestamp; } buckets[timestamp].next = buckets[prev].next; buckets[prev].next = timestamp; } function removeBucket(uint timestamp) internal { buckets[timestamp].value = 0; _removeBucket(timestamp); buckets[timestamp].next = 0; } function _removeBucket(uint timestamp) internal { if(firstBucket == lastBucket) { lastBucket = 0; firstBucket = 0; return; } if (timestamp != firstBucket) { uint prev = timestamp - 1 days; // assuming its more gas efficient to iterate over time instead of iterating the list from the beginning // not true if buckets are only sparsely populated over long periods of time while(buckets[prev].next != timestamp) {prev = prev - 1 days;} buckets[prev].next = buckets[timestamp].next; if(timestamp == lastBucket) { lastBucket = prev; } return; } firstBucket = buckets[timestamp].next; } } ////// src/borrower/feed/nftfeed.sol // Copyright (C) 2020 Centrifuge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ /* import "tinlake-auth/auth.sol"; */ /* import "tinlake-math/math.sol"; */ contract ShelfLike_2 { function shelf(uint loan) public view returns (address registry, uint tokenId); function nftlookup(bytes32 nftID) public returns (uint loan); } contract PileLike_2 { function setRate(uint loan, uint rate) public; function debt(uint loan) public returns (uint); function pie(uint loan) public returns (uint); function changeRate(uint loan, uint newRate) public; function loanRates(uint loan) public returns (uint); function file(bytes32, uint, uint) public; function rates(uint rate) public view returns (uint, uint, uint ,uint48, uint); function total() public view returns (uint); function rateDebt(uint rate) public view returns (uint); } // 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 { // nftID => nftValues mapping (bytes32 => uint) public nftValues; // nftID => risk mapping (bytes32 => uint) public risk; // risk => thresholdRatio // thresholdRatio is used to determine the liquidation threshold of the loan. thresholdRatio * nftValue = liquidation threshold // When loan debt reaches the liquidation threshold, it can be seized and collected by a whitelisted keeper. mapping (uint => uint) public thresholdRatio; // risk => ceilingRatio // ceilingRatio is used to determine the ax borrow amount (ceiling) of a loan. ceilingRatio * nftValue = max borrow amount // When loan debt reaches the liquidation threshold, it can be seized and collected by a whitelisted keeper. mapping (uint => uint) public ceilingRatio; // loan => borrowed // stores the already borrowed amounts for each loan // required to track the borrowed currency amount without accrued interest mapping (uint => uint) public borrowed; PileLike_2 pile; ShelfLike_2 shelf; constructor () public { wards[msg.sender] = 1; } // part of Feed interface function file(bytes32 name, uint value) public auth {} /// sets the dependency to another contract function depend(bytes32 contractName, address addr) external auth { if (contractName == "pile") {pile = PileLike_2(addr);} else if (contractName == "shelf") { shelf = ShelfLike_2(addr); } else revert(); } // returns a unique id based on the nft registry and tokenId // the nftID is used to set the risk group and value for nfts function nftID(address registry, uint tokenId) public pure returns (bytes32) { return keccak256(abi.encodePacked(registry, tokenId)); } // returns the nftID for the underlying collateral nft function nftID(uint loan) public view returns (bytes32) { (address registry, uint tokenId) = shelf.shelf(loan); return nftID(registry, tokenId); } function file(bytes32 name, uint risk_, uint thresholdRatio_, uint ceilingRatio_, uint rate_) public auth { if(name == "riskGroupNFT") { require(ceilingRatio[risk_] == 0, "risk-group-in-usage"); thresholdRatio[risk_] = thresholdRatio_; ceilingRatio[risk_] = ceilingRatio_; // set interestRate for risk group pile.file("rate", risk_, rate_); } else {revert ("unkown name");} } /// -- Oracle Updates -- // The nft value is to be updated by authenticated oracles function update(bytes32 nftID_, uint value) public auth { // switch of collateral risk group results in new: ceiling, threshold for existing loan nftValues[nftID_] = value; } // The nft value & risk group is to be updated by authenticated oracles function update(bytes32 nftID_, uint value, uint risk_) public auth { // the risk group has to exist require(thresholdRatio[risk_] != 0, "threshold for risk group not defined"); // switch of collateral risk group results in new: ceiling, threshold and interest rate for existing loan // change to new rate interestRate immediately in pile if loan debt exists uint loan = shelf.nftlookup(nftID_); if (pile.pie(loan) != 0) { pile.changeRate(loan, risk_); } risk[nftID_] = risk_; nftValues[nftID_] = value; } // function checks if the borrow amount does not exceed the max allowed borrow amount (=ceiling) function borrow(uint loan, uint amount) external auth returns (uint) { // increase borrowed amount -> note: max allowed borrow amount does not include accrued interest borrowed[loan] = safeAdd(borrowed[loan], amount); require(currentCeiling(loan) >= borrowed[loan], "borrow-amount-too-high"); return amount; } // part of Feed interface function repay(uint, uint amount) external auth returns (uint) { // note: borrowed amount is not decreased as the feed implements the principal and not credit line method return amount; } // borrowEvent is called by the shelf in the borrow method function borrowEvent(uint loan) public auth { uint risk_ = risk[nftID(loan)]; // when issued every loan has per default interest rate of risk group 0. // correct interest rate has to be set on first borrow event if(pile.loanRates(loan) != risk_) { // set loan interest rate to the one of the correct risk group pile.setRate(loan, risk_); } } // part of Feed interface function unlockEvent(uint loan) public auth {} /// -- 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) { if (borrowed[loan] > currentCeiling(loan)) { return 0; } return safeSub(currentCeiling(loan), borrowed[loan]); } function currentCeiling(uint loan) public view returns(uint) { bytes32 nftID_ = nftID(loan); return rmul(nftValues[nftID_], ceilingRatio[risk[nftID_]]); } // returns the threshold of a loan // if the loan debt is above the loan threshold the NFT can be seized function threshold(uint loan) public view returns (uint) { bytes32 nftID_ = nftID(loan); return rmul(nftValues[nftID_], thresholdRatio[risk[nftID_]]); } /// implements feed interface and returns poolValue as the total debt of all loans function totalValue() public view returns (uint) { return pile.total(); } } ////// src/fixed_point.sol // Copyright (C) 2020 Centrifuge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ contract FixedPoint { struct Fixed27 { uint value; } } ////// src/borrower/feed/navfeed.sol // Copyright (C) 2020 Centrifuge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* pragma experimental ABIEncoderV2; */ /* import "ds-note/note.sol"; */ /* import "tinlake-auth/auth.sol"; */ /* import "tinlake-math/interest.sol"; */ /* import "./nftfeed.sol"; */ /* import "./buckets.sol"; */ /* import "../../fixed_point.sol"; */ // The Nav Feed contract extends the functionality of the NFT Feed by the Net Asset Value (NAV) computation of a Tinlake pool. // NAV is computed as the sum of all discounted future values (fv) of ongoing loans (debt > 0) in the pool. // The applied discountRate is dependant on the maturity data of the underlying collateral. The discount decreases with the maturity date approaching. // To optimize the NAV calculation the discounting of future values happens bucketwise. FVs from assets with the same maturity date are added to one bucket. // This safes iterations & gas, as the same discountRates can be applied per bucket. contract NAVFeed is BaseNFTFeed, Interest, Buckets, FixedPoint { // maturityDate is the expected date of repayment for an asset // nftID => maturityDate mapping (bytes32 => uint) public maturityDate; // recoveryRatePD is a combined rate that includes the probability of default for an asset of a certain risk group and its recovery rate // risk => recoveryRatePD mapping (uint => Fixed27) public recoveryRatePD; // futureValue of an asset based on the loan debt, interest rate, maturity date and recoveryRatePD // nftID => futureValue mapping (bytes32 => uint) public futureValue; WriteOff [5] public writeOffs; struct WriteOff { uint rateGroup; // denominated in (10^27) Fixed27 percentage; } // discount rate applied on every asset's fv depending on its maturityDate. The discount decreases with the maturityDate approaching. Fixed27 public discountRate; // approximatedNAV is calculated in case of borrows & repayments between epoch executions. // It decreases/increases the NAV by the repaid/borrowed amount without running the NAV calculation routine. // This is required for more accurate Senior & JuniorAssetValue estimations between epochs uint public approximatedNAV; // rate group for write-offs in pile contract uint constant public WRITE_OFF_PHASE_A = 1000; uint constant public WRITE_OFF_PHASE_B = 1001; uint constant public WRITE_OFF_PHASE_C = 1002; uint constant public WRITE_OFF_PHASE_D = 1003; uint constant public WRITE_OFF_PHASE_E = 1004; constructor () public { wards[msg.sender] = 1; } function init() public { require(ceilingRatio[0] == 0, "already-initialized"); // gas optimized initialization of writeOffs and risk groups // write off are hardcoded in the contract instead of init function params // risk groups are extended by the recoveryRatePD parameter compared with NFTFeed // The following score cards just examples that are mostly optimized for the system test cases // risk group: 0 // risk group: 0 - APR: 2.78% file("riskGroup", 0, ONE, ONE, uint(1000000000881532217148655504), 99.9836*10**25); // risk group: 1 - APR: 3.02% file("riskGroup", 1, ONE, ONE, uint(1000000000957635717909690512), 99.9803*10**25); // risk group: 2 - APR: 3.26% file("riskGroup", 2, ONE, ONE, uint(1000000001033739218670725520), 99.977*10**25); // risk group: 3 - APR: 3.5% file("riskGroup", 3, ONE, ONE, uint(1000000001109842719431760527), 99.9737*10**25); // risk group: 4 - APR: 3.75% file("riskGroup", 4, ONE, ONE, uint(1000000001189117199391171993), 99.9704*10**25); // risk group: 5 - APR: 3.99% file("riskGroup", 5, ONE, ONE, uint(1000000001265220700152207001), 99.9671*10**25); // risk group: 6 - APR: 4.23% file("riskGroup", 6, ONE, ONE, uint(1000000001341324200913242009), 99.9638*10**25); // risk group: 7 - APR: 4.47% file("riskGroup", 7, ONE, ONE, uint(1000000001417427701674277016), 99.9605*10**25); // risk group: 8 - APR: 4.7% file("riskGroup", 8, ONE, ONE, uint(1000000001490360223236935565), 99.9573*10**25); // risk group: 9 - APR: 4.94% file("riskGroup", 9, ONE, ONE, uint(1000000001566463723997970573), 99.954*10**25); // risk group: 10 - APR: 5.18% file("riskGroup", 10, ONE, ONE, uint(1000000001642567224759005580), 99.9507*10**25); // risk group: 11 - APR: 5.42% file("riskGroup", 11, ONE, ONE, uint(1000000001718670725520040588), 99.9474*10**25); // risk group: 12 - APR: 5.65% file("riskGroup", 12, ONE, ONE, uint(1000000001791603247082699137), 99.9441*10**25); // risk group: 13 - APR: 5.89% file("riskGroup", 13, ONE, ONE, uint(1000000001867706747843734145), 99.9408*10**25); // risk group: 14 - APR: 6.13% file("riskGroup", 14, ONE, ONE, uint(1000000001943810248604769152), 99.9375*10**25); // risk group: 15 - APR: 6.36% file("riskGroup", 15, ONE, ONE, uint(1000000002016742770167427701), 99.9342*10**25); // risk group: 16 - APR: 6.59% file("riskGroup", 16, ONE, ONE, uint(1000000002089675291730086250), 99.931*10**25); // risk group: 17 - APR: 6.83% file("riskGroup", 17, ONE, ONE, uint(1000000002165778792491121258), 99.9277*10**25); // risk group: 18 - APR: 7.06% file("riskGroup", 18, ONE, ONE, uint(1000000002238711314053779807), 99.9244*10**25); // risk group: 19 - APR: 7.29% file("riskGroup", 19, ONE, ONE, uint(1000000002311643835616438356), 99.9211*10**25); // risk group: 20 - APR: 7.53% file("riskGroup", 20, ONE, ONE, uint(1000000002387747336377473363), 99.9178*10**25); // risk group: 21 - APR: 7.76% file("riskGroup", 21, ONE, ONE, uint(1000000002460679857940131912), 99.9145*10**25); // risk group: 22 - APR: 7.99% file("riskGroup", 22, ONE, ONE, uint(1000000002533612379502790461), 99.9112*10**25); // risk group: 23 - APR: 8.22% file("riskGroup", 23, ONE, ONE, uint(1000000002606544901065449010), 99.9079*10**25); // risk group: 24 - APR: 8.45% file("riskGroup", 24, ONE, ONE, uint(1000000002679477422628107559), 99.9047*10**25); // risk group: 25 - APR: 8.68% file("riskGroup", 25, ONE, ONE, uint(1000000002752409944190766108), 99.9014*10**25); // risk group: 26 - APR: 8.91% file("riskGroup", 26, ONE, ONE, uint(1000000002825342465753424657), 99.8981*10**25); // risk group: 27 - APR: 9.14% file("riskGroup", 27, ONE, ONE, uint(1000000002898274987316083206), 99.8948*10**25); // risk group: 28 - APR: 9.36% file("riskGroup", 28, ONE, ONE, uint(1000000002968036529680365296), 99.8915*10**25); // risk group: 29 - APR: 9.59% file("riskGroup", 29, ONE, ONE, uint(1000000003040969051243023845), 99.8882*10**25); // risk group: 30 - APR: 9.82% file("riskGroup", 30, ONE, ONE, uint(1000000003113901572805682394), 99.8849*10**25); // risk group: 31 - APR: 10.04% file("riskGroup", 31, ONE, ONE, uint(1000000003183663115169964485), 99.8816*10**25); // risk group: 32 - APR: 10.27% file("riskGroup", 32, ONE, ONE, uint(1000000003256595636732623033), 99.8784*10**25); // risk group: 33 - APR: 10.5% file("riskGroup", 33, ONE, ONE, uint(1000000003329528158295281582), 99.8751*10**25); // risk group: 34 - APR: 10.72% file("riskGroup", 34, ONE, ONE, uint(1000000003399289700659563673), 99.8718*10**25); // risk group: 35 - APR: 10.95% file("riskGroup", 35, ONE, ONE, uint(1000000003472222222222222222), 99.8685*10**25); // risk group: 36 - APR: 11.17% file("riskGroup", 36, ONE, ONE, uint(1000000003541983764586504312), 99.8652*10**25); // risk group: 37 - APR: 11.39% file("riskGroup", 37, ONE, ONE, uint(1000000003611745306950786402), 99.8619*10**25); // risk group: 38 - APR: 11.62% file("riskGroup", 38, ONE, ONE, uint(1000000003684677828513444951), 99.8586*10**25); // risk group: 39 - APR: 11.84% file("riskGroup", 39, ONE, ONE, uint(1000000003754439370877727042), 99.8553*10**25); // risk group: 40 - APR: 12.06% file("riskGroup", 40, ONE, ONE, uint(1000000003824200913242009132), 99.8521*10**25); /// Overdue loans (= loans that were not repaid by the maturityDate) are moved to write Offs // write-off group: 0 - 0% write off, 7.50% interest setWriteOff(0, WRITE_OFF_PHASE_A, uint(1000000002378234398782343987), ONE); // write-off group: 1 - 10% write off, 7.50% interest setWriteOff(1, WRITE_OFF_PHASE_B, uint(1000000002378234398782343987), 90*10**25); // write-off group: 2 - 25% write off setWriteOff(2, WRITE_OFF_PHASE_C, uint(1000000000000000000000000000), 75*10**25); // write-off group: 3 - 50% write off setWriteOff(3, WRITE_OFF_PHASE_D, uint(1000000000000000000000000000), 50*10**25); // write-off group: 4 - 100% write off setWriteOff(4, WRITE_OFF_PHASE_E, uint(1000000000000000000000000000), 0); } function file(bytes32 name, uint risk_, uint thresholdRatio_, uint ceilingRatio_, uint rate_, uint recoveryRatePD_) public auth { if(name == "riskGroup") { file("riskGroupNFT", risk_, thresholdRatio_, ceilingRatio_, rate_); recoveryRatePD[risk_] = Fixed27(recoveryRatePD_); } else {revert ("unknown name");} } function setWriteOff(uint phase_, uint group_, uint rate_, uint writeOffPercentage_) internal { writeOffs[phase_] = WriteOff(group_, Fixed27(writeOffPercentage_)); pile.file("rate", group_, rate_); } function uniqueDayTimestamp(uint timestamp) public pure returns (uint) { return (1 days) * (timestamp/(1 days)); } /// 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") { require((futureValue[nftID_] == 0), "can-not-change-maturityDate-outstanding-debt"); maturityDate[nftID_] = uniqueDayTimestamp(maturityDate_); } else { revert("unknown config parameter");} } function file(bytes32 name, uint value) public auth { if (name == "discountRate") { discountRate = Fixed27(value); } else { revert("unknown config parameter");} } // In case of successful borrow the approximatedNAV is increased by the borrowed amount function borrow(uint loan, uint amount) external auth returns(uint navIncrease) { navIncrease = _borrow(loan, amount); approximatedNAV = safeAdd(approximatedNAV, navIncrease); return navIncrease; } // On borrow: the discounted future value of the asset is computed based on the loan amount and addeed to the bucket with the according maturity Date function _borrow(uint loan, uint amount) internal returns(uint navIncrease) { // ceiling check uses existing loan debt require(ceiling(loan) >= safeAdd(borrowed[loan], amount), "borrow-amount-too-high"); bytes32 nftID_ = nftID(loan); uint maturityDate_ = maturityDate[nftID_]; // maturity date has to be a value in the future require(maturityDate_ > block.timestamp, "maturity-date-is-not-in-the-future"); // calculate amount including fixed fee if applicatable (, , , , uint fixedRate) = pile.rates(pile.loanRates(loan)); uint amountIncludingFixed = safeAdd(amount, rmul(amount, fixedRate)); // calculate future value FV uint fv = calcFutureValue(loan, amountIncludingFixed, maturityDate_, recoveryRatePD[risk[nftID_]].value); futureValue[nftID_] = safeAdd(futureValue[nftID_], fv); // add future value to the bucket of assets with the same maturity date if (buckets[maturityDate_].value == 0) { addBucket(maturityDate_, fv); } else { buckets[maturityDate_].value = safeAdd(buckets[maturityDate_].value, fv); } // increase borrowed amount for future ceiling computations borrowed[loan] = safeAdd(borrowed[loan], amount); // return increase NAV amount return calcDiscount(fv, uniqueDayTimestamp(block.timestamp), maturityDate_); } // calculate the future value based on the amount, maturityDate interestRate and recoveryRate 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_); } /// update the nft value and change the risk group function update(bytes32 nftID_, uint value, uint risk_) public auth { nftValues[nftID_] = value; // no change in risk group if (risk_ == risk[nftID_]) { return; } // nfts can only be added to risk groups that are part of the score card require(thresholdRatio[risk_] != 0, "risk group not defined in contract"); risk[nftID_] = risk_; // no currencyAmount borrowed yet if (futureValue[nftID_] == 0) { return; } uint loan = shelf.nftlookup(nftID_); uint maturityDate_ = maturityDate[nftID_]; // Changing the risk group of an nft, might lead to a new interest rate for the dependant loan. // New interest rate leads to a future value. // recalculation required buckets[maturityDate_].value = safeSub(buckets[maturityDate_].value, futureValue[nftID_]); futureValue[nftID_] = calcFutureValue(loan, pile.debt(loan), maturityDate[nftID_], recoveryRatePD[risk[nftID_]].value); buckets[maturityDate_].value = safeAdd(buckets[maturityDate_].value, futureValue[nftID_]); } // In case of successful repayment the approximatedNAV is decreased by the repaid amount function repay(uint loan, uint amount) external auth returns (uint navDecrease) { navDecrease = _repay(loan, amount); if (navDecrease > approximatedNAV) { approximatedNAV = 0; } if(navDecrease < approximatedNAV) { approximatedNAV = safeSub(approximatedNAV, navDecrease); return navDecrease; } approximatedNAV = 0; return navDecrease; } // On repayment: adjust future value bucket according to repayment amount function _repay(uint loan, uint amount) internal returns (uint navDecrease) { bytes32 nftID_ = nftID(loan); uint maturityDate_ = maturityDate[nftID_]; // no fv decrease calculation needed if maturity date is in the past if (maturityDate_ < uniqueDayTimestamp(block.timestamp)) { // if a loan is overdue, the portfolio value is initially equal to the existing debt // it will be reduced by a write off factor once it is moved to a write off group return amount; } // remove future value for loan from bucket buckets[maturityDate_].value = safeSub(buckets[maturityDate_].value, futureValue[nftID_]); uint debt = pile.debt(loan); debt = safeSub(debt, amount); uint fv = 0; uint preFutureValue = futureValue[nftID_]; // in case of partial repayment, compute the fv of the remaining debt and add to the according fv bucket if (debt != 0) { fv = calcFutureValue(loan, debt, maturityDate_, recoveryRatePD[risk[nftID_]].value); buckets[maturityDate_].value = safeAdd(buckets[maturityDate_].value, fv); } futureValue[nftID_] = fv; // remove buckets if no remaining assets if (buckets[maturityDate_].value == 0 && firstBucket != 0) { removeBucket(maturityDate_); } // return decrease NAV amount return calcDiscount(safeSub(preFutureValue, fv), uniqueDayTimestamp(block.timestamp), maturityDate_); } function calcDiscount(uint amount, uint normalizedBlockTimestamp, uint maturityDate_) public view returns (uint result) { return rdiv(amount, rpow(discountRate.value, safeSub(maturityDate_, normalizedBlockTimestamp), ONE)); } /// 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; uint currDate = normalizedBlockTimestamp; if (currDate > lastBucket) { return 0; } // only buckets after the block.timestamp are relevant for the discount // assuming its more gas efficient to iterate over time to find the first one instead of iterating the list from the beginning // not true if buckets are only sparsely populated over long periods of time while(buckets[currDate].next == 0) { currDate = currDate + 1 days; } while(currDate != NullDate) { sum = safeAdd(sum, calcDiscount(buckets[currDate].value, normalizedBlockTimestamp, currDate)); currDate = buckets[currDate].next; } return sum; } /// 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(); // include ovedue assets to the current NAV calculation for (uint i = 0; i < writeOffs.length; i++) { // multiply writeOffGroupDebt with the writeOff rate nav_ = safeAdd(nav_, rmul(pile.rateDebt(writeOffs[i].rateGroup), writeOffs[i].percentage.value)); } return nav_; } function calcUpdateNAV() public returns(uint) { // approximated NAV is updated and at this point in time 100% correct approximatedNAV = currentNAV(); return approximatedNAV; } /// workaround for transition phase between V2 & V3 function totalValue() public view returns(uint) { return currentNAV(); } function dateBucket(uint timestamp) public view returns (uint) { return buckets[timestamp].value; } }
File 7 of 7: Assessor
// Verified using https://dapp.tools // hevm: flattened sources of src/lender/assessor.sol pragma solidity >=0.5.15 >=0.5.15 <0.6.0; ////// lib/tinlake-auth/lib/ds-note/src/note.sol /// note.sol -- the `note' modifier, for logging calls as events // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15; */ contract DSNote { event LogNote( bytes4 indexed sig, address indexed guy, bytes32 indexed foo, bytes32 indexed bar, uint256 wad, bytes fax ) anonymous; modifier note { bytes32 foo; bytes32 bar; uint256 wad; assembly { foo := calldataload(4) bar := calldataload(36) wad := callvalue() } _; emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data); } } ////// lib/tinlake-auth/src/auth.sol // Copyright (C) Centrifuge 2020, based on MakerDAO dss https://github.com/makerdao/dss // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "ds-note/note.sol"; */ contract Auth is DSNote { mapping (address => uint) public wards; function rely(address usr) public auth note { wards[usr] = 1; } function deny(address usr) public auth note { wards[usr] = 0; } modifier auth { require(wards[msg.sender] == 1); _; } } ////// lib/tinlake-math/src/math.sol // Copyright (C) 2018 Rain <[email protected]> // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ contract Math { uint256 constant ONE = 10 ** 27; function safeAdd(uint x, uint y) public pure returns (uint z) { require((z = x + y) >= x, "safe-add-failed"); } function safeSub(uint x, uint y) public pure returns (uint z) { require((z = x - y) <= x, "safe-sub-failed"); } function safeMul(uint x, uint y) public pure returns (uint z) { require(y == 0 || (z = x * y) / y == x, "safe-mul-failed"); } function safeDiv(uint x, uint y) public pure returns (uint z) { z = x / y; } function rmul(uint x, uint y) public pure returns (uint z) { z = safeMul(x, y) / ONE; } function rdiv(uint x, uint y) public pure returns (uint z) { require(y > 0, "division by zero"); z = safeAdd(safeMul(x, ONE), y / 2) / y; } function rdivup(uint x, uint y) internal pure returns (uint z) { require(y > 0, "division by zero"); // always rounds up z = safeAdd(safeMul(x, ONE), safeSub(y, 1)) / y; } } ////// lib/tinlake-math/src/interest.sol // Copyright (C) 2018 Rain <[email protected]> and Centrifuge, referencing MakerDAO dss => https://github.com/makerdao/dss/blob/master/src/pot.sol // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "./math.sol"; */ contract Interest is Math { // @notice This function provides compounding in seconds // @param chi Accumulated interest rate over time // @param ratePerSecond Interest rate accumulation per second in RAD(10ˆ27) // @param lastUpdated When the interest rate was last updated // @param pie Total sum of all amounts accumulating under one interest rate, divided by that rate // @return The new accumulated rate, as well as the difference between the debt calculated with the old and new accumulated rates. function compounding(uint chi, uint ratePerSecond, uint lastUpdated, uint pie) public view returns (uint, uint) { require(block.timestamp >= lastUpdated, "tinlake-math/invalid-timestamp"); require(chi != 0); // instead of a interestBearingAmount we use a accumulated interest rate index (chi) uint updatedChi = _chargeInterest(chi ,ratePerSecond, lastUpdated, block.timestamp); return (updatedChi, safeSub(rmul(updatedChi, pie), rmul(chi, pie))); } // @notice This function charge interest on a interestBearingAmount // @param interestBearingAmount is the interest bearing amount // @param ratePerSecond Interest rate accumulation per second in RAD(10ˆ27) // @param lastUpdated last time the interest has been charged // @return interestBearingAmount + interest function chargeInterest(uint interestBearingAmount, uint ratePerSecond, uint lastUpdated) public view returns (uint) { if (block.timestamp >= lastUpdated) { interestBearingAmount = _chargeInterest(interestBearingAmount, ratePerSecond, lastUpdated, block.timestamp); } return interestBearingAmount; } function _chargeInterest(uint interestBearingAmount, uint ratePerSecond, uint lastUpdated, uint current) internal pure returns (uint) { return rmul(rpow(ratePerSecond, current - lastUpdated, ONE), interestBearingAmount); } // convert pie to debt/savings amount function toAmount(uint chi, uint pie) public pure returns (uint) { return rmul(pie, chi); } // convert debt/savings amount to pie function toPie(uint chi, uint amount) public pure returns (uint) { return rdivup(amount, chi); } function rpow(uint x, uint n, uint base) public pure returns (uint z) { assembly { switch x case 0 {switch n case 0 {z := base} default {z := 0}} default { switch mod(n, 2) case 0 { z := base } default { z := x } let half := div(base, 2) // for rounding. for { n := div(n, 2) } n { n := div(n,2) } { let xx := mul(x, x) if iszero(eq(div(xx, x), x)) { revert(0,0) } let xxRound := add(xx, half) if lt(xxRound, xx) { revert(0,0) } x := div(xxRound, base) if mod(n,2) { let zx := mul(z, x) if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } let zxRound := add(zx, half) if lt(zxRound, zx) { revert(0,0) } z := div(zxRound, base) } } } } } } ////// src/fixed_point.sol // Copyright (C) 2020 Centrifuge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ contract FixedPoint { struct Fixed27 { uint value; } } ////// src/lender/assessor.sol // Copyright (C) 2020 Centrifuge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. /* pragma solidity >=0.5.15 <0.6.0; */ /* import "./../fixed_point.sol"; */ /* import "tinlake-auth/auth.sol"; */ /* import "tinlake-math/interest.sol"; */ interface NAVFeedLike_2 { function calcUpdateNAV() external returns (uint); function approximatedNAV() external view returns (uint); function currentNAV() external view returns(uint); } interface TrancheLike_2 { function tokenSupply() external view returns (uint); } interface ReserveLike_2 { function totalBalance() external view returns(uint); function file(bytes32 what, uint currencyAmount) external; } contract Assessor is Auth, FixedPoint, Interest { // senior ratio from the last epoch executed Fixed27 public seniorRatio; // the seniorAsset value is stored in two variables // seniorDebt is the interest bearing amount for senior uint public seniorDebt_; // senior balance is the rest which is not used as interest // bearing amount uint public seniorBalance_; // interest rate per second for senior tranche Fixed27 public seniorInterestRate; // last time the senior interest has been updated uint public lastUpdateSeniorInterest; Fixed27 public maxSeniorRatio; Fixed27 public minSeniorRatio; uint public maxReserve; TrancheLike_2 public seniorTranche; TrancheLike_2 public juniorTranche; NAVFeedLike_2 public navFeed; ReserveLike_2 public reserve; constructor() public { wards[msg.sender] = 1; seniorInterestRate.value = ONE; lastUpdateSeniorInterest = block.timestamp; seniorRatio.value = 0; } function depend(bytes32 contractName, address addr) public auth { if (contractName == "navFeed") { navFeed = NAVFeedLike_2(addr); } else if (contractName == "seniorTranche") { seniorTranche = TrancheLike_2(addr); } else if (contractName == "juniorTranche") { juniorTranche = TrancheLike_2(addr); } else if (contractName == "reserve") { reserve = ReserveLike_2(addr); } else revert(); } function file(bytes32 name, uint value) public auth { if(name == "seniorInterestRate") { seniorInterestRate = Fixed27(value); } else if (name == "maxReserve") {maxReserve = value;} else if (name == "maxSeniorRatio") { require(value > minSeniorRatio.value, "value-too-small"); maxSeniorRatio = Fixed27(value); } else if (name == "minSeniorRatio") { require(value < maxSeniorRatio.value, "value-too-big"); minSeniorRatio = Fixed27(value); } else {revert("unknown-variable");} } function reBalance(uint seniorAsset_, uint seniorRatio_) internal { // re-balancing according to new ratio // we use the approximated NAV here because during the submission period // new loans might have been repaid in the meanwhile which are not considered in the epochNAV seniorDebt_ = rmul(navFeed.approximatedNAV(), seniorRatio_); if(seniorDebt_ > seniorAsset_) { seniorDebt_ = seniorAsset_; seniorBalance_ = 0; return; } seniorBalance_ = safeSub(seniorAsset_, seniorDebt_); } function changeSeniorAsset(uint seniorRatio_, uint seniorSupply, uint seniorRedeem) external auth { dripSeniorDebt(); uint seniorAsset = safeSub(safeAdd(safeAdd(seniorDebt_, seniorBalance_),seniorSupply), seniorRedeem); reBalance(seniorAsset, seniorRatio_); seniorRatio = Fixed27(seniorRatio_); } function seniorRatioBounds() public view returns (uint minSeniorRatio_, uint maxSeniorRatio_) { return (minSeniorRatio.value, maxSeniorRatio.value); } function calcUpdateNAV() external returns (uint) { return navFeed.calcUpdateNAV(); } function calcSeniorTokenPrice() external view returns(uint) { return calcSeniorTokenPrice(navFeed.currentNAV(), reserve.totalBalance()); } function calcJuniorTokenPrice() external view returns(uint) { return calcJuniorTokenPrice(navFeed.currentNAV(), reserve.totalBalance()); } function calcTokenPrices() external view returns (uint, uint) { uint epochNAV = navFeed.currentNAV(); uint epochReserve = reserve.totalBalance(); return calcTokenPrices(epochNAV, epochReserve); } function calcTokenPrices(uint epochNAV, uint epochReserve) public view returns (uint, uint) { return (calcJuniorTokenPrice(epochNAV, epochReserve), calcSeniorTokenPrice(epochNAV, epochReserve)); } function calcSeniorTokenPrice(uint epochNAV, uint epochReserve) public view returns(uint) { if ((epochNAV == 0 && epochReserve == 0) || seniorTranche.tokenSupply() == 0) { // initial token price at start 1.00 return ONE; } uint totalAssets = safeAdd(epochNAV, epochReserve); uint seniorAssetValue = calcSeniorAssetValue(seniorDebt(), seniorBalance_); if(totalAssets < seniorAssetValue) { seniorAssetValue = totalAssets; } return rdiv(seniorAssetValue, seniorTranche.tokenSupply()); } function calcJuniorTokenPrice(uint epochNAV, uint epochReserve) public view returns(uint) { if ((epochNAV == 0 && epochReserve == 0) || juniorTranche.tokenSupply() == 0) { // initial token price at start 1.00 return ONE; } uint totalAssets = safeAdd(epochNAV, epochReserve); uint seniorAssetValue = calcSeniorAssetValue(seniorDebt(), seniorBalance_); if(totalAssets < seniorAssetValue) { return 0; } return rdiv(safeSub(totalAssets, seniorAssetValue), juniorTranche.tokenSupply()); } /// repayment update keeps track of senior bookkeeping for repaid loans /// the seniorDebt needs to be decreased function repaymentUpdate(uint currencyAmount) public auth { dripSeniorDebt(); uint decAmount = rmul(currencyAmount, seniorRatio.value); if (decAmount > seniorDebt_) { seniorBalance_ = calcSeniorAssetValue(seniorDebt_, seniorBalance_); seniorDebt_ = 0; return; } seniorBalance_ = safeAdd(seniorBalance_, decAmount); // seniorDebt needs to be decreased for loan repayments seniorDebt_ = safeSub(seniorDebt_, decAmount); lastUpdateSeniorInterest = block.timestamp; } /// borrow update keeps track of the senior bookkeeping for new borrowed loans /// the seniorDebt needs to be increased to accumulate interest function borrowUpdate(uint currencyAmount) public auth { dripSeniorDebt(); // the current senior ratio defines // interest bearing amount (seniorDebt) increase uint incAmount = rmul(currencyAmount, seniorRatio.value); // this case should most likely never happen if (incAmount > seniorBalance_) { // all the currency of senior is used as interest bearing currencyAmount seniorDebt_ = calcSeniorAssetValue(seniorDebt_, seniorBalance_); seniorBalance_ = 0; return; } // seniorDebt needs to be increased for loan borrows seniorDebt_ = safeAdd(seniorDebt_, incAmount); seniorBalance_ = safeSub(seniorBalance_, incAmount); lastUpdateSeniorInterest = block.timestamp; } function calcSeniorAssetValue(uint _seniorDebt, uint _seniorBalance) public pure returns(uint) { return safeAdd(_seniorDebt, _seniorBalance); } function dripSeniorDebt() public returns (uint) { uint newSeniorDebt = seniorDebt(); if (newSeniorDebt > seniorDebt_) { seniorDebt_ = newSeniorDebt; lastUpdateSeniorInterest = block.timestamp; } return seniorDebt_; } function seniorDebt() public view returns (uint) { if (now >= lastUpdateSeniorInterest) { return chargeInterest(seniorDebt_, seniorInterestRate.value, lastUpdateSeniorInterest); } return seniorDebt_; } function seniorBalance() public view returns (uint) { return seniorBalance_; } function totalBalance() public view returns (uint) { return reserve.totalBalance(); } // changes the total amount available for borrowing loans function changeReserveAvailable(uint currencyAmount) public auth { reserve.file("currencyAvailable", currencyAmount); } }