Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Brechtpd committed Sep 9, 2021
1 parent 2af188d commit eac3aec
Show file tree
Hide file tree
Showing 18 changed files with 979 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build/
flattened/
node_modules/
.env*
*.DS_Store
137 changes: 137 additions & 0 deletions contracts/CoverCharts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./ERC721Tradable.sol";
import "./IPFS.sol";
import "./external/IL2MintableNFT.sol";


/**
* @title CoverCharts
*/
contract CoverCharts is ERC721Tradable, IL2MintableNFT
{
event MintFromL2(
address owner,
uint256 id,
uint amount,
address minter
);

event NameChanged(
uint256 id,
string name
);

address public immutable layer2Address;

// Per token name, settable by the token owner
mapping(uint256 => string) public tokenNames;

modifier onlyFromLayer2
{
require(msg.sender == layer2Address, "not authorized");
_;
}

constructor(
address _openseaProxyRegistryAddress,
address _layer2Address
)
ERC721Tradable(_openseaProxyRegistryAddress)
{
layer2Address = _layer2Address;
}

function initialize()
initializer
external
{
_initialize("Cover Charts", "CCH");
}

// Standard NFT

function safeMint(
address to,
uint256 tokenId
)
public
onlyOwner
{
_safeMint(to, tokenId);
}

function baseTokenURI()
public
pure
returns(string memory)
{
return "ipfs://";
}

function tokenURI(uint256 _tokenId)
override
public
pure
returns (string memory)
{
return string(
abi.encodePacked(
baseTokenURI(),
IPFS.encode(_tokenId),
"/metadata.json"
)
);
}

// Naming

function changeTokenName(
uint256 tokenId,
string memory name
)
public
{
address owner = ownerOf(tokenId);

require(_msgSender() == owner, "not the owner");
require(keccak256(bytes(name)) != keccak256(bytes(tokenNames[tokenId])), "new name is the same as the current one");

tokenNames[tokenId] = name;

emit NameChanged(tokenId, name);
}

// Layer 2 logic

function mintFromL2(
address to,
uint256 id,
uint amount,
address minter,
bytes calldata /*data*/
)
external
override
onlyFromLayer2
{
require(minter == owner(), "invalid minter");
require(amount == 1, "invalid amount");

_mint(to, id);
emit MintFromL2(to, id, amount, minter);
}

function minters()
public
view
override
returns (address[] memory)
{
address[] memory addresses = new address[](1);
addresses[0] = owner();
return addresses;
}
}
108 changes: 108 additions & 0 deletions contracts/ERC721Tradable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

import "./common/meta-transactions/ContentMixin.sol";
import "./common/meta-transactions/NativeMetaTransaction.sol";

contract OwnableDelegateProxy {}

contract ProxyRegistry {
mapping(address => OwnableDelegateProxy) public proxies;
}

/**
* @title ERC721Tradable
* ERC721Tradable - ERC721 contract that whitelists a trading address.
*/
abstract contract ERC721Tradable is ContextMixin, NativeMetaTransaction, Initializable, ERC721Upgradeable, ERC721EnumerableUpgradeable, PausableUpgradeable, OwnableUpgradeable, ERC721BurnableUpgradeable
{
address public immutable proxyRegistryAddress;

constructor(
address _proxyRegistryAddress
) {
proxyRegistryAddress = _proxyRegistryAddress;
}

function _initialize(string memory _name, string memory _symbol)
internal
{
__ERC721_init(_name, _symbol);
__ERC721Enumerable_init();
__Pausable_init();
__Ownable_init();

_initializeEIP712(_name);
}

function pause()
public
onlyOwner
{
_pause();
}

function unpause()
public
onlyOwner
{
_unpause();
}

function _beforeTokenTransfer(address from, address to, uint256 tokenId)
internal
whenNotPaused
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
{
super._beforeTokenTransfer(from, to, tokenId);
}

function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721Upgradeable, ERC721EnumerableUpgradeable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}

// OpenSea custom logic

/**
* Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
*/
function isApprovedForAll(address owner, address operator)
override
public
view
returns (bool)
{
// Whitelist OpenSea proxy contract for easy trading.
ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
if (address(proxyRegistry.proxies(owner)) == operator) {
return true;
}

return super.isApprovedForAll(owner, operator);
}

/**
* This is used instead of msg.sender as transactions won't be sent by the original token owner, but by OpenSea.
*/
function _msgSender()
internal
override
view
returns (address sender)
{
return ContextMixin.msgSender();
}
}
50 changes: 50 additions & 0 deletions contracts/IPFS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;


/// @title IPFS
/// @author Brecht Devos - <[email protected]>
library IPFS
{
bytes constant ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

// Encodes the 32 byte data as an IPFS v0 CID
function encode(uint256 data)
internal
pure
returns (string memory)
{
// We'll be always be encoding 34 bytes
bytes memory out = new bytes(46);

// Copy alphabet to memory
bytes memory alphabet = ALPHABET;

// We have to encode 0x1220data, which is 34 bytes and doesn't fit in a single uint256.
// Keep the first 32 bytes in the uint, but do the encoding as if 0x1220 was part of the data value.
// 0 = (0x12200000000000000000000000000000000000000000000000000000000000000000) % 58
out[45] = alphabet[data % 58];
data /= 58;
// 4 = (0x12200000000000000000000000000000000000000000000000000000000000000000 / 58) % 58
data += 4;
out[44] = alphabet[data % 58];
data /= 58;
// 40 = (0x12200000000000000000000000000000000000000000000000000000000000000000 / 58 / 58) % 58
data += 40;
out[43] = alphabet[data % 58];
data /= 58;

// Add the top bytes now there is anough space in the uint256
// This constant is 0x12200000000000000000000000000000000000000000000000000000000000000000 / 58 / 58 / 58
data += 2753676319555676466672318311740497214108679778017611511045364661305900823779;

// The rest is just simple base58 encoding
for (uint i = 3; i < 46; i++) {
out[45 - i] = alphabet[data % 58];
data /= 58;
}

return string(out);
}
}
25 changes: 25 additions & 0 deletions contracts/Migrations.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Migrations {
address public owner;
uint public last_completed_migration;

constructor() {
owner = msg.sender;
}

modifier restricted() {
if (msg.sender == owner) _;
}

function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}

function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}
26 changes: 26 additions & 0 deletions contracts/common/meta-transactions/ContentMixin.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

abstract contract ContextMixin {
function msgSender()
internal
view
returns (address payable sender)
{
if (msg.sender == address(this)) {
bytes memory array = msg.data;
uint256 index = msg.data.length;
assembly {
// Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those.
sender := and(
mload(add(array, index)),
0xffffffffffffffffffffffffffffffffffffffff
)
}
} else {
sender = payable(msg.sender);
}
return sender;
}
}
Loading

0 comments on commit eac3aec

Please sign in to comment.