Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: optimize NFTDescriptor contract #1113

Merged
merged 4 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion script/GenerateSVG.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ contract GenerateSVG is BaseScript, LockupNFTDescriptor {
progress: stringifyPercentage(progress),
progressNumerical: progress,
lockupAddress: LOCKUP.toHexString(),
lockupModel: "Lockup Linear",
status: status
})
);
Expand Down
54 changes: 7 additions & 47 deletions src/LockupNFTDescriptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ILockupNFTDescriptor } from "./interfaces/ILockupNFTDescriptor.sol";
import { ISablierLockup } from "./interfaces/ISablierLockup.sol";
import { ISablierLockupBase } from "./interfaces/ISablierLockupBase.sol";
import { Errors } from "./libraries/Errors.sol";
import { NFTSVG } from "./libraries/NFTSVG.sol";
import { SVGElements } from "./libraries/SVGElements.sol";
import { Lockup } from "./types/DataTypes.sol";

/*

██╗ ██████╗ ██████╗██╗ ██╗██╗ ██╗██████╗ ███╗ ██╗███████╗████████╗
Expand Down Expand Up @@ -50,7 +50,6 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {
bool isTransferable;
string json;
ISablierLockup lockup;
string lockupModel;
string lockupStringified;
bytes returnData;
string status;
Expand All @@ -65,22 +64,12 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {

// Load the contracts.
vars.lockup = ISablierLockup(address(lockup));
vars.lockupModel = mapSymbol(lockup);
vars.lockupStringified = address(lockup).toHexString();
vars.tokenSymbol = safeTokenSymbol(vars.token);
vars.depositedAmount = vars.lockup.getDepositedAmount(streamId);

// Retrieve the underlying token contract's address.
if (vars.lockupModel.equal("Sablier Lockup")) {
// For Lockup contract versions v2.0.0 and later, use the `getUnderlyingToken` function.
vars.token = address(vars.lockup.getUnderlyingToken(streamId));
}
// For Lockup contract versions earlier than v2.0.0, use the `getAsset` function.
else {
(, bytes memory returnData) =
address(lockup).staticcall(abi.encodeWithSignature("getAsset(uint256)", streamId));
vars.token = abi.decode(returnData, (address));
}
vars.token = address(vars.lockup.getUnderlyingToken(streamId));
vars.tokenSymbol = safeTokenSymbol(vars.token);

// Load the stream's data.
vars.status = stringifyStatus(vars.lockup.statusOf(streamId));
Expand All @@ -103,8 +92,7 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {
lockupAddress: vars.lockupStringified,
progress: stringifyPercentage(vars.streamedPercentage),
progressNumerical: vars.streamedPercentage,
status: vars.status,
lockupModel: vars.lockupModel
status: vars.status
})
);

Expand All @@ -125,15 +113,14 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {
}),
',"description":"',
generateDescription({
lockupModel: vars.lockupModel,
tokenSymbol: vars.tokenSymbol,
lockupStringified: vars.lockupStringified,
tokenAddress: vars.token.toHexString(),
streamId: streamId.toString(),
isTransferable: vars.isTransferable
}),
'","external_url":"https://sablier.com","name":"',
generateName({ lockupModel: vars.lockupModel, streamId: streamId.toString() }),
string.concat("Sablier Lockup #", streamId.toString()),
'","image":"data:image/svg+xml;base64,',
Base64.encode(bytes(vars.svg)),
'"}'
Expand Down Expand Up @@ -286,7 +273,6 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {

/// @notice Generates a string with the NFT's JSON metadata description, which provides a high-level overview.
function generateDescription(
string memory lockupModel,
string memory tokenSymbol,
string memory lockupStringified,
string memory tokenAddress,
Expand All @@ -304,15 +290,12 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {
: unicode"❕INFO: This NFT is non-transferable. It cannot be sold or transferred to another account.";

return string.concat(
"This NFT represents a payment stream in a Sablier Lockup ",
lockupModel,
" contract. The owner of this NFT can withdraw the streamed tokens, which are denominated in ",
"This NFT represents a stream in Sablier Lockup contract. The owner of this NFT can withdraw the streamed tokens, which are denominated in ",
tokenSymbol,
".\\n\\n- Stream ID: ",
streamId,
"\\n- ",
lockupModel,
" Address: ",
"Sablier Lockup Address: ",
lockupStringified,
"\\n- ",
tokenSymbol,
Expand All @@ -323,12 +306,6 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {
);
}

/// @notice Generates a string with the NFT's JSON metadata name, which is unique for each stream.
/// @dev The `streamId` is equivalent to the ERC-721 `tokenId`.
function generateName(string memory lockupModel, string memory streamId) internal pure returns (string memory) {
return string.concat("Sablier ", lockupModel, " #", streamId);
}

/// @notice Checks whether the provided string contains only alphanumeric characters, spaces, and dashes.
/// @dev Note that this returns true for empty strings.
function isAllowedCharacter(string memory str) internal pure returns (bool) {
Expand All @@ -352,23 +329,6 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor {
return true;
}

/// @notice Maps ERC-721 symbols to human-readable model names.
/// @dev Reverts if the symbol is unknown.
function mapSymbol(IERC721Metadata sablier) internal view returns (string memory) {
string memory symbol = sablier.symbol();
if (symbol.equal("SAB-LOCKUP")) {
return "Sablier Lockup";
} else if (symbol.equal("SAB-LOCKUP-LIN") || symbol.equal("SAB-V2-LOCKUP-LIN")) {
return "Sablier Lockup Linear";
} else if (symbol.equal("SAB-LOCKUP-DYN") || symbol.equal("SAB-V2-LOCKUP-DYN")) {
return "Sablier Lockup Dynamic";
} else if (symbol.equal("SAB-LOCKUP-TRA") || symbol.equal("SAB-V2-LOCKUP-TRA")) {
return "Sablier Lockup Tranched";
} else {
revert Errors.LockupNFTDescriptor_UnknownNFT(sablier, symbol);
}
}

/// @notice Retrieves the token's decimals safely, defaulting to "0" if an error occurs.
/// @dev Performs a low-level call to handle tokens in which the decimals are not implemented.
function safeTokenDecimals(address token) internal view returns (uint8) {
Expand Down
11 changes: 3 additions & 8 deletions src/libraries/NFTSVG.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ library NFTSVG {
string tokenSymbol;
string duration;
string lockupAddress;
string lockupModel;
string progress;
uint256 progressNumerical;
string status;
Expand Down Expand Up @@ -89,7 +88,7 @@ library NFTSVG {
'<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">',
SVGElements.BACKGROUND,
generateDefs(params.accentColor, params.status, vars.cards),
generateFloatingText(params.lockupAddress, params.lockupModel, params.tokenAddress, params.tokenSymbol),
generateFloatingText(params.lockupAddress, params.tokenAddress, params.tokenSymbol),
generateHrefs(vars.progressXPosition, vars.statusXPosition, vars.amountXPosition, vars.durationXPosition),
"</svg>"
);
Expand Down Expand Up @@ -119,7 +118,6 @@ library NFTSVG {

function generateFloatingText(
string memory lockupAddress,
string memory lockupModel,
string memory tokenAddress,
string memory tokenSymbol
)
Expand All @@ -131,12 +129,9 @@ library NFTSVG {
'<text text-rendering="optimizeSpeed">',
SVGElements.floatingText({
offset: "-100%",
text: string.concat(lockupAddress, unicode" • ", "Sablier ", lockupModel)
}),
SVGElements.floatingText({
offset: "0%",
text: string.concat(lockupAddress, unicode" • ", "Sablier ", lockupModel)
text: string.concat(lockupAddress, unicode" • ", "Sablier Lockup")
}),
SVGElements.floatingText({ offset: "0%", text: string.concat(lockupAddress, unicode" • ", "Sablier Lockup") }),
SVGElements.floatingText({ offset: "-50%", text: string.concat(tokenAddress, unicode" • ", tokenSymbol) }),
SVGElements.floatingText({ offset: "50%", text: string.concat(tokenAddress, unicode" • ", tokenSymbol) }),
"</text>"
Expand Down
Loading
Loading