Skip to content

Commit

Permalink
feat: add readme, remove some warning, change mintAndRegisterIpAndMak…
Browse files Browse the repository at this point in the history
…eDerivative to use function of SPG
  • Loading branch information
hoanm committed Oct 14, 2024
1 parent 3420cb7 commit 142045a
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 124 deletions.
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,55 @@
# punkga-contracts-solidity
# punkga-contracts-solidity

This project includes smart contracts of Punkga using on Story Protocol network.

## Building

Before building, users must install dependencies. Run the following command:

```bash
yarn
```

### Compile the contracts

```bash
force build
```

### Testing

To run the tests, use the following command:

```bash
force test
```

### Deployment

To deploy the contracts, you can use Foundry's `forge` tool. Before deploying, you must change the constructor's arguments of contract in `args` file (by rename `args_example`).

```bash
forge create --rpc-url https://testnet.storyrpc.io --private-key <YOUR_PRIVATE_KEY> contracts/<YOUR_CONTRACT>.sol:<YOUR_CONTRACT> --constructor-args-path args
```

Replace `<YOUR_PRIVATE_KEY>` with your private key, and `<YOUR_CONTRACT>` with the name of the contract you want to deploy.

## Contracts

The repository contains the following smart contracts:

- `AccessControl.sol`: Implements access control mechanisms.
- `ERC721Mock.sol`: A mock implementation of the ERC721 standard.
- `ILaunchpadNFT.sol`: Interface for the Launchpad NFT contract.
- `ISPGNFT.sol`: Interface for the SPG NFT contract.
- `IStoryProtocolGateway.sol`: Interface for the Story Protocol Gateway contract.
- `LaunchpadNFT.sol`: Implementation of the Launchpad NFT contract.
- `MockERC20.sol`: A mock implementation of the ERC20 standard.
- `multicall.sol`: Implements multicall functionality.
- `PunkgaContestNFT.sol`: Implementation of the Punkga Contest NFT contract.
- `StoryCampaign.sol`: Implementation of the Story Campaign contract.
- `StoryLaunchpad.sol`: Implementation of the Story Launchpad contract.

### License

This project is licensed under the MIT License. See the LICENSE file for details.
1 change: 1 addition & 0 deletions args_example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<owner_address> 0x69415CE984A79a3Cfbe3F51024C63b6C107331e3
50 changes: 25 additions & 25 deletions contracts/IStoryProtocolGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,36 @@ pragma solidity ^0.8.23;

import { PILTerms } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/IPILicenseTemplate.sol";

/// @notice Struct for metadata for NFT minting and IP registration.
/// @dev Leave the nftMetadataURI empty if not minting an NFT.
/// @param ipMetadataURI The URI of the metadata for the IP.
/// @param ipMetadataHash The hash of the metadata for the IP.
/// @param nftMetadataURI The URI of the metadata for the NFT.
/// @param nftMetadataHash The hash of the metadata for the IP NFT.
struct IPMetadata {
string ipMetadataURI;
bytes32 ipMetadataHash;
string nftMetadataURI;
bytes32 nftMetadataHash;
}

/// @notice Struct for creating a derivative IP without license tokens.
/// @param parentIpIds The IDs of the parent IPs to link the registered derivative IP.
/// @param licenseTemplate The address of the license template to be used for the linking.
/// @param licenseTermsIds The IDs of the license terms to be used for the linking.
/// @param royaltyContext The context for royalty module, should be empty for Royalty Policy LAP.
struct MakeDerivative {
address[] parentIpIds;
address licenseTemplate;
uint256[] licenseTermsIds;
bytes royaltyContext;
}

interface IStoryProtocolGateway {
/// @notice Event emitted when a new NFT collection is created.
/// @param nftContract The address of the newly created NFT collection.
event CollectionCreated(address indexed nftContract);

/// @notice Struct for metadata for NFT minting and IP registration.
/// @dev Leave the nftMetadataURI empty if not minting an NFT.
/// @param ipMetadataURI The URI of the metadata for the IP.
/// @param ipMetadataHash The hash of the metadata for the IP.
/// @param nftMetadataURI The URI of the metadata for the NFT.
/// @param nftMetadataHash The hash of the metadata for the IP NFT.
struct IPMetadata {
string ipMetadataURI;
bytes32 ipMetadataHash;
string nftMetadataURI;
bytes32 nftMetadataHash;
}

/// @notice Struct for signature data for execution via IP Account.
/// @param signer The address of the signer for execution with signature.
/// @param deadline The deadline for the signature.
Expand All @@ -31,18 +43,6 @@ interface IStoryProtocolGateway {
bytes signature;
}

/// @notice Struct for creating a derivative IP without license tokens.
/// @param parentIpIds The IDs of the parent IPs to link the registered derivative IP.
/// @param licenseTemplate The address of the license template to be used for the linking.
/// @param licenseTermsIds The IDs of the license terms to be used for the linking.
/// @param royaltyContext The context for royalty module, should be empty for Royalty Policy LAP.
struct MakeDerivative {
address[] parentIpIds;
address licenseTemplate;
uint256[] licenseTermsIds;
bytes royaltyContext;
}

/// @notice Creates a new NFT collection to be used by SPG.
/// @param name The name of the collection.
/// @param symbol The symbol of the collection.
Expand Down
145 changes: 48 additions & 97 deletions contracts/StoryCampaign.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,25 @@ import { ILicenseTemplate } from "@story-protocol/protocol-core/contracts/interf
import { IRoyaltyModule } from "@story-protocol/protocol-core/contracts/interfaces/modules/royalty/IRoyaltyModule.sol";
import { IIPAccount } from "@story-protocol/protocol-core/contracts/interfaces/IIPAccount.sol";

import { IStoryProtocolGateway } from "./IStoryProtocolGateway.sol";
import { IStoryProtocolGateway, MakeDerivative, IPMetadata } from "./IStoryProtocolGateway.sol";

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "./AccessControl.sol";
import "./PunkgaContestNFT.sol";
// import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
// import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import { AccessControl } from "./AccessControl.sol";
import { LaunchpadNFT } from "./PunkgaContestNFT.sol";

contract StoryCampaign is AccessControl, IERC721Receiver {
using SafeERC20 for IERC20;

address public ipAssetRegistry = 0x1a9d0d28a0422F26D31Be72Edc6f13ea4371E11B;
address public licensingModule = 0xd81fd78f557b457b4350cB95D20b547bFEb4D857;
address public licenseToken = 0xc7A302E03cd7A304394B401192bfED872af501BE;
address public licenseTemplate = 0x0752f61E59fD2D39193a74610F1bd9a6Ade2E3f9;
address public coreMetadataView = 0x48ecAa9F197135A4614d1c7A5Db5641ffd8ad2b9;
address public licenseRegistry = 0xedf8e338F05f7B1b857C3a8d3a0aBB4bc2c41723;
address public licenseTemplate = 0x0752f61E59fD2D39193a74610F1bd9a6Ade2E3f9;
address public coreMetadataView = 0x48ecAa9F197135A4614d1c7A5Db5641ffd8ad2b9;
address public licenseRegistry = 0xedf8e338F05f7B1b857C3a8d3a0aBB4bc2c41723;
address public royaltyModule = 0x3C27b2D7d30131D4b58C3584FD7c86e3358744de;

address public collectionAddress;
Expand All @@ -43,23 +44,10 @@ contract StoryCampaign is AccessControl, IERC721Receiver {
//creator address -> count
mapping(address => uint256) public userMintCount;

constructor(address _owner, address _storyProtocolGateway) public AccessControl(_owner) {
constructor(address _owner, address _storyProtocolGateway) public AccessControl(_owner) {
SPG_ADDRESS = _storyProtocolGateway;
}

struct MakeDerivative {
address[] parentIpIds;
uint256[] licenseTermsIds;
bytes royaltyContext;
}

struct IPMetadata {
string ipMetadataURI;
bytes32 ipMetadataHash;
string nftMetadataURI;
bytes32 nftMetadataHash;
}

event CollectionCreated(address indexed nftContract);
/**
* Always returns `IERC721Receiver.onERC721Received.selector`.
Expand All @@ -70,61 +58,53 @@ contract StoryCampaign is AccessControl, IERC721Receiver {

// function _owns(address _licensorIpid) internal view returns (bool) {
// return (ICoreMetadataViewModule(coreMetadataView).getOwner(_licensorIpid) == msg.sender);
// }
// }

function setStoryProtocolGateway(address _addr) public onlyOwner {
SPG_ADDRESS = _addr;
}

function setIpAssetRegistry(address _addr) public onlyOwner {
ipAssetRegistry = _addr;
}
}

function setLicensingModule(address _addr) public onlyOwner {
licensingModule = _addr;
}
}

function setMaxParents(uint256 _maxParents) public onlyOwner {
maxParents = _maxParents;
}
}

function setMaxIpasset(uint256 _maxIpasset) public onlyOwner {
maxParents = _maxIpasset;
}
}

function setLicenseToken(address _addr) public onlyOwner {
licenseToken = _addr;
}
}

function setCoreMetadataView(address _addr) public onlyOwner {
coreMetadataView = _addr;
}
}

function setLicenseRegistry(address _addr) public onlyOwner {
licenseRegistry = _addr;
}
}

function setCollectionAddress(address _addr) public onlyOwner {
collectionAddress = _addr;
}
}

function setLicenseTemplate(address _addr) public onlyOwner {
licenseTemplate = _addr;
}
}

function setRoyaltyModule(address _addr) public onlyOwner {
royaltyModule = _addr;
}

function getToken(address ipAccountAddress) public view returns (uint chainId, address tokenContract, uint tokenId) {
(chainId, tokenContract, tokenId) = IIPAccount(payable(ipAccountAddress)).token();
}
}

function transferHelper(
address token,
address payable add,
uint256 amount
) private {
function transferHelper(address token, address payable add, uint256 amount) private {
if (token == address(0)) {
add.transfer(amount);
} else {
Expand All @@ -135,85 +115,56 @@ contract StoryCampaign is AccessControl, IERC721Receiver {
function createCollection(string memory colectionName, string memory colectionSymbol) public onlyOperator {
collectionAddress = address(new LaunchpadNFT(msg.sender, colectionName, colectionSymbol));
emit CollectionCreated(address(collectionAddress));
}

function _mintAndRegisterIp(
address recipient,
string memory uri
) internal returns (address ipId, uint256 tokenId) {

tokenId = LaunchpadNFT(collectionAddress).mintTokens(recipient, uri);
require(tokenId > 0);

ipId = IIPAssetRegistry(ipAssetRegistry).register(block.chainid, collectionAddress, tokenId);
}
}

function mintAndRegisterIpAndAttach(
address recipient,
IStoryProtocolGateway.IPMetadata calldata ipMetadata,
IPMetadata calldata ipMetadata,
PILTerms calldata terms
) public onlyOperator returns (address ipId, uint256 tokenId, uint256 licenseTermsId) {
require(userMintCount[recipient] + 1 <= maxIpasset, "StoryCampaign: AboveMintLimit");

userMintCount[recipient] += 1;
// (ipId, tokenId) = _mintAndRegisterIp(address(this), uri);
// //Register and attack PIL
// licenseTermsId = _registerPILTermsAndAttach(ipId, terms);

// LaunchpadNFT(collectionAddress).safeTransferFrom(address(this), recipient, tokenId);
userMintCount[recipient] += 1;

IStoryProtocolGateway(SPG_ADDRESS).mintAndRegisterIpAndAttachPILTerms(collectionAddress, recipient, ipMetadata, terms);
IStoryProtocolGateway(SPG_ADDRESS).mintAndRegisterIpAndAttachPILTerms(
collectionAddress,
recipient,
ipMetadata,
terms
);
}

function _registerPILTermsAndAttach(
address ipId,
PILTerms calldata terms
) internal returns (uint256 licenseTermsId) {

licenseTermsId = IPILicenseTemplate(licenseTemplate).registerLicenseTerms(terms);
// Returns if license terms are already attached.
if (ILicenseRegistry(licenseRegistry).hasIpAttachedLicenseTerms(ipId, licenseTemplate, licenseTermsId)) return licenseTermsId;

ILicensingModule(licensingModule).attachLicenseTerms(ipId, licenseTemplate, licenseTermsId);
if (ILicenseRegistry(licenseRegistry).hasIpAttachedLicenseTerms(ipId, licenseTemplate, licenseTermsId))
return licenseTermsId;

ILicensingModule(licensingModule).attachLicenseTerms(ipId, licenseTemplate, licenseTermsId);
}

function mintAndRegisterIpAndMakeDerivative(
MakeDerivative calldata derivData,
IPMetadata calldata ipMetadata,
address recipient
) external onlyOperator returns (address ipId, uint256 tokenId) {

require(derivData.parentIpIds.length <= maxParents, "StoryCampaign: AboveParentLimit");
require(derivData.parentIpIds.length <= maxParents, "StoryCampaign: Parent Limit Reached");

for (uint256 i = 0; i < derivData.parentIpIds.length; i++) {
(, address nftContract,) = getToken(derivData.parentIpIds[i]);
(, address nftContract, ) = IIPAccount(payable(derivData.parentIpIds[i])).token();
require(nftContract == collectionAddress, "StoryCampaign: Ipasset not come from this contest collection");
}

tokenId = LaunchpadNFT(collectionAddress).mintTokens(address(this), ipMetadata.nftMetadataURI);
require(tokenId > 0);

ipId = IIPAssetRegistry(ipAssetRegistry).register(block.chainid, collectionAddress, tokenId);

// MetadataHelper.setMetadata(ipId, address(CORE_METADATA_MODULE), ipMetadata);

_collectMintFeesAndSetApproval(
msg.sender,
derivData.parentIpIds,
derivData.licenseTermsIds
);

ILicensingModule(licensingModule).registerDerivative({
childIpId: ipId,
parentIpIds: derivData.parentIpIds,
licenseTermsIds: derivData.licenseTermsIds,
licenseTemplate: licenseTemplate,
royaltyContext: derivData.royaltyContext
});
}

LaunchpadNFT(collectionAddress).safeTransferFrom(address(this), recipient, tokenId);
}
IStoryProtocolGateway(SPG_ADDRESS).mintAndRegisterIpAndMakeDerivative(
collectionAddress,
derivData,
ipMetadata,
recipient
);
}

/// @dev Aggregate license mint fees for all parent IPs.
/// @param payerAddress The address of the payer for the license mint fees.
Expand Down Expand Up @@ -268,5 +219,5 @@ contract StoryCampaign is AccessControl, IERC721Receiver {
IERC20(mintFeeCurrencyToken).forceApprove(royaltyModule, totalMintFee);
}
}
}
}
}
1 change: 0 additions & 1 deletion contracts/StoryLaunchpad.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.23;
pragma experimental ABIEncoderV2;

Expand Down

0 comments on commit 142045a

Please sign in to comment.