From 896a0406ca5a08735ec74d87d754053dfee895d7 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 28 Jun 2023 12:52:17 -0400 Subject: [PATCH 01/30] adding nft art code --- .../contracts/contracts/LenderManager.sol | 57 +- .../contracts/libraries/LenderManagerArt.sol | 901 ++++++++++++++++++ 2 files changed, 957 insertions(+), 1 deletion(-) create mode 100644 packages/contracts/contracts/libraries/LenderManagerArt.sol diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index 138e12211..afdbf6c29 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -11,12 +11,15 @@ import "./interfaces/ILenderManager.sol"; import "./interfaces/ITellerV2.sol"; import "./interfaces/IMarketRegistry.sol"; +import {Base64, Strings, LenderManagerArt} from "./libraries/LenderManagerArt.sol"; + contract LenderManager is Initializable, OwnableUpgradeable, ERC721Upgradeable, ILenderManager { + // using Strings for uint256; IMarketRegistry public immutable marketRegistry; constructor(IMarketRegistry _marketRegistry) { @@ -79,6 +82,58 @@ contract LenderManager is } function _baseURI() internal view override returns (string memory) { - return ""; + return "data:image/svg+xml;charset=utf-8,"; } + + function tokenURI(uint256 tokenId) public view override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + + + string memory image_svg_encoded = Base64.encode(bytes( + LenderManagerArt.generateSVG( + tokenId, + bidId, + principalAmount, + principalTokenAddress, + collateralAmount, + collateralTokenAddress, + interestRate, + duration + ) )); + + + string memory name = "Teller Loan NFT"; + string memory description = "This token represents ownership of a loan. Repayments of principal and interest will be sent to the owner of this token. If the loan defaults, the owner of this token will be able to claim the underlying collateral."; + + + + string memory encoded_svg = string( + abi.encodePacked( + 'data:application/json;base64,', + Base64.encode( + bytes( + abi.encodePacked( + '{"name":"', + name, + '", "description":"', + description, + '", "image": "', //use image_data so its a dynamic svg not cached ? + 'data:image/svg+xml;base64,', + image_svg_encoded, + '"}' + ) + ) + ) + ) + ); + + return encoded_svg; + } + + } + + + + \ No newline at end of file diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol new file mode 100644 index 000000000..32e5d3236 --- /dev/null +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -0,0 +1,901 @@ +/** + *Submitted for verification at Etherscan.io on 2023-06-21 +*/ + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.4; + + + + +/** + * @dev Provides a set of functions to operate with Base64 strings. + * + * _Available since v4.5._ + */ +library Base64 { + /** + * @dev Base64 Encoding/Decoding Table + */ + string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /** + * @dev Converts a `bytes` to its Bytes64 `string` representation. + */ + function encode(bytes memory data) internal pure returns (string memory) { + /** + * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence + * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol + */ + if (data.length == 0) return ""; + + // Loads the table into memory + string memory table = _TABLE; + + // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter + // and split into 4 numbers of 6 bits. + // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up + // - `data.length + 2` -> Round up + // - `/ 3` -> Number of 3-bytes chunks + // - `4 *` -> 4 characters for each chunk + string memory result = new string(4 * ((data.length + 2) / 3)); + + /// @solidity memory-safe-assembly + assembly { + // Prepare the lookup table (skip the first "length" byte) + let tablePtr := add(table, 1) + + // Prepare result pointer, jump over length + let resultPtr := add(result, 32) + + // Run over the input, 3 bytes at a time + for { + let dataPtr := data + let endPtr := add(data, mload(data)) + } lt(dataPtr, endPtr) { + + } { + // Advance 3 bytes + dataPtr := add(dataPtr, 3) + let input := mload(dataPtr) + + // To write each character, shift the 3 bytes (18 bits) chunk + // 4 times in blocks of 6 bits for each character (18, 12, 6, 0) + // and apply logical AND with 0x3F which is the number of + // the previous character in the ASCII table prior to the Base64 Table + // The result is then added to the table to get the character to write, + // and finally write it in the result pointer but with a left shift + // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits + + mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + + mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) + resultPtr := add(resultPtr, 1) // Advance + } + + // When data `bytes` is not exactly 3 bytes long + // it is padded with `=` characters at the end + switch mod(mload(data), 3) + case 1 { + mstore8(sub(resultPtr, 1), 0x3d) + mstore8(sub(resultPtr, 2), 0x3d) + } + case 2 { + mstore8(sub(resultPtr, 1), 0x3d) + } + } + + return result; + } +} + + +/** + * @dev Standard math utilities missing in the Solidity language. + */ +library Math { + /** + * @dev Muldiv operation overflow. + */ + error MathOverflowedMulDiv(); + + enum Rounding { + Down, // Toward negative infinity + Up, // Toward infinity + Zero // Toward zero + } + + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v5.0._ + */ + function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the subtraction of two unsigned integers, with an overflow flag. + * + * _Available since v5.0._ + */ + function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v5.0._ + */ + function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + // 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-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v5.0._ + */ + function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v5.0._ + */ + function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two numbers. + */ + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two numbers. The result is rounded towards + * zero. + */ + function average(uint256 a, uint256 b) internal pure returns (uint256) { + // (a + b) / 2 can overflow. + return (a & b) + (a ^ b) / 2; + } + + /** + * @dev Returns the ceiling of the division of two numbers. + * + * This differs from standard division with `/` in that it rounds up instead + * of rounding down. + */ + function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { + if (b == 0) { + // Guarantee the same behavior as in a regular Solidity division. + return a / b; + } + + // (a + b - 1) / b can overflow on addition, so we distribute. + return a == 0 ? 0 : (a - 1) / b + 1; + } + + /** + * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) + * with further edits by Uniswap Labs also under MIT license. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { + unchecked { + // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use + // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2^256 + prod0. + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(x, y, not(0)) + prod0 := mul(x, y) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division. + if (prod1 == 0) { + // Solidity will revert if denominator == 0, unlike the div opcode on its own. + // The surrounding unchecked block does not change this fact. + // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. + return prod0 / denominator; + } + + // Make sure the result is less than 2^256. Also prevents denominator == 0. + if (denominator <= prod1) { + revert MathOverflowedMulDiv(); + } + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0]. + uint256 remainder; + assembly { + // Compute remainder using mulmod. + remainder := mulmod(x, y, denominator) + + // Subtract 256 bit number from 512 bit number. + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. + // See https://cs.stackexchange.com/q/138556/92363. + + // Does not overflow because the denominator cannot be zero at this stage in the function. + uint256 twos = denominator & (~denominator + 1); + assembly { + // Divide denominator by twos. + denominator := div(denominator, twos) + + // Divide [prod1 prod0] by twos. + prod0 := div(prod0, twos) + + // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. + twos := add(div(sub(0, twos), twos), 1) + } + + // Shift in bits from prod1 into prod0. + prod0 |= prod1 * twos; + + // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such + // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for + // four bits. That is, denominator * inv = 1 mod 2^4. + uint256 inverse = (3 * denominator) ^ 2; + + // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works + // in modular arithmetic, doubling the correct bits in each step. + inverse *= 2 - denominator * inverse; // inverse mod 2^8 + inverse *= 2 - denominator * inverse; // inverse mod 2^16 + inverse *= 2 - denominator * inverse; // inverse mod 2^32 + inverse *= 2 - denominator * inverse; // inverse mod 2^64 + inverse *= 2 - denominator * inverse; // inverse mod 2^128 + inverse *= 2 - denominator * inverse; // inverse mod 2^256 + + // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. + // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is + // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inverse; + return result; + } + } + + /** + * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. + */ + function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { + uint256 result = mulDiv(x, y, denominator); + if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { + result += 1; + } + return result; + } + + /** + * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. + * + * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + */ + function sqrt(uint256 a) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. + // + // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have + // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. + // + // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` + // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` + // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` + // + // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. + uint256 result = 1 << (log2(a) >> 1); + + // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, + // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at + // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision + // into the expected uint128 result. + unchecked { + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + result = (result + a / result) >> 1; + return min(result, a / result); + } + } + + /** + * @notice Calculates sqrt(a), following the selected rounding direction. + */ + function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = sqrt(a); + return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); + } + } + + /** + * @dev Return the log in base 2, rounded down, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 128; + } + if (value >> 64 > 0) { + value >>= 64; + result += 64; + } + if (value >> 32 > 0) { + value >>= 32; + result += 32; + } + if (value >> 16 > 0) { + value >>= 16; + result += 16; + } + if (value >> 8 > 0) { + value >>= 8; + result += 8; + } + if (value >> 4 > 0) { + value >>= 4; + result += 4; + } + if (value >> 2 > 0) { + value >>= 2; + result += 2; + } + if (value >> 1 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 2, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log2(value); + return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 10, rounded down, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >= 10 ** 64) { + value /= 10 ** 64; + result += 64; + } + if (value >= 10 ** 32) { + value /= 10 ** 32; + result += 32; + } + if (value >= 10 ** 16) { + value /= 10 ** 16; + result += 16; + } + if (value >= 10 ** 8) { + value /= 10 ** 8; + result += 8; + } + if (value >= 10 ** 4) { + value /= 10 ** 4; + result += 4; + } + if (value >= 10 ** 2) { + value /= 10 ** 2; + result += 2; + } + if (value >= 10 ** 1) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 10, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log10(value); + return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); + } + } + + /** + * @dev Return the log in base 256, rounded down, of a positive value. + * Returns 0 if given 0. + * + * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + */ + function log256(uint256 value) internal pure returns (uint256) { + uint256 result = 0; + unchecked { + if (value >> 128 > 0) { + value >>= 128; + result += 16; + } + if (value >> 64 > 0) { + value >>= 64; + result += 8; + } + if (value >> 32 > 0) { + value >>= 32; + result += 4; + } + if (value >> 16 > 0) { + value >>= 16; + result += 2; + } + if (value >> 8 > 0) { + result += 1; + } + } + return result; + } + + /** + * @dev Return the log in base 256, following the selected rounding direction, of a positive value. + * Returns 0 if given 0. + */ + function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { + unchecked { + uint256 result = log256(value); + return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); + } + } +} + + +/** + * @dev Standard signed math utilities missing in the Solidity language. + */ +library SignedMath { + /** + * @dev Returns the largest of two signed numbers. + */ + function max(int256 a, int256 b) internal pure returns (int256) { + return a > b ? a : b; + } + + /** + * @dev Returns the smallest of two signed numbers. + */ + function min(int256 a, int256 b) internal pure returns (int256) { + return a < b ? a : b; + } + + /** + * @dev Returns the average of two signed numbers without overflow. + * The result is rounded towards zero. + */ + function average(int256 a, int256 b) internal pure returns (int256) { + // Formula from the book "Hacker's Delight" + int256 x = (a & b) + ((a ^ b) >> 1); + return x + (int256(uint256(x) >> 255) & (a ^ b)); + } + + /** + * @dev Returns the absolute unsigned value of a signed value. + */ + function abs(int256 n) internal pure returns (uint256) { + unchecked { + // must be unchecked in order to support `n = type(int256).min` + return uint256(n >= 0 ? n : -n); + } + } +} + + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant _SYMBOLS = "0123456789abcdef"; + uint8 private constant _ADDRESS_LENGTH = 20; + + /** + * @dev The `value` string doesn't fit in the specified `length`. + */ + error StringsInsufficientHexLength(uint256 value, uint256 length); + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + unchecked { + uint256 length = Math.log10(value) + 1; + string memory buffer = new string(length); + uint256 ptr; + /// @solidity memory-safe-assembly + assembly { + ptr := add(buffer, add(32, length)) + } + while (true) { + ptr--; + /// @solidity memory-safe-assembly + assembly { + mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) + } + value /= 10; + if (value == 0) break; + } + return buffer; + } + } + + /** + * @dev Converts a `int256` to its ASCII `string` decimal representation. + */ + /*function toStringSigned(int256 value) internal pure returns (string memory) { + return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); + }*/ + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + unchecked { + return toHexString(value, Math.log256(value) + 1); + } + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + uint256 localValue = value; + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _SYMBOLS[localValue & 0xf]; + localValue >>= 4; + } + if (localValue != 0) { + revert StringsInsufficientHexLength(value, length); + } + return string(buffer); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); + } + + /** + * @dev Returns true if the two strings are equal. + */ + function equal(string memory a, string memory b) internal pure returns (bool) { + return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); + } +} + + + +library LenderManagerArt { + + + +bytes constant _bg_defs_filter = abi.encodePacked ( + + + "", + "", + "", + "", + "", + "", + "", + "" + "", + "" + +); + + +bytes constant _bg_defs_clip_path = abi.encodePacked ( + + + "", + "", + "", + + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + +); + + +bytes constant _bg_defs_mask = abi.encodePacked ( + "", + "", + "", + "", + "", + "", + "", + "", + "", + + "", + "", + "", + "", + "", + "", + "" +); + +bytes constant _bg_defs = abi.encodePacked( + "", + + + _bg_defs_filter, + + _bg_defs_clip_path, + + _bg_defs_mask, + + + + +"" + +); + + +bytes constant _clip_path_corners = abi.encodePacked( + + "", + "", + "", + "", + "", + "", + "", + "", + "" + + +); + + + +bytes constant _teller_logo_path_1 = abi.encodePacked( +"", +"", +"", +"", +"", +"", +"", +"" + +); + + +bytes constant _teller_logo_path_2 = abi.encodePacked( + +"", +"", +"", +"", +"", +"", +"", +"", +"", +"" + + + +); + +bytes constant _teller_logo = abi.encodePacked( + + +"", + "", +"", + _teller_logo_path_1, + _teller_logo_path_2, +"" + + +); + + + +function _generate_large_title( + string memory amount, + string memory symbol +) public pure returns (string memory) { + + + return string(abi.encodePacked( + + "", +"", +"AMOUNT", +"",amount," ",symbol,"", +"", +"" + + + + + )); +} + + +function _generate_text_label( + string memory label, + string memory value, + + uint256 y_offset +) public pure returns (string memory) { + + + return string(abi.encodePacked( + "", + "", + "", + "", label, "",value, + "", + "" + )); +} + +function generateSVG( + uint256 tokenId, + uint256 bidId, + uint256 principalAmount, + address principalTokenAddress, + uint256 collateralAmount, + address collateralTokenAddress, + uint16 interestRate, + uint32 duration + ) public pure returns (string memory) { + + string memory svgData = string(abi.encodePacked( + +"", + +_bg_defs, + + +_clip_path_corners, + + +_generate_large_title( + "1.2540", + "USDC" +), + +_teller_logo, + + +_generate_text_label( + "Loan ID:", + "111111", + 354 +), + + +_generate_text_label( + "Collateral:", + "3.00 WMATIC", + 384 +), + + +_generate_text_label( + "APR:", + "30 %", + 414 +), + + +_generate_text_label( + "Duration:", + "7 days", + 444 +), + + +"" + )); + + return svgData; +} + + + + + +} + + + From cbe2eda68f93226989aa1d71b3d9cf5410392bc8 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 28 Jun 2023 13:10:27 -0400 Subject: [PATCH 02/30] fixing --- .../contracts/contracts/LenderManager.sol | 9 ++- packages/contracts/contracts/TellerV2.sol | 6 +- .../contracts/libraries/LenderManagerArt.sol | 56 +++++++++++++++++-- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index afdbf6c29..47b46dc64 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -88,12 +88,17 @@ contract LenderManager is function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + (address borrower, address lender, uint256 marketId, address principalTokenAddress, uint256 principalAmount, + uint32 acceptedTimestamp, uint32 lastRepaidTimestamp, BidState bidState, + uint16 interestRate, uint32 duration ) = ITellerV2(owner()).getLoanSummary(tokenId); + + uint256 collateralAmount = 0; + address collateralTokenAddress = address(0); string memory image_svg_encoded = Base64.encode(bytes( LenderManagerArt.generateSVG( - tokenId, - bidId, + tokenId, //tokenId == bidId principalAmount, principalTokenAddress, collateralAmount, diff --git a/packages/contracts/contracts/TellerV2.sol b/packages/contracts/contracts/TellerV2.sol index ace9c2c4a..53df43d84 100644 --- a/packages/contracts/contracts/TellerV2.sol +++ b/packages/contracts/contracts/TellerV2.sol @@ -1050,7 +1050,9 @@ contract TellerV2 is uint256 principalAmount, uint32 acceptedTimestamp, uint32 lastRepaidTimestamp, - BidState bidState + BidState bidState, + uint16 interestRate, + uint32 duration ) { Bid storage bid = bids[_bidId]; @@ -1063,6 +1065,8 @@ contract TellerV2 is acceptedTimestamp = bid.loanDetails.acceptedTimestamp; lastRepaidTimestamp = V2Calculations.lastRepaidTimestamp(bids[_bidId]); bidState = bid.state; + interestRate = bid.terms.apr; + duration = bid.loanDetails.loanDuration; } /** OpenZeppelin Override Functions **/ diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 32e5d3236..736ecdc79 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -828,6 +828,31 @@ function _generate_text_label( )); } +/* +Not working quite right yet +*/ +function _get_token_amount_formatted( + uint256 amount, + uint256 decimals, + uint256 precision +) public pure returns (string memory) { + + uint256 before_decimal = amount / 10 ** decimals; + + uint256 after_decimal = amount % 10 ** decimals; + + return string(abi.encodePacked( + Strings.toString(before_decimal), + ".", + Strings.toString(after_decimal) + + )); + + + +} + + function generateSVG( uint256 tokenId, uint256 bidId, @@ -839,6 +864,29 @@ function generateSVG( uint32 duration ) public pure returns (string memory) { + + + +string memory principal_amount_formatted = _get_token_amount_formatted( + 12540000000000000000, + 18 , + 5 +); + +string memory principal_token_symbol = "USDC"; + + +string memory collateral_amount_formatted = _get_token_amount_formatted( + 2000000000000000000000, + 18 , + 3 +); + +string memory collateral_token_symbol = "WMATIC"; + + + + string memory svgData = string(abi.encodePacked( "", @@ -850,8 +898,8 @@ _clip_path_corners, _generate_large_title( - "1.2540", - "USDC" + principal_amount_formatted, + principal_token_symbol ), _teller_logo, @@ -859,14 +907,14 @@ _teller_logo, _generate_text_label( "Loan ID:", - "111111", + Strings.toString(bidId), 354 ), _generate_text_label( "Collateral:", - "3.00 WMATIC", + string(abi.encodePacked(collateral_amount_formatted," ",collateral_token_symbol)), 384 ), From 18234962a8bc8718d2a9e94d4d60cfe3387cac7e Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 28 Jun 2023 13:19:30 -0400 Subject: [PATCH 03/30] fixing --- packages/contracts/contracts/TellerV2.sol | 2 +- packages/contracts/contracts/libraries/LenderManagerArt.sol | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/contracts/contracts/TellerV2.sol b/packages/contracts/contracts/TellerV2.sol index 53df43d84..3785befdc 100644 --- a/packages/contracts/contracts/TellerV2.sol +++ b/packages/contracts/contracts/TellerV2.sol @@ -1065,7 +1065,7 @@ contract TellerV2 is acceptedTimestamp = bid.loanDetails.acceptedTimestamp; lastRepaidTimestamp = V2Calculations.lastRepaidTimestamp(bids[_bidId]); bidState = bid.state; - interestRate = bid.terms.apr; + interestRate = bid.terms.APR; duration = bid.loanDetails.loanDuration; } diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 736ecdc79..d4b153f59 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -853,8 +853,7 @@ function _get_token_amount_formatted( } -function generateSVG( - uint256 tokenId, +function generateSVG( uint256 bidId, uint256 principalAmount, address principalTokenAddress, From 92b177217ec07e96bf0e98d78e2b184078c538fe Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 28 Jun 2023 17:00:37 -0400 Subject: [PATCH 04/30] fixing imports --- .../contracts/contracts/LenderManager.sol | 47 +- packages/contracts/contracts/TellerV2.sol | 8 +- .../contracts/interfaces/ITellerV2.sol | 2 + .../contracts/interfaces/ITellerV2Storage.sol | 10 + .../contracts/libraries/LenderManagerArt.sol | 714 ++---------------- 5 files changed, 105 insertions(+), 676 deletions(-) create mode 100644 packages/contracts/contracts/interfaces/ITellerV2Storage.sol diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index 334f8e3d9..f8d08012a 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -6,12 +6,17 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import "@openzeppelin/contracts/utils/Base64.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + + // Interfaces import "./interfaces/ILenderManager.sol"; import "./interfaces/ITellerV2.sol"; +import "./interfaces/ITellerV2Storage.sol"; import "./interfaces/IMarketRegistry.sol"; -import {Base64, Strings, LenderManagerArt} from "./libraries/LenderManagerArt.sol"; +import { LenderManagerArt} from "./libraries/LenderManagerArt.sol"; contract LenderManager is Initializable, @@ -85,12 +90,38 @@ contract LenderManager is return "data:image/svg+xml;charset=utf-8,"; } + struct LoanInformation { + + address principalTokenAddress; + uint256 principalAmount; + uint16 interestRate; + uint32 loanDuration; + + + } + + + function _getLoanInformation(uint256 tokenId) + internal view + returns (LoanInformation memory loanInformation_) { + + Bid memory bid = ITellerV2Storage(owner()).bids(tokenId); + + LoanInformation memory loanInformation = LoanInformation({ + principalTokenAddress: address(bid.loanDetails.lendingToken), + principalAmount: bid.loanDetails.principal, + interestRate: bid.terms.APR, + loanDuration: bid.loanDetails.loanDuration + }); + + + return loanInformation_; + } + function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - (address borrower, address lender, uint256 marketId, address principalTokenAddress, uint256 principalAmount, - uint32 acceptedTimestamp, uint32 lastRepaidTimestamp, BidState bidState, - uint16 interestRate, uint32 duration ) = ITellerV2(owner()).getLoanSummary(tokenId); + LoanInformation memory loanInformation = _getLoanInformation(tokenId); uint256 collateralAmount = 0; address collateralTokenAddress = address(0); @@ -99,12 +130,12 @@ contract LenderManager is string memory image_svg_encoded = Base64.encode(bytes( LenderManagerArt.generateSVG( tokenId, //tokenId == bidId - principalAmount, - principalTokenAddress, + loanInformation.principalAmount, + loanInformation.principalTokenAddress, collateralAmount, collateralTokenAddress, - interestRate, - duration + loanInformation.interestRate, + loanInformation.loanDuration ) )); diff --git a/packages/contracts/contracts/TellerV2.sol b/packages/contracts/contracts/TellerV2.sol index a47a17be1..3a2bcc4df 100644 --- a/packages/contracts/contracts/TellerV2.sol +++ b/packages/contracts/contracts/TellerV2.sol @@ -1183,9 +1183,7 @@ contract TellerV2 is uint256 principalAmount, uint32 acceptedTimestamp, uint32 lastRepaidTimestamp, - BidState bidState, - uint16 interestRate, - uint32 duration + BidState bidState ) { Bid storage bid = bids[_bidId]; @@ -1197,9 +1195,7 @@ contract TellerV2 is principalAmount = bid.loanDetails.principal; acceptedTimestamp = bid.loanDetails.acceptedTimestamp; lastRepaidTimestamp = V2Calculations.lastRepaidTimestamp(bids[_bidId]); - bidState = bid.state; - interestRate = bid.terms.APR; - duration = bid.loanDetails.loanDuration; + bidState = bid.state; } /** OpenZeppelin Override Functions **/ diff --git a/packages/contracts/contracts/interfaces/ITellerV2.sol b/packages/contracts/contracts/interfaces/ITellerV2.sol index ae3efc120..21b421cf1 100644 --- a/packages/contracts/contracts/interfaces/ITellerV2.sol +++ b/packages/contracts/contracts/interfaces/ITellerV2.sol @@ -148,4 +148,6 @@ interface ITellerV2 { uint32 lastRepaidTimestamp, BidState bidState ); + + } diff --git a/packages/contracts/contracts/interfaces/ITellerV2Storage.sol b/packages/contracts/contracts/interfaces/ITellerV2Storage.sol new file mode 100644 index 000000000..6ec72b705 --- /dev/null +++ b/packages/contracts/contracts/interfaces/ITellerV2Storage.sol @@ -0,0 +1,10 @@ +import { Bid } from "../TellerV2Storage.sol"; + +interface ITellerV2Storage { + + function bids(uint256 _bidId) + external + view + returns (Bid memory); + +} \ No newline at end of file diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index d4b153f59..d65b9bdf1 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -7,637 +7,10 @@ pragma solidity ^0.8.4; - - -/** - * @dev Provides a set of functions to operate with Base64 strings. - * - * _Available since v4.5._ - */ -library Base64 { - /** - * @dev Base64 Encoding/Decoding Table - */ - string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - /** - * @dev Converts a `bytes` to its Bytes64 `string` representation. - */ - function encode(bytes memory data) internal pure returns (string memory) { - /** - * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence - * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol - */ - if (data.length == 0) return ""; - - // Loads the table into memory - string memory table = _TABLE; - - // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter - // and split into 4 numbers of 6 bits. - // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up - // - `data.length + 2` -> Round up - // - `/ 3` -> Number of 3-bytes chunks - // - `4 *` -> 4 characters for each chunk - string memory result = new string(4 * ((data.length + 2) / 3)); - - /// @solidity memory-safe-assembly - assembly { - // Prepare the lookup table (skip the first "length" byte) - let tablePtr := add(table, 1) - - // Prepare result pointer, jump over length - let resultPtr := add(result, 32) - - // Run over the input, 3 bytes at a time - for { - let dataPtr := data - let endPtr := add(data, mload(data)) - } lt(dataPtr, endPtr) { - - } { - // Advance 3 bytes - dataPtr := add(dataPtr, 3) - let input := mload(dataPtr) - - // To write each character, shift the 3 bytes (18 bits) chunk - // 4 times in blocks of 6 bits for each character (18, 12, 6, 0) - // and apply logical AND with 0x3F which is the number of - // the previous character in the ASCII table prior to the Base64 Table - // The result is then added to the table to get the character to write, - // and finally write it in the result pointer but with a left shift - // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits - - mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F)))) - resultPtr := add(resultPtr, 1) // Advance - - mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F)))) - resultPtr := add(resultPtr, 1) // Advance - - mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F)))) - resultPtr := add(resultPtr, 1) // Advance - - mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F)))) - resultPtr := add(resultPtr, 1) // Advance - } - - // When data `bytes` is not exactly 3 bytes long - // it is padded with `=` characters at the end - switch mod(mload(data), 3) - case 1 { - mstore8(sub(resultPtr, 1), 0x3d) - mstore8(sub(resultPtr, 2), 0x3d) - } - case 2 { - mstore8(sub(resultPtr, 1), 0x3d) - } - } - - return result; - } -} - - -/** - * @dev Standard math utilities missing in the Solidity language. - */ -library Math { - /** - * @dev Muldiv operation overflow. - */ - error MathOverflowedMulDiv(); - - enum Rounding { - Down, // Toward negative infinity - Up, // Toward infinity - Zero // Toward zero - } - - /** - * @dev Returns the addition of two unsigned integers, with an overflow flag. - * - * _Available since v5.0._ - */ - function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - uint256 c = a + b; - if (c < a) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the subtraction of two unsigned integers, with an overflow flag. - * - * _Available since v5.0._ - */ - function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b > a) return (false, 0); - return (true, a - b); - } - } - - /** - * @dev Returns the multiplication of two unsigned integers, with an overflow flag. - * - * _Available since v5.0._ - */ - function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - // 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-contracts/pull/522 - if (a == 0) return (true, 0); - uint256 c = a * b; - if (c / a != b) return (false, 0); - return (true, c); - } - } - - /** - * @dev Returns the division of two unsigned integers, with a division by zero flag. - * - * _Available since v5.0._ - */ - function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a / b); - } - } - - /** - * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. - * - * _Available since v5.0._ - */ - function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) { - unchecked { - if (b == 0) return (false, 0); - return (true, a % b); - } - } - - /** - * @dev Returns the largest of two numbers. - */ - function max(uint256 a, uint256 b) internal pure returns (uint256) { - return a > b ? a : b; - } - - /** - * @dev Returns the smallest of two numbers. - */ - function min(uint256 a, uint256 b) internal pure returns (uint256) { - return a < b ? a : b; - } - - /** - * @dev Returns the average of two numbers. The result is rounded towards - * zero. - */ - function average(uint256 a, uint256 b) internal pure returns (uint256) { - // (a + b) / 2 can overflow. - return (a & b) + (a ^ b) / 2; - } - - /** - * @dev Returns the ceiling of the division of two numbers. - * - * This differs from standard division with `/` in that it rounds up instead - * of rounding down. - */ - function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { - if (b == 0) { - // Guarantee the same behavior as in a regular Solidity division. - return a / b; - } - - // (a + b - 1) / b can overflow on addition, so we distribute. - return a == 0 ? 0 : (a - 1) / b + 1; - } - - /** - * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 - * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) - * with further edits by Uniswap Labs also under MIT license. - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { - unchecked { - // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use - // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 - // variables such that product = prod1 * 2^256 + prod0. - uint256 prod0; // Least significant 256 bits of the product - uint256 prod1; // Most significant 256 bits of the product - assembly { - let mm := mulmod(x, y, not(0)) - prod0 := mul(x, y) - prod1 := sub(sub(mm, prod0), lt(mm, prod0)) - } - - // Handle non-overflow cases, 256 by 256 division. - if (prod1 == 0) { - // Solidity will revert if denominator == 0, unlike the div opcode on its own. - // The surrounding unchecked block does not change this fact. - // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. - return prod0 / denominator; - } - - // Make sure the result is less than 2^256. Also prevents denominator == 0. - if (denominator <= prod1) { - revert MathOverflowedMulDiv(); - } - - /////////////////////////////////////////////// - // 512 by 256 division. - /////////////////////////////////////////////// - - // Make division exact by subtracting the remainder from [prod1 prod0]. - uint256 remainder; - assembly { - // Compute remainder using mulmod. - remainder := mulmod(x, y, denominator) - - // Subtract 256 bit number from 512 bit number. - prod1 := sub(prod1, gt(remainder, prod0)) - prod0 := sub(prod0, remainder) - } - - // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1. - // See https://cs.stackexchange.com/q/138556/92363. - - // Does not overflow because the denominator cannot be zero at this stage in the function. - uint256 twos = denominator & (~denominator + 1); - assembly { - // Divide denominator by twos. - denominator := div(denominator, twos) - - // Divide [prod1 prod0] by twos. - prod0 := div(prod0, twos) - - // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one. - twos := add(div(sub(0, twos), twos), 1) - } - - // Shift in bits from prod1 into prod0. - prod0 |= prod1 * twos; - - // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such - // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for - // four bits. That is, denominator * inv = 1 mod 2^4. - uint256 inverse = (3 * denominator) ^ 2; - - // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works - // in modular arithmetic, doubling the correct bits in each step. - inverse *= 2 - denominator * inverse; // inverse mod 2^8 - inverse *= 2 - denominator * inverse; // inverse mod 2^16 - inverse *= 2 - denominator * inverse; // inverse mod 2^32 - inverse *= 2 - denominator * inverse; // inverse mod 2^64 - inverse *= 2 - denominator * inverse; // inverse mod 2^128 - inverse *= 2 - denominator * inverse; // inverse mod 2^256 - - // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. - // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is - // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1 - // is no longer required. - result = prod0 * inverse; - return result; - } - } - - /** - * @notice Calculates x * y / denominator with full precision, following the selected rounding direction. - */ - function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { - uint256 result = mulDiv(x, y, denominator); - if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) { - result += 1; - } - return result; - } - - /** - * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down. - * - * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). - */ - function sqrt(uint256 a) internal pure returns (uint256) { - if (a == 0) { - return 0; - } - - // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target. - // - // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have - // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`. - // - // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)` - // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))` - // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)` - // - // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit. - uint256 result = 1 << (log2(a) >> 1); - - // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128, - // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at - // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision - // into the expected uint128 result. - unchecked { - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - result = (result + a / result) >> 1; - return min(result, a / result); - } - } - - /** - * @notice Calculates sqrt(a), following the selected rounding direction. - */ - function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = sqrt(a); - return result + (rounding == Rounding.Up && result * result < a ? 1 : 0); - } - } - - /** - * @dev Return the log in base 2, rounded down, of a positive value. - * Returns 0 if given 0. - */ - function log2(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >> 128 > 0) { - value >>= 128; - result += 128; - } - if (value >> 64 > 0) { - value >>= 64; - result += 64; - } - if (value >> 32 > 0) { - value >>= 32; - result += 32; - } - if (value >> 16 > 0) { - value >>= 16; - result += 16; - } - if (value >> 8 > 0) { - value >>= 8; - result += 8; - } - if (value >> 4 > 0) { - value >>= 4; - result += 4; - } - if (value >> 2 > 0) { - value >>= 2; - result += 2; - } - if (value >> 1 > 0) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 2, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log2(value); - return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0); - } - } - - /** - * @dev Return the log in base 10, rounded down, of a positive value. - * Returns 0 if given 0. - */ - function log10(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >= 10 ** 64) { - value /= 10 ** 64; - result += 64; - } - if (value >= 10 ** 32) { - value /= 10 ** 32; - result += 32; - } - if (value >= 10 ** 16) { - value /= 10 ** 16; - result += 16; - } - if (value >= 10 ** 8) { - value /= 10 ** 8; - result += 8; - } - if (value >= 10 ** 4) { - value /= 10 ** 4; - result += 4; - } - if (value >= 10 ** 2) { - value /= 10 ** 2; - result += 2; - } - if (value >= 10 ** 1) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 10, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log10(value); - return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0); - } - } - - /** - * @dev Return the log in base 256, rounded down, of a positive value. - * Returns 0 if given 0. - * - * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. - */ - function log256(uint256 value) internal pure returns (uint256) { - uint256 result = 0; - unchecked { - if (value >> 128 > 0) { - value >>= 128; - result += 16; - } - if (value >> 64 > 0) { - value >>= 64; - result += 8; - } - if (value >> 32 > 0) { - value >>= 32; - result += 4; - } - if (value >> 16 > 0) { - value >>= 16; - result += 2; - } - if (value >> 8 > 0) { - result += 1; - } - } - return result; - } - - /** - * @dev Return the log in base 256, following the selected rounding direction, of a positive value. - * Returns 0 if given 0. - */ - function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { - unchecked { - uint256 result = log256(value); - return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0); - } - } -} - - -/** - * @dev Standard signed math utilities missing in the Solidity language. - */ -library SignedMath { - /** - * @dev Returns the largest of two signed numbers. - */ - function max(int256 a, int256 b) internal pure returns (int256) { - return a > b ? a : b; - } - - /** - * @dev Returns the smallest of two signed numbers. - */ - function min(int256 a, int256 b) internal pure returns (int256) { - return a < b ? a : b; - } - - /** - * @dev Returns the average of two signed numbers without overflow. - * The result is rounded towards zero. - */ - function average(int256 a, int256 b) internal pure returns (int256) { - // Formula from the book "Hacker's Delight" - int256 x = (a & b) + ((a ^ b) >> 1); - return x + (int256(uint256(x) >> 255) & (a ^ b)); - } - - /** - * @dev Returns the absolute unsigned value of a signed value. - */ - function abs(int256 n) internal pure returns (uint256) { - unchecked { - // must be unchecked in order to support `n = type(int256).min` - return uint256(n >= 0 ? n : -n); - } - } -} - - -/** - * @dev String operations. - */ -library Strings { - bytes16 private constant _SYMBOLS = "0123456789abcdef"; - uint8 private constant _ADDRESS_LENGTH = 20; - - /** - * @dev The `value` string doesn't fit in the specified `length`. - */ - error StringsInsufficientHexLength(uint256 value, uint256 length); - - /** - * @dev Converts a `uint256` to its ASCII `string` decimal representation. - */ - function toString(uint256 value) internal pure returns (string memory) { - unchecked { - uint256 length = Math.log10(value) + 1; - string memory buffer = new string(length); - uint256 ptr; - /// @solidity memory-safe-assembly - assembly { - ptr := add(buffer, add(32, length)) - } - while (true) { - ptr--; - /// @solidity memory-safe-assembly - assembly { - mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) - } - value /= 10; - if (value == 0) break; - } - return buffer; - } - } - - /** - * @dev Converts a `int256` to its ASCII `string` decimal representation. - */ - /*function toStringSigned(int256 value) internal pure returns (string memory) { - return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); - }*/ - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. - */ - function toHexString(uint256 value) internal pure returns (string memory) { - unchecked { - return toHexString(value, Math.log256(value) + 1); - } - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. - */ - function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { - uint256 localValue = value; - bytes memory buffer = new bytes(2 * length + 2); - buffer[0] = "0"; - buffer[1] = "x"; - for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = _SYMBOLS[localValue & 0xf]; - localValue >>= 4; - } - if (localValue != 0) { - revert StringsInsufficientHexLength(value, length); - } - return string(buffer); - } - - /** - * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. - */ - function toHexString(address addr) internal pure returns (string memory) { - return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); - } - - /** - * @dev Returns true if the two strings are equal. - */ - function equal(string memory a, string memory b) internal pure returns (bool) { - return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); - } -} - +import "@openzeppelin/contracts/utils/math/Math.sol"; +import "@openzeppelin/contracts/utils/math/SignedMath.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; + library LenderManagerArt { @@ -853,40 +226,12 @@ function _get_token_amount_formatted( } -function generateSVG( - uint256 bidId, - uint256 principalAmount, - address principalTokenAddress, - uint256 collateralAmount, - address collateralTokenAddress, - uint16 interestRate, - uint32 duration - ) public pure returns (string memory) { +function _buildSvgData ( +) returns (string memory) { - -string memory principal_amount_formatted = _get_token_amount_formatted( - 12540000000000000000, - 18 , - 5 -); - -string memory principal_token_symbol = "USDC"; - - -string memory collateral_amount_formatted = _get_token_amount_formatted( - 2000000000000000000000, - 18 , - 3 -); - -string memory collateral_token_symbol = "WMATIC"; - - - - - string memory svgData = string(abi.encodePacked( + return string(abi.encodePacked( "", @@ -935,6 +280,51 @@ _generate_text_label( "" )); + + +} + + +function generateSVG( + uint256 bidId, + uint256 principalAmount, + address principalTokenAddress, + uint256 collateralAmount, + address collateralTokenAddress, + uint16 interestRate, + uint32 duration + ) public pure returns (string memory) { + + + + + string memory principal_amount_formatted = _get_token_amount_formatted( + 12540000000000000000, + 18 , + 5 + ); + + string memory principal_token_symbol = "USDC"; + + + string memory collateral_amount_formatted = _get_token_amount_formatted( + 2000000000000000000000, + 18 , + 3 + ); + + string memory collateral_token_symbol = "WMATIC"; + + + + + string memory svgData = _buildSvgData( + + + + ) ; + + return svgData; } From d0ce2665c88c09a4dde56dc29e2032a94dc7e1f6 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Wed, 28 Jun 2023 17:52:39 -0400 Subject: [PATCH 05/30] adding duration format --- .../contracts/libraries/LenderManagerArt.sol | 141 ++++++++++++++---- .../contracts/tests/LenderManagerArt_Test.sol | 42 ++++++ .../contracts/tests/LenderManager_Test.sol | 2 +- 3 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 packages/contracts/tests/LenderManagerArt_Test.sol diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index d65b9bdf1..72eaaeddf 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -201,24 +201,77 @@ function _generate_text_label( )); } -/* -Not working quite right yet -*/ + function _get_token_amount_formatted( uint256 amount, uint256 decimals, uint256 precision ) public pure returns (string memory) { - uint256 before_decimal = amount / 10 ** decimals; + + + require(precision <= decimals, "Precision cannot be greater than decimals"); + + uint256 before_decimal = amount / (10 ** decimals); + + uint256 after_decimal = amount % (10 ** decimals); + + // truncate to the required precision + after_decimal = after_decimal / (10 ** (decimals - precision)); + + if(before_decimal >= 1000000000000000){ + return "> RANGE"; + } + + if(before_decimal >= 1000000000000){ + uint256 trillions = before_decimal / 1000000000000; + uint256 billions = (before_decimal % 1000000000000) / 100000000000; // Get the first digit after the decimal point + return string(abi.encodePacked( + Strings.toString(trillions), + ".", + Strings.toString(billions), + "T" + )); + } + + if(before_decimal >= 1000000000){ + uint256 billions = before_decimal / 1000000000; + uint256 millions = (before_decimal % 1000000000) / 100000000; // Get the first digit after the decimal point + return string(abi.encodePacked( + Strings.toString(billions), + ".", + Strings.toString(millions), + "B" + )); + } + + if(before_decimal >= 1000000){ + uint256 millions = before_decimal / 1000000; + uint256 thousands = (before_decimal % 1000000) / 100000; // Get the first digit after the decimal point + return string(abi.encodePacked( + Strings.toString(millions), + ".", + Strings.toString(thousands), + "M" + )); + } + + if(before_decimal >= 1000){ + uint256 fullThousands = before_decimal / 1000; + uint256 remainder = (before_decimal % 1000) / 100; // Get the first digit after the decimal point + return string(abi.encodePacked( + Strings.toString(fullThousands), + ".", + Strings.toString(remainder), + "K" + )); + } - uint256 after_decimal = amount % 10 ** decimals; - return string(abi.encodePacked( + return string(abi.encodePacked( Strings.toString(before_decimal), ".", Strings.toString(after_decimal) - )); @@ -226,10 +279,16 @@ function _get_token_amount_formatted( } -function _buildSvgData ( +function _buildSvgData ( + string memory loanId, + string memory principalAmountFormatted, + string memory principalTokenSymbol, + string memory collateralLabel, + string memory interestRateLabel, + string memory loanDurationLabel -) returns (string memory) { +) internal pure returns (string memory) { return string(abi.encodePacked( @@ -242,8 +301,8 @@ _clip_path_corners, _generate_large_title( - principal_amount_formatted, - principal_token_symbol + principalAmountFormatted, + principalTokenSymbol ), _teller_logo, @@ -251,28 +310,28 @@ _teller_logo, _generate_text_label( "Loan ID:", - Strings.toString(bidId), + loanId, //Strings.toString(bidId), 354 ), _generate_text_label( "Collateral:", - string(abi.encodePacked(collateral_amount_formatted," ",collateral_token_symbol)), + collateralLabel, //string(abi.encodePacked(collateral_amount_formatted," ",collateral_token_symbol)), 384 ), _generate_text_label( "APR:", - "30 %", + interestRateLabel, // "30 %", 414 ), _generate_text_label( "Duration:", - "7 days", + loanDurationLabel, //"7 days", 444 ), @@ -285,6 +344,29 @@ _generate_text_label( } + + function _get_duration_formatted(uint32 sec) internal pure returns (string memory) { + + uint32 _weeks = sec / 60 / 60 / 24 / 7; + uint32 _days = sec / 60 / 60 / 24 % 7; + uint32 _hours = sec / 60 / 60 % 24; + uint32 _minutes = sec / 60 % 60; + sec = sec % 60; + + if (_weeks > 0) { + return string(abi.encodePacked(_weeks.toString(), " weeks ", days.toString(), " days")); + } else if (_days > 0) { + return string(abi.encodePacked(_days.toString(), " days ", hours.toString(), " hours")); + } else if (_hours > 0) { + return string(abi.encodePacked(_hours.toString(), " hours ", minutes.toString(), " minutes")); + } else if (_minutes > 0) { + return string(abi.encodePacked(_minutes.toString(), " minutes ", sec.toString(), " seconds")); + } else { + return string(abi.encodePacked(sec.toString(), " seconds")); + } + } + + function generateSVG( uint256 bidId, uint256 principalAmount, @@ -292,35 +374,40 @@ function generateSVG( uint256 collateralAmount, address collateralTokenAddress, uint16 interestRate, - uint32 duration + uint32 loanDuration ) public pure returns (string memory) { - string memory principal_amount_formatted = _get_token_amount_formatted( - 12540000000000000000, - 18 , - 5 + string memory principalAmountFormatted = _get_token_amount_formatted( + principalAmount, + _get_token_decimals(principalTokenAddress), + 3 ); - string memory principal_token_symbol = "USDC"; + string memory principalTokenSymbol = _get_token_symbol(principalTokenAddress); - string memory collateral_amount_formatted = _get_token_amount_formatted( - 2000000000000000000000, - 18 , + string memory collateralAmountFormatted = _get_token_amount_formatted( + collateralAmount, + _get_token_decimals(collateralTokenAddress), 3 ); - string memory collateral_token_symbol = "WMATIC"; + string memory collateralTokenSymbol = _get_token_symbol(collateralTokenAddress); string memory svgData = _buildSvgData( - - + Strings.toString(bidId), + principalAmountFormatted, + principalTokenSymbol, + string(abi.encodePacked(collateralAmountFormatted," ",collateralTokenSymbol)), + string(abi.encodePacked(_get_interest_rate_formatted(interestRate)," %")), + _get_duration_formatted(loanDuration) + //"7 days" ) ; diff --git a/packages/contracts/tests/LenderManagerArt_Test.sol b/packages/contracts/tests/LenderManagerArt_Test.sol new file mode 100644 index 000000000..f6129d999 --- /dev/null +++ b/packages/contracts/tests/LenderManagerArt_Test.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + + + +import "lib/forge-std/src/console.sol"; + +import { Testable } from "./Testable.sol"; +import { LenderManagerArt } from "../contracts/libraries/LenderManagerArt.sol"; + + + +contract LenderManagerArt_Test is Testable { + + + constructor() {} + + function setUp() public { + + } + + function test_generateSVG() public { + string memory svg = LenderManagerArt.generateSVG( + 22, + 82330000000000420055000, + address(0), + 20000, + address(0), + 300, + 55000 + ); + + + console.log("the svg:"); + console.log(svg); + + } + + + + +} \ No newline at end of file diff --git a/packages/contracts/tests/LenderManager_Test.sol b/packages/contracts/tests/LenderManager_Test.sol index 27713a3e0..761d0d5ce 100644 --- a/packages/contracts/tests/LenderManager_Test.sol +++ b/packages/contracts/tests/LenderManager_Test.sol @@ -88,7 +88,7 @@ contract LenderManager_Test is Testable { string memory baseURI = lenderManager._baseURISuper(); - assertEq(baseURI, "", "Base URI is not correct"); + assertEq(baseURI, "data:image/svg+xml;charset=utf-8,", "Base URI is not correct"); } function test_mint() public { From f8d5e954d945f009029d5f39ea70919b24aba552 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Thu, 29 Jun 2023 10:54:19 -0400 Subject: [PATCH 06/30] improving format --- .../contracts/libraries/LenderManagerArt.sol | 96 +++++++++++++------ 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 72eaaeddf..48f69afd1 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -12,9 +12,23 @@ import "@openzeppelin/contracts/utils/math/SignedMath.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; +//must use this custom interface since the OZ one doesnt support decimals +interface IERC20 { + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + function decimals() external view returns (uint8); + function symbol() external view returns (string memory); +} library LenderManagerArt { + using Strings for uint256; +using Strings for uint32; +using Strings for uint16; bytes constant _bg_defs_filter = abi.encodePacked ( @@ -204,10 +218,9 @@ function _generate_text_label( function _get_token_amount_formatted( uint256 amount, - uint256 decimals, - uint256 precision + uint256 decimals ) public pure returns (string memory) { - + uint256 precision = Math.min(3,decimals); require(precision <= decimals, "Precision cannot be greater than decimals"); @@ -343,28 +356,53 @@ _generate_text_label( } + function _get_token_decimals(address token) internal view returns (uint256) { + if(token.code.length == 0){ + return 0; + } + + try IERC20(token).decimals() returns (uint8 decimals) { + return decimals; + } catch { + return 18; // Fallback to a standard number of decimals if the call fails + } + } + function _get_token_symbol(address token) internal view returns (string memory) { + if(token.code.length == 0){ + return "?"; + } + + try IERC20(token).symbol() returns (string memory symbol) { + return symbol; + } catch { + return "?"; // Fallback to 'Unknown' if the call fails + } + } - function _get_duration_formatted(uint32 sec) internal pure returns (string memory) { - - uint32 _weeks = sec / 60 / 60 / 24 / 7; - uint32 _days = sec / 60 / 60 / 24 % 7; - uint32 _hours = sec / 60 / 60 % 24; - uint32 _minutes = sec / 60 % 60; - sec = sec % 60; +function _get_interest_rate_formatted(uint16 interestRate) internal pure returns (string memory) { + return string(abi.encodePacked( (interestRate / 100).toString(), " %")); +} - if (_weeks > 0) { - return string(abi.encodePacked(_weeks.toString(), " weeks ", days.toString(), " days")); +function _get_duration_formatted(uint32 sec) internal pure returns (string memory) { + uint32 _months = sec / 4 weeks; + uint32 _weeks = sec / 1 weeks; + uint32 _days = sec / 1 days; + uint32 _hours = sec / 1 hours; + uint32 _minutes = sec / 1 minutes; + + if (_months > 0) { + return string(abi.encodePacked(_months.toString(), " months ")); + } else if (_weeks > 0) { + return string(abi.encodePacked(_weeks.toString(), " weeks ")); } else if (_days > 0) { - return string(abi.encodePacked(_days.toString(), " days ", hours.toString(), " hours")); + return string(abi.encodePacked(_days.toString(), " days ")); } else if (_hours > 0) { - return string(abi.encodePacked(_hours.toString(), " hours ", minutes.toString(), " minutes")); - } else if (_minutes > 0) { - return string(abi.encodePacked(_minutes.toString(), " minutes ", sec.toString(), " seconds")); + return string(abi.encodePacked(_hours.toString(), " hours ")); } else { - return string(abi.encodePacked(sec.toString(), " seconds")); + return string(abi.encodePacked(_minutes.toString(), " minutes")); } - } + } function generateSVG( @@ -375,39 +413,37 @@ function generateSVG( address collateralTokenAddress, uint16 interestRate, uint32 loanDuration - ) public pure returns (string memory) { - + ) public view returns (string memory) { + // uint256 principalTokenDecimals = _get_token_decimals(principalTokenAddress); string memory principalAmountFormatted = _get_token_amount_formatted( principalAmount, - _get_token_decimals(principalTokenAddress), - 3 + _get_token_decimals(principalTokenAddress) + // Math.min(3,principalTokenDecimals ) ); string memory principalTokenSymbol = _get_token_symbol(principalTokenAddress); - + // uint256 collateralTokenDecimals = _get_token_decimals(collateralTokenAddress); string memory collateralAmountFormatted = _get_token_amount_formatted( collateralAmount, - _get_token_decimals(collateralTokenAddress), - 3 + _get_token_decimals(collateralTokenAddress) + // Math.min(3,collateralTokenDecimals) ); string memory collateralTokenSymbol = _get_token_symbol(collateralTokenAddress); - string memory svgData = _buildSvgData( - Strings.toString(bidId), + (bidId).toString(), principalAmountFormatted, principalTokenSymbol, string(abi.encodePacked(collateralAmountFormatted," ",collateralTokenSymbol)), - string(abi.encodePacked(_get_interest_rate_formatted(interestRate)," %")), - _get_duration_formatted(loanDuration) - //"7 days" + _get_interest_rate_formatted(interestRate), + _get_duration_formatted(loanDuration) ) ; From 46dc8febe15985eb4f140a79f437c0c4d3e5883b Mon Sep 17 00:00:00 2001 From: Admazzola Date: Thu, 29 Jun 2023 11:36:51 -0400 Subject: [PATCH 07/30] rendering collateral data now --- .../contracts/contracts/LenderManager.sol | 32 ++++++++-- .../contracts/interfaces/ITellerV2Storage.sol | 3 + .../contracts/libraries/LenderManagerArt.sol | 60 ++++++++++++------- .../contracts/tests/LenderManagerArt_Test.sol | 22 ++++++- 4 files changed, 90 insertions(+), 27 deletions(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index f8d08012a..c1ea495d7 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -14,9 +14,11 @@ import "@openzeppelin/contracts/utils/Strings.sol"; import "./interfaces/ILenderManager.sol"; import "./interfaces/ITellerV2.sol"; import "./interfaces/ITellerV2Storage.sol"; +import "./interfaces/ICollateralManager.sol"; import "./interfaces/IMarketRegistry.sol"; import { LenderManagerArt} from "./libraries/LenderManagerArt.sol"; +import {CollateralType,Collateral} from "./interfaces/escrow/ICollateralEscrowV1.sol"; contract LenderManager is Initializable, @@ -118,13 +120,35 @@ contract LenderManager is return loanInformation_; } + + function _getCollateralInformation(uint256 tokenId) + internal view + returns (Collateral memory collateral_) { + + address collateralManager = ITellerV2Storage(owner()).collateralManager(); + + Collateral[] memory collateralArray = ICollateralManager(collateralManager).getCollateralInfo(tokenId); + + if(collateralArray.length == 0) { + return Collateral({ + _amount: 0, + _collateralAddress: address(0), + _collateralType: CollateralType.ERC20, + _tokenId: 0 + + }); + } + + return collateralArray[0]; + } + function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); LoanInformation memory loanInformation = _getLoanInformation(tokenId); + - uint256 collateralAmount = 0; - address collateralTokenAddress = address(0); + Collateral memory collateral = _getCollateralInformation(tokenId); string memory image_svg_encoded = Base64.encode(bytes( @@ -132,8 +156,8 @@ contract LenderManager is tokenId, //tokenId == bidId loanInformation.principalAmount, loanInformation.principalTokenAddress, - collateralAmount, - collateralTokenAddress, + collateral, + //collateralTokenAddress, loanInformation.interestRate, loanInformation.loanDuration ) )); diff --git a/packages/contracts/contracts/interfaces/ITellerV2Storage.sol b/packages/contracts/contracts/interfaces/ITellerV2Storage.sol index 6ec72b705..174539a1c 100644 --- a/packages/contracts/contracts/interfaces/ITellerV2Storage.sol +++ b/packages/contracts/contracts/interfaces/ITellerV2Storage.sol @@ -7,4 +7,7 @@ interface ITellerV2Storage { view returns (Bid memory); + function collateralManager() external view returns (address); + + } \ No newline at end of file diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 48f69afd1..d38c7f7a8 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -10,8 +10,10 @@ pragma solidity ^0.8.4; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/SignedMath.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; - + +import { Collateral, CollateralType } from "../interfaces/escrow/ICollateralEscrowV1.sol"; + //must use this custom interface since the OZ one doesnt support decimals interface IERC20 { function totalSupply() external view returns (uint256); @@ -220,10 +222,10 @@ function _get_token_amount_formatted( uint256 amount, uint256 decimals ) public pure returns (string memory) { - uint256 precision = Math.min(3,decimals); + uint256 precision = Math.min(3,decimals); - require(precision <= decimals, "Precision cannot be greater than decimals"); + //require(precision <= decimals, "Precision cannot be greater than decimals"); uint256 before_decimal = amount / (10 ** decimals); @@ -404,44 +406,62 @@ function _get_duration_formatted(uint32 sec) internal pure returns (string memor } } +function _get_collateral_label(Collateral memory collateral) internal view returns (string memory) { + + if(collateral._collateralAddress == address(0)){ + return "None"; + } + + if(collateral._collateralType == CollateralType.ERC20){ + string memory collateralAmountFormatted = _get_token_amount_formatted( + collateral._amount, + _get_token_decimals(collateral._collateralAddress) + + ); + + string memory collateralTokenSymbol = _get_token_symbol(collateral._collateralAddress); + + return string(abi.encodePacked(collateralAmountFormatted," ",collateralTokenSymbol)); + } + + if(collateral._collateralType == CollateralType.ERC721){ + return "ERC721"; + } + + if(collateral._collateralType == CollateralType.ERC1155){ + return "ERC1155"; + } + + + +} function generateSVG( uint256 bidId, uint256 principalAmount, address principalTokenAddress, - uint256 collateralAmount, - address collateralTokenAddress, + Collateral memory collateral, uint16 interestRate, uint32 loanDuration ) public view returns (string memory) { - - - // uint256 principalTokenDecimals = _get_token_decimals(principalTokenAddress); + string memory principalAmountFormatted = _get_token_amount_formatted( principalAmount, _get_token_decimals(principalTokenAddress) - // Math.min(3,principalTokenDecimals ) + ); string memory principalTokenSymbol = _get_token_symbol(principalTokenAddress); - // uint256 collateralTokenDecimals = _get_token_decimals(collateralTokenAddress); - string memory collateralAmountFormatted = _get_token_amount_formatted( - collateralAmount, - _get_token_decimals(collateralTokenAddress) - // Math.min(3,collateralTokenDecimals) - ); - - string memory collateralTokenSymbol = _get_token_symbol(collateralTokenAddress); - - + string memory svgData = _buildSvgData( (bidId).toString(), principalAmountFormatted, principalTokenSymbol, - string(abi.encodePacked(collateralAmountFormatted," ",collateralTokenSymbol)), + _get_collateral_label(collateral), + _get_interest_rate_formatted(interestRate), _get_duration_formatted(loanDuration) diff --git a/packages/contracts/tests/LenderManagerArt_Test.sol b/packages/contracts/tests/LenderManagerArt_Test.sol index f6129d999..4aa86f755 100644 --- a/packages/contracts/tests/LenderManagerArt_Test.sol +++ b/packages/contracts/tests/LenderManagerArt_Test.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import {CollateralType,Collateral} from "../contracts/interfaces/escrow/ICollateralEscrowV1.sol"; import "lib/forge-std/src/console.sol"; @@ -9,23 +10,38 @@ import { Testable } from "./Testable.sol"; import { LenderManagerArt } from "../contracts/libraries/LenderManagerArt.sol"; +import "../contracts/mock/WethMock.sol"; contract LenderManagerArt_Test is Testable { + WethMock wethMock; constructor() {} function setUp() public { + wethMock = new WethMock(); + } function test_generateSVG() public { + + + + Collateral memory _collateral = Collateral({ + _collateralType: CollateralType.ERC20, + _collateralAddress: address(wethMock), + _amount: 20000, + _tokenId : 0 + }); + string memory svg = LenderManagerArt.generateSVG( 22, 82330000000000420055000, - address(0), - 20000, - address(0), + address(wethMock), + // 20000, + // address(0), + _collateral, 300, 55000 ); From d3af4e7e252b11737f01388cfc8997bc8166443c Mon Sep 17 00:00:00 2001 From: Admazzola Date: Thu, 29 Jun 2023 16:04:39 -0400 Subject: [PATCH 08/30] update art --- .../contracts/libraries/LenderManagerArt.sol | 79 +++++++++++-------- .../contracts/tests/LenderManagerArt_Test.sol | 24 +++--- 2 files changed, 58 insertions(+), 45 deletions(-) diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index d38c7f7a8..66436e31e 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -6,6 +6,7 @@ pragma solidity ^0.8.4; +import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "@openzeppelin/contracts/utils/math/SignedMath.sol"; @@ -28,9 +29,10 @@ interface IERC20 { library LenderManagerArt { - using Strings for uint256; +using Strings for uint256; using Strings for uint32; using Strings for uint16; +using Address for address; bytes constant _bg_defs_filter = abi.encodePacked ( @@ -191,9 +193,7 @@ function _generate_large_title( "",amount," ",symbol,"", "", "" - - )); } @@ -223,10 +223,7 @@ function _get_token_amount_formatted( uint256 decimals ) public pure returns (string memory) { uint256 precision = Math.min(3,decimals); - - - //require(precision <= decimals, "Precision cannot be greater than decimals"); - + uint256 before_decimal = amount / (10 ** decimals); uint256 after_decimal = amount % (10 ** decimals); @@ -324,28 +321,28 @@ _teller_logo, _generate_text_label( - "Loan ID:", + "Loan ID: ", loanId, //Strings.toString(bidId), 354 ), _generate_text_label( - "Collateral:", + "Collateral: ", collateralLabel, //string(abi.encodePacked(collateral_amount_formatted," ",collateral_token_symbol)), 384 ), _generate_text_label( - "APR:", + "APR: ", interestRateLabel, // "30 %", 414 ), _generate_text_label( - "Duration:", + "Duration: ", loanDurationLabel, //"7 days", 444 ), @@ -359,29 +356,24 @@ _generate_text_label( } function _get_token_decimals(address token) internal view returns (uint256) { - if(token.code.length == 0){ + + + bytes memory data = abi.encodeWithSignature("decimals()"); + + (bool success, bytes memory result) = token.staticcall(data); + + if (!success) { return 0; } - try IERC20(token).decimals() returns (uint8 decimals) { - return decimals; - } catch { - return 18; // Fallback to a standard number of decimals if the call fails - } - } + // Decode the result + uint8 decimals = abi.decode(result, (uint8)); - function _get_token_symbol(address token) internal view returns (string memory) { - if(token.code.length == 0){ - return "?"; - } - - try IERC20(token).symbol() returns (string memory symbol) { - return symbol; - } catch { - return "?"; // Fallback to 'Unknown' if the call fails - } + return decimals; } + + function _get_interest_rate_formatted(uint16 interestRate) internal pure returns (string memory) { return string(abi.encodePacked( (interestRate / 100).toString(), " %")); } @@ -406,6 +398,24 @@ function _get_duration_formatted(uint32 sec) internal pure returns (string memor } } + + function _get_token_symbol(address nftContract, string memory _fallback) internal view returns (string memory) { + bytes memory data = abi.encodeWithSignature("symbol()"); + + (bool success, bytes memory result) = nftContract.staticcall(data); + + if (!success) { + return _fallback ; + } + + // Decode the result from bytes to string + string memory symbol = abi.decode(result, (string)); + + return symbol; + +} + + function _get_collateral_label(Collateral memory collateral) internal view returns (string memory) { if(collateral._collateralAddress == address(0)){ @@ -419,17 +429,19 @@ function _get_collateral_label(Collateral memory collateral) internal view retur ); - string memory collateralTokenSymbol = _get_token_symbol(collateral._collateralAddress); + string memory collateralTokenSymbol = _get_token_symbol(collateral._collateralAddress,"?"); return string(abi.encodePacked(collateralAmountFormatted," ",collateralTokenSymbol)); } - if(collateral._collateralType == CollateralType.ERC721){ - return "ERC721"; + if(collateral._collateralType == CollateralType.ERC721){ + + return _get_token_symbol(collateral._collateralAddress, "ERC721"); } if(collateral._collateralType == CollateralType.ERC1155){ - return "ERC1155"; + + return _get_token_symbol(collateral._collateralAddress, "ERC1155"); } @@ -452,9 +464,8 @@ function generateSVG( ); - string memory principalTokenSymbol = _get_token_symbol(principalTokenAddress); + string memory principalTokenSymbol = _get_token_symbol(principalTokenAddress, "?"); - string memory svgData = _buildSvgData( (bidId).toString(), diff --git a/packages/contracts/tests/LenderManagerArt_Test.sol b/packages/contracts/tests/LenderManagerArt_Test.sol index 4aa86f755..afb47bd8e 100644 --- a/packages/contracts/tests/LenderManagerArt_Test.sol +++ b/packages/contracts/tests/LenderManagerArt_Test.sol @@ -8,20 +8,24 @@ import "lib/forge-std/src/console.sol"; import { Testable } from "./Testable.sol"; import { LenderManagerArt } from "../contracts/libraries/LenderManagerArt.sol"; - - + +import "./tokens/TestERC1155Token.sol"; +import "./tokens/TestERC721Token.sol"; import "../contracts/mock/WethMock.sol"; contract LenderManagerArt_Test is Testable { WethMock wethMock; + TestERC721Token erc721Mock; + TestERC1155Token erc1155Mock; constructor() {} function setUp() public { wethMock = new WethMock(); - + erc721Mock = new TestERC721Token("BAYC", "BAYC"); + erc1155Mock = new TestERC1155Token("SAND"); } function test_generateSVG() public { @@ -29,21 +33,19 @@ contract LenderManagerArt_Test is Testable { Collateral memory _collateral = Collateral({ - _collateralType: CollateralType.ERC20, - _collateralAddress: address(wethMock), - _amount: 20000, - _tokenId : 0 + _collateralType: CollateralType.ERC721, + _collateralAddress: address(erc721Mock), + _amount: 1, + _tokenId : 150 }); string memory svg = LenderManagerArt.generateSVG( 22, 82330000000000420055000, - address(wethMock), - // 20000, - // address(0), + address(wethMock), _collateral, 300, - 55000 + 550000 ); From 02a43450c2d1898b6dc35ff3ead2835180be6580 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Thu, 29 Jun 2023 16:29:21 -0400 Subject: [PATCH 09/30] comment --- packages/contracts/tests/LenderManagerArt_Test.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/contracts/tests/LenderManagerArt_Test.sol b/packages/contracts/tests/LenderManagerArt_Test.sol index afb47bd8e..4e904afc8 100644 --- a/packages/contracts/tests/LenderManagerArt_Test.sol +++ b/packages/contracts/tests/LenderManagerArt_Test.sol @@ -55,6 +55,10 @@ contract LenderManagerArt_Test is Testable { } + //add more unit tests here + + + } \ No newline at end of file From ef52f9713e7cc505933991e99c8f98bea1944ec7 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Thu, 29 Jun 2023 16:57:41 -0400 Subject: [PATCH 10/30] adding tests --- .../contracts/libraries/LenderManagerArt.sol | 36 +++++++++---------- .../tests/LenderManagerArt_Override.sol | 21 +++++++++++ .../contracts/tests/LenderManagerArt_Test.sol | 23 ++++++++++-- 3 files changed, 60 insertions(+), 20 deletions(-) create mode 100644 packages/contracts/tests/LenderManagerArt_Override.sol diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 66436e31e..6d6b632d0 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -349,36 +349,36 @@ _generate_text_label( "" - )); +)); -} - function _get_token_decimals(address token) internal view returns (uint256) { - +} - bytes memory data = abi.encodeWithSignature("decimals()"); - - (bool success, bytes memory result) = token.staticcall(data); +function _get_token_decimals(address token) public view returns (uint256) { + + bytes memory data = abi.encodeWithSignature("decimals()"); + + (bool success, bytes memory result) = token.staticcall(data); - if (!success) { - return 0; - } + if (!success) { + return 0; + } - // Decode the result - uint8 decimals = abi.decode(result, (uint8)); + // Decode the result + uint8 decimals = abi.decode(result, (uint8)); - return decimals; - } + return decimals; +} -function _get_interest_rate_formatted(uint16 interestRate) internal pure returns (string memory) { +function _get_interest_rate_formatted(uint16 interestRate) public pure returns (string memory) { return string(abi.encodePacked( (interestRate / 100).toString(), " %")); } -function _get_duration_formatted(uint32 sec) internal pure returns (string memory) { +function _get_duration_formatted(uint32 sec) public pure returns (string memory) { uint32 _months = sec / 4 weeks; uint32 _weeks = sec / 1 weeks; uint32 _days = sec / 1 days; @@ -399,7 +399,7 @@ function _get_duration_formatted(uint32 sec) internal pure returns (string memor } - function _get_token_symbol(address nftContract, string memory _fallback) internal view returns (string memory) { + function _get_token_symbol(address nftContract, string memory _fallback) public view returns (string memory) { bytes memory data = abi.encodeWithSignature("symbol()"); (bool success, bytes memory result) = nftContract.staticcall(data); @@ -416,7 +416,7 @@ function _get_duration_formatted(uint32 sec) internal pure returns (string memor } -function _get_collateral_label(Collateral memory collateral) internal view returns (string memory) { +function _get_collateral_label(Collateral memory collateral) public view returns (string memory) { if(collateral._collateralAddress == address(0)){ return "None"; diff --git a/packages/contracts/tests/LenderManagerArt_Override.sol b/packages/contracts/tests/LenderManagerArt_Override.sol new file mode 100644 index 000000000..cfeea8729 --- /dev/null +++ b/packages/contracts/tests/LenderManagerArt_Override.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {CollateralType,Collateral} from "../contracts/interfaces/escrow/ICollateralEscrowV1.sol"; + + +import "lib/forge-std/src/console.sol"; + +import { Testable } from "./Testable.sol"; +import { LenderManagerArt } from "../contracts/libraries/LenderManagerArt.sol"; + + +contract LenderManagerArt_Override { + + + + + + + +} \ No newline at end of file diff --git a/packages/contracts/tests/LenderManagerArt_Test.sol b/packages/contracts/tests/LenderManagerArt_Test.sol index 4e904afc8..5cd006a9a 100644 --- a/packages/contracts/tests/LenderManagerArt_Test.sol +++ b/packages/contracts/tests/LenderManagerArt_Test.sol @@ -8,7 +8,7 @@ import "lib/forge-std/src/console.sol"; import { Testable } from "./Testable.sol"; import { LenderManagerArt } from "../contracts/libraries/LenderManagerArt.sol"; - + import "./tokens/TestERC1155Token.sol"; import "./tokens/TestERC721Token.sol"; import "../contracts/mock/WethMock.sol"; @@ -56,7 +56,26 @@ contract LenderManagerArt_Test is Testable { //add more unit tests here - + function test_get_token_decimals() public { + + + uint256 decimals = LenderManagerArt._get_token_decimals(address(wethMock)); + + + assertEq(decimals, 18); + + + } + + function test_get_token_decimals_erc721() public { + + uint256 decimals = LenderManagerArt._get_token_decimals(address(erc721Mock)); + + + assertEq(decimals, 0); + + } + From ac87d9d5d203ce0519642a9904f94c52ceaadb50 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Thu, 29 Jun 2023 17:12:37 -0400 Subject: [PATCH 11/30] adding tests --- .../contracts/libraries/LenderManagerArt.sol | 24 ++++++--- .../contracts/tests/LenderManagerArt_Test.sol | 52 +++++++++++++++++++ 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 6d6b632d0..953382239 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -359,6 +359,10 @@ _generate_text_label( function _get_token_decimals(address token) public view returns (uint256) { bytes memory data = abi.encodeWithSignature("decimals()"); + + if(token.code.length == 0){ + return 0; + } (bool success, bytes memory result) = token.staticcall(data); @@ -385,14 +389,14 @@ function _get_duration_formatted(uint32 sec) public pure returns (string memory) uint32 _hours = sec / 1 hours; uint32 _minutes = sec / 1 minutes; - if (_months > 0) { - return string(abi.encodePacked(_months.toString(), " months ")); - } else if (_weeks > 0) { - return string(abi.encodePacked(_weeks.toString(), " weeks ")); - } else if (_days > 0) { - return string(abi.encodePacked(_days.toString(), " days ")); - } else if (_hours > 0) { - return string(abi.encodePacked(_hours.toString(), " hours ")); + if (_months >= 2) { + return string(abi.encodePacked(_months.toString(), " months")); + } else if (_weeks >= 2) { + return string(abi.encodePacked(_weeks.toString(), " weeks")); + } else if (_days >= 2) { + return string(abi.encodePacked(_days.toString(), " days")); + } else if (_hours >= 2) { + return string(abi.encodePacked(_hours.toString(), " hours")); } else { return string(abi.encodePacked(_minutes.toString(), " minutes")); } @@ -401,6 +405,10 @@ function _get_duration_formatted(uint32 sec) public pure returns (string memory) function _get_token_symbol(address nftContract, string memory _fallback) public view returns (string memory) { bytes memory data = abi.encodeWithSignature("symbol()"); + + if(nftContract.code.length == 0){ + return _fallback; + } (bool success, bytes memory result) = nftContract.staticcall(data); diff --git a/packages/contracts/tests/LenderManagerArt_Test.sol b/packages/contracts/tests/LenderManagerArt_Test.sol index 5cd006a9a..ec823a232 100644 --- a/packages/contracts/tests/LenderManagerArt_Test.sol +++ b/packages/contracts/tests/LenderManagerArt_Test.sol @@ -76,8 +76,60 @@ contract LenderManagerArt_Test is Testable { } + function test_get_interest_rate_formatted() public { + string memory interestRate = LenderManagerArt._get_interest_rate_formatted(30000); + + assertEq(interestRate, "300 %"); + + } + + function test_get_duration_formatted() public { + + string memory duration = LenderManagerArt._get_duration_formatted(30000); + + assertEq(duration, "8 hours"); + + + } + + function test_get_duration_formatted_2() public { + + string memory duration = LenderManagerArt._get_duration_formatted(3000000); + + assertEq(duration, "4 weeks"); + + + } + + + function test_get_token_symbol() public { + string memory symbol = LenderManagerArt._get_token_symbol(address(wethMock),"fallback"); + + assertEq(symbol, "WETH"); + } + + function test_get_token_symbol_fallback() public { + string memory symbol = LenderManagerArt._get_token_symbol(address(0),"fallback"); + + assertEq(symbol, "fallback"); + } + function test_get_collateral_label() public { + + + Collateral memory _collateral = Collateral({ + _collateralType: CollateralType.ERC721, + _collateralAddress: address(erc721Mock), + _amount: 1, + _tokenId : 150 + }); + + string memory label = LenderManagerArt._get_collateral_label(_collateral); + + assertEq(label, "BAYC"); + + } } \ No newline at end of file From 34052c65bc08af58e718f91e53677a6fd2d7724a Mon Sep 17 00:00:00 2001 From: andy Date: Thu, 6 Jul 2023 10:09:48 -0400 Subject: [PATCH 12/30] adding deploy script --- .../contracts/contracts/LenderManager.sol | 7 +- .../deploy/upgrades/02_lender_manager_art.ts | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 packages/contracts/deploy/upgrades/02_lender_manager_art.ts diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index c1ea495d7..1a95a85a5 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -17,7 +17,7 @@ import "./interfaces/ITellerV2Storage.sol"; import "./interfaces/ICollateralManager.sol"; import "./interfaces/IMarketRegistry.sol"; -import { LenderManagerArt} from "./libraries/LenderManagerArt.sol"; +import {LenderManagerArt} from "./libraries/LenderManagerArt.sol"; import {CollateralType,Collateral} from "./interfaces/escrow/ICollateralEscrowV1.sol"; contract LenderManager is @@ -157,14 +157,13 @@ contract LenderManager is loanInformation.principalAmount, loanInformation.principalTokenAddress, collateral, - //collateralTokenAddress, loanInformation.interestRate, loanInformation.loanDuration ) )); string memory name = "Teller Loan NFT"; - string memory description = "This token represents ownership of a loan. Repayments of principal and interest will be sent to the owner of this token. If the loan defaults, the owner of this token will be able to claim the underlying collateral."; + string memory description = "This token represents ownership of a loan. Repayments of principal and interest will be sent to the owner of this token. If the loan defaults, the owner of this token will be able to claim the underlying collateral. Please externally verify the parameter of the loan as this rendering is only a summary."; @@ -178,7 +177,7 @@ contract LenderManager is name, '", "description":"', description, - '", "image": "', //use image_data so its a dynamic svg not cached ? + '", "image": "', 'data:image/svg+xml;base64,', image_svg_encoded, '"}' diff --git a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts new file mode 100644 index 000000000..342f5ed93 --- /dev/null +++ b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts @@ -0,0 +1,64 @@ +import { DeployFunction } from 'hardhat-deploy/dist/types' + +const deployFn: DeployFunction = async (hre) => { + hre.log('----------') + hre.log('') + hre.log('Lender Manager: Proposing upgrade...') + + const marketRegistry = await hre.contracts.get('MarketRegistry') + + const lenderManager = await hre.contracts.get('LenderManager') + const lenderManagerArt = await hre.contracts.get('lenderManagerArt') + + await hre.defender.proposeBatchTimelock( + 'Lender Manager: Art Upgrade', + ` +# Lender Manager + +* Updates the tokenURI function so it returns an svg image rendering with loan summary data. + +`, + [ + //how to deploy and link the lib ? + { + deploy: lenderManagerArt + + }, + + { + proxy: lenderManager.address, + implFactory: await hre.ethers.getContractFactory('LenderManager'), + + opts: { + unsafeAllow: ['constructor', 'state-variable-immutable'], + constructorArgs: [marketRegistry.address], + }, + }, + + ] + ) + + hre.log('done.') + hre.log('') + hre.log('----------') + + return true +} + +// tags and deployment +deployFn.id = 'lender-manager:upgrade-art' +deployFn.tags = [ + 'proposal', + 'upgrade', + 'lender-manager', + 'lender-manager:upgrade-art', +] +deployFn.dependencies = [ + 'market-registry:deploy', + 'teller-v2:deploy', +] +deployFn.skip = async (hre) => { + return false + +} +export default deployFn From 1b267acf1da3f88d4fadc3289eb133978f1ac0e9 Mon Sep 17 00:00:00 2001 From: andy Date: Thu, 6 Jul 2023 12:39:10 -0400 Subject: [PATCH 13/30] added library to script --- .../contracts/deploy/lender_manager/deploy.ts | 7 ++++++- .../deploy/lender_manager/lender_manager_art.ts | 15 +++++++++++++++ .../deploy/upgrades/02_lender_manager_art.ts | 16 ++++++++-------- 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 packages/contracts/deploy/lender_manager/lender_manager_art.ts diff --git a/packages/contracts/deploy/lender_manager/deploy.ts b/packages/contracts/deploy/lender_manager/deploy.ts index 7744f572e..0a9fe9d75 100644 --- a/packages/contracts/deploy/lender_manager/deploy.ts +++ b/packages/contracts/deploy/lender_manager/deploy.ts @@ -3,9 +3,14 @@ import { DeployFunction } from 'hardhat-deploy/dist/types' const deployFn: DeployFunction = async (hre) => { const marketRegistry = await hre.contracts.get('MarketRegistry') + const lenderManagerArt = await hre.contracts.get('LenderManagerArt') + const lenderManager = await hre.deployProxy('LenderManager', { constructorArgs: [marketRegistry.address], unsafeAllow: ['constructor', 'state-variable-immutable'], + libraries: { + LenderManagerArt: lenderManagerArt.address, + }, }) return true @@ -14,5 +19,5 @@ const deployFn: DeployFunction = async (hre) => { // tags and deployment deployFn.id = 'lender-manager:deploy' deployFn.tags = ['lender-manager', 'lender-manager:deploy'] -deployFn.dependencies = ['market-registry:deploy'] +deployFn.dependencies = ['market-registry:deploy','lender-manager:lender-manager-art'] export default deployFn diff --git a/packages/contracts/deploy/lender_manager/lender_manager_art.ts b/packages/contracts/deploy/lender_manager/lender_manager_art.ts new file mode 100644 index 000000000..68de96202 --- /dev/null +++ b/packages/contracts/deploy/lender_manager/lender_manager_art.ts @@ -0,0 +1,15 @@ +import { DeployFunction } from 'hardhat-deploy/dist/types' +import { deploy } from 'helpers/deploy-helpers' + +const deployFn: DeployFunction = async (hre) => { + const lenderManagerArt = await deploy({ + contract: 'LenderManagerArt', + hre, + }) +} + +// tags and deployment +deployFn.id = 'lender-manager:lender-manager-art' +deployFn.tags = ['lender-manager', 'lender-manager:lender-manager-art'] +deployFn.dependencies = [''] +export default deployFn diff --git a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts index 342f5ed93..9bf0c8eb3 100644 --- a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts +++ b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts @@ -19,20 +19,20 @@ const deployFn: DeployFunction = async (hre) => { `, [ - //how to deploy and link the lib ? - { - deploy: lenderManagerArt - - }, { proxy: lenderManager.address, - implFactory: await hre.ethers.getContractFactory('LenderManager'), - + implFactory: await hre.ethers.getContractFactory('LenderManager', { + libraries: { + LenderManagerArt: lenderManagerArt.address, + }, + }), opts: { unsafeAllow: ['constructor', 'state-variable-immutable'], constructorArgs: [marketRegistry.address], + }, + }, ] @@ -55,7 +55,7 @@ deployFn.tags = [ ] deployFn.dependencies = [ 'market-registry:deploy', - 'teller-v2:deploy', + 'lender-manager:deploy', ] deployFn.skip = async (hre) => { return false From 6d41b4d06506770f314e150b2030e849b29be37e Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Thu, 6 Jul 2023 12:53:04 -0400 Subject: [PATCH 14/30] Update 02_lender_manager_art.ts --- packages/contracts/deploy/upgrades/02_lender_manager_art.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts index 9bf0c8eb3..a7d43f0c6 100644 --- a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts +++ b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts @@ -8,7 +8,7 @@ const deployFn: DeployFunction = async (hre) => { const marketRegistry = await hre.contracts.get('MarketRegistry') const lenderManager = await hre.contracts.get('LenderManager') - const lenderManagerArt = await hre.contracts.get('lenderManagerArt') + const lenderManagerArt = await hre.contracts.get('LenderManagerArt') await hre.defender.proposeBatchTimelock( 'Lender Manager: Art Upgrade', @@ -28,7 +28,7 @@ const deployFn: DeployFunction = async (hre) => { }, }), opts: { - unsafeAllow: ['constructor', 'state-variable-immutable'], + unsafeAllow: ['constructor', 'state-variable-immutable','external-library-linking',], constructorArgs: [marketRegistry.address], }, From 4ba87016726b3ab57a7bd9d18311f1a81f423736 Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Thu, 6 Jul 2023 12:57:54 -0400 Subject: [PATCH 15/30] Update .migrations.json --- packages/contracts/deployments/goerli/.migrations.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/contracts/deployments/goerli/.migrations.json b/packages/contracts/deployments/goerli/.migrations.json index 90f2d6e6f..9eccf6e1e 100644 --- a/packages/contracts/deployments/goerli/.migrations.json +++ b/packages/contracts/deployments/goerli/.migrations.json @@ -13,5 +13,6 @@ "escrow-vault:deploy": 1688578801, "default-proxy-admin:transfer": 1688587184, "sherlock-audit:upgrade": 1688587576, - "teller-v2:transfer-ownership-to-proxy-admin": 1688588967 -} \ No newline at end of file + "teller-v2:transfer-ownership-to-proxy-admin": 1688588967, + "lender-manager:upgrade-art": 1688662351 +} From 43b38f89710f128d1ff8713b790300ae43239392 Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Thu, 6 Jul 2023 13:27:31 -0400 Subject: [PATCH 16/30] Update goerli.json --- packages/contracts/.openzeppelin/goerli.json | 408 +++++++++++++++++++ 1 file changed, 408 insertions(+) diff --git a/packages/contracts/.openzeppelin/goerli.json b/packages/contracts/.openzeppelin/goerli.json index e205d8bd0..5ebc19f4a 100644 --- a/packages/contracts/.openzeppelin/goerli.json +++ b/packages/contracts/.openzeppelin/goerli.json @@ -3797,6 +3797,414 @@ } } } + }, + "11e87680e61edb14d5bb459b9b20812a37069ffc0e756eae131a73cdc5ddbe6d": { + "address": "0xED4344702453248eEaf936dc9Fb11650C4157a11", + "txHash": "0x37d20dd0a56215eef6c70eaaf60499c31da20a9312d6528d5e75d7040ae8ee81", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "TellerV2MarketForwarder", + "src": "contracts/TellerV2MarketForwarder.sol:152" + }, + { + "label": "commitments", + "offset": 0, + "slot": "101", + "type": "t_mapping(t_uint256,t_struct(Commitment)11985_storage)", + "contract": "LenderCommitmentForwarder", + "src": "contracts/LenderCommitmentForwarder.sol:60" + }, + { + "label": "commitmentCount", + "offset": 0, + "slot": "102", + "type": "t_uint256", + "contract": "LenderCommitmentForwarder", + "src": "contracts/LenderCommitmentForwarder.sol:62" + }, + { + "label": "commitmentBorrowersList", + "offset": 0, + "slot": "103", + "type": "t_mapping(t_uint256,t_struct(AddressSet)4033_storage)", + "contract": "LenderCommitmentForwarder", + "src": "contracts/LenderCommitmentForwarder.sol:65" + }, + { + "label": "commitmentPrincipalAccepted", + "offset": 0, + "slot": "104", + "type": "t_mapping(t_uint256,t_uint256)", + "contract": "LenderCommitmentForwarder", + "src": "contracts/LenderCommitmentForwarder.sol:68" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]", + "numberOfBytes": "32" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_enum(CommitmentCollateralType)11961": { + "label": "enum LenderCommitmentForwarder.CommitmentCollateralType", + "members": [ + "NONE", + "ERC20", + "ERC721", + "ERC1155", + "ERC721_ANY_ID", + "ERC1155_ANY_ID", + "ERC721_MERKLE_PROOF", + "ERC1155_MERKLE_PROOF" + ], + "numberOfBytes": "1" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(AddressSet)4033_storage)": { + "label": "mapping(uint256 => struct EnumerableSetUpgradeable.AddressSet)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_struct(Commitment)11985_storage)": { + "label": "mapping(uint256 => struct LenderCommitmentForwarder.Commitment)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)", + "numberOfBytes": "32" + }, + "t_struct(AddressSet)4033_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)3718_storage", + "offset": 0, + "slot": "0" + } + ], + "numberOfBytes": "64" + }, + "t_struct(Commitment)11985_storage": { + "label": "struct LenderCommitmentForwarder.Commitment", + "members": [ + { + "label": "maxPrincipal", + "type": "t_uint256", + "offset": 0, + "slot": "0" + }, + { + "label": "expiration", + "type": "t_uint32", + "offset": 0, + "slot": "1" + }, + { + "label": "maxDuration", + "type": "t_uint32", + "offset": 4, + "slot": "1" + }, + { + "label": "minInterestRate", + "type": "t_uint16", + "offset": 8, + "slot": "1" + }, + { + "label": "collateralTokenAddress", + "type": "t_address", + "offset": 10, + "slot": "1" + }, + { + "label": "collateralTokenId", + "type": "t_uint256", + "offset": 0, + "slot": "2" + }, + { + "label": "maxPrincipalPerCollateralAmount", + "type": "t_uint256", + "offset": 0, + "slot": "3" + }, + { + "label": "collateralTokenType", + "type": "t_enum(CommitmentCollateralType)11961", + "offset": 0, + "slot": "4" + }, + { + "label": "lender", + "type": "t_address", + "offset": 1, + "slot": "4" + }, + { + "label": "marketId", + "type": "t_uint256", + "offset": 0, + "slot": "5" + }, + { + "label": "principalTokenAddress", + "type": "t_address", + "offset": 0, + "slot": "6" + } + ], + "numberOfBytes": "224" + }, + "t_struct(Set)3718_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage", + "offset": 0, + "slot": "0" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)", + "offset": 0, + "slot": "1" + } + ], + "numberOfBytes": "64" + }, + "t_uint16": { + "label": "uint16", + "numberOfBytes": "2" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "f7e687d03348b5404e02b20629f7fc8f48e86b0f51f9b7aa9b23ddbaa83efc01": { + "address": "0x62babFC668494145051a473112De8D3e93d3927E", + "txHash": "0x1de99cc00cf5eae3fb3b169ab9c6eb29a19dc827b951e1b3e7103509572f04f9", + "layout": { + "solcVersion": "0.8.9", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "51", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "52", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC165Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41" + }, + { + "label": "_name", + "offset": 0, + "slot": "151", + "type": "t_string_storage", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:25" + }, + { + "label": "_symbol", + "offset": 0, + "slot": "152", + "type": "t_string_storage", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:28" + }, + { + "label": "_owners", + "offset": 0, + "slot": "153", + "type": "t_mapping(t_uint256,t_address)", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:31" + }, + { + "label": "_balances", + "offset": 0, + "slot": "154", + "type": "t_mapping(t_address,t_uint256)", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:34" + }, + { + "label": "_tokenApprovals", + "offset": 0, + "slot": "155", + "type": "t_mapping(t_uint256,t_address)", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:37" + }, + { + "label": "_operatorApprovals", + "offset": 0, + "slot": "156", + "type": "t_mapping(t_address,t_mapping(t_address,t_bool))", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:40" + }, + { + "label": "__gap", + "offset": 0, + "slot": "157", + "type": "t_array(t_uint256)44_storage", + "contract": "ERC721Upgradeable", + "src": "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol:514" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]", + "numberOfBytes": "1408" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_mapping(t_address,t_bool))": { + "label": "mapping(address => mapping(address => bool))", + "numberOfBytes": "32" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_address)": { + "label": "mapping(uint256 => address)", + "numberOfBytes": "32" + }, + "t_string_storage": { + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } } } } From 08bc50f471a89e5f800c665caff0ca52cbc73265 Mon Sep 17 00:00:00 2001 From: andy Date: Thu, 6 Jul 2023 16:38:52 -0400 Subject: [PATCH 17/30] fix get loan information --- packages/contracts/contracts/LenderManager.sol | 8 +++----- packages/contracts/tests/LenderManager_Test.sol | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index 1a95a85a5..9f83bd3d7 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -109,15 +109,13 @@ contract LenderManager is Bid memory bid = ITellerV2Storage(owner()).bids(tokenId); - LoanInformation memory loanInformation = LoanInformation({ + loanInformation_ = LoanInformation({ principalTokenAddress: address(bid.loanDetails.lendingToken), principalAmount: bid.loanDetails.principal, interestRate: bid.terms.APR, loanDuration: bid.loanDetails.loanDuration - }); - - - return loanInformation_; + }); + } diff --git a/packages/contracts/tests/LenderManager_Test.sol b/packages/contracts/tests/LenderManager_Test.sol index 761d0d5ce..ba23ec16d 100644 --- a/packages/contracts/tests/LenderManager_Test.sol +++ b/packages/contracts/tests/LenderManager_Test.sol @@ -67,6 +67,7 @@ contract LenderManager_Test is Testable { "Market id is not correct" ); } + function test_hasMarketVerification() public { lenderManager.initialize(); From fab0021c6d62ce50126037c6f5867d653143c137 Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 10 Jul 2023 15:17:31 -0400 Subject: [PATCH 18/30] format --- .../contracts/contracts/LenderManager.sol | 161 ++-- packages/contracts/contracts/TellerV2.sol | 2 +- .../contracts/interfaces/ITellerV2.sol | 2 - .../contracts/interfaces/ITellerV2Storage.sol | 12 +- .../contracts/libraries/LenderManagerArt.sol | 762 +++++++++--------- .../contracts/deploy/lender_manager/deploy.ts | 5 +- .../deploy/upgrades/02_lender_manager_art.ts | 26 +- .../tests/LenderManagerArt_Override.sol | 14 +- .../contracts/tests/LenderManagerArt_Test.sol | 96 +-- .../contracts/tests/LenderManager_Test.sol | 7 +- 10 files changed, 518 insertions(+), 569 deletions(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index 9f83bd3d7..a73be91b9 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -9,7 +9,6 @@ import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts/utils/Base64.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; - // Interfaces import "./interfaces/ILenderManager.sol"; import "./interfaces/ITellerV2.sol"; @@ -17,8 +16,8 @@ import "./interfaces/ITellerV2Storage.sol"; import "./interfaces/ICollateralManager.sol"; import "./interfaces/IMarketRegistry.sol"; -import {LenderManagerArt} from "./libraries/LenderManagerArt.sol"; -import {CollateralType,Collateral} from "./interfaces/escrow/ICollateralEscrowV1.sol"; +import { LenderManagerArt } from "./libraries/LenderManagerArt.sol"; +import { CollateralType, Collateral } from "./interfaces/escrow/ICollateralEscrowV1.sol"; contract LenderManager is Initializable, @@ -26,7 +25,7 @@ contract LenderManager is ERC721Upgradeable, ILenderManager { - // using Strings for uint256; + // using Strings for uint256; IMarketRegistry public immutable marketRegistry; constructor(IMarketRegistry _marketRegistry) { @@ -93,104 +92,104 @@ contract LenderManager is } struct LoanInformation { - address principalTokenAddress; uint256 principalAmount; uint16 interestRate; uint32 loanDuration; - - } - - function _getLoanInformation(uint256 tokenId) - internal view - returns (LoanInformation memory loanInformation_) { - + function _getLoanInformation(uint256 tokenId) + internal + view + returns (LoanInformation memory loanInformation_) + { Bid memory bid = ITellerV2Storage(owner()).bids(tokenId); - - loanInformation_ = LoanInformation({ + + loanInformation_ = LoanInformation({ principalTokenAddress: address(bid.loanDetails.lendingToken), principalAmount: bid.loanDetails.principal, interestRate: bid.terms.APR, loanDuration: bid.loanDetails.loanDuration - }); - + }); } - function _getCollateralInformation(uint256 tokenId) - internal view - returns (Collateral memory collateral_) { - - address collateralManager = ITellerV2Storage(owner()).collateralManager(); - - Collateral[] memory collateralArray = ICollateralManager(collateralManager).getCollateralInfo(tokenId); - - if(collateralArray.length == 0) { - return Collateral({ - _amount: 0, - _collateralAddress: address(0), - _collateralType: CollateralType.ERC20, - _tokenId: 0 - - }); - } + internal + view + returns (Collateral memory collateral_) + { + address collateralManager = ITellerV2Storage(owner()) + .collateralManager(); + + Collateral[] memory collateralArray = ICollateralManager( + collateralManager + ).getCollateralInfo(tokenId); + + if (collateralArray.length == 0) { + return + Collateral({ + _amount: 0, + _collateralAddress: address(0), + _collateralType: CollateralType.ERC20, + _tokenId: 0 + }); + } return collateralArray[0]; } - function tokenURI(uint256 tokenId) public view override returns (string memory) { - require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - - LoanInformation memory loanInformation = _getLoanInformation(tokenId); - - - Collateral memory collateral = _getCollateralInformation(tokenId); - - - string memory image_svg_encoded = Base64.encode(bytes( - LenderManagerArt.generateSVG( - tokenId, //tokenId == bidId - loanInformation.principalAmount, - loanInformation.principalTokenAddress, - collateral, - loanInformation.interestRate, - loanInformation.loanDuration - ) )); - - - string memory name = "Teller Loan NFT"; - string memory description = "This token represents ownership of a loan. Repayments of principal and interest will be sent to the owner of this token. If the loan defaults, the owner of this token will be able to claim the underlying collateral. Please externally verify the parameter of the loan as this rendering is only a summary."; - - - - string memory encoded_svg = string( - abi.encodePacked( - 'data:application/json;base64,', - Base64.encode( - bytes( - abi.encodePacked( - '{"name":"', - name, - '", "description":"', - description, - '", "image": "', - 'data:image/svg+xml;base64,', - image_svg_encoded, - '"}' - ) + function tokenURI(uint256 tokenId) + public + view + override + returns (string memory) + { + require( + _exists(tokenId), + "ERC721Metadata: URI query for nonexistent token" + ); + + LoanInformation memory loanInformation = _getLoanInformation(tokenId); + + Collateral memory collateral = _getCollateralInformation(tokenId); + + string memory image_svg_encoded = Base64.encode( + bytes( + LenderManagerArt.generateSVG( + tokenId, //tokenId == bidId + loanInformation.principalAmount, + loanInformation.principalTokenAddress, + collateral, + loanInformation.interestRate, + loanInformation.loanDuration + ) + ) + ); + + string memory name = "Teller Loan NFT"; + string + memory description = "This token represents ownership of a loan. Repayments of principal and interest will be sent to the owner of this token. If the loan defaults, the owner of this token will be able to claim the underlying collateral. Please externally verify the parameter of the loan as this rendering is only a summary."; + + string memory encoded_svg = string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode( + bytes( + abi.encodePacked( + '{"name":"', + name, + '", "description":"', + description, + '", "image": "', + "data:image/svg+xml;base64,", + image_svg_encoded, + '"}' ) ) ) - ); - + ) + ); + return encoded_svg; } - - } - - - - \ No newline at end of file diff --git a/packages/contracts/contracts/TellerV2.sol b/packages/contracts/contracts/TellerV2.sol index 3adab83c7..9998d182a 100644 --- a/packages/contracts/contracts/TellerV2.sol +++ b/packages/contracts/contracts/TellerV2.sol @@ -1115,7 +1115,7 @@ contract TellerV2 is principalAmount = bid.loanDetails.principal; acceptedTimestamp = bid.loanDetails.acceptedTimestamp; lastRepaidTimestamp = V2Calculations.lastRepaidTimestamp(bids[_bidId]); - bidState = bid.state; + bidState = bid.state; } /** OpenZeppelin Override Functions **/ diff --git a/packages/contracts/contracts/interfaces/ITellerV2.sol b/packages/contracts/contracts/interfaces/ITellerV2.sol index 6d7d3f249..4f69d8c29 100644 --- a/packages/contracts/contracts/interfaces/ITellerV2.sol +++ b/packages/contracts/contracts/interfaces/ITellerV2.sol @@ -148,6 +148,4 @@ interface ITellerV2 { uint32 lastRepaidTimestamp, BidState bidState ); - - } diff --git a/packages/contracts/contracts/interfaces/ITellerV2Storage.sol b/packages/contracts/contracts/interfaces/ITellerV2Storage.sol index 174539a1c..ad7eb8812 100644 --- a/packages/contracts/contracts/interfaces/ITellerV2Storage.sol +++ b/packages/contracts/contracts/interfaces/ITellerV2Storage.sol @@ -1,13 +1,7 @@ import { Bid } from "../TellerV2Storage.sol"; interface ITellerV2Storage { + function bids(uint256 _bidId) external view returns (Bid memory); - function bids(uint256 _bidId) - external - view - returns (Bid memory); - - function collateralManager() external view returns (address); - - -} \ No newline at end of file + function collateralManager() external view returns (address); +} diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 953382239..a6b11e83d 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -1,6 +1,6 @@ /** *Submitted for verification at Etherscan.io on 2023-06-21 -*/ + */ // SPDX-License-Identifier: MIT @@ -13,382 +13,365 @@ import "@openzeppelin/contracts/utils/math/SignedMath.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import { Collateral, CollateralType } from "../interfaces/escrow/ICollateralEscrowV1.sol"; - //must use this custom interface since the OZ one doesnt support decimals interface IERC20 { function totalSupply() external view returns (uint256); - function balanceOf(address account) external view returns (uint256); - function transfer(address recipient, uint256 amount) external returns (bool); - function allowance(address owner, address spender) external view returns (uint256); - function approve(address spender, uint256 amount) external returns (bool); - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); - function decimals() external view returns (uint8); - function symbol() external view returns (string memory); -} - -library LenderManagerArt { - -using Strings for uint256; -using Strings for uint32; -using Strings for uint16; -using Address for address; - - -bytes constant _bg_defs_filter = abi.encodePacked ( - - - "", - "", - "", - "", - "", - "", - "", - "" - "", - "" - -); - - -bytes constant _bg_defs_clip_path = abi.encodePacked ( - - - "", - "", - "", - - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - -); - - -bytes constant _bg_defs_mask = abi.encodePacked ( - "", - "", - "", - "", - "", - "", - "", - "", - "", - - "", - "", - "", - "", - "", - "", - "" -); - -bytes constant _bg_defs = abi.encodePacked( - "", - - - _bg_defs_filter, - - _bg_defs_clip_path, - - _bg_defs_mask, - - - - -"" - -); - - -bytes constant _clip_path_corners = abi.encodePacked( - - "", - "", - "", - "", - "", - "", - "", - "", - "" - - -); - - - -bytes constant _teller_logo_path_1 = abi.encodePacked( -"", -"", -"", -"", -"", -"", -"", -"" - -); - - -bytes constant _teller_logo_path_2 = abi.encodePacked( - -"", -"", -"", -"", -"", -"", -"", -"", -"", -"" - - - -); - -bytes constant _teller_logo = abi.encodePacked( - - -"", - "", -"", - _teller_logo_path_1, - _teller_logo_path_2, -"" + function balanceOf(address account) external view returns (uint256); -); - + function transfer(address recipient, uint256 amount) + external + returns (bool); + function allowance(address owner, address spender) + external + view + returns (uint256); -function _generate_large_title( - string memory amount, - string memory symbol -) public pure returns (string memory) { + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) + external + returns (bool); - return string(abi.encodePacked( + function decimals() external view returns (uint8); - "", -"", -"AMOUNT", -"",amount," ",symbol,"", -"", -"" - - - )); + function symbol() external view returns (string memory); } +library LenderManagerArt { + using Strings for uint256; + using Strings for uint32; + using Strings for uint16; + using Address for address; + + bytes constant _bg_defs_filter = + abi.encodePacked( + "", + "", + "", + "", + "", + "", + "", + "" + "", + "" + ); + + bytes constant _bg_defs_clip_path = + abi.encodePacked( + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ); + + bytes constant _bg_defs_mask = + abi.encodePacked( + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ); + + bytes constant _bg_defs = + abi.encodePacked( + "", + _bg_defs_filter, + _bg_defs_clip_path, + _bg_defs_mask, + "" + ); + + bytes constant _clip_path_corners = + abi.encodePacked( + "", + "", + "", + "", + "", + "", + "", + "", + "" + ); + + bytes constant _teller_logo_path_1 = + abi.encodePacked( + "", + "", + "", + "", + "", + "", + "", + "" + ); + + bytes constant _teller_logo_path_2 = + abi.encodePacked( + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ); + + bytes constant _teller_logo = + abi.encodePacked( + "", + "", + "", + _teller_logo_path_1, + _teller_logo_path_2, + "" + ); + + function _generate_large_title(string memory amount, string memory symbol) + public + pure + returns (string memory) + { + return + string( + abi.encodePacked( + "", + "", + "AMOUNT", + "", + amount, + " ", + symbol, + "", + "", + "" + ) + ); + } -function _generate_text_label( - string memory label, - string memory value, - - uint256 y_offset -) public pure returns (string memory) { - + function _generate_text_label( + string memory label, + string memory value, + uint256 y_offset + ) public pure returns (string memory) { + return + string( + abi.encodePacked( + "", + "", + "", + "", + label, + "", + value, + "", + "" + ) + ); + } - return string(abi.encodePacked( - "", - "", - "", - "", label, "",value, - "", - "" - )); -} + function _get_token_amount_formatted(uint256 amount, uint256 decimals) + public + pure + returns (string memory) + { + uint256 precision = Math.min(3, decimals); - -function _get_token_amount_formatted( - uint256 amount, - uint256 decimals -) public pure returns (string memory) { - uint256 precision = Math.min(3,decimals); - - uint256 before_decimal = amount / (10 ** decimals); + uint256 before_decimal = amount / (10**decimals); - uint256 after_decimal = amount % (10 ** decimals); + uint256 after_decimal = amount % (10**decimals); - // truncate to the required precision - after_decimal = after_decimal / (10 ** (decimals - precision)); + // truncate to the required precision + after_decimal = after_decimal / (10**(decimals - precision)); - if(before_decimal >= 1000000000000000){ + if (before_decimal >= 1000000000000000) { return "> RANGE"; } - if(before_decimal >= 1000000000000){ + if (before_decimal >= 1000000000000) { uint256 trillions = before_decimal / 1000000000000; - uint256 billions = (before_decimal % 1000000000000) / 100000000000; // Get the first digit after the decimal point - return string(abi.encodePacked( - Strings.toString(trillions), - ".", - Strings.toString(billions), - "T" - )); + uint256 billions = (before_decimal % 1000000000000) / 100000000000; // Get the first digit after the decimal point + return + string( + abi.encodePacked( + Strings.toString(trillions), + ".", + Strings.toString(billions), + "T" + ) + ); } - if(before_decimal >= 1000000000){ + if (before_decimal >= 1000000000) { uint256 billions = before_decimal / 1000000000; - uint256 millions = (before_decimal % 1000000000) / 100000000; // Get the first digit after the decimal point - return string(abi.encodePacked( - Strings.toString(billions), - ".", - Strings.toString(millions), - "B" - )); + uint256 millions = (before_decimal % 1000000000) / 100000000; // Get the first digit after the decimal point + return + string( + abi.encodePacked( + Strings.toString(billions), + ".", + Strings.toString(millions), + "B" + ) + ); } - if(before_decimal >= 1000000){ + if (before_decimal >= 1000000) { uint256 millions = before_decimal / 1000000; - uint256 thousands = (before_decimal % 1000000) / 100000; // Get the first digit after the decimal point - return string(abi.encodePacked( - Strings.toString(millions), - ".", - Strings.toString(thousands), - "M" - )); + uint256 thousands = (before_decimal % 1000000) / 100000; // Get the first digit after the decimal point + return + string( + abi.encodePacked( + Strings.toString(millions), + ".", + Strings.toString(thousands), + "M" + ) + ); } - if(before_decimal >= 1000){ + if (before_decimal >= 1000) { uint256 fullThousands = before_decimal / 1000; - uint256 remainder = (before_decimal % 1000) / 100; // Get the first digit after the decimal point - return string(abi.encodePacked( - Strings.toString(fullThousands), - ".", - Strings.toString(remainder), - "K" - )); + uint256 remainder = (before_decimal % 1000) / 100; // Get the first digit after the decimal point + return + string( + abi.encodePacked( + Strings.toString(fullThousands), + ".", + Strings.toString(remainder), + "K" + ) + ); } - - return string(abi.encodePacked( - Strings.toString(before_decimal), - ".", - Strings.toString(after_decimal) - )); - - - -} - - -function _buildSvgData ( - string memory loanId, - string memory principalAmountFormatted, - string memory principalTokenSymbol, - string memory collateralLabel, - string memory interestRateLabel, - string memory loanDurationLabel - - -) internal pure returns (string memory) { - - return string(abi.encodePacked( - -"", - -_bg_defs, - - -_clip_path_corners, - - -_generate_large_title( - principalAmountFormatted, - principalTokenSymbol -), - -_teller_logo, - - -_generate_text_label( - "Loan ID: ", - loanId, //Strings.toString(bidId), - 354 -), - - -_generate_text_label( - "Collateral: ", - collateralLabel, //string(abi.encodePacked(collateral_amount_formatted," ",collateral_token_symbol)), - 384 -), - - -_generate_text_label( - "APR: ", - interestRateLabel, // "30 %", - 414 -), - - -_generate_text_label( - "Duration: ", - loanDurationLabel, //"7 days", - 444 -), + return + string( + abi.encodePacked( + Strings.toString(before_decimal), + ".", + Strings.toString(after_decimal) + ) + ); + } - -"" + function _buildSvgData( + string memory loanId, + string memory principalAmountFormatted, + string memory principalTokenSymbol, + string memory collateralLabel, + string memory interestRateLabel, + string memory loanDurationLabel + ) internal pure returns (string memory) { + return + string( + abi.encodePacked( + "", + _bg_defs, + _clip_path_corners, + _generate_large_title( + principalAmountFormatted, + principalTokenSymbol + ), + _teller_logo, + _generate_text_label( + "Loan ID: ", + loanId, //Strings.toString(bidId), + 354 + ), + _generate_text_label( + "Collateral: ", + collateralLabel, //string(abi.encodePacked(collateral_amount_formatted," ",collateral_token_symbol)), + 384 + ), + _generate_text_label( + "APR: ", + interestRateLabel, // "30 %", + 414 + ), + _generate_text_label( + "Duration: ", + loanDurationLabel, //"7 days", + 444 + ), + "" + ) + ); + } -)); + function _get_token_decimals(address token) public view returns (uint256) { + bytes memory data = abi.encodeWithSignature("decimals()"); + if (token.code.length == 0) { + return 0; + } + (bool success, bytes memory result) = token.staticcall(data); -} + if (!success) { + return 0; + } -function _get_token_decimals(address token) public view returns (uint256) { - - bytes memory data = abi.encodeWithSignature("decimals()"); + // Decode the result + uint8 decimals = abi.decode(result, (uint8)); - if(token.code.length == 0){ - return 0; + return decimals; } - - (bool success, bytes memory result) = token.staticcall(data); - if (!success) { - return 0; + function _get_interest_rate_formatted(uint16 interestRate) + public + pure + returns (string memory) + { + return string(abi.encodePacked((interestRate / 100).toString(), " %")); } - // Decode the result - uint8 decimals = abi.decode(result, (uint8)); - - return decimals; -} - - - -function _get_interest_rate_formatted(uint16 interestRate) public pure returns (string memory) { - return string(abi.encodePacked( (interestRate / 100).toString(), " %")); -} - -function _get_duration_formatted(uint32 sec) public pure returns (string memory) { + function _get_duration_formatted(uint32 sec) + public + pure + returns (string memory) + { uint32 _months = sec / 4 weeks; uint32 _weeks = sec / 1 weeks; uint32 _days = sec / 1 days; uint32 _hours = sec / 1 hours; uint32 _minutes = sec / 1 minutes; - + if (_months >= 2) { return string(abi.encodePacked(_months.toString(), " months")); } else if (_weeks >= 2) { @@ -402,99 +385,96 @@ function _get_duration_formatted(uint32 sec) public pure returns (string memory) } } - - function _get_token_symbol(address nftContract, string memory _fallback) public view returns (string memory) { + function _get_token_symbol(address nftContract, string memory _fallback) + public + view + returns (string memory) + { bytes memory data = abi.encodeWithSignature("symbol()"); - if(nftContract.code.length == 0){ + if (nftContract.code.length == 0) { return _fallback; } - + (bool success, bytes memory result) = nftContract.staticcall(data); if (!success) { - return _fallback ; + return _fallback; } // Decode the result from bytes to string string memory symbol = abi.decode(result, (string)); return symbol; - -} - - -function _get_collateral_label(Collateral memory collateral) public view returns (string memory) { - - if(collateral._collateralAddress == address(0)){ - return "None"; } - - if(collateral._collateralType == CollateralType.ERC20){ - string memory collateralAmountFormatted = _get_token_amount_formatted( - collateral._amount, - _get_token_decimals(collateral._collateralAddress) - - ); - string memory collateralTokenSymbol = _get_token_symbol(collateral._collateralAddress,"?"); + function _get_collateral_label(Collateral memory collateral) + public + view + returns (string memory) + { + if (collateral._collateralAddress == address(0)) { + return "None"; + } - return string(abi.encodePacked(collateralAmountFormatted," ",collateralTokenSymbol)); - } + if (collateral._collateralType == CollateralType.ERC20) { + string + memory collateralAmountFormatted = _get_token_amount_formatted( + collateral._amount, + _get_token_decimals(collateral._collateralAddress) + ); + + string memory collateralTokenSymbol = _get_token_symbol( + collateral._collateralAddress, + "?" + ); - if(collateral._collateralType == CollateralType.ERC721){ + return + string( + abi.encodePacked( + collateralAmountFormatted, + " ", + collateralTokenSymbol + ) + ); + } - return _get_token_symbol(collateral._collateralAddress, "ERC721"); - } + if (collateral._collateralType == CollateralType.ERC721) { + return _get_token_symbol(collateral._collateralAddress, "ERC721"); + } - if(collateral._collateralType == CollateralType.ERC1155){ - - return _get_token_symbol(collateral._collateralAddress, "ERC1155"); + if (collateral._collateralType == CollateralType.ERC1155) { + return _get_token_symbol(collateral._collateralAddress, "ERC1155"); + } } - - - -} -function generateSVG( + function generateSVG( uint256 bidId, uint256 principalAmount, address principalTokenAddress, - Collateral memory collateral, + Collateral memory collateral, uint16 interestRate, uint32 loanDuration - ) public view returns (string memory) { - - - string memory principalAmountFormatted = _get_token_amount_formatted( - principalAmount, - _get_token_decimals(principalTokenAddress) - - ); - - string memory principalTokenSymbol = _get_token_symbol(principalTokenAddress, "?"); - - - string memory svgData = _buildSvgData( - (bidId).toString(), + ) public view returns (string memory) { + string memory principalAmountFormatted = _get_token_amount_formatted( + principalAmount, + _get_token_decimals(principalTokenAddress) + ); + + string memory principalTokenSymbol = _get_token_symbol( + principalTokenAddress, + "?" + ); + + string memory svgData = _buildSvgData( + (bidId).toString(), principalAmountFormatted, principalTokenSymbol, _get_collateral_label(collateral), - _get_interest_rate_formatted(interestRate), - _get_duration_formatted(loanDuration) - - ) ; - - - return svgData; -} - - - - + _get_duration_formatted(loanDuration) + ); + return svgData; + } } - - - diff --git a/packages/contracts/deploy/lender_manager/deploy.ts b/packages/contracts/deploy/lender_manager/deploy.ts index 0a9fe9d75..6144bfda4 100644 --- a/packages/contracts/deploy/lender_manager/deploy.ts +++ b/packages/contracts/deploy/lender_manager/deploy.ts @@ -19,5 +19,8 @@ const deployFn: DeployFunction = async (hre) => { // tags and deployment deployFn.id = 'lender-manager:deploy' deployFn.tags = ['lender-manager', 'lender-manager:deploy'] -deployFn.dependencies = ['market-registry:deploy','lender-manager:lender-manager-art'] +deployFn.dependencies = [ + 'market-registry:deploy', + 'lender-manager:lender-manager-art', +] export default deployFn diff --git a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts index a7d43f0c6..ab32e9959 100644 --- a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts +++ b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts @@ -4,12 +4,12 @@ const deployFn: DeployFunction = async (hre) => { hre.log('----------') hre.log('') hre.log('Lender Manager: Proposing upgrade...') - + const marketRegistry = await hre.contracts.get('MarketRegistry') - + const lenderManager = await hre.contracts.get('LenderManager') const lenderManagerArt = await hre.contracts.get('LenderManagerArt') - + await hre.defender.proposeBatchTimelock( 'Lender Manager: Art Upgrade', ` @@ -19,22 +19,22 @@ const deployFn: DeployFunction = async (hre) => { `, [ - { proxy: lenderManager.address, implFactory: await hre.ethers.getContractFactory('LenderManager', { libraries: { LenderManagerArt: lenderManagerArt.address, - }, - }), + }, + }), opts: { - unsafeAllow: ['constructor', 'state-variable-immutable','external-library-linking',], + unsafeAllow: [ + 'constructor', + 'state-variable-immutable', + 'external-library-linking', + ], constructorArgs: [marketRegistry.address], - }, - }, - ] ) @@ -53,12 +53,8 @@ deployFn.tags = [ 'lender-manager', 'lender-manager:upgrade-art', ] -deployFn.dependencies = [ - 'market-registry:deploy', - 'lender-manager:deploy', -] +deployFn.dependencies = ['market-registry:deploy', 'lender-manager:deploy'] deployFn.skip = async (hre) => { return false - } export default deployFn diff --git a/packages/contracts/tests/LenderManagerArt_Override.sol b/packages/contracts/tests/LenderManagerArt_Override.sol index cfeea8729..2a45a3291 100644 --- a/packages/contracts/tests/LenderManagerArt_Override.sol +++ b/packages/contracts/tests/LenderManagerArt_Override.sol @@ -1,21 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; - -import {CollateralType,Collateral} from "../contracts/interfaces/escrow/ICollateralEscrowV1.sol"; +import { CollateralType, Collateral } from "../contracts/interfaces/escrow/ICollateralEscrowV1.sol"; import "lib/forge-std/src/console.sol"; import { Testable } from "./Testable.sol"; import { LenderManagerArt } from "../contracts/libraries/LenderManagerArt.sol"; - -contract LenderManagerArt_Override { - - - - - - - -} \ No newline at end of file +contract LenderManagerArt_Override {} diff --git a/packages/contracts/tests/LenderManagerArt_Test.sol b/packages/contracts/tests/LenderManagerArt_Test.sol index ec823a232..579fc5946 100644 --- a/packages/contracts/tests/LenderManagerArt_Test.sol +++ b/packages/contracts/tests/LenderManagerArt_Test.sol @@ -1,135 +1,121 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; - -import {CollateralType,Collateral} from "../contracts/interfaces/escrow/ICollateralEscrowV1.sol"; +import { CollateralType, Collateral } from "../contracts/interfaces/escrow/ICollateralEscrowV1.sol"; import "lib/forge-std/src/console.sol"; import { Testable } from "./Testable.sol"; import { LenderManagerArt } from "../contracts/libraries/LenderManagerArt.sol"; - + import "./tokens/TestERC1155Token.sol"; import "./tokens/TestERC721Token.sol"; import "../contracts/mock/WethMock.sol"; contract LenderManagerArt_Test is Testable { - - WethMock wethMock; - TestERC721Token erc721Mock; - TestERC1155Token erc1155Mock; + WethMock wethMock; + TestERC721Token erc721Mock; + TestERC1155Token erc1155Mock; constructor() {} function setUp() public { - - wethMock = new WethMock(); - erc721Mock = new TestERC721Token("BAYC", "BAYC"); - erc1155Mock = new TestERC1155Token("SAND"); + wethMock = new WethMock(); + erc721Mock = new TestERC721Token("BAYC", "BAYC"); + erc1155Mock = new TestERC1155Token("SAND"); } function test_generateSVG() public { - - - Collateral memory _collateral = Collateral({ _collateralType: CollateralType.ERC721, _collateralAddress: address(erc721Mock), _amount: 1, - _tokenId : 150 + _tokenId: 150 }); string memory svg = LenderManagerArt.generateSVG( 22, 82330000000000420055000, - address(wethMock), + address(wethMock), _collateral, 300, 550000 ); - console.log("the svg:"); console.log(svg); - } - - //add more unit tests here + //add more unit tests here function test_get_token_decimals() public { - - - uint256 decimals = LenderManagerArt._get_token_decimals(address(wethMock)); - + uint256 decimals = LenderManagerArt._get_token_decimals( + address(wethMock) + ); assertEq(decimals, 18); - - } function test_get_token_decimals_erc721() public { - - uint256 decimals = LenderManagerArt._get_token_decimals(address(erc721Mock)); - + uint256 decimals = LenderManagerArt._get_token_decimals( + address(erc721Mock) + ); assertEq(decimals, 0); - } function test_get_interest_rate_formatted() public { + string memory interestRate = LenderManagerArt + ._get_interest_rate_formatted(30000); - string memory interestRate = LenderManagerArt._get_interest_rate_formatted(30000); - assertEq(interestRate, "300 %"); - } function test_get_duration_formatted() public { + string memory duration = LenderManagerArt._get_duration_formatted( + 30000 + ); - string memory duration = LenderManagerArt._get_duration_formatted(30000); - assertEq(duration, "8 hours"); - - } function test_get_duration_formatted_2() public { + string memory duration = LenderManagerArt._get_duration_formatted( + 3000000 + ); - string memory duration = LenderManagerArt._get_duration_formatted(3000000); - assertEq(duration, "4 weeks"); - - } - function test_get_token_symbol() public { - string memory symbol = LenderManagerArt._get_token_symbol(address(wethMock),"fallback"); - + string memory symbol = LenderManagerArt._get_token_symbol( + address(wethMock), + "fallback" + ); + assertEq(symbol, "WETH"); } function test_get_token_symbol_fallback() public { - string memory symbol = LenderManagerArt._get_token_symbol(address(0),"fallback"); - + string memory symbol = LenderManagerArt._get_token_symbol( + address(0), + "fallback" + ); + assertEq(symbol, "fallback"); } function test_get_collateral_label() public { - - Collateral memory _collateral = Collateral({ _collateralType: CollateralType.ERC721, _collateralAddress: address(erc721Mock), _amount: 1, - _tokenId : 150 + _tokenId: 150 }); - string memory label = LenderManagerArt._get_collateral_label(_collateral); - - assertEq(label, "BAYC"); + string memory label = LenderManagerArt._get_collateral_label( + _collateral + ); + assertEq(label, "BAYC"); } - - -} \ No newline at end of file +} diff --git a/packages/contracts/tests/LenderManager_Test.sol b/packages/contracts/tests/LenderManager_Test.sol index ba23ec16d..9e5ad6b09 100644 --- a/packages/contracts/tests/LenderManager_Test.sol +++ b/packages/contracts/tests/LenderManager_Test.sol @@ -67,7 +67,6 @@ contract LenderManager_Test is Testable { "Market id is not correct" ); } - function test_hasMarketVerification() public { lenderManager.initialize(); @@ -89,7 +88,11 @@ contract LenderManager_Test is Testable { string memory baseURI = lenderManager._baseURISuper(); - assertEq(baseURI, "data:image/svg+xml;charset=utf-8,", "Base URI is not correct"); + assertEq( + baseURI, + "data:image/svg+xml;charset=utf-8,", + "Base URI is not correct" + ); } function test_mint() public { From f654602151606dfdc3523202c5ae04a7b986393a Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Mon, 10 Jul 2023 16:12:23 -0400 Subject: [PATCH 19/30] Update packages/contracts/contracts/LenderManager.sol Co-authored-by: passabilities.eth --- packages/contracts/contracts/LenderManager.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index a73be91b9..7c28a8c32 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -25,7 +25,6 @@ contract LenderManager is ERC721Upgradeable, ILenderManager { - // using Strings for uint256; IMarketRegistry public immutable marketRegistry; constructor(IMarketRegistry _marketRegistry) { From e2d7cbe800ee5402dc14f97df476efd759d83cc3 Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Mon, 10 Jul 2023 16:12:35 -0400 Subject: [PATCH 20/30] Update packages/contracts/contracts/LenderManager.sol Co-authored-by: passabilities.eth --- packages/contracts/contracts/LenderManager.sol | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index 7c28a8c32..de83c481a 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -124,17 +124,9 @@ contract LenderManager is collateralManager ).getCollateralInfo(tokenId); - if (collateralArray.length == 0) { - return - Collateral({ - _amount: 0, - _collateralAddress: address(0), - _collateralType: CollateralType.ERC20, - _tokenId: 0 - }); + if (collateralArray.length > 0) { + collateral_ = collateralArray[0]; } - - return collateralArray[0]; } function tokenURI(uint256 tokenId) From b64c045d91f80943ffbd0115f515db4f9232badd Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Mon, 10 Jul 2023 16:13:54 -0400 Subject: [PATCH 21/30] Update packages/contracts/contracts/libraries/LenderManagerArt.sol Co-authored-by: passabilities.eth --- packages/contracts/contracts/libraries/LenderManagerArt.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index a6b11e83d..7138c1fdf 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -311,7 +311,7 @@ library LenderManagerArt { _teller_logo, _generate_text_label( "Loan ID: ", - loanId, //Strings.toString(bidId), + loanId, 354 ), _generate_text_label( From 136198b89d4cd487b479dc5127173081a442fe7b Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Mon, 10 Jul 2023 16:14:01 -0400 Subject: [PATCH 22/30] Update packages/contracts/contracts/libraries/LenderManagerArt.sol Co-authored-by: passabilities.eth --- packages/contracts/contracts/libraries/LenderManagerArt.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 7138c1fdf..c9b835b95 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -326,7 +326,7 @@ library LenderManagerArt { ), _generate_text_label( "Duration: ", - loanDurationLabel, //"7 days", + loanDurationLabel, 444 ), "" From 6aa61f1cb77239f060f6a24e6c04abfb106bda76 Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Mon, 10 Jul 2023 16:14:08 -0400 Subject: [PATCH 23/30] Update packages/contracts/contracts/libraries/LenderManagerArt.sol Co-authored-by: passabilities.eth --- packages/contracts/contracts/libraries/LenderManagerArt.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index c9b835b95..0477103c7 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -321,7 +321,7 @@ library LenderManagerArt { ), _generate_text_label( "APR: ", - interestRateLabel, // "30 %", + interestRateLabel, 414 ), _generate_text_label( From cd3e871fe013aad1d9fb1b4f3a4718d2728a033f Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Mon, 10 Jul 2023 16:14:16 -0400 Subject: [PATCH 24/30] Update packages/contracts/contracts/libraries/LenderManagerArt.sol Co-authored-by: passabilities.eth --- packages/contracts/contracts/libraries/LenderManagerArt.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 0477103c7..6ebccd4a5 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -316,7 +316,7 @@ library LenderManagerArt { ), _generate_text_label( "Collateral: ", - collateralLabel, //string(abi.encodePacked(collateral_amount_formatted," ",collateral_token_symbol)), + collateralLabel, 384 ), _generate_text_label( From d499facfb86814d9eb417355af0d29b15e41e8e1 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Mon, 10 Jul 2023 16:34:23 -0400 Subject: [PATCH 25/30] update deploy script --- ...merkle.ts => 01_merkle_root_lender_art.ts} | 39 ++++++++++-- .../deploy/upgrades/02_lender_manager_art.ts | 60 ------------------- 2 files changed, 33 insertions(+), 66 deletions(-) rename packages/contracts/deploy/upgrades/{01_lender_commitment_merkle.ts => 01_merkle_root_lender_art.ts} (60%) delete mode 100644 packages/contracts/deploy/upgrades/02_lender_manager_art.ts diff --git a/packages/contracts/deploy/upgrades/01_lender_commitment_merkle.ts b/packages/contracts/deploy/upgrades/01_merkle_root_lender_art.ts similarity index 60% rename from packages/contracts/deploy/upgrades/01_lender_commitment_merkle.ts rename to packages/contracts/deploy/upgrades/01_merkle_root_lender_art.ts index 0208221e6..dddfd5218 100644 --- a/packages/contracts/deploy/upgrades/01_lender_commitment_merkle.ts +++ b/packages/contracts/deploy/upgrades/01_merkle_root_lender_art.ts @@ -3,7 +3,7 @@ import { DeployFunction } from 'hardhat-deploy/dist/types' const deployFn: DeployFunction = async (hre) => { hre.log('----------') hre.log('') - hre.log('LenderCommitmentForwarder: Proposing upgrade...') + hre.log('Proposing upgrade...') const tellerV2 = await hre.contracts.get('TellerV2') const marketRegistry = await hre.contracts.get('MarketRegistry') @@ -11,8 +11,11 @@ const deployFn: DeployFunction = async (hre) => { 'LenderCommitmentForwarder' ) + const lenderManager = await hre.contracts.get('LenderManager') + const lenderManagerArt = await hre.contracts.get('LenderManagerArt') + await hre.defender.proposeBatchTimelock( - 'Lender Commitment Forwarder Merkle Upgrade', + 'Merkle Root + Lender Art Upgrade', ` # LenderCommitmentForwarder @@ -20,6 +23,11 @@ const deployFn: DeployFunction = async (hre) => { * Adds two new collateral types, ERC721_MERKLE_PROOF and ERC1155_MERKLE_PROOF. * Add a new function acceptCommitmentWithProof which is explicitly used with these new types. * Merkle proofs can be used to create commitments for a set of tokenIds for an ERC721 or ERC1155 collection. + +# Lender Manager + +* Updates the tokenURI function so it returns an svg image rendering with loan summary data. + `, [ { @@ -30,9 +38,25 @@ const deployFn: DeployFunction = async (hre) => { opts: { unsafeAllow: ['constructor', 'state-variable-immutable'], - constructorArgs: [tellerV2.address, marketRegistry.address], - }, + constructorArgs: [tellerV2.address, marketRegistry.address] + } }, + { + proxy: lenderManager.address, + implFactory: await hre.ethers.getContractFactory('LenderManager', { + libraries: { + LenderManagerArt: lenderManagerArt.address + } + }), + opts: { + unsafeAllow: [ + 'constructor', + 'state-variable-immutable', + 'external-library-linking' + ], + constructorArgs: [marketRegistry.address] + } + } ] ) @@ -44,17 +68,20 @@ const deployFn: DeployFunction = async (hre) => { } // tags and deployment -deployFn.id = 'lender-commitment-forwarder:merkle-upgrade' +deployFn.id = 'merkle-root-lender-art:upgrade' deployFn.tags = [ 'proposal', 'upgrade', 'lender-commitment-forwarder', - 'lender-commitment-forwarder:merkle-upgrade', + 'lender-manager', + 'merkle-root-lender-art:upgrade' ] deployFn.dependencies = [ 'market-registry:deploy', 'teller-v2:deploy', 'lender-commitment-forwarder:deploy', + 'market-registry:deploy', + 'lender-manager:deploy' ] deployFn.skip = async (hre) => { return false diff --git a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts b/packages/contracts/deploy/upgrades/02_lender_manager_art.ts deleted file mode 100644 index ab32e9959..000000000 --- a/packages/contracts/deploy/upgrades/02_lender_manager_art.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { DeployFunction } from 'hardhat-deploy/dist/types' - -const deployFn: DeployFunction = async (hre) => { - hre.log('----------') - hre.log('') - hre.log('Lender Manager: Proposing upgrade...') - - const marketRegistry = await hre.contracts.get('MarketRegistry') - - const lenderManager = await hre.contracts.get('LenderManager') - const lenderManagerArt = await hre.contracts.get('LenderManagerArt') - - await hre.defender.proposeBatchTimelock( - 'Lender Manager: Art Upgrade', - ` -# Lender Manager - -* Updates the tokenURI function so it returns an svg image rendering with loan summary data. - -`, - [ - { - proxy: lenderManager.address, - implFactory: await hre.ethers.getContractFactory('LenderManager', { - libraries: { - LenderManagerArt: lenderManagerArt.address, - }, - }), - opts: { - unsafeAllow: [ - 'constructor', - 'state-variable-immutable', - 'external-library-linking', - ], - constructorArgs: [marketRegistry.address], - }, - }, - ] - ) - - hre.log('done.') - hre.log('') - hre.log('----------') - - return true -} - -// tags and deployment -deployFn.id = 'lender-manager:upgrade-art' -deployFn.tags = [ - 'proposal', - 'upgrade', - 'lender-manager', - 'lender-manager:upgrade-art', -] -deployFn.dependencies = ['market-registry:deploy', 'lender-manager:deploy'] -deployFn.skip = async (hre) => { - return false -} -export default deployFn From e8c8cf4c4a3ab7f062cd21f71a482f03b453ae23 Mon Sep 17 00:00:00 2001 From: Ethereumdegen Date: Mon, 10 Jul 2023 16:38:33 -0400 Subject: [PATCH 26/30] Update packages/contracts/deployments/goerli/.migrations.json Co-authored-by: passabilities.eth --- packages/contracts/deployments/goerli/.migrations.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/contracts/deployments/goerli/.migrations.json b/packages/contracts/deployments/goerli/.migrations.json index 49458ed6b..8a6cca6a8 100644 --- a/packages/contracts/deployments/goerli/.migrations.json +++ b/packages/contracts/deployments/goerli/.migrations.json @@ -13,7 +13,6 @@ "escrow-vault:deploy": 1688578801, "default-proxy-admin:transfer": 1688587184, "sherlock-audit:upgrade": 1688587576, - "lender-manager:upgrade-art": 1688662351, - "lender-commitment-forwarder:merkle-upgrade": 1688596796, + "merkle-root-lender-art:upgrade": 1688596796, "teller-v2:transfer-ownership-to-safe": 1688588967 } From 2db6f324126b9b5be7744c5bd12945476bff17e9 Mon Sep 17 00:00:00 2001 From: Noah Passalacqua Date: Mon, 10 Jul 2023 15:47:47 -0500 Subject: [PATCH 27/30] format --- .../contracts/contracts/LenderManager.sol | 1 - .../contracts/libraries/LenderManagerArt.sol | 24 ++++--------------- .../upgrades/01_merkle_root_lender_art.ts | 20 ++++++++-------- 3 files changed, 14 insertions(+), 31 deletions(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index fe5dd04a6..d7bc57cf6 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -135,7 +135,6 @@ contract LenderManager is override returns (string memory) { - LoanInformation memory loanInformation = _getLoanInformation(tokenId); Collateral memory collateral = _getCollateralInformation(tokenId); diff --git a/packages/contracts/contracts/libraries/LenderManagerArt.sol b/packages/contracts/contracts/libraries/LenderManagerArt.sol index 6ebccd4a5..23655e8e7 100644 --- a/packages/contracts/contracts/libraries/LenderManagerArt.sol +++ b/packages/contracts/contracts/libraries/LenderManagerArt.sol @@ -309,26 +309,10 @@ library LenderManagerArt { principalTokenSymbol ), _teller_logo, - _generate_text_label( - "Loan ID: ", - loanId, - 354 - ), - _generate_text_label( - "Collateral: ", - collateralLabel, - 384 - ), - _generate_text_label( - "APR: ", - interestRateLabel, - 414 - ), - _generate_text_label( - "Duration: ", - loanDurationLabel, - 444 - ), + _generate_text_label("Loan ID: ", loanId, 354), + _generate_text_label("Collateral: ", collateralLabel, 384), + _generate_text_label("APR: ", interestRateLabel, 414), + _generate_text_label("Duration: ", loanDurationLabel, 444), "" ) ); diff --git a/packages/contracts/deploy/upgrades/01_merkle_root_lender_art.ts b/packages/contracts/deploy/upgrades/01_merkle_root_lender_art.ts index dddfd5218..e8f3887c9 100644 --- a/packages/contracts/deploy/upgrades/01_merkle_root_lender_art.ts +++ b/packages/contracts/deploy/upgrades/01_merkle_root_lender_art.ts @@ -38,25 +38,25 @@ const deployFn: DeployFunction = async (hre) => { opts: { unsafeAllow: ['constructor', 'state-variable-immutable'], - constructorArgs: [tellerV2.address, marketRegistry.address] - } + constructorArgs: [tellerV2.address, marketRegistry.address], + }, }, { proxy: lenderManager.address, implFactory: await hre.ethers.getContractFactory('LenderManager', { libraries: { - LenderManagerArt: lenderManagerArt.address - } + LenderManagerArt: lenderManagerArt.address, + }, }), opts: { unsafeAllow: [ 'constructor', 'state-variable-immutable', - 'external-library-linking' + 'external-library-linking', ], - constructorArgs: [marketRegistry.address] - } - } + constructorArgs: [marketRegistry.address], + }, + }, ] ) @@ -74,14 +74,14 @@ deployFn.tags = [ 'upgrade', 'lender-commitment-forwarder', 'lender-manager', - 'merkle-root-lender-art:upgrade' + 'merkle-root-lender-art:upgrade', ] deployFn.dependencies = [ 'market-registry:deploy', 'teller-v2:deploy', 'lender-commitment-forwarder:deploy', 'market-registry:deploy', - 'lender-manager:deploy' + 'lender-manager:deploy', ] deployFn.skip = async (hre) => { return false From 847072ef16cdc1a093f4819ef03ec33e0587dbc1 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Mon, 10 Jul 2023 17:06:51 -0400 Subject: [PATCH 28/30] remove uri --- packages/contracts/contracts/LenderManager.sol | 2 +- packages/contracts/tests/LenderManager_Test.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index d7bc57cf6..d30081093 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -87,7 +87,7 @@ contract LenderManager is } function _baseURI() internal view override returns (string memory) { - return "data:image/svg+xml;charset=utf-8,"; + return ""; } struct LoanInformation { diff --git a/packages/contracts/tests/LenderManager_Test.sol b/packages/contracts/tests/LenderManager_Test.sol index 9e5ad6b09..cd759bb51 100644 --- a/packages/contracts/tests/LenderManager_Test.sol +++ b/packages/contracts/tests/LenderManager_Test.sol @@ -90,7 +90,7 @@ contract LenderManager_Test is Testable { assertEq( baseURI, - "data:image/svg+xml;charset=utf-8,", + "", "Base URI is not correct" ); } From c30904aa4d4657b53a77e4a00e66851129b71cdd Mon Sep 17 00:00:00 2001 From: Noah Passalacqua Date: Tue, 11 Jul 2023 12:19:38 -0500 Subject: [PATCH 29/30] fully remove uri --- packages/contracts/contracts/LenderManager.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/contracts/contracts/LenderManager.sol b/packages/contracts/contracts/LenderManager.sol index d30081093..d19977872 100644 --- a/packages/contracts/contracts/LenderManager.sol +++ b/packages/contracts/contracts/LenderManager.sol @@ -86,10 +86,6 @@ contract LenderManager is require(_hasMarketVerification(to, tokenId), "Not approved by market"); } - function _baseURI() internal view override returns (string memory) { - return ""; - } - struct LoanInformation { address principalTokenAddress; uint256 principalAmount; From de070b56fd43cd094d71b5b5c4a68fd25cac0ec0 Mon Sep 17 00:00:00 2001 From: Admazzola Date: Tue, 15 Aug 2023 10:24:38 -0400 Subject: [PATCH 30/30] merge --- packages/contracts/teller-math-lib | 2 +- packages/services/graph-node | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 160000 packages/services/graph-node diff --git a/packages/contracts/teller-math-lib b/packages/contracts/teller-math-lib index 1dd27b8a1..c2a17d57a 160000 --- a/packages/contracts/teller-math-lib +++ b/packages/contracts/teller-math-lib @@ -1 +1 @@ -Subproject commit 1dd27b8a176b5fddd917ad1f752421b4b54a5204 +Subproject commit c2a17d57a11399ffdf2a9ed650275574ddd35fa4 diff --git a/packages/services/graph-node b/packages/services/graph-node deleted file mode 160000 index 3e733e3dd..000000000 --- a/packages/services/graph-node +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3e733e3dd69e6317ac0a6a49942f08330788cc6e