Transaction Hash:
Block:
12905484 at Jul-27-2021 02:25:40 AM +UTC
Transaction Fee:
0.022162344 ETH
$61.73
Gas Used:
923,431 Gas / 24 Gwei
Emitted Events:
245 |
0x224b867ba9db850671f5a0fc6b75a2a825ce8e19.0x5133bb164b64ffa4461bc0c782a5c0e71cdc9d6c6ef5aa9af84f7fd2cd966d8e( 0x5133bb164b64ffa4461bc0c782a5c0e71cdc9d6c6ef5aa9af84f7fd2cd966d8e, 000000000000000000000000d8aaa606dba50f7615db4ebf0e439af062befeb3, 0000000000000000000000000000000000000000000000000000000000000080, 00000000000000000000000000000000000000000000000000000000000000c0, 000000000000000000000000dc1f98682f4f8a5c6d54f345f448437b83f5e432, 0000000000000000000000000000000000000000000000000000000000000008, 4c696f6e2044414f000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000004, 4c494f4e00000000000000000000000000000000000000000000000000000000 )
|
246 |
CrowdfundEditions.EditionCreated( quantity=100, price=100000000000000000, fundingRecipient=TieredCrowdfundProxy, editionId=14, contentHash=ABE0B169738B36229B199A41B12F72ACA93BDF8BB4B0CDBDE6F3C1433021023C )
|
247 |
CrowdfundEditions.EditionCreated( quantity=30, price=1000000000000000000, fundingRecipient=TieredCrowdfundProxy, editionId=15, contentHash=2074E50D528C62B975B880DBA63E5BDC9A0127E6EF5A8FC8FA76E270C28D3723 )
|
248 |
CrowdfundEditions.EditionCreated( quantity=10, price=3000000000000000000, fundingRecipient=TieredCrowdfundProxy, editionId=16, contentHash=7271CF232C4FBEFDE55D8039493B8EDC963FAA2CB62AE3EED0016956F3791D53 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x224B867b...825Ce8e19 | |||||
0x5A0b54D5...D3E029c4c
Miner
| (Spark Pool) | 61.734296082165182851 Eth | 61.756458426165182851 Eth | 0.022162344 | |
0xD8Aaa606...062BEfEb3 |
0 Eth
Nonce: 0
|
0 Eth
Nonce: 1
| |||
0xdC1F9868...b83f5E432 | (Sybil Delegate: BlockchainatCU) |
0.279540472498973855 Eth
Nonce: 12
|
0.257378128498973855 Eth
Nonce: 13
| 0.022162344 | |
0xEF3c951e...5BcbF393C |
Execution Trace
0x224b867ba9db850671f5a0fc6b75a2a825ce8e19.ef067c2d( )
TieredCrowdfundProxy.60806040( )
-
0x224b867ba9db850671f5a0fc6b75a2a825ce8e19.CALL( )
-
0x224b867ba9db850671f5a0fc6b75a2a825ce8e19.CALL( )
-
0x224b867ba9db850671f5a0fc6b75a2a825ce8e19.CALL( )
-
-
CrowdfundEditions.createEditions( tiers=, fundingRecipient=0xD8Aaa606DBA50F7615Db4EBF0E439AF062BEfEb3, minter=0xD8Aaa606DBA50F7615Db4EBF0E439AF062BEfEb3 )
File 1 of 2: CrowdfundEditions
File 2 of 2: TieredCrowdfundProxy
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.5; interface IERC721 { event Transfer( address indexed from, address indexed to, uint256 indexed tokenId ); event Approval( address indexed owner, address indexed approved, uint256 indexed tokenId ); event ApprovalForAll( address indexed owner, address indexed operator, bool approved ); function balanceOf(address owner) external view returns (uint256 balance); function ownerOf(uint256 tokenId) external view returns (address owner); function safeTransferFrom( address from, address to, uint256 tokenId ) external; function transferFrom( address from, address to, uint256 tokenId ) external; function approve(address to, uint256 tokenId) external; function getApproved(uint256 tokenId) external view returns (address operator); function setApprovalForAll(address operator, bool _approved) external; function isApprovedForAll(address owner, address operator) external view returns (bool); function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data ) external; } interface IERC721Metadata { function name() external view returns (string memory); function symbol() external view returns (string memory); function tokenURI(uint256 tokenId) external view returns (string memory); } interface IERC721Receiver { function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4); } interface IERC165 { function supportsInterface(bytes4 interfaceId) external view returns (bool); } abstract contract ERC165 is IERC165 { function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC165).interfaceId; } } /** * Based on: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol */ contract ERC721 is ERC165, IERC721 { mapping(uint256 => address) private _owners; mapping(address => uint256) private _balances; mapping(uint256 => address) private _tokenApprovals; mapping(address => mapping(address => bool)) private _operatorApprovals; function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || super.supportsInterface(interfaceId); } function balanceOf(address owner) public view virtual override returns (uint256) { require( owner != address(0), "ERC721: balance query for the zero address" ); return _balances[owner]; } function ownerOf(uint256 tokenId) public view virtual override returns (address) { address owner = _owners[tokenId]; require( owner != address(0), "ERC721: owner query for nonexistent token" ); return owner; } function tokenURI(uint256 tokenId) public view virtual returns (string memory) { require( _exists(tokenId), "ERC721Metadata: URI query for nonexistent token" ); string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId)) : ""; } /** * @dev Base URI for computing {tokenURI}. Empty by default, can be overriden * in child contracts. */ function _baseURI() internal view virtual returns (string memory) { return ""; } function approve(address to, uint256 tokenId) public virtual override { address owner = ERC721.ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require( msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all" ); _approve(to, tokenId); } function getApproved(uint256 tokenId) public view virtual override returns (address) { require( _exists(tokenId), "ERC721: approved query for nonexistent token" ); return _tokenApprovals[tokenId]; } function setApprovalForAll(address operator, bool approved) public virtual override { require(operator != msg.sender, "ERC721: approve to caller"); _operatorApprovals[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { return _operatorApprovals[owner][operator]; } function transferFrom( address from, address to, uint256 tokenId ) public virtual override { //solhint-disable-next-line max-line-length require( _isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved" ); _transfer(from, to, tokenId); } function safeTransferFrom( address from, address to, uint256 tokenId ) public virtual override { safeTransferFrom(from, to, tokenId, ""); } function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory _data ) public virtual override { require( _isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved" ); _safeTransfer(from, to, tokenId, _data); } function _safeTransfer( address from, address to, uint256 tokenId, bytes memory _data ) internal virtual { _transfer(from, to, tokenId); require( _checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer" ); } function _exists(uint256 tokenId) internal view virtual returns (bool) { return _owners[tokenId] != address(0); } function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { require( _exists(tokenId), "ERC721: operator query for nonexistent token" ); address owner = ERC721.ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } function _safeMint(address to, uint256 tokenId) internal virtual { _safeMint(to, tokenId, ""); } function _safeMint( address to, uint256 tokenId, bytes memory _data ) internal virtual { _mint(to, tokenId); require( _checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer" ); } function _mint(address to, uint256 tokenId) internal virtual { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _balances[to] += 1; _owners[tokenId] = to; emit Transfer(address(0), to, tokenId); } function _burn(uint256 tokenId) internal virtual { address owner = ERC721.ownerOf(tokenId); // Clear approvals _approve(address(0), tokenId); _balances[owner] -= 1; delete _owners[tokenId]; emit Transfer(owner, address(0), tokenId); } function _transfer( address from, address to, uint256 tokenId ) internal virtual { require( ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own" ); require(to != address(0), "ERC721: transfer to the zero address"); // Clear approvals from the previous owner _approve(address(0), tokenId); _balances[from] -= 1; _balances[to] += 1; _owners[tokenId] = to; emit Transfer(from, to, tokenId); } function _approve(address to, uint256 tokenId) internal virtual { _tokenApprovals[tokenId] = to; emit Approval(ERC721.ownerOf(tokenId), to, tokenId); } function _checkOnERC721Received( address from, address to, uint256 tokenId, bytes memory _data ) private returns (bool) { if (isContract(to)) { try IERC721Receiver(to).onERC721Received( msg.sender, from, tokenId, _data ) returns (bytes4 retval) { return retval == IERC721Receiver(to).onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert( "ERC721: transfer to non ERC721Receiver implementer" ); } else { // solhint-disable-next-line no-inline-assembly assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7f6a1666fac8ecff5dd467d0938069bc221ea9e0/contracts/utils/Address.sol function isContract(address account) internal view returns (bool) { uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } } // File contracts/interface/ICrowdfundEditions.sol interface ICrowdfundEditions { struct Edition { // The maximum number of tokens that can be sold. uint256 quantity; // The price at which each token will be sold, in ETH. uint256 price; // The account that will receive sales revenue. address payable fundingRecipient; // The number of tokens sold so far. uint256 numSold; bytes32 contentHash; } struct EditionTier { // The maximum number of tokens that can be sold. uint256 quantity; // The price at which each token will be sold, in ETH. uint256 price; bytes32 contentHash; } function buyEdition(uint256 editionId, address recipient) external payable returns (uint256 tokenId); function editionPrice(uint256 editionId) external view returns (uint256); function createEditions( EditionTier[] memory tier, // The account that should receive the revenue. address payable fundingRecipient, address minter ) external; function contractURI() external view returns (string memory); } // File contracts/CrowdfundEditions.sol /** * @title CrowdfundEditions * @author MirrorXYZ */ contract CrowdfundEditions is ERC721, ICrowdfundEditions { // ============ Constants ============ string public constant name = "Crowdfunded Mirror Editions"; string public constant symbol = "CROWDFUND_EDITIONS"; uint256 internal constant REENTRANCY_NOT_ENTERED = 1; uint256 internal constant REENTRANCY_ENTERED = 2; // ============ Setup Storage ============ // The CrowdfundFactory that is able to create editions. address public editionCreator; // ============ Mutable Storage ============ // Mapping of edition id to descriptive data. mapping(uint256 => Edition) public editions; // Mapping of token id to edition id. mapping(uint256 => uint256) public tokenToEdition; // The contract that is able to mint. mapping(uint256 => address) public editionToMinter; // `nextTokenId` increments with each token purchased, globally across all editions. uint256 private nextTokenId; // Editions start at 1, in order that unsold tokens don't map to the first edition. uint256 private nextEditionId = 1; // Reentrancy uint256 internal reentrancyStatus; // Administration address public owner; address public nextOwner; // Base URI can be modified by multisig owner, for intended future // migration of API domain to a decentralized one. string public baseURI; // ============ Events ============ event EditionCreated( uint256 quantity, uint256 price, address fundingRecipient, uint256 indexed editionId, bytes32 contentHash ); event EditionPurchased( uint256 indexed editionId, uint256 indexed tokenId, // `numSold` at time of purchase represents the "serial number" of the NFT. uint256 numSold, uint256 amountPaid, // The account that paid for and received the NFT. address buyer, address receiver ); event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); event EditionCreatorChanged( address indexed previousCreator, address indexed newCreator ); // ============ Modifiers ============ modifier onlyOwner() { require(isOwner(), "caller is not the owner."); _; } modifier onlyNextOwner() { require(isNextOwner(), "current owner must set caller as next owner."); _; } // ============ Constructor ============ constructor(string memory baseURI_, address owner_) { baseURI = baseURI_; owner = owner_; } // ============ Setup ============ function setEditionCreator(address editionCreator_) external { require(editionCreator == address(0), "already set"); editionCreator = editionCreator_; emit EditionCreatorChanged(address(0), editionCreator_); } // ============ Edition Methods ============ function createEditions( EditionTier[] memory tiers, // The account that should receive the revenue. address payable fundingRecipient, // The address (e.g. crowdfund proxy) that is allowed to mint // tokens in this edition. address minter ) external override { // Only the crowdfund factory can create editions. require(msg.sender == editionCreator); // Copy the next edition id, which we reference in the loop. uint256 firstEditionId = nextEditionId; // Update the next edition id to what we expect after the loop. nextEditionId += tiers.length; // Execute a loop that created editions. for (uint8 x = 0; x < tiers.length; x++) { uint256 id = firstEditionId + x; uint256 quantity = tiers[x].quantity; uint256 price = tiers[x].price; bytes32 contentHash = tiers[x].contentHash; editions[id] = Edition({ quantity: quantity, price: price, fundingRecipient: fundingRecipient, numSold: 0, contentHash: contentHash }); editionToMinter[id] = minter; emit EditionCreated( quantity, price, fundingRecipient, id, contentHash ); } } function buyEdition(uint256 editionId, address recipient) external payable override returns (uint256 tokenId) { // Only the minter can call this function. // This allows us to mint through another contract, and // there not have to transfer funds into this contract to purchase. require(msg.sender == editionToMinter[editionId]); // Track and update token id. tokenId = nextTokenId; nextTokenId++; // Check that the edition exists. Note: this is redundant // with the next check, but it is useful for clearer error messaging. require(editions[editionId].quantity > 0, "Edition does not exist"); // Check that there are still tokens available to purchase. require( editions[editionId].numSold < editions[editionId].quantity, "This edition is already sold out." ); // Increment the number of tokens sold for this edition. editions[editionId].numSold++; // Mint a new token for the sender, using the `tokenId`. _mint(recipient, tokenId); // Store the mapping of token id to the edition being purchased. tokenToEdition[tokenId] = editionId; emit EditionPurchased( editionId, tokenId, editions[editionId].numSold, msg.value, msg.sender, recipient ); return tokenId; } // ============ NFT Methods ============ // Returns e.g. https://mirror-api.com/editions/[editionId]/[tokenId] function tokenURI(uint256 tokenId) public view override returns (string memory) { // If the token does not map to an edition, it'll be 0. require(tokenToEdition[tokenId] > 0, "Token has not been sold yet"); // Concatenate the components, baseURI, editionId and tokenId, to create URI. return string( abi.encodePacked( baseURI, _toString(tokenToEdition[tokenId]), "/", _toString(tokenId) ) ); } // Returns e.g. https://mirror-api.com/editions/metadata function contractURI() public view override returns (string memory) { // Concatenate the components, baseURI, editionId and tokenId, to create URI. return string(abi.encodePacked(baseURI, "metadata")); } // Given an edition's ID, returns its price. function editionPrice(uint256 editionId) external view override returns (uint256) { return editions[editionId].price; } // The hash of the given content for the NFT. Can be used // for IPFS storage, verifying authenticity, etc. function getContentHash(uint256 tokenId) public view returns (bytes32) { // If the token does not map to an edition, it'll be 0. require(tokenToEdition[tokenId] > 0, "Token has not been sold yet"); // Concatenate the components, baseURI, editionId and tokenId, to create URI. return editions[tokenToEdition[tokenId]].contentHash; } function getRoyaltyRecipient(uint256 tokenId) public view returns (address) { require(tokenToEdition[tokenId] > 0, "Token has not been minted yet"); return editions[tokenToEdition[tokenId]].fundingRecipient; } function setRoyaltyRecipient( uint256 editionId, address payable newFundingRecipient ) public { require( editions[editionId].fundingRecipient == msg.sender, "Only current fundingRecipient can modify its value" ); editions[editionId].fundingRecipient = newFundingRecipient; } // ============ Admin Methods ============ function changeBaseURI(string memory baseURI_) public onlyOwner { baseURI = baseURI_; } // Allows the creator contract to be swapped out for an upgraded one. // NOTE: This does not affect existing editions already minted. function changeEditionCreator(address editionCreator_) public onlyOwner { emit EditionCreatorChanged(editionCreator, editionCreator_); editionCreator = editionCreator_; } function isOwner() public view returns (bool) { return msg.sender == owner; } function isNextOwner() public view returns (bool) { return msg.sender == nextOwner; } function transferOwnership(address nextOwner_) external onlyOwner { require(nextOwner_ != address(0), "Next owner is the zero address."); nextOwner = nextOwner_; } function cancelOwnershipTransfer() external onlyOwner { delete nextOwner; } function acceptOwnership() external onlyNextOwner { delete nextOwner; emit OwnershipTransferred(owner, msg.sender); owner = msg.sender; } function renounceOwnership() external onlyOwner { emit OwnershipTransferred(owner, address(0)); owner = address(0); } // ============ Private Methods ============ // From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Strings.sol function _toString(uint256 value) internal pure returns (string memory) { // Inspired by OraclizeAPI's implementation - MIT licence // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol if (value == 0) { return "0"; } uint256 temp = value; uint256 digits; while (temp != 0) { digits++; temp /= 10; } bytes memory buffer = new bytes(digits); while (value != 0) { digits -= 1; buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); value /= 10; } return string(buffer); } }
File 2 of 2: TieredCrowdfundProxy
// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity 0.8.5; /** * @title TieredCrowdfundStorage * @author MirrorXYZ */ contract TieredCrowdfundStorage { // The two states that this contract can exist in. "FUNDING" allows // contributors to add funds. enum Status {FUNDING, TRADING} // ============ Constants ============ // The factor by which ETH contributions will multiply into crowdfund tokens. uint16 internal constant TOKEN_SCALE = 1000; uint256 internal constant REENTRANCY_NOT_ENTERED = 1; uint256 internal constant REENTRANCY_ENTERED = 2; uint8 public constant decimals = 18; // ============ Immutable Storage ============ // The operator has a special role to change contract status. address payable public operator; address payable public fundingRecipient; // We add a hard cap to prevent raising more funds than deemed reasonable. uint256 public fundingCap; // The operator takes some equity in the tokens, represented by this percent. uint256 public operatorPercent; string public symbol; string public name; // ============ Mutable Storage ============ // Represents the current state of the campaign. Status public status; uint256 internal reentrancy_status; // ============ Mutable ERC20 Attributes ============ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; mapping(address => uint256) public nonces; // ============ Delegation logic ============ address public logic; // ============ Tiered Campaigns ============ // Address of the editions contract to purchase from. address public editions; } // File contracts/TieredCrowdfundProxy.sol interface ITieredCrowdfundFactory { function mediaAddress() external returns (address); function logic() external returns (address); function editions() external returns (address); // ERC20 data. function parameters() external returns ( address payable operator, address payable fundingRecipient, uint256 fundingCap, uint256 operatorPercent, string memory name, string memory symbol ); } /** * @title TieredCrowdfundProxy * @author MirrorXYZ */ contract TieredCrowdfundProxy is TieredCrowdfundStorage { constructor() { logic = ITieredCrowdfundFactory(msg.sender).logic(); editions = ITieredCrowdfundFactory(msg.sender).editions(); // Crowdfund-specific data. ( operator, fundingRecipient, fundingCap, operatorPercent, name, symbol ) = ITieredCrowdfundFactory(msg.sender).parameters(); // Initialize mutable storage. status = Status.FUNDING; } fallback() external payable { address _impl = logic; assembly { let ptr := mload(0x40) calldatacopy(ptr, 0, calldatasize()) let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0) let size := returndatasize() returndatacopy(ptr, 0, size) switch result case 0 { revert(ptr, size) } default { return(ptr, size) } } } receive() external payable {} }