ETH Price: $2,785.35 (+4.05%)

Transaction Decoder

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 Code
0x224B867b...825Ce8e19
(Spark Pool)
61.734296082165182851 Eth61.756458426165182851 Eth0.022162344
0xD8Aaa606...062BEfEb3
0 Eth
Nonce: 0
0 Eth
Nonce: 1
From: 0 To: 151245542690229483057956598622931959859981820690891054240196049377165430801705279707409787838586131644687936755788916412329472770865528960674992408406660322164869488382239614193090275295468547598020697009569830283438152001488942403318858924112923497198636893429384722098129335938219714347240640843603028821604331713608035146479776096132926457849819077337657037987828944177910346239637224498304993613811865357454784537256612033250225718321351424652578796275077686095235761686646170853399416975643094847359866057544908464481135832049155275029846911154208791905719018631858000571773322246856583513208641542465520640928495983896869526322363719023858613164534440141790527447370700830301983485883470254107724314171281232379461961089249117478293610780674075257635712016761318684745251802324625865118486521467366054201224324876240677625978461570878030589241219563705910570628173563439067268277164076093792825234437847634634065673953594533053822752955756887157439821124354464912627480876030143887487640304904750434278158448238745968482240272572289176661134033607690112002008379693574180112171074611656631889499791326867875804818618885873380684660378869889200198872287484395586851821378297161386022491103447794457054667478051059580239480359174943451967744815044891188841448825457579939629593497706085085644264647774197425147051197620091278016351980141434782619147464143465740633466531976262557034760981625862954490548842400996880645486287281398632514320660517295937804817984380384544225537657899038244580791224615178533308424284296741789657405574287766576481416126352737830877589783138030298431843026936492263227239668893003743097203253350891374807707108857271800018801322519841004286681380433981351352553792974054410642928090209162312426051304746221245758918014423251089249540832888686339276027142934652750454635623350070683709855954748726828547245775129129890241028458694995949872512674874845283775255951169368009648724046204173810566234313903925584330381270226417821985817881646917090821902637207507272196149880243552926059565896686694867009064690795954285778496060352000524866242357973520746735504440060414591422094996552965514117063214987485334635874391683312914394377230873261582344433726232428087729026286875378690121751163023805497285745514337998087838259711530853029067290287108203779090215559072583759969947988650743602186818524949456039107326416126039607764867549366807680251762200386682541837459787262073680781236630169821413379600002981919994015919872772482291673961580111799302201333300129222836900655501784487571399450215226172374298465004691634001339292773477265926480361896095435584076051859179382448532296417105273305886825246001677367366229516690111916621969657463639911446404564325260918244229780951803248991638986770533832846466953833519109782097054708750894346088330889204095260525122797772806466299245590053170509202445953424127577328038228173124186345448611340726697178417286414607445589735589136047961908881417858918197186930314977613235510236859885701376149809562375834927810588627427191385642133924937393017041725054405761735696213969380440894679588685948426226089212050458354797096869703230882252689691831502620157231120637240688436290993359044771379568665327316617200381149833198898726366382658080055430921420337455002204426756877973516104612913108185250417494431125247237198726050925930373852549114513271424667072987803113426610748092026280370467570873092167413079657554778342119250002691317220247014681902688203655321130643245100843865589387910568398058436862354740623449684051113694111835660491837535469806738162259565852186051210058085809061939
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
      // 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 {}
      }