From 76af62e1e79631611ddc68b40e7e7d997d800f8a Mon Sep 17 00:00:00 2001 From: dianakocsis Date: Thu, 17 Oct 2024 14:56:18 -0400 Subject: [PATCH] Pashov L-04 display Native instead of the zero address --- .../positionDescriptor bytecode size.snap | 2 +- src/PositionDescriptor.sol | 89 ++++++------ ...ortOrder.sol => AddressRatioSortOrder.sol} | 8 +- src/libraries/Descriptor.sol | 137 +++++++++--------- src/libraries/SVG.sol | 61 ++++---- ...encyMetadata.sol => SafeERC20Metadata.sol} | 39 +++-- test/PositionDescriptor.t.sol | 42 +++--- 7 files changed, 187 insertions(+), 191 deletions(-) rename src/libraries/{CurrencyRatioSortOrder.sol => AddressRatioSortOrder.sol} (65%) rename src/libraries/{SafeCurrencyMetadata.sol => SafeERC20Metadata.sol} (64%) diff --git a/.forge-snapshots/positionDescriptor bytecode size.snap b/.forge-snapshots/positionDescriptor bytecode size.snap index ec121d73..fcd5df5e 100644 --- a/.forge-snapshots/positionDescriptor bytecode size.snap +++ b/.forge-snapshots/positionDescriptor bytecode size.snap @@ -1 +1 @@ -31065 \ No newline at end of file +31144 \ No newline at end of file diff --git a/src/PositionDescriptor.sol b/src/PositionDescriptor.sol index 7cf39d94..716f7497 100644 --- a/src/PositionDescriptor.sol +++ b/src/PositionDescriptor.sol @@ -10,8 +10,8 @@ import {IPositionManager} from "./interfaces/IPositionManager.sol"; import {IPositionDescriptor} from "./interfaces/IPositionDescriptor.sol"; import {PositionInfo, PositionInfoLibrary} from "./libraries/PositionInfoLibrary.sol"; import {Descriptor} from "./libraries/Descriptor.sol"; -import {CurrencyRatioSortOrder} from "./libraries/CurrencyRatioSortOrder.sol"; -import {SafeCurrencyMetadata} from "./libraries/SafeCurrencyMetadata.sol"; +import {AddressRatioSortOrder} from "./libraries/AddressRatioSortOrder.sol"; +import {SafeERC20Metadata} from "./libraries/SafeERC20Metadata.sol"; /// @title Describes NFT token positions /// @notice Produces a string containing the data URI for a JSON metadata string @@ -31,14 +31,14 @@ contract PositionDescriptor is IPositionDescriptor { address private constant WBTC = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599; address public immutable wrappedNative; - string public nativeCurrencyLabel; + string public nativeAddressLabel; IPoolManager public immutable poolManager; - constructor(IPoolManager _poolManager, address _wrappedNative, string memory _nativeCurrencyLabel) { + constructor(IPoolManager _poolManager, address _wrappedNative, string memory _nativeAddressLabel) { poolManager = _poolManager; wrappedNative = _wrappedNative; - nativeCurrencyLabel = _nativeCurrencyLabel; + nativeAddressLabel = _nativeAddressLabel; } /// @inheritdoc IPositionDescriptor @@ -54,24 +54,27 @@ contract PositionDescriptor is IPositionDescriptor { } (, int24 tick,,) = poolManager.getSlot0(poolKey.toId()); - // If possible, flip currencies to get the larger currency as the base currency, so that the price (quote/base) is more readable - // flip if currency0 priority is greater than currency1 priority - bool _flipRatio = flipRatio(Currency.unwrap(poolKey.currency0), Currency.unwrap(poolKey.currency1)); + address address0 = Currency.unwrap(poolKey.currency0); + address address1 = Currency.unwrap(poolKey.currency1); - // If not flipped, quote currency is currency1, base currency is currency0 - // If flipped, quote currency is currency0, base currency is currency1 - Currency quoteCurrency = !_flipRatio ? poolKey.currency1 : poolKey.currency0; - Currency baseCurrency = !_flipRatio ? poolKey.currency0 : poolKey.currency1; + // If possible, flip addresses to get the larger one as the base, so that the price (quote/base) is more readable + // flip if address0 priority is greater than address1 priority + bool _flipRatio = flipRatio(address0, address1); + + // If not flipped, quote address is address1, base address is address0 + // If flipped, quote address is address0, base address is address1 + address quoteAddress = !_flipRatio ? address1 : address0; + address baseAddress = !_flipRatio ? address0 : address1; return Descriptor.constructTokenURI( Descriptor.ConstructTokenURIParams({ tokenId: tokenId, - quoteCurrency: quoteCurrency, - baseCurrency: baseCurrency, - quoteCurrencySymbol: SafeCurrencyMetadata.currencySymbol(quoteCurrency, nativeCurrencyLabel), - baseCurrencySymbol: SafeCurrencyMetadata.currencySymbol(baseCurrency, nativeCurrencyLabel), - quoteCurrencyDecimals: SafeCurrencyMetadata.currencyDecimals(quoteCurrency), - baseCurrencyDecimals: SafeCurrencyMetadata.currencyDecimals(baseCurrency), + quoteAddress: quoteAddress, + baseAddress: baseAddress, + quoteAddressSymbol: SafeERC20Metadata.addressSymbol(quoteAddress, nativeAddressLabel), + baseAddressSymbol: SafeERC20Metadata.addressSymbol(baseAddress, nativeAddressLabel), + quoteAddressDecimals: SafeERC20Metadata.addressDecimals(quoteAddress), + baseAddressDecimals: SafeERC20Metadata.addressDecimals(baseAddress), flipRatio: _flipRatio, tickLower: positionInfo.tickLower(), tickUpper: positionInfo.tickUpper(), @@ -84,37 +87,37 @@ contract PositionDescriptor is IPositionDescriptor { ); } - /// @notice Returns true if currency0 has higher priority than currency1 - /// @param currency0 The first currency address - /// @param currency1 The second currency address - /// @return flipRatio True if currency0 has higher priority than currency1 - function flipRatio(address currency0, address currency1) public view returns (bool) { - return currencyRatioPriority(currency0) > currencyRatioPriority(currency1); + /// @notice Returns true if address0 has higher priority than address1 + /// @param address0 The first address + /// @param address1 The second address + /// @return flipRatio True if address0 has higher priority than address1 + function flipRatio(address address0, address address1) public view returns (bool) { + return addressRatioPriority(address0) > addressRatioPriority(address1); } - /// @notice Returns the priority of a currency. - /// For certain currencies on mainnet, the smaller the currency, the higher the priority - /// @param currency The currency address - /// @return priority The priority of the currency - function currencyRatioPriority(address currency) public view returns (int256) { - // Currencies in order of priority on mainnet: USDC, USDT, DAI, (ETH, WETH), TBTC, WBTC + /// @notice Returns the priority of an address. + /// For certain addresses on mainnet, the smaller the address, the higher the priority + /// @param addr The address + /// @return priority The priority of the address + function addressRatioPriority(address addr) public view returns (int256) { + // Addresses in order of priority on mainnet: USDC, USDT, DAI, (ETH, WETH), TBTC, WBTC // wrapped native is different address on different chains. passed in constructor - // native currency - if (currency == address(0) || currency == wrappedNative) { - return CurrencyRatioSortOrder.DENOMINATOR; + // native address + if (addr == address(0) || addr == wrappedNative) { + return AddressRatioSortOrder.DENOMINATOR; } if (block.chainid == 1) { - if (currency == USDC) { - return CurrencyRatioSortOrder.NUMERATOR_MOST; - } else if (currency == USDT) { - return CurrencyRatioSortOrder.NUMERATOR_MORE; - } else if (currency == DAI) { - return CurrencyRatioSortOrder.NUMERATOR; - } else if (currency == TBTC) { - return CurrencyRatioSortOrder.DENOMINATOR_MORE; - } else if (currency == WBTC) { - return CurrencyRatioSortOrder.DENOMINATOR_MOST; + if (addr == USDC) { + return AddressRatioSortOrder.NUMERATOR_MOST; + } else if (addr == USDT) { + return AddressRatioSortOrder.NUMERATOR_MORE; + } else if (addr == DAI) { + return AddressRatioSortOrder.NUMERATOR; + } else if (addr == TBTC) { + return AddressRatioSortOrder.DENOMINATOR_MORE; + } else if (addr == WBTC) { + return AddressRatioSortOrder.DENOMINATOR_MOST; } else { return 0; } diff --git a/src/libraries/CurrencyRatioSortOrder.sol b/src/libraries/AddressRatioSortOrder.sol similarity index 65% rename from src/libraries/CurrencyRatioSortOrder.sol rename to src/libraries/AddressRatioSortOrder.sol index 1f3a719a..71fdfcf9 100644 --- a/src/libraries/CurrencyRatioSortOrder.sol +++ b/src/libraries/AddressRatioSortOrder.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -/// @title CurrencyRatioSortOrder -/// @notice Provides constants for sorting currencies when displaying price ratios -/// Currencies given larger values will be in the numerator of the price ratio +/// @title AddressRatioSortOrder +/// @notice Provides constants for sorting addresses when displaying price ratios +/// Addresses given larger values will be in the numerator of the price ratio /// @dev Reference: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/TokenRatioSortOrder.sol -library CurrencyRatioSortOrder { +library AddressRatioSortOrder { int256 constant NUMERATOR_MOST = 300; int256 constant NUMERATOR_MORE = 200; int256 constant NUMERATOR = 100; diff --git a/src/libraries/Descriptor.sol b/src/libraries/Descriptor.sol index 527e7bd8..56f7cb39 100644 --- a/src/libraries/Descriptor.sol +++ b/src/libraries/Descriptor.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; @@ -23,12 +22,12 @@ library Descriptor { struct ConstructTokenURIParams { uint256 tokenId; - Currency quoteCurrency; - Currency baseCurrency; - string quoteCurrencySymbol; - string baseCurrencySymbol; - uint8 quoteCurrencyDecimals; - uint8 baseCurrencyDecimals; + address quoteAddress; + address baseAddress; + string quoteAddressSymbol; + string baseAddressSymbol; + uint8 quoteAddressDecimals; + uint8 baseAddressDecimals; bool flipRatio; int24 tickLower; int24 tickUpper; @@ -45,15 +44,15 @@ library Descriptor { function constructTokenURI(ConstructTokenURIParams memory params) internal pure returns (string memory) { string memory name = generateName(params, feeToPercentString(params.fee)); string memory descriptionPartOne = generateDescriptionPartOne( - escapeQuotes(params.quoteCurrencySymbol), - escapeQuotes(params.baseCurrencySymbol), + escapeQuotes(params.quoteAddressSymbol), + escapeQuotes(params.baseAddressSymbol), addressToString(params.poolManager) ); string memory descriptionPartTwo = generateDescriptionPartTwo( params.tokenId.toString(), - escapeQuotes(params.baseCurrencySymbol), - addressToString(Currency.unwrap(params.quoteCurrency)), - addressToString(Currency.unwrap(params.baseCurrency)), + escapeQuotes(params.baseAddressSymbol), + params.quoteAddress == address(0) ? "Native" : addressToString(params.quoteAddress), + params.baseAddress == address(0) ? "Native" : addressToString(params.baseAddress), addressToString(params.hooks), feeToPercentString(params.fee) ); @@ -109,56 +108,56 @@ library Descriptor { } /// @notice Generates the first part of the description for a Uniswap v4 NFT - /// @param quoteCurrencySymbol The symbol of the quote currency - /// @param baseCurrencySymbol The symbol of the base currency + /// @param quoteAddressSymbol The symbol of the quote address + /// @param baseAddressSymbol The symbol of the base address /// @param poolManager The address of the pool manager /// @return The first part of the description function generateDescriptionPartOne( - string memory quoteCurrencySymbol, - string memory baseCurrencySymbol, + string memory quoteAddressSymbol, + string memory baseAddressSymbol, string memory poolManager ) private pure returns (string memory) { - // displays quote currency first, then base currency + // displays quote address first, then base address return string( abi.encodePacked( "This NFT represents a liquidity position in a Uniswap v4 ", - quoteCurrencySymbol, + quoteAddressSymbol, "-", - baseCurrencySymbol, + baseAddressSymbol, " pool. ", "The owner of this NFT can modify or redeem the position.\\n", "\\nPool Manager Address: ", poolManager, "\\n", - quoteCurrencySymbol + quoteAddressSymbol ) ); } /// @notice Generates the second part of the description for a Uniswap v4 NFTs /// @param tokenId The token ID - /// @param baseCurrencySymbol The symbol of the base currency - /// @param quoteCurrency The address of the quote currency - /// @param baseCurrency The address of the base currency + /// @param baseAddressSymbol The symbol of the base address + /// @param quoteAddress The address of the quote address + /// @param baseAddress The address of the base address /// @param hooks The address of the hooks contract /// @param feeTier The fee tier of the pool /// @return The second part of the description function generateDescriptionPartTwo( string memory tokenId, - string memory baseCurrencySymbol, - string memory quoteCurrency, - string memory baseCurrency, + string memory baseAddressSymbol, + string memory quoteAddress, + string memory baseAddress, string memory hooks, string memory feeTier ) private pure returns (string memory) { return string( abi.encodePacked( " Address: ", - quoteCurrency, + quoteAddress, "\\n", - baseCurrencySymbol, + baseAddressSymbol, " Address: ", - baseCurrency, + baseAddress, "\\nHook Address: ", hooks, "\\nFee Tier: ", @@ -166,7 +165,7 @@ library Descriptor { "\\nToken ID: ", tokenId, "\\n\\n", - unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + unicode"⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure addresses match the expected addresses, as symbols may be imitated." ) ); } @@ -180,29 +179,29 @@ library Descriptor { pure returns (string memory) { - // image shows in terms of price, ie quoteCurrency/baseCurrency + // image shows in terms of price, ie quoteAddress/baseAddress return string( abi.encodePacked( "Uniswap - ", feeTier, " - ", - escapeQuotes(params.quoteCurrencySymbol), + escapeQuotes(params.quoteAddressSymbol), "/", - escapeQuotes(params.baseCurrencySymbol), + escapeQuotes(params.baseAddressSymbol), " - ", tickToDecimalString( !params.flipRatio ? params.tickLower : params.tickUpper, params.tickSpacing, - params.baseCurrencyDecimals, - params.quoteCurrencyDecimals, + params.baseAddressDecimals, + params.quoteAddressDecimals, params.flipRatio ), "<>", tickToDecimalString( !params.flipRatio ? params.tickUpper : params.tickLower, params.tickSpacing, - params.baseCurrencyDecimals, - params.quoteCurrencyDecimals, + params.baseAddressDecimals, + params.quoteAddressDecimals, params.flipRatio ) ) @@ -262,15 +261,15 @@ library Descriptor { /// MIN or MAX are returned if tick is at the bottom or top of the price curve /// @param tick The tick (either tickLower or tickUpper) /// @param tickSpacing The tick spacing of the pool - /// @param baseCurrencyDecimals The decimals of the base currency - /// @param quoteCurrencyDecimals The decimals of the quote currency + /// @param baseAddressDecimals The decimals of the base address + /// @param quoteAddressDecimals The decimals of the quote address /// @param flipRatio True if the ratio was flipped /// @return The ratio value as a string function tickToDecimalString( int24 tick, int24 tickSpacing, - uint8 baseCurrencyDecimals, - uint8 quoteCurrencyDecimals, + uint8 baseAddressDecimals, + uint8 quoteAddressDecimals, bool flipRatio ) internal pure returns (string memory) { if (tick == (TickMath.MIN_TICK / tickSpacing) * tickSpacing) { @@ -282,7 +281,7 @@ library Descriptor { if (flipRatio) { sqrtRatioX96 = uint160(uint256(1 << 192) / sqrtRatioX96); } - return fixedPointToDecimalString(sqrtRatioX96, baseCurrencyDecimals, quoteCurrencyDecimals); + return fixedPointToDecimalString(sqrtRatioX96, baseAddressDecimals, quoteAddressDecimals); } } @@ -306,17 +305,17 @@ library Descriptor { /// @notice Adjusts the sqrt price for different currencies with different decimals /// @param sqrtRatioX96 The sqrt price at a specific tick - /// @param baseCurrencyDecimals The decimals of the base currency - /// @param quoteCurrencyDecimals The decimals of the quote currency + /// @param baseAddressDecimals The decimals of the base address + /// @param quoteAddressDecimals The decimals of the quote address /// @return adjustedSqrtRatioX96 The adjusted sqrt price - function adjustForDecimalPrecision(uint160 sqrtRatioX96, uint8 baseCurrencyDecimals, uint8 quoteCurrencyDecimals) + function adjustForDecimalPrecision(uint160 sqrtRatioX96, uint8 baseAddressDecimals, uint8 quoteAddressDecimals) private pure returns (uint256 adjustedSqrtRatioX96) { - uint256 difference = abs(int256(uint256(baseCurrencyDecimals)) - (int256(uint256(quoteCurrencyDecimals)))); + uint256 difference = abs(int256(uint256(baseAddressDecimals)) - (int256(uint256(quoteAddressDecimals)))); if (difference > 0 && difference <= 18) { - if (baseCurrencyDecimals > quoteCurrencyDecimals) { + if (baseAddressDecimals > quoteAddressDecimals) { adjustedSqrtRatioX96 = sqrtRatioX96 * (10 ** (difference / 2)); if (difference % 2 == 1) { adjustedSqrtRatioX96 = FullMath.mulDiv(adjustedSqrtRatioX96, sqrt10X128, 1 << 128); @@ -339,13 +338,13 @@ library Descriptor { return uint256(x >= 0 ? x : -x); } - function fixedPointToDecimalString(uint160 sqrtRatioX96, uint8 baseCurrencyDecimals, uint8 quoteCurrencyDecimals) + function fixedPointToDecimalString(uint160 sqrtRatioX96, uint8 baseAddressDecimals, uint8 quoteAddressDecimals) internal pure returns (string memory) { uint256 adjustedSqrtRatioX96 = - adjustForDecimalPrecision(sqrtRatioX96, baseCurrencyDecimals, quoteCurrencyDecimals); + adjustForDecimalPrecision(sqrtRatioX96, baseAddressDecimals, quoteAddressDecimals); uint256 value = FullMath.mulDiv(adjustedSqrtRatioX96, adjustedSqrtRatioX96, 1 << 64); bool priceBelow1 = adjustedSqrtRatioX96 < 2 ** 96; @@ -462,27 +461,27 @@ library Descriptor { /// @return svg The SVG image as a string function generateSVGImage(ConstructTokenURIParams memory params) internal pure returns (string memory svg) { SVG.SVGParams memory svgParams = SVG.SVGParams({ - quoteCurrency: addressToString(Currency.unwrap(params.quoteCurrency)), - baseCurrency: addressToString(Currency.unwrap(params.baseCurrency)), + quoteAddress: addressToString(params.quoteAddress), + baseAddress: addressToString(params.baseAddress), hooks: params.hooks, - quoteCurrencySymbol: params.quoteCurrencySymbol, - baseCurrencySymbol: params.baseCurrencySymbol, + quoteAddressSymbol: params.quoteAddressSymbol, + baseAddressSymbol: params.baseAddressSymbol, feeTier: feeToPercentString(params.fee), tickLower: params.tickLower, tickUpper: params.tickUpper, tickSpacing: params.tickSpacing, overRange: overRange(params.tickLower, params.tickUpper, params.tickCurrent), tokenId: params.tokenId, - color0: currencyToColorHex(params.quoteCurrency.toId(), 136), - color1: currencyToColorHex(params.baseCurrency.toId(), 136), - color2: currencyToColorHex(params.quoteCurrency.toId(), 0), - color3: currencyToColorHex(params.baseCurrency.toId(), 0), - x1: scale(getCircleCoord(params.quoteCurrency.toId(), 16, params.tokenId), 0, 255, 16, 274), - y1: scale(getCircleCoord(params.baseCurrency.toId(), 16, params.tokenId), 0, 255, 100, 484), - x2: scale(getCircleCoord(params.quoteCurrency.toId(), 32, params.tokenId), 0, 255, 16, 274), - y2: scale(getCircleCoord(params.baseCurrency.toId(), 32, params.tokenId), 0, 255, 100, 484), - x3: scale(getCircleCoord(params.quoteCurrency.toId(), 48, params.tokenId), 0, 255, 16, 274), - y3: scale(getCircleCoord(params.baseCurrency.toId(), 48, params.tokenId), 0, 255, 100, 484) + color0: addressToColorHex(uint256(uint160(params.quoteAddress)), 136), + color1: addressToColorHex(uint256(uint160(params.baseAddress)), 136), + color2: addressToColorHex(uint256(uint160(params.quoteAddress)), 0), + color3: addressToColorHex(uint256(uint160(params.baseAddress)), 0), + x1: scale(getCircleCoord(uint256(uint160(params.quoteAddress)), 16, params.tokenId), 0, 255, 16, 274), + y1: scale(getCircleCoord(uint256(uint160(params.baseAddress)), 16, params.tokenId), 0, 255, 100, 484), + x2: scale(getCircleCoord(uint256(uint160(params.quoteAddress)), 32, params.tokenId), 0, 255, 16, 274), + y2: scale(getCircleCoord(uint256(uint160(params.baseAddress)), 32, params.tokenId), 0, 255, 100, 484), + x3: scale(getCircleCoord(uint256(uint160(params.quoteAddress)), 48, params.tokenId), 0, 255, 16, 274), + y3: scale(getCircleCoord(uint256(uint160(params.baseAddress)), 48, params.tokenId), 0, 255, 100, 484) }); return SVG.generateSVG(svgParams); @@ -511,15 +510,15 @@ library Descriptor { return ((n - inMn) * (outMx - outMn) / (inMx - inMn) + outMn).toString(); } - function currencyToColorHex(uint256 currency, uint256 offset) internal pure returns (string memory str) { - return string((currency >> offset).toHexStringNoPrefix(3)); + function addressToColorHex(uint256 addr, uint256 offset) internal pure returns (string memory str) { + return string((addr >> offset).toHexStringNoPrefix(3)); } - function getCircleCoord(uint256 currency, uint256 offset, uint256 tokenId) internal pure returns (uint256) { - return (sliceCurrencyHex(currency, offset) * tokenId) % 255; + function getCircleCoord(uint256 addr, uint256 offset, uint256 tokenId) internal pure returns (uint256) { + return (sliceAddressHex(addr, offset) * tokenId) % 255; } - function sliceCurrencyHex(uint256 currency, uint256 offset) internal pure returns (uint256) { - return uint256(uint8(currency >> offset)); + function sliceAddressHex(uint256 addr, uint256 offset) internal pure returns (uint256) { + return uint256(uint8(addr >> offset)); } } diff --git a/src/libraries/SVG.sol b/src/libraries/SVG.sol index dea00e66..0da00a50 100644 --- a/src/libraries/SVG.sol +++ b/src/libraries/SVG.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {BitMath} from "@uniswap/v4-core/src/libraries/BitMath.sol"; import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; @@ -26,11 +25,11 @@ library SVG { string constant curve8 = "M1 1C1 97 49 145 145 145"; struct SVGParams { - string quoteCurrency; - string baseCurrency; + string quoteAddress; + string baseAddress; address hooks; - string quoteCurrencySymbol; - string baseCurrencySymbol; + string quoteAddressSymbol; + string baseAddressSymbol; string feeTier; int24 tickLower; int24 tickUpper; @@ -57,9 +56,9 @@ library SVG { abi.encodePacked( generateSVGDefs(params), generateSVGBorderText( - params.quoteCurrency, params.baseCurrency, params.quoteCurrencySymbol, params.baseCurrencySymbol + params.quoteAddress, params.baseAddress, params.quoteAddressSymbol, params.baseAddressSymbol ), - generateSVGCardMantle(params.quoteCurrencySymbol, params.baseCurrencySymbol, params.feeTier), + generateSVGCardMantle(params.quoteAddressSymbol, params.baseAddressSymbol, params.feeTier), generageSvgCurve(params.tickLower, params.tickUpper, params.tickSpacing, params.overRange), generateSVGPositionDataAndLocationCurve( params.tokenId.toString(), params.hooks, params.tickLower, params.tickUpper @@ -158,61 +157,61 @@ library SVG { ); } - /// @notice Generate the SVG for the moving border text displaying the quote and base currency addresses with their symbols - /// @param quoteCurrency The quote currency - /// @param baseCurrency The base currency - /// @param quoteCurrencySymbol The quote currency symbol - /// @param baseCurrencySymbol The base currency symbol + /// @notice Generate the SVG for the moving border text displaying the quote and base addresses with their symbols + /// @param quoteAddress The quote address + /// @param baseAddress The base address + /// @param quoteAddressSymbol The quote address symbol + /// @param baseAddressSymbol The base address symbol /// @return svg The SVG for the border NFT's border text function generateSVGBorderText( - string memory quoteCurrency, - string memory baseCurrency, - string memory quoteCurrencySymbol, - string memory baseCurrencySymbol + string memory quoteAddress, + string memory baseAddress, + string memory quoteAddressSymbol, + string memory baseAddressSymbol ) private pure returns (string memory svg) { svg = string( abi.encodePacked( '', '', - baseCurrency, + baseAddress, unicode" • ", - baseCurrencySymbol, + baseAddressSymbol, ' ', ' ', - baseCurrency, + baseAddress, unicode" • ", - baseCurrencySymbol, + baseAddressSymbol, ' ', '', - quoteCurrency, + quoteAddress, unicode" • ", - quoteCurrencySymbol, + quoteAddressSymbol, ' ', - quoteCurrency, + quoteAddress, unicode" • ", - quoteCurrencySymbol, + quoteAddressSymbol, ' ' ) ); } - /// @notice Generate the SVG for the card mantle displaying the quote and base currency symbols and fee tier - /// @param quoteCurrencySymbol The quote currency symbol - /// @param baseCurrencySymbol The base currency symbol + /// @notice Generate the SVG for the card mantle displaying the quote and base address symbols and fee tier + /// @param quoteAddressSymbol The quote address symbol + /// @param baseAddressSymbol The base address symbol /// @param feeTier The fee tier /// @return svg The SVG for the card mantle function generateSVGCardMantle( - string memory quoteCurrencySymbol, - string memory baseCurrencySymbol, + string memory quoteAddressSymbol, + string memory baseAddressSymbol, string memory feeTier ) private pure returns (string memory svg) { svg = string( abi.encodePacked( ' ', - quoteCurrencySymbol, + quoteAddressSymbol, "/", - baseCurrencySymbol, + baseAddressSymbol, '', feeTier, "", diff --git a/src/libraries/SafeCurrencyMetadata.sol b/src/libraries/SafeERC20Metadata.sol similarity index 64% rename from src/libraries/SafeCurrencyMetadata.sol rename to src/libraries/SafeERC20Metadata.sol index f4d7cf27..1346a3f7 100644 --- a/src/libraries/SafeCurrencyMetadata.sol +++ b/src/libraries/SafeERC20Metadata.sol @@ -2,41 +2,36 @@ pragma solidity ^0.8.0; import {IERC20Metadata} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {AddressStringUtil} from "./AddressStringUtil.sol"; -/// @title SafeCurrencyMetadata +/// @title SafeERC20Metadata /// @notice can produce symbols and decimals from inconsistent or absent ERC20 implementations /// @dev Reference: https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/SafeERC20Namer.sol -library SafeCurrencyMetadata { - using CurrencyLibrary for Currency; - +library SafeERC20Metadata { /// @notice attempts to extract the token symbol. if it does not implement symbol, returns a symbol derived from the address - /// @param currency The currency + /// @param addr The address /// @param nativeLabel The native label /// @return the token symbol - function currencySymbol(Currency currency, string memory nativeLabel) internal view returns (string memory) { - if (currency.isAddressZero()) { + function addressSymbol(address addr, string memory nativeLabel) internal view returns (string memory) { + if (addr == address(0)) { return nativeLabel; } - address currencyAddress = Currency.unwrap(currency); - string memory symbol = callAndParseStringReturn(currencyAddress, IERC20Metadata.symbol.selector); + string memory symbol = callAndParseStringReturn(addr, IERC20Metadata.symbol.selector); if (bytes(symbol).length == 0) { // fallback to 6 uppercase hex of address - return addressToSymbol(currencyAddress); + return addressToSymbol(addr); } return symbol; } /// @notice attempts to extract the token decimals, returns 0 if not implemented or not a uint8 - /// @param currency The currency + /// @param addr The address /// @return the token decimals - function currencyDecimals(Currency currency) internal view returns (uint8) { - if (currency.isAddressZero()) { + function addressDecimals(address addr) internal view returns (uint8) { + if (addr == address(0)) { return 18; } - (bool success, bytes memory data) = - Currency.unwrap(currency).staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + (bool success, bytes memory data) = addr.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); if (!success) { return 0; } @@ -64,18 +59,18 @@ library SafeCurrencyMetadata { } /// @notice produces a symbol from the address - the first 6 hex of the address string in upper case - /// @param currencyAddress the address of the currency + /// @param addr The address /// @return the symbol - function addressToSymbol(address currencyAddress) private pure returns (string memory) { - return AddressStringUtil.toAsciiString(currencyAddress, 6); + function addressToSymbol(address addr) private pure returns (string memory) { + return AddressStringUtil.toAsciiString(addr, 6); } /// @notice calls an external view contract method that returns a symbol, and parses the output into a string - /// @param currencyAddress the address of the currency + /// @param addr The address /// @param selector the selector of the symbol method /// @return the symbol - function callAndParseStringReturn(address currencyAddress, bytes4 selector) private view returns (string memory) { - (bool success, bytes memory data) = currencyAddress.staticcall(abi.encodeWithSelector(selector)); + function callAndParseStringReturn(address addr, bytes4 selector) private view returns (string memory) { + (bool success, bytes memory data) = addr.staticcall(abi.encodeWithSelector(selector)); // if not implemented, return empty string if (!success) { return ""; diff --git a/test/PositionDescriptor.t.sol b/test/PositionDescriptor.t.sol index dabe8393..381a4cdb 100644 --- a/test/PositionDescriptor.t.sol +++ b/test/PositionDescriptor.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; import {PositionDescriptor} from "../src/PositionDescriptor.sol"; -import {CurrencyRatioSortOrder} from "../src/libraries/CurrencyRatioSortOrder.sol"; +import {AddressRatioSortOrder} from "../src/libraries/AddressRatioSortOrder.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {LiquidityAmounts} from "@uniswap/v4-core/test/utils/LiquidityAmounts.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; @@ -44,30 +44,30 @@ contract PositionDescriptorTest is Test, PosmTestSetup, GasSnapshot { function test_setup_succeeds() public view { assertEq(address(positionDescriptor.poolManager()), address(manager)); assertEq(positionDescriptor.wrappedNative(), WETH9); - assertEq(positionDescriptor.nativeCurrencyLabel(), nativeCurrencyLabel); + assertEq(positionDescriptor.nativeAddressLabel(), nativeCurrencyLabel); } - function test_currencyRatioPriority_mainnet_succeeds() public { + function test_addressRatioPriority_mainnet_succeeds() public { vm.chainId(1); - assertEq(positionDescriptor.currencyRatioPriority(WETH9), CurrencyRatioSortOrder.DENOMINATOR); - assertEq(positionDescriptor.currencyRatioPriority(address(0)), CurrencyRatioSortOrder.DENOMINATOR); - assertEq(positionDescriptor.currencyRatioPriority(USDC), CurrencyRatioSortOrder.NUMERATOR_MOST); - assertEq(positionDescriptor.currencyRatioPriority(USDT), CurrencyRatioSortOrder.NUMERATOR_MORE); - assertEq(positionDescriptor.currencyRatioPriority(DAI), CurrencyRatioSortOrder.NUMERATOR); - assertEq(positionDescriptor.currencyRatioPriority(TBTC), CurrencyRatioSortOrder.DENOMINATOR_MORE); - assertEq(positionDescriptor.currencyRatioPriority(WBTC), CurrencyRatioSortOrder.DENOMINATOR_MOST); - assertEq(positionDescriptor.currencyRatioPriority(makeAddr("ALICE")), 0); + assertEq(positionDescriptor.addressRatioPriority(WETH9), AddressRatioSortOrder.DENOMINATOR); + assertEq(positionDescriptor.addressRatioPriority(address(0)), AddressRatioSortOrder.DENOMINATOR); + assertEq(positionDescriptor.addressRatioPriority(USDC), AddressRatioSortOrder.NUMERATOR_MOST); + assertEq(positionDescriptor.addressRatioPriority(USDT), AddressRatioSortOrder.NUMERATOR_MORE); + assertEq(positionDescriptor.addressRatioPriority(DAI), AddressRatioSortOrder.NUMERATOR); + assertEq(positionDescriptor.addressRatioPriority(TBTC), AddressRatioSortOrder.DENOMINATOR_MORE); + assertEq(positionDescriptor.addressRatioPriority(WBTC), AddressRatioSortOrder.DENOMINATOR_MOST); + assertEq(positionDescriptor.addressRatioPriority(makeAddr("ALICE")), 0); } - function test_currencyRatioPriority_notMainnet_succeeds() public { - assertEq(positionDescriptor.currencyRatioPriority(WETH9), CurrencyRatioSortOrder.DENOMINATOR); - assertEq(positionDescriptor.currencyRatioPriority(address(0)), CurrencyRatioSortOrder.DENOMINATOR); - assertEq(positionDescriptor.currencyRatioPriority(USDC), 0); - assertEq(positionDescriptor.currencyRatioPriority(USDT), 0); - assertEq(positionDescriptor.currencyRatioPriority(DAI), 0); - assertEq(positionDescriptor.currencyRatioPriority(TBTC), 0); - assertEq(positionDescriptor.currencyRatioPriority(WBTC), 0); - assertEq(positionDescriptor.currencyRatioPriority(makeAddr("ALICE")), 0); + function test_addressRatioPriority_notMainnet_succeeds() public { + assertEq(positionDescriptor.addressRatioPriority(WETH9), AddressRatioSortOrder.DENOMINATOR); + assertEq(positionDescriptor.addressRatioPriority(address(0)), AddressRatioSortOrder.DENOMINATOR); + assertEq(positionDescriptor.addressRatioPriority(USDC), 0); + assertEq(positionDescriptor.addressRatioPriority(USDT), 0); + assertEq(positionDescriptor.addressRatioPriority(DAI), 0); + assertEq(positionDescriptor.addressRatioPriority(TBTC), 0); + assertEq(positionDescriptor.addressRatioPriority(WBTC), 0); + assertEq(positionDescriptor.addressRatioPriority(makeAddr("ALICE")), 0); } function test_flipRatio_succeeds() public { @@ -122,7 +122,7 @@ contract PositionDescriptorTest is Test, PosmTestSetup, GasSnapshot { assertEq(token.name, "Uniswap - 0.3% - TEST/TEST - 1.0060<>1.0121"); assertEq( token.description, - unicode"This NFT represents a liquidity position in a Uniswap v4 TEST-TEST pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: 0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f\nTEST Address: 0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\nTEST Address: 0x2e234dae75c793f67a35089c9d99245e1c58470b\nHook Address: 0x0000000000000000000000000000000000000000\nFee Tier: 0.3%\nToken ID: 1\n\n⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure currency addresses match the expected currencies, as currency symbols may be imitated." + unicode"This NFT represents a liquidity position in a Uniswap v4 TEST-TEST pool. The owner of this NFT can modify or redeem the position.\n\nPool Manager Address: 0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f\nTEST Address: 0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\nTEST Address: 0x2e234dae75c793f67a35089c9d99245e1c58470b\nHook Address: 0x0000000000000000000000000000000000000000\nFee Tier: 0.3%\nToken ID: 1\n\n⚠️ DISCLAIMER: Due diligence is imperative when assessing this NFT. Make sure addresses match the expected addresses, as symbols may be imitated." ); }