From bd90cee89ef9fd178e76d424b738eb74bd994a05 Mon Sep 17 00:00:00 2001 From: dianakocsis Date: Thu, 3 Oct 2024 22:58:03 -0400 Subject: [PATCH] currency name plus more tests --- script/DeployPosm.s.sol | 4 +- src/PositionDescriptor.sol | 10 +- src/libraries/Descriptor.sol | 14 +-- ...0Metadata.sol => SafeCurrencyMetadata.sol} | 92 +++++++++---------- test/libraries/Descriptor.t.sol | 32 +++++++ 5 files changed, 92 insertions(+), 60 deletions(-) rename src/libraries/{SafeERC20Metadata.sol => SafeCurrencyMetadata.sol} (78%) diff --git a/script/DeployPosm.s.sol b/script/DeployPosm.s.sol index 4233208c..5bbb6184 100644 --- a/script/DeployPosm.s.sol +++ b/script/DeployPosm.s.sol @@ -18,12 +18,12 @@ contract DeployPosmTest is Script { address poolManager, address permit2, uint256 unsubscribeGasLimit, - address weth, + address wrappedNative, string memory nativeCurrencyLabel ) public returns (PositionDescriptor positionDescriptor, PositionManager posm) { vm.startBroadcast(); - positionDescriptor = new PositionDescriptor(IPoolManager(poolManager), weth, nativeCurrencyLabel); + positionDescriptor = new PositionDescriptor(IPoolManager(poolManager), wrappedNative, nativeCurrencyLabel); console2.log("PositionDescriptor", address(positionDescriptor)); posm = new PositionManager{salt: hex"03"}( diff --git a/src/PositionDescriptor.sol b/src/PositionDescriptor.sol index 4e26346c..572146d0 100644 --- a/src/PositionDescriptor.sol +++ b/src/PositionDescriptor.sol @@ -11,7 +11,7 @@ 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 {SafeERC20Metadata} from "./libraries/SafeERC20Metadata.sol"; +import {SafeCurrencyMetadata} from "./libraries/SafeCurrencyMetadata.sol"; /// @title Describes NFT token positions /// @notice Produces a string containing the data URI for a JSON metadata string @@ -63,10 +63,10 @@ contract PositionDescriptor is IPositionDescriptor { tokenId: tokenId, quoteCurrency: quoteCurrency, baseCurrency: baseCurrency, - quoteCurrencySymbol: SafeERC20Metadata.currencySymbol(quoteCurrency, nativeCurrencyLabel), - baseCurrencySymbol: SafeERC20Metadata.currencySymbol(baseCurrency, nativeCurrencyLabel), - quoteCurrencyDecimals: SafeERC20Metadata.currencyDecimals(quoteCurrency), - baseCurrencyDecimals: SafeERC20Metadata.currencyDecimals(baseCurrency), + quoteCurrencySymbol: SafeCurrencyMetadata.currencySymbol(quoteCurrency, nativeCurrencyLabel), + baseCurrencySymbol: SafeCurrencyMetadata.currencySymbol(baseCurrency, nativeCurrencyLabel), + quoteCurrencyDecimals: SafeCurrencyMetadata.currencyDecimals(quoteCurrency), + baseCurrencyDecimals: SafeCurrencyMetadata.currencyDecimals(baseCurrency), flipRatio: _flipRatio, tickLower: positionInfo.tickLower(), tickUpper: positionInfo.tickUpper(), diff --git a/src/libraries/Descriptor.sol b/src/libraries/Descriptor.sol index 8e32a35d..09a3cf5c 100644 --- a/src/libraries/Descriptor.sol +++ b/src/libraries/Descriptor.sol @@ -439,16 +439,16 @@ library Descriptor { } else { // represents fee less than 1% // else if decimal < 1 - nZeros = 5 - digits; // number of zeros, inlcuding the zero before decimal - params.zerosStartIndex = 2; // leading zeros will start after the decimal point - params.zerosEndIndex = uint8(nZeros + params.zerosStartIndex - 1); // end index for leading zeros - params.bufferLength = uint8(nZeros + numSigfigs + 2); // total length of string buffer, including "0." and "%" - params.sigfigIndex = uint8(params.bufferLength - 2); // index of starting signficant figure + nZeros = 5 - digits; + params.zerosStartIndex = 2; + params.zerosEndIndex = uint8(nZeros + params.zerosStartIndex - 1); + params.bufferLength = uint8(nZeros + numSigfigs + 2); + params.sigfigIndex = uint8(params.bufferLength - 2); params.isLessThanOne = true; } - params.sigfigs = uint256(fee) / (10 ** (digits - numSigfigs)); // the signficant figures of the fee + params.sigfigs = uint256(fee) / (10 ** (digits - numSigfigs)); params.isPercent = true; - params.decimalIndex = digits > 4 ? uint8(digits - 4) : 0; // based on total number of digits in the fee + params.decimalIndex = digits > 4 ? uint8(digits - 4) : 0; return generateDecimalString(params); } diff --git a/src/libraries/SafeERC20Metadata.sol b/src/libraries/SafeCurrencyMetadata.sol similarity index 78% rename from src/libraries/SafeERC20Metadata.sol rename to src/libraries/SafeCurrencyMetadata.sol index 0e4b9e86..84aa2881 100644 --- a/src/libraries/SafeERC20Metadata.sol +++ b/src/libraries/SafeCurrencyMetadata.sol @@ -5,56 +5,12 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; import {AddressStringUtil} from "./AddressStringUtil.sol"; -/// @title SafeERC20Metadata +/// @title SafeCurrencyMetadata /// @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 SafeERC20Metadata { +library SafeCurrencyMetadata { using CurrencyLibrary for Currency; - function bytes32ToString(bytes32 x) private pure returns (string memory) { - bytes memory bytesString = new bytes(32); - uint256 charCount = 0; - for (uint256 j = 0; j < 32; j++) { - bytes1 char = x[j]; - if (char != 0) { - bytesString[charCount] = char; - charCount++; - } - } - bytes memory bytesStringTrimmed = new bytes(charCount); - for (uint256 j = 0; j < charCount; j++) { - bytesStringTrimmed[j] = bytesString[j]; - } - return string(bytesStringTrimmed); - } - - /// @notice produces a token symbol from the address - the first 6 hex of the address string in upper case - /// @param token the token address - /// @return the token symbol - function addressToSymbol(address token) private pure returns (string memory) { - return AddressStringUtil.toAsciiString(token, 6); - } - - /// @notice calls an external view token contract method that returns a symbol, and parses the output into a string - /// @param token the token address - /// @param selector the selector of the symbol method - /// @return the token symbol - function callAndParseStringReturn(address token, bytes4 selector) private view returns (string memory) { - (bool success, bytes memory data) = token.staticcall(abi.encodeWithSelector(selector)); - // if not implemented, return empty string - if (!success) { - return ""; - } - // bytes32 data always has length 32 - if (data.length == 32) { - bytes32 decoded = abi.decode(data, (bytes32)); - return bytes32ToString(decoded); - } else if (data.length > 64) { - return abi.decode(data, (string)); - } - return ""; - } - /// @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 nativeLabel The native label @@ -89,4 +45,48 @@ library SafeERC20Metadata { } return 0; } + + function bytes32ToString(bytes32 x) private pure returns (string memory) { + bytes memory bytesString = new bytes(32); + uint256 charCount = 0; + for (uint256 j = 0; j < 32; j++) { + bytes1 char = x[j]; + if (char != 0) { + bytesString[charCount] = char; + charCount++; + } + } + bytes memory bytesStringTrimmed = new bytes(charCount); + for (uint256 j = 0; j < charCount; j++) { + bytesStringTrimmed[j] = bytesString[j]; + } + return string(bytesStringTrimmed); + } + + /// @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 + /// @return the symbol + function addressToSymbol(address currencyAddress) private pure returns (string memory) { + return AddressStringUtil.toAsciiString(currencyAddress, 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 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)); + // if not implemented, return empty string + if (!success) { + return ""; + } + // bytes32 data always has length 32 + if (data.length == 32) { + bytes32 decoded = abi.decode(data, (bytes32)); + return bytes32ToString(decoded); + } else if (data.length > 64) { + return abi.decode(data, (string)); + } + return ""; + } } diff --git a/test/libraries/Descriptor.t.sol b/test/libraries/Descriptor.t.sol index 2dd6339a..e191c5a5 100644 --- a/test/libraries/Descriptor.t.sol +++ b/test/libraries/Descriptor.t.sol @@ -7,6 +7,7 @@ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; contract DescriptorTest is Test { function test_feeToPercentString_succeeds() public pure { + assertEq(Descriptor.feeToPercentString(0x800000), "Dynamic"); assertEq(Descriptor.feeToPercentString(0), "0%"); assertEq(Descriptor.feeToPercentString(1), "0.0001%"); assertEq(Descriptor.feeToPercentString(30), "0.003%"); @@ -115,4 +116,35 @@ contract DescriptorTest is Test { assertEq(Descriptor.tickToDecimalString(1000, tickSpacing, 18, 10, true), "90484000"); assertEq(Descriptor.tickToDecimalString(1000, tickSpacing, 10, 18, true), "0.0000000090484"); } + + function test_fixedPointToDecimalString() public pure { + assertEq( + Descriptor.fixedPointToDecimalString(1457647476727839560029885420909913413788472405159, 18, 18), + "338490000000000000000000000000000000000" + ); + assertEq( + Descriptor.fixedPointToDecimalString(4025149349925610116743993887520032712, 18, 18), "2581100000000000" + ); + assertEq(Descriptor.fixedPointToDecimalString(3329657202331788924044422905302854, 18, 18), "1766200000"); + assertEq(Descriptor.fixedPointToDecimalString(16241966553695418990605751641065, 18, 18), "42026"); + assertEq(Descriptor.fixedPointToDecimalString(2754475062069337566441091812235, 18, 18), "1208.7"); + assertEq(Descriptor.fixedPointToDecimalString(871041495427277622831427623669, 18, 18), "120.87"); + assertEq(Descriptor.fixedPointToDecimalString(275447506206933756644109181223, 18, 18), "12.087"); + + assertEq(Descriptor.fixedPointToDecimalString(88028870788706913884596530851, 18, 18), "1.2345"); + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 18, 18), "1.0000"); + assertEq(Descriptor.fixedPointToDecimalString(27837173154497669652482281089, 18, 18), "0.12345"); + assertEq(Descriptor.fixedPointToDecimalString(1559426812423768092342, 18, 18), "0.00000000000000038741"); + assertEq(Descriptor.fixedPointToDecimalString(74532606916587, 18, 18), "0.00000000000000000000000000000088498"); + assertEq( + Descriptor.fixedPointToDecimalString(4947797163, 18, 18), "0.0000000000000000000000000000000000000029387" + ); + + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 18, 16), "100.00"); + assertEq(Descriptor.fixedPointToDecimalString(250541448375047931186413801569, 18, 17), "100.00"); + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 24, 5), "1.0000"); + + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 10, 18), "0.000000010000"); + assertEq(Descriptor.fixedPointToDecimalString(79228162514264337593543950336, 7, 18), "0.000000000010000"); + } }