From d03e4d6ffe4164d7f060bb7770e1996c91c75982 Mon Sep 17 00:00:00 2001 From: Andrea Franz Date: Wed, 2 Oct 2024 17:21:20 +0200 Subject: [PATCH] chore(XPNFTToken): add base XPNFTToken --- src/XPNFTToken.sol | 128 +++++++++++++++++++++++++++++++++++ test/RewardsStreamer.t.sol | 2 +- test/RewardsStreamerMP.t.sol | 3 +- test/XPNFTToken.t.sol | 32 +++++++++ 4 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 src/XPNFTToken.sol create mode 100644 test/XPNFTToken.t.sol diff --git a/src/XPNFTToken.sol b/src/XPNFTToken.sol new file mode 100644 index 0000000..1e363f7 --- /dev/null +++ b/src/XPNFTToken.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { Base64 } from "@openzeppelin/contracts/utils/Base64.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +interface IERC20 { + function balanceOf(address account) external view returns (uint256); +} + +contract XPNFTToken is Ownable { + error XPNFT__TransferNotAllowed(); + error XPNFT__InvalidTokenId(); + + string private _name = "XPNFT"; + string private _symbol = "XPNFT"; + string private _imagePrefix = ""; + string private _imageSuffix = ""; + + IERC20 private _xpToken; + + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + modifier onlyValidTokenId(uint256 tokenId) { + if (tokenId > type(uint160).max) { + revert XPNFT__InvalidTokenId(); + } + _; + } + + constructor(address xpTokenAddress, string memory imagePrefix, string memory imageSuffix) Ownable(msg.sender) { + _xpToken = IERC20(xpTokenAddress); + _imagePrefix = imagePrefix; + _imageSuffix = imageSuffix; + } + + function setImageStrings(string memory imagePrefix, string memory imageSuffix) external onlyOwner { + _imagePrefix = imagePrefix; + _imageSuffix = imageSuffix; + } + + function name() external view returns (string memory) { + return _name; + } + + function symbol() external view returns (string memory) { + return _symbol; + } + + function mint() external { + emit Transfer(msg.sender, msg.sender, uint256(uint160(msg.sender))); + } + + function balanceOf(address) external pure returns (uint256) { + return 1; + } + + function ownerOf(uint256 tokenId) external pure onlyValidTokenId(tokenId) returns (address) { + address owner = address(uint160(tokenId)); + return owner; + } + + function safeTransferFrom(address, address, uint256, bytes calldata) external pure { + revert XPNFT__TransferNotAllowed(); + } + + function safeTransferFrom(address, address, uint256) external pure { + revert XPNFT__TransferNotAllowed(); + } + + function transferFrom(address, address, uint256) external pure { + revert XPNFT__TransferNotAllowed(); + } + + function approve(address, uint256) external pure { + revert XPNFT__TransferNotAllowed(); + } + + function setApprovalForAll(address, bool) external pure { + revert XPNFT__TransferNotAllowed(); + } + + function getApproved(uint256) external pure returns (address) { + return address(0); + } + + function isApprovedForAll(address, address) external pure returns (bool) { + return false; + } + + function tokenURI(uint256 tokenId) external view onlyValidTokenId(tokenId) returns (string memory) { + address owner = address(uint160(tokenId)); + return _createTokenURI(owner); + } + + function _createTokenURI(address owner) internal view returns (string memory) { + string memory baseName = "XPNFT Token "; + string memory baseDescription = "This is a XPNFT token for address "; + uint256 balance = _xpToken.balanceOf(owner) / 1e18; + + string memory propName = string(abi.encodePacked(baseName, Strings.toHexString(owner))); + string memory propDescription = string( + abi.encodePacked(baseDescription, Strings.toHexString(owner), " with balance ", Strings.toString(balance)) + ); + string memory image = _generateImage(balance); + + bytes memory json = abi.encodePacked( + "{\"name\":\"", + propName, + "\",\"description\":\"", + propDescription, + "\",\"image\":\"data:image/svg+xml;base64,", + image, + "\"}" + ); + + string memory jsonBase64 = Base64.encode(json); + return string(abi.encodePacked("data:application/json;base64,", jsonBase64)); + } + + function _generateImage(uint256 balance) internal view returns (string memory) { + string memory text = Strings.toString(balance); + bytes memory svg = abi.encodePacked(_imagePrefix, text, _imageSuffix); + + return Base64.encode(svg); + } +} diff --git a/test/RewardsStreamer.t.sol b/test/RewardsStreamer.t.sol index 9ed3a2c..32c15a1 100644 --- a/test/RewardsStreamer.t.sol +++ b/test/RewardsStreamer.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; -import { Test, console } from "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; import { RewardsStreamer } from "../src/RewardsStreamer.sol"; import { MockToken } from "./mocks/MockToken.sol"; diff --git a/test/RewardsStreamerMP.t.sol b/test/RewardsStreamerMP.t.sol index ee3c4e7..d83ecfc 100644 --- a/test/RewardsStreamerMP.t.sol +++ b/test/RewardsStreamerMP.t.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.26; -import { Test, console } from "forge-std/Test.sol"; +import { Test } from "forge-std/Test.sol"; import { RewardsStreamerMP } from "../src/RewardsStreamerMP.sol"; import { MockToken } from "./mocks/MockToken.sol"; -import "forge-std/console.sol"; contract RewardsStreamerMPTest is Test { MockToken rewardToken; diff --git a/test/XPNFTToken.t.sol b/test/XPNFTToken.t.sol new file mode 100644 index 0000000..b371c9c --- /dev/null +++ b/test/XPNFTToken.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { Test, console } from "forge-std/Test.sol"; +import { MockToken } from "./mocks/MockToken.sol"; +import { XPNFTToken } from "../src/XPNFTToken.sol"; + +contract XPNFTTokenTest is Test { + MockToken erc20Token; + XPNFTToken nft; + + address alice = makeAddr("alice"); + + string imagePrefix = + ''; + string imageSuffix = ""; + + function setUp() public { + erc20Token = new MockToken("Test", "TEST"); + nft = new XPNFTToken(address(erc20Token), imagePrefix, imageSuffix); + + address[1] memory users = [alice]; + for (uint256 i = 0; i < users.length; i++) { + erc20Token.mint(users[i], 10e18); + } + } + + function test() public { + string memory metadata = nft.tokenURI(uint256(uint160(alice))); + console.log(metadata); + } +}