Transaction Hash:
Block:
12486389 at May-22-2021 08:53:50 PM +UTC
Transaction Fee:
0.005484892 ETH
$14.56
Gas Used:
176,932 Gas / 31 Gwei
Emitted Events:
152 |
0x7e3abde9d9e80fa2d1a02c89e0eae91b233cde35.0xa1decd0961bf6d65a7d418a0bbb8c396454f25fdf821a5ecb7957b993ccc0f70( 0xa1decd0961bf6d65a7d418a0bbb8c396454f25fdf821a5ecb7957b993ccc0f70, 000000000000000000000000000000000000000000000000000000000000ebaf, 0000000000000000000000002a46f2ffd99e19a89476e2f62270e0a35bbf0756, 00000000000000000000000000000000000000000000000002c68af0bb140000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x69024B51...F0E4559e4 |
0.005869728 Eth
Nonce: 2
|
0.000384836 Eth
Nonce: 3
| 0.005484892 | ||
0x7e3abdE9...b233CDE35 | |||||
0xc8F595E2...25223b7C9
Miner
| (Miner: 0xc8F...7C9) | 1,454.239202600176585996 Eth | 1,454.244687492176585996 Eth | 0.005484892 |
Execution Trace
0x7e3abde9d9e80fa2d1a02c89e0eae91b233cde35.ab23e6fd( )
DigitalMediaCore.isApprovedForAll( _owner=0x69024B5165A8D47a1d131e67ec101eAF0E4559e4, _operator=0x7e3abdE9D9E80fA2d1A02c89E0eae91b233CDE35 ) => ( True )
-
0xa6ec692942dc8c590693dc2a1cba5a7413de851f.67d6a7dc( )
-
-
DigitalMediaCore.ownerOf( _tokenId=60335 ) => ( 0x69024B5165A8D47a1d131e67ec101eAF0E4559e4 )
-
DigitalMediaCore.getDigitalMediaRelease( _id=60335 ) => ( id=60335, printEdition=1, digitalMediaId=42590 )
DigitalMediaCore.getDigitalMedia( _id=42590 ) => ( id=42590, totalSupply=1, printIndex=1, collectionId=0, creator=0x69024B5165A8D47a1d131e67ec101eAF0E4559e4, metadataPath=QmY3oEtA7FQnymQtugw2XbaEZMg5eps4HL5T3cfdScrvtd )
-
0xe75479ba035175a251179919080bc7bd4e700f6e.55df4275( )
-
// File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/math/SafeMath.sol pragma solidity ^0.4.21; /** * @title SafeMath * @dev Math operations with safety checks that throw on error */ library SafeMath { /** * @dev Multiplies two numbers, throws on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { if (a == 0) { return 0; } c = a * b; assert(c / a == b); return c; } /** * @dev Integer division of two numbers, truncating the quotient. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // assert(b > 0); // Solidity automatically throws when dividing by 0 // uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return a / b; } /** * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { assert(b <= a); return a - b; } /** * @dev Adds two numbers, throws on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256 c) { c = a + b; assert(c >= a); return c; } } // File: REMIX_FILE_SYNC/ApprovedCreatorRegistryInterface.sol pragma solidity ^0.4.22; /** * Interface to the digital media store external contract that is * responsible for storing the common digital media and collection data. * This allows for new token contracts to be deployed and continue to reference * the digital media and collection data. */ contract ApprovedCreatorRegistryInterface { function getVersion() public pure returns (uint); function typeOfContract() public pure returns (string); function isOperatorApprovedForCustodialAccount( address _operator, address _custodialAddress) public view returns (bool); } // File: REMIX_FILE_SYNC/DigitalMediaStoreInterface.sol pragma solidity 0.4.25; /** * Interface to the digital media store external contract that is * responsible for storing the common digital media and collection data. * This allows for new token contracts to be deployed and continue to reference * the digital media and collection data. */ contract DigitalMediaStoreInterface { function getDigitalMediaStoreVersion() public pure returns (uint); function getStartingDigitalMediaId() public view returns (uint256); function registerTokenContractAddress() external; /** * Creates a new digital media object in storage * @param _creator address the address of the creator * @param _printIndex uint32 the current print index for the limited edition media * @param _totalSupply uint32 the total allowable prints for this media * @param _collectionId uint256 the collection id that this media belongs to * @param _metadataPath string the ipfs metadata path * @return the id of the new digital media created */ function createDigitalMedia( address _creator, uint32 _printIndex, uint32 _totalSupply, uint256 _collectionId, string _metadataPath) external returns (uint); /** * Increments the current print index of the digital media object * @param _digitalMediaId uint256 the id of the digital media * @param _increment uint32 the amount to increment by */ function incrementDigitalMediaPrintIndex( uint256 _digitalMediaId, uint32 _increment) external; /** * Retrieves the digital media object by id * @param _digitalMediaId uint256 the address of the creator */ function getDigitalMedia(uint256 _digitalMediaId) external view returns( uint256 id, uint32 totalSupply, uint32 printIndex, uint256 collectionId, address creator, string metadataPath); /** * Creates a new collection * @param _creator address the address of the creator * @param _metadataPath string the ipfs metadata path * @return the id of the new collection created */ function createCollection(address _creator, string _metadataPath) external returns (uint); /** * Retrieves a collection by id * @param _collectionId uint256 */ function getCollection(uint256 _collectionId) external view returns( uint256 id, address creator, string metadataPath); } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/ownership/Ownable.sol pragma solidity ^0.4.21; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); emit OwnershipTransferred(owner, newOwner); owner = newOwner; } } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/lifecycle/Pausable.sol pragma solidity ^0.4.21; /** * @title Pausable * @dev Base contract which allows children to implement an emergency stop mechanism. */ contract Pausable is Ownable { event Pause(); event Unpause(); bool public paused = false; /** * @dev Modifier to make a function callable only when the contract is not paused. */ modifier whenNotPaused() { require(!paused); _; } /** * @dev Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { require(paused); _; } /** * @dev called by the owner to pause, triggers stopped state */ function pause() onlyOwner whenNotPaused public { paused = true; emit Pause(); } /** * @dev called by the owner to unpause, returns to normal state */ function unpause() onlyOwner whenPaused public { paused = false; emit Unpause(); } } // File: REMIX_FILE_SYNC/MediaStoreVersionControl.sol pragma solidity 0.4.25; /** * A special control class that is used to configure and manage a token contract's * different digital media store versions. * * Older versions of token contracts had the ability to increment the digital media's * print edition in the media store, which was necessary in the early stages to provide * upgradeability and flexibility. * * New verions will get rid of this ability now that token contract logic * is more stable and we've built in burn capabilities. * * In order to support the older tokens, we need to be able to look up the appropriate digital * media store associated with a given digital media id on the latest token contract. */ contract MediaStoreVersionControl is Pausable { // The single allowed creator for this digital media contract. DigitalMediaStoreInterface public v1DigitalMediaStore; // The current digitial media store, used for this tokens creation. DigitalMediaStoreInterface public currentDigitalMediaStore; uint256 public currentStartingDigitalMediaId; /** * Validates that the managers are initialized. */ modifier managersInitialized() { require(v1DigitalMediaStore != address(0)); require(currentDigitalMediaStore != address(0)); _; } /** * Sets a digital media store address upon construction. * Once set it's immutable, so that a token contract is always * tied to one digital media store. */ function setDigitalMediaStoreAddress(address _dmsAddress) internal { DigitalMediaStoreInterface candidateDigitalMediaStore = DigitalMediaStoreInterface(_dmsAddress); require(candidateDigitalMediaStore.getDigitalMediaStoreVersion() == 2, "Incorrect version."); currentDigitalMediaStore = candidateDigitalMediaStore; currentDigitalMediaStore.registerTokenContractAddress(); currentStartingDigitalMediaId = currentDigitalMediaStore.getStartingDigitalMediaId(); } /** * Publicly callable by the owner, but can only be set one time, so don't make * a mistake when setting it. * * Will also check that the version on the other end of the contract is in fact correct. */ function setV1DigitalMediaStoreAddress(address _dmsAddress) public onlyOwner { require(address(v1DigitalMediaStore) == 0, "V1 media store already set."); DigitalMediaStoreInterface candidateDigitalMediaStore = DigitalMediaStoreInterface(_dmsAddress); require(candidateDigitalMediaStore.getDigitalMediaStoreVersion() == 1, "Incorrect version."); v1DigitalMediaStore = candidateDigitalMediaStore; v1DigitalMediaStore.registerTokenContractAddress(); } /** * Depending on the digital media id, determines whether to return the previous * version of the digital media manager. */ function _getDigitalMediaStore(uint256 _digitalMediaId) internal view managersInitialized returns (DigitalMediaStoreInterface) { if (_digitalMediaId < currentStartingDigitalMediaId) { return v1DigitalMediaStore; } else { return currentDigitalMediaStore; } } } // File: REMIX_FILE_SYNC/DigitalMediaManager.sol pragma solidity 0.4.25; /** * Manager that interfaces with the underlying digital media store contract. */ contract DigitalMediaManager is MediaStoreVersionControl { struct DigitalMedia { uint256 id; uint32 totalSupply; uint32 printIndex; uint256 collectionId; address creator; string metadataPath; } struct DigitalMediaCollection { uint256 id; address creator; string metadataPath; } ApprovedCreatorRegistryInterface public creatorRegistryStore; // Set the creator registry address upon construction. Immutable. function setCreatorRegistryStore(address _crsAddress) internal { ApprovedCreatorRegistryInterface candidateCreatorRegistryStore = ApprovedCreatorRegistryInterface(_crsAddress); require(candidateCreatorRegistryStore.getVersion() == 1); // Simple check to make sure we are adding the registry contract indeed // https://fravoll.github.io/solidity-patterns/string_equality_comparison.html require(keccak256(candidateCreatorRegistryStore.typeOfContract()) == keccak256("approvedCreatorRegistry")); creatorRegistryStore = candidateCreatorRegistryStore; } /** * Validates that the Registered store is initialized. */ modifier registryInitialized() { require(creatorRegistryStore != address(0)); _; } /** * Retrieves a collection object by id. */ function _getCollection(uint256 _id) internal view managersInitialized returns(DigitalMediaCollection) { uint256 id; address creator; string memory metadataPath; (id, creator, metadataPath) = currentDigitalMediaStore.getCollection(_id); DigitalMediaCollection memory collection = DigitalMediaCollection({ id: id, creator: creator, metadataPath: metadataPath }); return collection; } /** * Retrieves a digital media object by id. */ function _getDigitalMedia(uint256 _id) internal view managersInitialized returns(DigitalMedia) { uint256 id; uint32 totalSupply; uint32 printIndex; uint256 collectionId; address creator; string memory metadataPath; DigitalMediaStoreInterface _digitalMediaStore = _getDigitalMediaStore(_id); (id, totalSupply, printIndex, collectionId, creator, metadataPath) = _digitalMediaStore.getDigitalMedia(_id); DigitalMedia memory digitalMedia = DigitalMedia({ id: id, creator: creator, totalSupply: totalSupply, printIndex: printIndex, collectionId: collectionId, metadataPath: metadataPath }); return digitalMedia; } /** * Increments the print index of a digital media object by some increment. */ function _incrementDigitalMediaPrintIndex(DigitalMedia _dm, uint32 _increment) internal managersInitialized { DigitalMediaStoreInterface _digitalMediaStore = _getDigitalMediaStore(_dm.id); _digitalMediaStore.incrementDigitalMediaPrintIndex(_dm.id, _increment); } // Check if the token operator is approved for the owner address function isOperatorApprovedForCustodialAccount( address _operator, address _owner) internal view registryInitialized returns(bool) { return creatorRegistryStore.isOperatorApprovedForCustodialAccount( _operator, _owner); } } // File: REMIX_FILE_SYNC/SingleCreatorControl.sol pragma solidity 0.4.25; /** * A special control class that's used to help enforce that a DigitalMedia contract * will service only a single creator's address. This is used when deploying a * custom token contract owned and managed by a single creator. */ contract SingleCreatorControl { // The single allowed creator for this digital media contract. address public singleCreatorAddress; // The single creator has changed. event SingleCreatorChanged( address indexed previousCreatorAddress, address indexed newCreatorAddress); /** * Sets the single creator associated with this contract. This function * can only ever be called once, and should ideally be called at the point * of constructing the smart contract. */ function setSingleCreator(address _singleCreatorAddress) internal { require(singleCreatorAddress == address(0), "Single creator address already set."); singleCreatorAddress = _singleCreatorAddress; } /** * Checks whether a given creator address matches the single creator address. * Will always return true if a single creator address was never set. */ function isAllowedSingleCreator(address _creatorAddress) internal view returns (bool) { require(_creatorAddress != address(0), "0x0 creator addresses are not allowed."); return singleCreatorAddress == address(0) || singleCreatorAddress == _creatorAddress; } /** * A publicly accessible function that allows the current single creator * assigned to this contract to change to another address. */ function changeSingleCreator(address _newCreatorAddress) public { require(_newCreatorAddress != address(0)); require(msg.sender == singleCreatorAddress, "Not approved to change single creator."); singleCreatorAddress = _newCreatorAddress; emit SingleCreatorChanged(singleCreatorAddress, _newCreatorAddress); } } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/token/ERC721/ERC721Basic.sol pragma solidity ^0.4.21; /** * @title ERC721 Non-Fungible Token Standard basic interface * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Basic { event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); function balanceOf(address _owner) public view returns (uint256 _balance); function ownerOf(uint256 _tokenId) public view returns (address _owner); function exists(uint256 _tokenId) public view returns (bool _exists); function approve(address _to, uint256 _tokenId) public; function getApproved(uint256 _tokenId) public view returns (address _operator); function setApprovalForAll(address _operator, bool _approved) public; function isApprovedForAll(address _owner, address _operator) public view returns (bool); function transferFrom(address _from, address _to, uint256 _tokenId) public; function safeTransferFrom(address _from, address _to, uint256 _tokenId) public; function safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes _data ) public; } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/token/ERC721/ERC721.sol pragma solidity ^0.4.21; /** * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Enumerable is ERC721Basic { function totalSupply() public view returns (uint256); function tokenOfOwnerByIndex(address _owner, uint256 _index) public view returns (uint256 _tokenId); function tokenByIndex(uint256 _index) public view returns (uint256); } /** * @title ERC-721 Non-Fungible Token Standard, optional metadata extension * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Metadata is ERC721Basic { function name() public view returns (string _name); function symbol() public view returns (string _symbol); function tokenURI(uint256 _tokenId) public view returns (string); } /** * @title ERC-721 Non-Fungible Token Standard, full implementation interface * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata { } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/token/ERC721/ERC721Receiver.sol pragma solidity ^0.4.21; /** * @title ERC721 token receiver interface * @dev Interface for any contract that wants to support safeTransfers * from ERC721 asset contracts. */ contract ERC721Receiver { /** * @dev Magic value to be returned upon successful reception of an NFT * Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`, * which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` */ bytes4 constant ERC721_RECEIVED = 0xf0b9e5ba; /** * @notice Handle the receipt of an NFT * @dev The ERC721 smart contract calls this function on the recipient * after a `safetransfer`. This function MAY throw to revert and reject the * transfer. This function MUST use 50,000 gas or less. Return of other * than the magic value MUST result in the transaction being reverted. * Note: the contract address is always the message sender. * @param _from The sending address * @param _tokenId The NFT identifier which is being transfered * @param _data Additional data with no specified format * @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` */ function onERC721Received(address _from, uint256 _tokenId, bytes _data) public returns(bytes4); } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/AddressUtils.sol pragma solidity ^0.4.21; /** * Utility library of inline functions on addresses */ library AddressUtils { /** * Returns whether the target address is a contract * @dev This function will return false if invoked during the constructor of a contract, * as the code is not actually created until after the constructor finishes. * @param addr address to check * @return whether the target address is a contract */ function isContract(address addr) internal view returns (bool) { uint256 size; // XXX Currently there is no better way to check if there is a contract in an address // than to check the size of the code at that address. // See https://ethereum.stackexchange.com/a/14016/36603 // for more details about how this works. // TODO Check this again before the Serenity release, because all addresses will be // contracts then. assembly { size := extcodesize(addr) } // solium-disable-line security/no-inline-assembly return size > 0; } } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/token/ERC721/ERC721BasicToken.sol pragma solidity ^0.4.21; /** * @title ERC721 Non-Fungible Token Standard basic implementation * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721BasicToken is ERC721Basic { using SafeMath for uint256; using AddressUtils for address; // Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` bytes4 constant ERC721_RECEIVED = 0xf0b9e5ba; // Mapping from token ID to owner mapping (uint256 => address) internal tokenOwner; // Mapping from token ID to approved address mapping (uint256 => address) internal tokenApprovals; // Mapping from owner to number of owned token mapping (address => uint256) internal ownedTokensCount; // Mapping from owner to operator approvals mapping (address => mapping (address => bool)) internal operatorApprovals; /** * @dev Guarantees msg.sender is owner of the given token * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender */ modifier onlyOwnerOf(uint256 _tokenId) { require(ownerOf(_tokenId) == msg.sender); _; } /** * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator * @param _tokenId uint256 ID of the token to validate */ modifier canTransfer(uint256 _tokenId) { require(isApprovedOrOwner(msg.sender, _tokenId)); _; } /** * @dev Gets the balance of the specified address * @param _owner address to query the balance of * @return uint256 representing the amount owned by the passed address */ function balanceOf(address _owner) public view returns (uint256) { require(_owner != address(0)); return ownedTokensCount[_owner]; } /** * @dev Gets the owner of the specified token ID * @param _tokenId uint256 ID of the token to query the owner of * @return owner address currently marked as the owner of the given token ID */ function ownerOf(uint256 _tokenId) public view returns (address) { address owner = tokenOwner[_tokenId]; require(owner != address(0)); return owner; } /** * @dev Returns whether the specified token exists * @param _tokenId uint256 ID of the token to query the existance of * @return whether the token exists */ function exists(uint256 _tokenId) public view returns (bool) { address owner = tokenOwner[_tokenId]; return owner != address(0); } /** * @dev Approves another address to transfer the given token ID * @dev The zero address indicates there is no approved address. * @dev There can only be one approved address per token at a given time. * @dev Can only be called by the token owner or an approved operator. * @param _to address to be approved for the given token ID * @param _tokenId uint256 ID of the token to be approved */ function approve(address _to, uint256 _tokenId) public { address owner = ownerOf(_tokenId); require(_to != owner); require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); if (getApproved(_tokenId) != address(0) || _to != address(0)) { tokenApprovals[_tokenId] = _to; emit Approval(owner, _to, _tokenId); } } /** * @dev Gets the approved address for a token ID, or zero if no address set * @param _tokenId uint256 ID of the token to query the approval of * @return address currently approved for a the given token ID */ function getApproved(uint256 _tokenId) public view returns (address) { return tokenApprovals[_tokenId]; } /** * @dev Sets or unsets the approval of a given operator * @dev An operator is allowed to transfer all tokens of the sender on their behalf * @param _to operator address to set the approval * @param _approved representing the status of the approval to be set */ function setApprovalForAll(address _to, bool _approved) public { require(_to != msg.sender); operatorApprovals[msg.sender][_to] = _approved; emit ApprovalForAll(msg.sender, _to, _approved); } /** * @dev Tells whether an operator is approved by a given owner * @param _owner owner address which you want to query the approval of * @param _operator operator address which you want to query the approval of * @return bool whether the given operator is approved by the given owner */ function isApprovedForAll(address _owner, address _operator) public view returns (bool) { return operatorApprovals[_owner][_operator]; } /** * @dev Transfers the ownership of a given token ID to another address * @dev Usage of this method is discouraged, use `safeTransferFrom` whenever possible * @dev Requires the msg sender to be the owner, approved, or operator * @param _from current owner of the token * @param _to address to receive the ownership of the given token ID * @param _tokenId uint256 ID of the token to be transferred */ function transferFrom(address _from, address _to, uint256 _tokenId) public canTransfer(_tokenId) { require(_from != address(0)); require(_to != address(0)); clearApproval(_from, _tokenId); removeTokenFrom(_from, _tokenId); addTokenTo(_to, _tokenId); emit Transfer(_from, _to, _tokenId); } /** * @dev Safely transfers the ownership of a given token ID to another address * @dev If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise, * the transfer is reverted. * @dev Requires the msg sender to be the owner, approved, or operator * @param _from current owner of the token * @param _to address to receive the ownership of the given token ID * @param _tokenId uint256 ID of the token to be transferred */ function safeTransferFrom( address _from, address _to, uint256 _tokenId ) public canTransfer(_tokenId) { // solium-disable-next-line arg-overflow safeTransferFrom(_from, _to, _tokenId, ""); } /** * @dev Safely transfers the ownership of a given token ID to another address * @dev If the target address is a contract, it must implement `onERC721Received`, * which is called upon a safe transfer, and return the magic value * `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; otherwise, * the transfer is reverted. * @dev Requires the msg sender to be the owner, approved, or operator * @param _from current owner of the token * @param _to address to receive the ownership of the given token ID * @param _tokenId uint256 ID of the token to be transferred * @param _data bytes data to send along with a safe transfer check */ function safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes _data ) public canTransfer(_tokenId) { transferFrom(_from, _to, _tokenId); // solium-disable-next-line arg-overflow require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); } /** * @dev Returns whether the given spender can transfer a given token ID * @param _spender address of the spender to query * @param _tokenId uint256 ID of the token to be transferred * @return bool whether the msg.sender is approved for the given token ID, * is an operator of the owner, or is the owner of the token */ function isApprovedOrOwner(address _spender, uint256 _tokenId) internal view returns (bool) { address owner = ownerOf(_tokenId); return _spender == owner || getApproved(_tokenId) == _spender || isApprovedForAll(owner, _spender); } /** * @dev Internal function to mint a new token * @dev Reverts if the given token ID already exists * @param _to The address that will own the minted token * @param _tokenId uint256 ID of the token to be minted by the msg.sender */ function _mint(address _to, uint256 _tokenId) internal { require(_to != address(0)); addTokenTo(_to, _tokenId); emit Transfer(address(0), _to, _tokenId); } /** * @dev Internal function to burn a specific token * @dev Reverts if the token does not exist * @param _tokenId uint256 ID of the token being burned by the msg.sender */ function _burn(address _owner, uint256 _tokenId) internal { clearApproval(_owner, _tokenId); removeTokenFrom(_owner, _tokenId); emit Transfer(_owner, address(0), _tokenId); } /** * @dev Internal function to clear current approval of a given token ID * @dev Reverts if the given address is not indeed the owner of the token * @param _owner owner of the token * @param _tokenId uint256 ID of the token to be transferred */ function clearApproval(address _owner, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _owner); if (tokenApprovals[_tokenId] != address(0)) { tokenApprovals[_tokenId] = address(0); emit Approval(_owner, address(0), _tokenId); } } /** * @dev Internal function to add a token ID to the list of a given address * @param _to address representing the new owner of the given token ID * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address */ function addTokenTo(address _to, uint256 _tokenId) internal { require(tokenOwner[_tokenId] == address(0)); tokenOwner[_tokenId] = _to; ownedTokensCount[_to] = ownedTokensCount[_to].add(1); } /** * @dev Internal function to remove a token ID from the list of a given address * @param _from address representing the previous owner of the given token ID * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address */ function removeTokenFrom(address _from, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _from); ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); tokenOwner[_tokenId] = address(0); } /** * @dev Internal function to invoke `onERC721Received` on a target address * @dev The call is not executed if the target address is not a contract * @param _from address representing the previous owner of the given token ID * @param _to target address that will receive the tokens * @param _tokenId uint256 ID of the token to be transferred * @param _data bytes optional data to send along with the call * @return whether the call correctly returned the expected magic value */ function checkAndCallSafeTransfer( address _from, address _to, uint256 _tokenId, bytes _data ) internal returns (bool) { if (!_to.isContract()) { return true; } bytes4 retval = ERC721Receiver(_to).onERC721Received(_from, _tokenId, _data); return (retval == ERC721_RECEIVED); } } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/token/ERC721/ERC721Token.sol pragma solidity ^0.4.21; /** * @title Full ERC721 Token * This implementation includes all the required and some optional functionality of the ERC721 standard * Moreover, it includes approve all functionality using operator terminology * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Token is ERC721, ERC721BasicToken { // Token name string internal name_; // Token symbol string internal symbol_; // Mapping from owner to list of owned token IDs mapping (address => uint256[]) internal ownedTokens; // Mapping from token ID to index of the owner tokens list mapping(uint256 => uint256) internal ownedTokensIndex; // Array with all token ids, used for enumeration uint256[] internal allTokens; // Mapping from token id to position in the allTokens array mapping(uint256 => uint256) internal allTokensIndex; // Optional mapping for token URIs mapping(uint256 => string) internal tokenURIs; /** * @dev Constructor function */ function ERC721Token(string _name, string _symbol) public { name_ = _name; symbol_ = _symbol; } /** * @dev Gets the token name * @return string representing the token name */ function name() public view returns (string) { return name_; } /** * @dev Gets the token symbol * @return string representing the token symbol */ function symbol() public view returns (string) { return symbol_; } /** * @dev Returns an URI for a given token ID * @dev Throws if the token ID does not exist. May return an empty string. * @param _tokenId uint256 ID of the token to query */ function tokenURI(uint256 _tokenId) public view returns (string) { require(exists(_tokenId)); return tokenURIs[_tokenId]; } /** * @dev Gets the token ID at a given index of the tokens list of the requested owner * @param _owner address owning the tokens list to be accessed * @param _index uint256 representing the index to be accessed of the requested tokens list * @return uint256 token ID at the given index of the tokens list owned by the requested address */ function tokenOfOwnerByIndex(address _owner, uint256 _index) public view returns (uint256) { require(_index < balanceOf(_owner)); return ownedTokens[_owner][_index]; } /** * @dev Gets the total amount of tokens stored by the contract * @return uint256 representing the total amount of tokens */ function totalSupply() public view returns (uint256) { return allTokens.length; } /** * @dev Gets the token ID at a given index of all the tokens in this contract * @dev Reverts if the index is greater or equal to the total number of tokens * @param _index uint256 representing the index to be accessed of the tokens list * @return uint256 token ID at the given index of the tokens list */ function tokenByIndex(uint256 _index) public view returns (uint256) { require(_index < totalSupply()); return allTokens[_index]; } /** * @dev Internal function to set the token URI for a given token * @dev Reverts if the token ID does not exist * @param _tokenId uint256 ID of the token to set its URI * @param _uri string URI to assign */ function _setTokenURI(uint256 _tokenId, string _uri) internal { require(exists(_tokenId)); tokenURIs[_tokenId] = _uri; } /** * @dev Internal function to add a token ID to the list of a given address * @param _to address representing the new owner of the given token ID * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address */ function addTokenTo(address _to, uint256 _tokenId) internal { super.addTokenTo(_to, _tokenId); uint256 length = ownedTokens[_to].length; ownedTokens[_to].push(_tokenId); ownedTokensIndex[_tokenId] = length; } /** * @dev Internal function to remove a token ID from the list of a given address * @param _from address representing the previous owner of the given token ID * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address */ function removeTokenFrom(address _from, uint256 _tokenId) internal { super.removeTokenFrom(_from, _tokenId); uint256 tokenIndex = ownedTokensIndex[_tokenId]; uint256 lastTokenIndex = ownedTokens[_from].length.sub(1); uint256 lastToken = ownedTokens[_from][lastTokenIndex]; ownedTokens[_from][tokenIndex] = lastToken; ownedTokens[_from][lastTokenIndex] = 0; // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping // the lastToken to the first position, and then dropping the element placed in the last position of the list ownedTokens[_from].length--; ownedTokensIndex[_tokenId] = 0; ownedTokensIndex[lastToken] = tokenIndex; } /** * @dev Internal function to mint a new token * @dev Reverts if the given token ID already exists * @param _to address the beneficiary that will own the minted token * @param _tokenId uint256 ID of the token to be minted by the msg.sender */ function _mint(address _to, uint256 _tokenId) internal { super._mint(_to, _tokenId); allTokensIndex[_tokenId] = allTokens.length; allTokens.push(_tokenId); } /** * @dev Internal function to burn a specific token * @dev Reverts if the token does not exist * @param _owner owner of the token to burn * @param _tokenId uint256 ID of the token being burned by the msg.sender */ function _burn(address _owner, uint256 _tokenId) internal { super._burn(_owner, _tokenId); // Clear metadata (if any) if (bytes(tokenURIs[_tokenId]).length != 0) { delete tokenURIs[_tokenId]; } // Reorg all tokens array uint256 tokenIndex = allTokensIndex[_tokenId]; uint256 lastTokenIndex = allTokens.length.sub(1); uint256 lastToken = allTokens[lastTokenIndex]; allTokens[tokenIndex] = lastToken; allTokens[lastTokenIndex] = 0; allTokens.length--; allTokensIndex[_tokenId] = 0; allTokensIndex[lastToken] = tokenIndex; } } // File: REMIX_FILE_SYNC/ERC721Safe.sol pragma solidity 0.4.25; // We have to specify what version of compiler this code will compile with contract ERC721Safe is ERC721Token { bytes4 constant internal InterfaceSignature_ERC165 = bytes4(keccak256('supportsInterface(bytes4)')); bytes4 constant internal InterfaceSignature_ERC721 = bytes4(keccak256('name()')) ^ bytes4(keccak256('symbol()')) ^ bytes4(keccak256('totalSupply()')) ^ bytes4(keccak256('balanceOf(address)')) ^ bytes4(keccak256('ownerOf(uint256)')) ^ bytes4(keccak256('approve(address,uint256)')) ^ bytes4(keccak256('safeTransferFrom(address,address,uint256)')); function supportsInterface(bytes4 _interfaceID) external view returns (bool); } // File: REMIX_FILE_SYNC/Memory.sol pragma solidity 0.4.25; library Memory { // Size of a word, in bytes. uint internal constant WORD_SIZE = 32; // Size of the header of a 'bytes' array. uint internal constant BYTES_HEADER_SIZE = 32; // Address of the free memory pointer. uint internal constant FREE_MEM_PTR = 0x40; // Compares the 'len' bytes starting at address 'addr' in memory with the 'len' // bytes starting at 'addr2'. // Returns 'true' if the bytes are the same, otherwise 'false'. function equals(uint addr, uint addr2, uint len) internal pure returns (bool equal) { assembly { equal := eq(keccak256(addr, len), keccak256(addr2, len)) } } // Compares the 'len' bytes starting at address 'addr' in memory with the bytes stored in // 'bts'. It is allowed to set 'len' to a lower value then 'bts.length', in which case only // the first 'len' bytes will be compared. // Requires that 'bts.length >= len' function equals(uint addr, uint len, bytes memory bts) internal pure returns (bool equal) { require(bts.length >= len); uint addr2; assembly { addr2 := add(bts, /*BYTES_HEADER_SIZE*/32) } return equals(addr, addr2, len); } // Allocates 'numBytes' bytes in memory. This will prevent the Solidity compiler // from using this area of memory. It will also initialize the area by setting // each byte to '0'. function allocate(uint numBytes) internal pure returns (uint addr) { // Take the current value of the free memory pointer, and update. assembly { addr := mload(/*FREE_MEM_PTR*/0x40) mstore(/*FREE_MEM_PTR*/0x40, add(addr, numBytes)) } uint words = (numBytes + WORD_SIZE - 1) / WORD_SIZE; for (uint i = 0; i < words; i++) { assembly { mstore(add(addr, mul(i, /*WORD_SIZE*/32)), 0) } } } // Copy 'len' bytes from memory address 'src', to address 'dest'. // This function does not check the or destination, it only copies // the bytes. function copy(uint src, uint dest, uint len) internal pure { // Copy word-length chunks while possible for (; len >= WORD_SIZE; len -= WORD_SIZE) { assembly { mstore(dest, mload(src)) } dest += WORD_SIZE; src += WORD_SIZE; } // Copy remaining bytes uint mask = 256 ** (WORD_SIZE - len) - 1; assembly { let srcpart := and(mload(src), not(mask)) let destpart := and(mload(dest), mask) mstore(dest, or(destpart, srcpart)) } } // Returns a memory pointer to the provided bytes array. function ptr(bytes memory bts) internal pure returns (uint addr) { assembly { addr := bts } } // Returns a memory pointer to the data portion of the provided bytes array. function dataPtr(bytes memory bts) internal pure returns (uint addr) { assembly { addr := add(bts, /*BYTES_HEADER_SIZE*/32) } } // This function does the same as 'dataPtr(bytes memory)', but will also return the // length of the provided bytes array. function fromBytes(bytes memory bts) internal pure returns (uint addr, uint len) { len = bts.length; assembly { addr := add(bts, /*BYTES_HEADER_SIZE*/32) } } // Creates a 'bytes memory' variable from the memory address 'addr', with the // length 'len'. The function will allocate new memory for the bytes array, and // the 'len bytes starting at 'addr' will be copied into that new memory. function toBytes(uint addr, uint len) internal pure returns (bytes memory bts) { bts = new bytes(len); uint btsptr; assembly { btsptr := add(bts, /*BYTES_HEADER_SIZE*/32) } copy(addr, btsptr, len); } // Get the word stored at memory address 'addr' as a 'uint'. function toUint(uint addr) internal pure returns (uint n) { assembly { n := mload(addr) } } // Get the word stored at memory address 'addr' as a 'bytes32'. function toBytes32(uint addr) internal pure returns (bytes32 bts) { assembly { bts := mload(addr) } } /* // Get the byte stored at memory address 'addr' as a 'byte'. function toByte(uint addr, uint8 index) internal pure returns (byte b) { require(index < WORD_SIZE); uint8 n; assembly { n := byte(index, mload(addr)) } b = byte(n); } */ } // File: REMIX_FILE_SYNC/HelperUtils.sol pragma solidity 0.4.25; /** * Internal helper functions */ contract HelperUtils { // converts bytes32 to a string // enable this when you use it. Saving gas for now // function bytes32ToString(bytes32 x) private pure returns (string) { // bytes memory bytesString = new bytes(32); // uint charCount = 0; // for (uint j = 0; j < 32; j++) { // byte char = byte(bytes32(uint(x) * 2 ** (8 * j))); // if (char != 0) { // bytesString[charCount] = char; // charCount++; // } // } // bytes memory bytesStringTrimmed = new bytes(charCount); // for (j = 0; j < charCount; j++) { // bytesStringTrimmed[j] = bytesString[j]; // } // return string(bytesStringTrimmed); // } /** * Concatenates two strings * @param _a string * @param _b string * @return string concatenation of two string */ function strConcat(string _a, string _b) internal pure returns (string) { bytes memory _ba = bytes(_a); bytes memory _bb = bytes(_b); string memory ab = new string(_ba.length + _bb.length); bytes memory bab = bytes(ab); uint k = 0; for (uint i = 0; i < _ba.length; i++) bab[k++] = _ba[i]; for (i = 0; i < _bb.length; i++) bab[k++] = _bb[i]; return string(bab); } } // File: REMIX_FILE_SYNC/DigitalMediaToken.sol pragma solidity 0.4.25; /** * The DigitalMediaToken contract. Fully implements the ERC721 contract * from OpenZeppelin without any modifications to it. * * This contract allows for the creation of: * 1. New Collections * 2. New DigitalMedia objects * 3. New DigitalMediaRelease objects * * The primary piece of logic is to ensure that an ERC721 token can * have a supply and print edition that is enforced by this contract. */ contract DigitalMediaToken is DigitalMediaManager, ERC721Safe, HelperUtils, SingleCreatorControl { event DigitalMediaReleaseCreateEvent( uint256 id, address owner, uint32 printEdition, string tokenURI, uint256 digitalMediaId); // Event fired when a new digital media is created event DigitalMediaCreateEvent( uint256 id, address storeContractAddress, address creator, uint32 totalSupply, uint32 printIndex, uint256 collectionId, string metadataPath); // Event fired when a digital media's collection is event DigitalMediaCollectionCreateEvent( uint256 id, address storeContractAddress, address creator, string metadataPath); // Event fired when a digital media is burned event DigitalMediaBurnEvent( uint256 id, address caller, address storeContractAddress); // Event fired when burning a token event DigitalMediaReleaseBurnEvent( uint256 tokenId, address owner); event UpdateDigitalMediaPrintIndexEvent( uint256 digitalMediaId, uint32 printEdition); // Event fired when a creator assigns a new creator address. event ChangedCreator( address creator, address newCreator); struct DigitalMediaRelease { // The unique edition number of this digital media release uint32 printEdition; // Reference ID to the digital media metadata uint256 digitalMediaId; } // Maps internal ERC721 token ID to digital media release object. mapping (uint256 => DigitalMediaRelease) public tokenIdToDigitalMediaRelease; // Maps a creator address to a new creator address. Useful if a creator // changes their address or the previous address gets compromised. mapping (address => address) public approvedCreators; // Token ID counter uint256 internal tokenIdCounter = 0; constructor (string _tokenName, string _tokenSymbol, uint256 _tokenIdStartingCounter) public ERC721Token(_tokenName, _tokenSymbol) { tokenIdCounter = _tokenIdStartingCounter; } /** * Creates a new digital media object. * @param _creator address the creator of this digital media * @param _totalSupply uint32 the total supply a creation could have * @param _collectionId uint256 the collectionId that it belongs to * @param _metadataPath string the path to the ipfs metadata * @return uint the new digital media id */ function _createDigitalMedia( address _creator, uint32 _totalSupply, uint256 _collectionId, string _metadataPath) internal returns (uint) { require(_validateCollection(_collectionId, _creator), "Creator for collection not approved."); uint256 newDigitalMediaId = currentDigitalMediaStore.createDigitalMedia( _creator, 0, _totalSupply, _collectionId, _metadataPath); emit DigitalMediaCreateEvent( newDigitalMediaId, address(currentDigitalMediaStore), _creator, _totalSupply, 0, _collectionId, _metadataPath); return newDigitalMediaId; } /** * Burns a token for a given tokenId and caller. * @param _tokenId the id of the token to burn. * @param _caller the address of the caller. */ function _burnToken(uint256 _tokenId, address _caller) internal { address owner = ownerOf(_tokenId); require(_caller == owner || getApproved(_tokenId) == _caller || isApprovedForAll(owner, _caller), "Failed token burn. Caller is not approved."); _burn(owner, _tokenId); delete tokenIdToDigitalMediaRelease[_tokenId]; emit DigitalMediaReleaseBurnEvent(_tokenId, owner); } /** * Burns a digital media. Once this function succeeds, this digital media * will no longer be able to mint any more tokens. Existing tokens need to be * burned individually though. * @param _digitalMediaId the id of the digital media to burn * @param _caller the address of the caller. */ function _burnDigitalMedia(uint256 _digitalMediaId, address _caller) internal { DigitalMedia memory _digitalMedia = _getDigitalMedia(_digitalMediaId); require(_checkApprovedCreator(_digitalMedia.creator, _caller) || isApprovedForAll(_digitalMedia.creator, _caller), "Failed digital media burn. Caller not approved."); uint32 increment = _digitalMedia.totalSupply - _digitalMedia.printIndex; _incrementDigitalMediaPrintIndex(_digitalMedia, increment); address _burnDigitalMediaStoreAddress = address(_getDigitalMediaStore(_digitalMedia.id)); emit DigitalMediaBurnEvent( _digitalMediaId, _caller, _burnDigitalMediaStoreAddress); } /** * Creates a new collection * @param _creator address the creator of this collection * @param _metadataPath string the path to the collection ipfs metadata * @return uint the new collection id */ function _createCollection( address _creator, string _metadataPath) internal returns (uint) { uint256 newCollectionId = currentDigitalMediaStore.createCollection( _creator, _metadataPath); emit DigitalMediaCollectionCreateEvent( newCollectionId, address(currentDigitalMediaStore), _creator, _metadataPath); return newCollectionId; } /** * Creates _count number of new digital media releases (i.e a token). * Bumps up the print index by _count. * @param _owner address the owner of the digital media object * @param _digitalMediaId uint256 the digital media id */ function _createDigitalMediaReleases( address _owner, uint256 _digitalMediaId, uint32 _count) internal { require(_count > 0, "Failed print edition. Creation count must be > 0."); require(_count < 10000, "Cannot print more than 10K tokens at once"); DigitalMedia memory _digitalMedia = _getDigitalMedia(_digitalMediaId); uint32 currentPrintIndex = _digitalMedia.printIndex; require(_checkApprovedCreator(_digitalMedia.creator, _owner), "Creator not approved."); require(isAllowedSingleCreator(_owner), "Creator must match single creator address."); require(_count + currentPrintIndex <= _digitalMedia.totalSupply, "Total supply exceeded."); string memory tokenURI = HelperUtils.strConcat("ipfs://ipfs/", _digitalMedia.metadataPath); for (uint32 i=0; i < _count; i++) { uint32 newPrintEdition = currentPrintIndex + 1 + i; DigitalMediaRelease memory _digitalMediaRelease = DigitalMediaRelease({ printEdition: newPrintEdition, digitalMediaId: _digitalMediaId }); uint256 newDigitalMediaReleaseId = _getNextTokenId(); tokenIdToDigitalMediaRelease[newDigitalMediaReleaseId] = _digitalMediaRelease; emit DigitalMediaReleaseCreateEvent( newDigitalMediaReleaseId, _owner, newPrintEdition, tokenURI, _digitalMediaId ); // This will assign ownership and also emit the Transfer event as per ERC721 _mint(_owner, newDigitalMediaReleaseId); _setTokenURI(newDigitalMediaReleaseId, tokenURI); tokenIdCounter = tokenIdCounter.add(1); } _incrementDigitalMediaPrintIndex(_digitalMedia, _count); emit UpdateDigitalMediaPrintIndexEvent(_digitalMediaId, currentPrintIndex + _count); } /** * Checks that a given caller is an approved creator and is allowed to mint or burn * tokens. If the creator was changed it will check against the updated creator. * @param _caller the calling address * @return bool allowed or not */ function _checkApprovedCreator(address _creator, address _caller) internal view returns (bool) { address approvedCreator = approvedCreators[_creator]; if (approvedCreator != address(0)) { return approvedCreator == _caller; } else { return _creator == _caller; } } /** * Validates the an address is allowed to create a digital media on a * given collection. Collections are tied to addresses. */ function _validateCollection(uint256 _collectionId, address _address) private view returns (bool) { if (_collectionId == 0 ) { return true; } DigitalMediaCollection memory collection = _getCollection(_collectionId); return _checkApprovedCreator(collection.creator, _address); } /** * Generates a new token id. */ function _getNextTokenId() private view returns (uint256) { return tokenIdCounter.add(1); } /** * Changes the creator that is approved to printing new tokens and creations. * Either the _caller must be the _creator or the _caller must be the existing * approvedCreator. * @param _caller the address of the caller * @param _creator the address of the current creator * @param _newCreator the address of the new approved creator */ function _changeCreator(address _caller, address _creator, address _newCreator) internal { address approvedCreator = approvedCreators[_creator]; require(_caller != address(0) && _creator != address(0), "Creator must be valid non 0x0 address."); require(_caller == _creator || _caller == approvedCreator, "Unauthorized caller."); if (approvedCreator == address(0)) { approvedCreators[_caller] = _newCreator; } else { require(_caller == approvedCreator, "Unauthorized caller."); approvedCreators[_creator] = _newCreator; } emit ChangedCreator(_creator, _newCreator); } /** * Introspection interface as per ERC-165 (https://github.com/ethereum/EIPs/issues/165). */ function supportsInterface(bytes4 _interfaceID) external view returns (bool) { return ((_interfaceID == InterfaceSignature_ERC165) || (_interfaceID == InterfaceSignature_ERC721)); } } // File: REMIX_FILE_SYNC/OBOControl.sol pragma solidity 0.4.25; contract OBOControl is Pausable { // List of approved on behalf of users. mapping (address => bool) public approvedOBOs; /** * Add a new approved on behalf of user address. */ function addApprovedOBO(address _oboAddress) external onlyOwner { approvedOBOs[_oboAddress] = true; } /** * Removes an approved on bhealf of user address. */ function removeApprovedOBO(address _oboAddress) external onlyOwner { delete approvedOBOs[_oboAddress]; } /** * @dev Modifier to make the obo calls only callable by approved addressess */ modifier isApprovedOBO() { require(approvedOBOs[msg.sender] == true); _; } } // File: REMIX_FILE_SYNC/WithdrawFundsControl.sol pragma solidity 0.4.25; contract WithdrawFundsControl is Pausable { // List of approved on withdraw addresses mapping (address => uint256) public approvedWithdrawAddresses; // Full day wait period before an approved withdraw address becomes active uint256 constant internal withdrawApprovalWaitPeriod = 60 * 60 * 24; event WithdrawAddressAdded(address withdrawAddress); event WithdrawAddressRemoved(address widthdrawAddress); /** * Add a new approved on behalf of user address. */ function addApprovedWithdrawAddress(address _withdrawAddress) external onlyOwner { approvedWithdrawAddresses[_withdrawAddress] = now; emit WithdrawAddressAdded(_withdrawAddress); } /** * Removes an approved on bhealf of user address. */ function removeApprovedWithdrawAddress(address _withdrawAddress) external onlyOwner { delete approvedWithdrawAddresses[_withdrawAddress]; emit WithdrawAddressRemoved(_withdrawAddress); } /** * Checks that a given withdraw address ia approved and is past it's required * wait time. */ function isApprovedWithdrawAddress(address _withdrawAddress) internal view returns (bool) { uint256 approvalTime = approvedWithdrawAddresses[_withdrawAddress]; require (approvalTime > 0); return now - approvalTime > withdrawApprovalWaitPeriod; } } // File: REMIX_FILE_SYNC/openzeppelin-solidity/contracts/token/ERC721/ERC721Holder.sol pragma solidity ^0.4.21; contract ERC721Holder is ERC721Receiver { function onERC721Received(address, uint256, bytes) public returns(bytes4) { return ERC721_RECEIVED; } } // File: REMIX_FILE_SYNC/DigitalMediaSaleBase.sol pragma solidity 0.4.25; /** * Base class that manages the underlying functions of a Digital Media Sale, * most importantly the escrow of digital tokens. * * Manages ensuring that only approved addresses interact with this contract. * */ contract DigitalMediaSaleBase is ERC721Holder, Pausable, OBOControl, WithdrawFundsControl { using SafeMath for uint256; // Mapping of token contract address to bool indicated approval. mapping (address => bool) public approvedTokenContracts; /** * Adds a new token contract address to be approved to be called. */ function addApprovedTokenContract(address _tokenContractAddress) public onlyOwner { approvedTokenContracts[_tokenContractAddress] = true; } /** * Remove an approved token contract address from the list of approved addresses. */ function removeApprovedTokenContract(address _tokenContractAddress) public onlyOwner { delete approvedTokenContracts[_tokenContractAddress]; } /** * Checks that a particular token contract address is a valid address. */ function _isValidTokenContract(address _tokenContractAddress) internal view returns (bool) { return approvedTokenContracts[_tokenContractAddress]; } /** * Returns an ERC721 instance of a token contract address. Throws otherwise. * Only valid and approved token contracts are allowed to be interacted with. */ function _getTokenContract(address _tokenContractAddress) internal view returns (ERC721Safe) { require(_isValidTokenContract(_tokenContractAddress)); return ERC721Safe(_tokenContractAddress); } /** * Checks with the ERC-721 token contract that the _claimant actually owns the token. */ function _owns(address _claimant, uint256 _tokenId, address _tokenContractAddress) internal view returns (bool) { ERC721Safe tokenContract = _getTokenContract(_tokenContractAddress); return (tokenContract.ownerOf(_tokenId) == _claimant); } /** * Checks with the ERC-721 token contract the owner of the a token */ function _ownerOf(uint256 _tokenId, address _tokenContractAddress) internal view returns (address) { ERC721Safe tokenContract = _getTokenContract(_tokenContractAddress); return tokenContract.ownerOf(_tokenId); } /** * Checks to ensure that the token owner has approved the escrow contract */ function _approvedForEscrow(address _seller, uint256 _tokenId, address _tokenContractAddress) internal view returns (bool) { ERC721Safe tokenContract = _getTokenContract(_tokenContractAddress); return (tokenContract.isApprovedForAll(_seller, this) || tokenContract.getApproved(_tokenId) == address(this)); } /** * Escrows an ERC-721 token from the seller to this contract. Assumes that the escrow contract * is already approved to make the transfer, otherwise it will fail. */ function _escrow(address _seller, uint256 _tokenId, address _tokenContractAddress) internal { // it will throw if transfer fails ERC721Safe tokenContract = _getTokenContract(_tokenContractAddress); tokenContract.safeTransferFrom(_seller, this, _tokenId); } /** * Transfer an ERC-721 token from escrow to the buyer. This is to be called after a purchase is * completed. */ function _transfer(address _receiver, uint256 _tokenId, address _tokenContractAddress) internal { // it will throw if transfer fails ERC721Safe tokenContract = _getTokenContract(_tokenContractAddress); tokenContract.safeTransferFrom(this, _receiver, _tokenId); } /** * Method to check whether this is an escrow contract */ function isEscrowContract() public pure returns(bool) { return true; } /** * Withdraws all the funds to a specified non-zero address */ function withdrawFunds(address _withdrawAddress) public onlyOwner { require(isApprovedWithdrawAddress(_withdrawAddress)); _withdrawAddress.transfer(address(this).balance); } } // File: REMIX_FILE_SYNC/DigitalMediaCore.sol pragma solidity 0.4.25; /** * This is the main driver contract that is used to control and run the service. Funds * are managed through this function, underlying contracts are also updated through * this contract. * * This class also exposes a set of creation methods that are allowed to be created * by an approved token creator, on behalf of a particular address. This is meant * to simply the creation flow for MakersToken users that aren't familiar with * the blockchain. The ERC721 tokens that are created are still fully compliant, * although it is possible for a malicious token creator to mint unwanted tokens * on behalf of a creator. Worst case, the creator can burn those tokens. */ contract DigitalMediaCore is DigitalMediaToken { using SafeMath for uint32; // List of approved token creators (on behalf of the owner) mapping (address => bool) public approvedTokenCreators; // Mapping from owner to operator accounts. mapping (address => mapping (address => bool)) internal oboOperatorApprovals; // Mapping of all disabled OBO operators. mapping (address => bool) public disabledOboOperators; // OboApproveAll Event event OboApprovalForAll( address _owner, address _operator, bool _approved); // Fired when disbaling obo capability. event OboDisabledForAll(address _operator); constructor ( string _tokenName, string _tokenSymbol, uint256 _tokenIdStartingCounter, address _dmsAddress, address _crsAddress) public DigitalMediaToken( _tokenName, _tokenSymbol, _tokenIdStartingCounter) { paused = true; setDigitalMediaStoreAddress(_dmsAddress); setCreatorRegistryStore(_crsAddress); } /** * Retrieves a Digital Media object. */ function getDigitalMedia(uint256 _id) external view returns ( uint256 id, uint32 totalSupply, uint32 printIndex, uint256 collectionId, address creator, string metadataPath) { DigitalMedia memory digitalMedia = _getDigitalMedia(_id); require(digitalMedia.creator != address(0), "DigitalMedia not found."); id = _id; totalSupply = digitalMedia.totalSupply; printIndex = digitalMedia.printIndex; collectionId = digitalMedia.collectionId; creator = digitalMedia.creator; metadataPath = digitalMedia.metadataPath; } /** * Retrieves a collection. */ function getCollection(uint256 _id) external view returns ( uint256 id, address creator, string metadataPath) { DigitalMediaCollection memory digitalMediaCollection = _getCollection(_id); require(digitalMediaCollection.creator != address(0), "Collection not found."); id = _id; creator = digitalMediaCollection.creator; metadataPath = digitalMediaCollection.metadataPath; } /** * Retrieves a Digital Media Release (i.e a token) */ function getDigitalMediaRelease(uint256 _id) external view returns ( uint256 id, uint32 printEdition, uint256 digitalMediaId) { require(exists(_id)); DigitalMediaRelease storage digitalMediaRelease = tokenIdToDigitalMediaRelease[_id]; id = _id; printEdition = digitalMediaRelease.printEdition; digitalMediaId = digitalMediaRelease.digitalMediaId; } /** * Creates a new collection. * * No creations of any kind are allowed when the contract is paused. */ function createCollection(string _metadataPath) external whenNotPaused { _createCollection(msg.sender, _metadataPath); } /** * Creates a new digital media object. */ function createDigitalMedia(uint32 _totalSupply, uint256 _collectionId, string _metadataPath) external whenNotPaused { _createDigitalMedia(msg.sender, _totalSupply, _collectionId, _metadataPath); } /** * Creates a new digital media object and mints it's first digital media release token. * * No creations of any kind are allowed when the contract is paused. */ function createDigitalMediaAndReleases( uint32 _totalSupply, uint256 _collectionId, string _metadataPath, uint32 _numReleases) external whenNotPaused { uint256 digitalMediaId = _createDigitalMedia(msg.sender, _totalSupply, _collectionId, _metadataPath); _createDigitalMediaReleases(msg.sender, digitalMediaId, _numReleases); } /** * Creates a new collection, a new digital media object within it and mints a new * digital media release token. * * No creations of any kind are allowed when the contract is paused. */ function createDigitalMediaAndReleasesInNewCollection( uint32 _totalSupply, string _digitalMediaMetadataPath, string _collectionMetadataPath, uint32 _numReleases) external whenNotPaused { uint256 collectionId = _createCollection(msg.sender, _collectionMetadataPath); uint256 digitalMediaId = _createDigitalMedia(msg.sender, _totalSupply, collectionId, _digitalMediaMetadataPath); _createDigitalMediaReleases(msg.sender, digitalMediaId, _numReleases); } /** * Creates a new digital media release (token) for a given digital media id. * * No creations of any kind are allowed when the contract is paused. */ function createDigitalMediaReleases(uint256 _digitalMediaId, uint32 _numReleases) external whenNotPaused { _createDigitalMediaReleases(msg.sender, _digitalMediaId, _numReleases); } /** * Deletes a token / digital media release. Doesn't modify the current print index * and total to be printed. Although dangerous, the owner of a token should always * be able to burn a token they own. * * Only the owner of the token or accounts approved by the owner can burn this token. */ function burnToken(uint256 _tokenId) external { _burnToken(_tokenId, msg.sender); } /* Support ERC721 burn method */ function burn(uint256 tokenId) public { _burnToken(tokenId, msg.sender); } /** * Ends the production run of a digital media. Afterwards no more tokens * will be allowed to be printed for this digital media. Used when a creator * makes a mistake and wishes to burn and recreate their digital media. * * When a contract is paused we do not allow new tokens to be created, * so stopping the production of a token doesn't have much purpose. */ function burnDigitalMedia(uint256 _digitalMediaId) external whenNotPaused { _burnDigitalMedia(_digitalMediaId, msg.sender); } /** * Resets the approval rights for a given tokenId. */ function resetApproval(uint256 _tokenId) external { clearApproval(msg.sender, _tokenId); } /** * Changes the creator for the current sender, in the event we * need to be able to mint new tokens from an existing digital media * print production. When changing creator, the old creator will * no longer be able to mint tokens. * * A creator may need to be changed: * 1. If we want to allow a creator to take control over their token minting (i.e go decentralized) * 2. If we want to re-issue private keys due to a compromise. For this reason, we can call this function * when the contract is paused. * @param _creator the creator address * @param _newCreator the new creator address */ function changeCreator(address _creator, address _newCreator) external { _changeCreator(msg.sender, _creator, _newCreator); } /**********************************************************************/ /**Calls that are allowed to be called by approved creator addresses **/ /**********************************************************************/ /** * Add a new approved token creator. * * Only the owner of this contract can update approved Obo accounts. */ function addApprovedTokenCreator(address _creatorAddress) external onlyOwner { require(disabledOboOperators[_creatorAddress] != true, "Address disabled."); approvedTokenCreators[_creatorAddress] = true; } /** * Removes an approved token creator. * * Only the owner of this contract can update approved Obo accounts. */ function removeApprovedTokenCreator(address _creatorAddress) external onlyOwner { delete approvedTokenCreators[_creatorAddress]; } /** * @dev Modifier to make the approved creation calls only callable by approved token creators */ modifier isApprovedCreator() { require( (approvedTokenCreators[msg.sender] == true && disabledOboOperators[msg.sender] != true), "Unapproved OBO address."); _; } /** * Only the owner address can set a special obo approval list. * When issuing OBO management accounts, we should give approvals through * this method only so that we can very easily reset it's approval in * the event of a disaster scenario. * * Only the owner themselves is allowed to give OboApproveAll access. */ function setOboApprovalForAll(address _to, bool _approved) public { require(_to != msg.sender, "Approval address is same as approver."); require(approvedTokenCreators[_to], "Unrecognized OBO address."); require(disabledOboOperators[_to] != true, "Approval address is disabled."); oboOperatorApprovals[msg.sender][_to] = _approved; emit OboApprovalForAll(msg.sender, _to, _approved); } /** * Only called in a disaster scenario if the account has been compromised. * There's no turning back from this and the oboAddress will no longer be * able to be given approval rights or perform obo functions. * * Only the owner of this contract is allowed to disable an Obo address. * */ function disableOboAddress(address _oboAddress) public onlyOwner { require(approvedTokenCreators[_oboAddress], "Unrecognized OBO address."); disabledOboOperators[_oboAddress] = true; delete approvedTokenCreators[_oboAddress]; emit OboDisabledForAll(_oboAddress); } /** * Override the isApprovalForAll to check for a special oboApproval list. Reason for this * is that we can can easily remove obo operators if they every become compromised. */ function isApprovedForAll(address _owner, address _operator) public view returns (bool) { if (disabledOboOperators[_operator] == true) { return false; } else if (isOperatorApprovedForCustodialAccount(_operator, _owner) == true) { return true; } else if (oboOperatorApprovals[_owner][_operator]) { return true; } else { return super.isApprovedForAll(_owner, _operator); } } /** * Creates a new digital media object and mints it's digital media release tokens. * Called on behalf of the _owner. Pass count to mint `n` number of tokens. * * Only approved creators are allowed to create Obo. * * No creations of any kind are allowed when the contract is paused. */ function oboCreateDigitalMediaAndReleases( address _owner, uint32 _totalSupply, uint256 _collectionId, string _metadataPath, uint32 _numReleases) external whenNotPaused isApprovedCreator { uint256 digitalMediaId = _createDigitalMedia(_owner, _totalSupply, _collectionId, _metadataPath); _createDigitalMediaReleases(_owner, digitalMediaId, _numReleases); } /** * Creates a new collection, a new digital media object within it and mints a new * digital media release token. * Called on behalf of the _owner. * * Only approved creators are allowed to create Obo. * * No creations of any kind are allowed when the contract is paused. */ function oboCreateDigitalMediaAndReleasesInNewCollection( address _owner, uint32 _totalSupply, string _digitalMediaMetadataPath, string _collectionMetadataPath, uint32 _numReleases) external whenNotPaused isApprovedCreator { uint256 collectionId = _createCollection(_owner, _collectionMetadataPath); uint256 digitalMediaId = _createDigitalMedia(_owner, _totalSupply, collectionId, _digitalMediaMetadataPath); _createDigitalMediaReleases(_owner, digitalMediaId, _numReleases); } /** * Creates multiple digital media releases (tokens) for a given digital media id. * Called on behalf of the _owner. * * Only approved creators are allowed to create Obo. * * No creations of any kind are allowed when the contract is paused. */ function oboCreateDigitalMediaReleases( address _owner, uint256 _digitalMediaId, uint32 _numReleases) external whenNotPaused isApprovedCreator { _createDigitalMediaReleases(_owner, _digitalMediaId, _numReleases); } }