Skip to content

Commit

Permalink
feat(nfts): trailblazer profile picture (#18009)
Browse files Browse the repository at this point in the history
Co-authored-by: Bernat Canal Garceran <[email protected]>
  • Loading branch information
bennettyong and bearni95 authored Sep 4, 2024
1 parent f1aeac3 commit 6f2004f
Show file tree
Hide file tree
Showing 5 changed files with 526 additions and 0 deletions.
97 changes: 97 additions & 0 deletions packages/nfts/contracts/profile/RegisterProfilePicture.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from
"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { Ownable2StepUpgradeable } from
"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";

/// @title A store for trailblazer profile pictures
/// @author Bennett Yogn
/// @dev All function calls are currently implemented without side effects
contract RegisterProfilePicture is Initializable, UUPSUpgradeable, Ownable2StepUpgradeable {
error InvalidNFTContract(address nftContract);
error NotTokenOwner(address nftContract, uint256 tokenId, address caller);

/// @notice struct of nft contract address and token id
struct ProfilePicture {
address nftContract;
uint256 tokenId;
}

/// @notice mapping of user id to profile picture
mapping(address user => ProfilePicture pfp) public profilePicture;

event ProfilePictureSet(
address indexed user, address indexed nftContract, uint256 indexed tokenId
);

/// @notice Contract initializer
function initialize() public initializer {
_transferOwnership(_msgSender());
}

/// @notice Set the profile picture
/// @param nftContract The address of the nft to set as the profile picture
/// @param tokenId The tokenId of the nft to set as the profile picture
function setPFP(address nftContract, uint256 tokenId) external {
if (IERC721(nftContract).supportsInterface(type(IERC721).interfaceId)) {
// Check if the provided contract address is a valid ERC721 contract
if (IERC721(nftContract).ownerOf(tokenId) != _msgSender()) {
revert NotTokenOwner(nftContract, tokenId, _msgSender());
}
} else if (IERC1155(nftContract).supportsInterface(type(IERC1155).interfaceId)) {
// Check if the provided contract address is a valid ERC1155 contract
if (IERC1155(nftContract).balanceOf(_msgSender(), tokenId) == 0) {
revert NotTokenOwner(nftContract, tokenId, _msgSender());
}
} else {
// If the contract does not support ERC721 or ERC1155 interfaces
revert InvalidNFTContract(nftContract);
}

// Set the PFP
profilePicture[_msgSender()] = ProfilePicture(nftContract, tokenId);

emit ProfilePictureSet(_msgSender(), nftContract, tokenId);
}

/// @notice Get the profile picture of a user
/// @param user The address of user
function getProfilePicture(address user) external view returns (string memory) {
ProfilePicture memory userProfilePicture = profilePicture[user];

if (IERC721(userProfilePicture.nftContract).supportsInterface(type(IERC721).interfaceId)) {
// ERC721 case: Check ownership before returning the URI
if (IERC721(userProfilePicture.nftContract).ownerOf(userProfilePicture.tokenId) != user)
{
revert NotTokenOwner(
userProfilePicture.nftContract, userProfilePicture.tokenId, user
);
}
return ERC721(userProfilePicture.nftContract).tokenURI(userProfilePicture.tokenId);
} else if (
IERC1155(userProfilePicture.nftContract).supportsInterface(type(IERC1155).interfaceId)
) {
// ERC1155 case: Check ownership before returning the URI
if (
IERC1155(userProfilePicture.nftContract).balanceOf(user, userProfilePicture.tokenId)
== 0
) {
revert NotTokenOwner(
userProfilePicture.nftContract, userProfilePicture.tokenId, user
);
}
return ERC1155(userProfilePicture.nftContract).uri(userProfilePicture.tokenId);
} else {
// If the contract does not support ERC721 or ERC1155 interfaces
revert InvalidNFTContract(userProfilePicture.nftContract);
}
}

/// @notice Internal method to authorize an upgrade
function _authorizeUpgrade(address) internal virtual override onlyOwner { }
}
49 changes: 49 additions & 0 deletions packages/nfts/script/profile/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { UtilsScript } from "./Utils.s.sol";
import { Script, console } from "forge-std/src/Script.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { RegisterProfilePicture } from "../../contracts/profile/RegisterProfilePicture.sol";

contract DeployScript is Script {
UtilsScript public utils;
string public jsonLocation;
uint256 public deployerPrivateKey;
address public deployerAddress;

function setUp() public {
utils = new UtilsScript();
utils.setUp();

jsonLocation = utils.getContractJsonLocation();
deployerPrivateKey = utils.getPrivateKey();
deployerAddress = utils.getAddress();
}

function run() public {
string memory jsonRoot = "root";

vm.startBroadcast(deployerPrivateKey);

// deploy token with empty root
address impl = address(new RegisterProfilePicture());
address proxy = address(
new ERC1967Proxy(
impl,
abi.encodeCall(
RegisterProfilePicture.initialize, ()
)
)
);

RegisterProfilePicture profile = RegisterProfilePicture(proxy);

console.log("Deployed TaikoPartyTicket to:", address(profile));

string memory finalJson = vm.serializeAddress(jsonRoot, "RegisterProfilePicture", address(profile));
vm.writeJson(finalJson, jsonLocation);

vm.stopBroadcast();
}
}
61 changes: 61 additions & 0 deletions packages/nfts/script/profile/Utils.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { Script, console } from "forge-std/src/Script.sol";
import "forge-std/src/StdJson.sol";

contract UtilsScript is Script {
using stdJson for string;

uint256 public chainId;

string public lowercaseNetworkKey;
string public uppercaseNetworkKey;

function setUp() public {
// load all network configs
chainId = block.chainid;

if (chainId == 31_337) {
lowercaseNetworkKey = "localhost";
uppercaseNetworkKey = "LOCALHOST";
} else if (chainId == 17_000) {
lowercaseNetworkKey = "holesky";
uppercaseNetworkKey = "HOLESKY";
} else if (chainId == 167_001) {
lowercaseNetworkKey = "devnet";
uppercaseNetworkKey = "DEVNET";
} else if (chainId == 11_155_111) {
lowercaseNetworkKey = "sepolia";
uppercaseNetworkKey = "SEPOLIA";
} else if (chainId == 167_008) {
lowercaseNetworkKey = "katla";
uppercaseNetworkKey = "KATLA";
} else if (chainId == 167_000) {
lowercaseNetworkKey = "mainnet";
uppercaseNetworkKey = "MAINNET";
} else if (chainId == 167_009) {
lowercaseNetworkKey = "hekla";
uppercaseNetworkKey = "HEKLA";
} else {
revert("Unsupported chainId");
}
}

function getPrivateKey() public view returns (uint256) {
string memory lookupKey = string.concat(uppercaseNetworkKey, "_PRIVATE_KEY");
return vm.envUint(lookupKey);
}

function getAddress() public view returns (address) {
string memory lookupKey = string.concat(uppercaseNetworkKey, "_ADDRESS");
return vm.envAddress(lookupKey);
}

function getContractJsonLocation() public view returns (string memory) {
string memory root = vm.projectRoot();
return string.concat(root, "/deployments/profile/", lowercaseNetworkKey, ".json");
}

function run() public { }
}
Loading

0 comments on commit 6f2004f

Please sign in to comment.