Skip to content

Instantly share code, notes, and snippets.

@MoMannn
Created December 12, 2019 17:00
Show Gist options
  • Save MoMannn/f232580dcdb7a7948d9adeb0a7c7ae23 to your computer and use it in GitHub Desktop.
Save MoMannn/f232580dcdb7a7948d9adeb0a7c7ae23 to your computer and use it in GitHub Desktop.
pragma solidity 0.5.11;
pragma experimental ABIEncoderV2;
interface Xcert {
function create(
address _to,
uint256 _id,
bytes32 _imprint
)
external;
function setUri(
string calldata _uriPrefix,
string calldata _uriPostfix
)
external;
function schemaId()
external
view
returns (bytes32 _schemaId);
function tokenImprint(
uint256 _tokenId
)
external
view
returns(bytes32 imprint);
}
interface XcertBurnable {
function destroy(
uint256 _tokenId
)
external;
}
interface XcertMutable {
function updateTokenImprint(
uint256 _tokenId,
bytes32 _imprint
)
external;
}
interface XcertPausable {
function setPause(
bool _isPaused
)
external;
}
interface XcertRevokable {
function revoke(
uint256 _tokenId
)
external;
}
library SafeMath {
string constant OVERFLOW = "008001";
string constant SUBTRAHEND_GREATER_THEN_MINUEND = "008002";
string constant DIVISION_BY_ZERO = "008003";
function mul(
uint256 _factor1,
uint256 _factor2
)
internal
pure
returns (uint256 product)
{
if (_factor1 == 0)
{
return 0;
}
product = _factor1 * _factor2;
require(product / _factor1 == _factor2, OVERFLOW);
}
function div(
uint256 _dividend,
uint256 _divisor
)
internal
pure
returns (uint256 quotient)
{
require(_divisor > 0, DIVISION_BY_ZERO);
quotient = _dividend / _divisor;
}
function sub(
uint256 _minuend,
uint256 _subtrahend
)
internal
pure
returns (uint256 difference)
{
require(_subtrahend <= _minuend, SUBTRAHEND_GREATER_THEN_MINUEND);
difference = _minuend - _subtrahend;
}
function add(
uint256 _addend1,
uint256 _addend2
)
internal
pure
returns (uint256 sum)
{
sum = _addend1 + _addend2;
require(sum >= _addend1, OVERFLOW);
}
function mod(
uint256 _dividend,
uint256 _divisor
)
internal
pure
returns (uint256 remainder)
{
require(_divisor != 0, DIVISION_BY_ZERO);
remainder = _dividend % _divisor;
}
}
contract Abilitable {
using SafeMath for uint;
string constant NOT_AUTHORIZED = "017001";
string constant INVALID_INPUT = "017002";
uint8 constant SUPER_ABILITY = 1;
uint8 constant ALLOW_SUPER_ABILITY = 2;
uint8 constant EMPTY_SLOT_1 = 4;
uint8 constant EMPTY_SLOT_2 = 8;
uint8 constant ALL_DEFAULT_ABILITIES = 15;
mapping(address => uint256) public addressToAbility;
event SetAbilities(
address indexed _target,
uint256 indexed _abilities
);
modifier hasAbilities(
uint256 _abilities
)
{
require(_abilities > 0, INVALID_INPUT);
require(
addressToAbility[msg.sender] & _abilities == _abilities,
NOT_AUTHORIZED
);
_;
}
constructor()
public
{
addressToAbility[msg.sender] = ALL_DEFAULT_ABILITIES;
}
function grantAbilities(
address _target,
uint256 _abilities
)
external
hasAbilities(SUPER_ABILITY)
{
addressToAbility[_target] |= _abilities;
emit SetAbilities(_target, addressToAbility[_target]);
}
function revokeAbilities(
address _target,
uint256 _abilities
)
external
hasAbilities(SUPER_ABILITY)
{
addressToAbility[_target] &= ~_abilities;
emit SetAbilities(_target, addressToAbility[_target]);
}
function setAbilities(
address _target,
uint256 _abilities
)
external
hasAbilities(SUPER_ABILITY)
{
addressToAbility[_target] = _abilities;
emit SetAbilities(_target, _abilities);
}
function isAble(
address _target,
uint256 _abilities
)
external
view
returns (bool)
{
require(_abilities > 0, INVALID_INPUT);
return (addressToAbility[_target] & _abilities) == _abilities;
}
}
interface ERC721 {
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 safeTransferFrom(
address _from,
address _to,
uint256 _tokenId,
bytes calldata _data
)
external;
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId
)
external;
function transferFrom(
address _from,
address _to,
uint256 _tokenId
)
external;
function approve(
address _approved,
uint256 _tokenId
)
external;
function setApprovalForAll(
address _operator,
bool _approved
)
external;
function balanceOf(
address _owner
)
external
view
returns (uint256);
function ownerOf(
uint256 _tokenId
)
external
view
returns (address);
function getApproved(
uint256 _tokenId
)
external
view
returns (address);
function isApprovedForAll(
address _owner,
address _operator
)
external
view
returns (bool);
}
interface ERC721Metadata {
function name()
external
view
returns (string memory _name);
function symbol()
external
view
returns (string memory _symbol);
function tokenURI(uint256 _tokenId)
external
view
returns (string memory);
}
interface ERC721Enumerable {
function totalSupply()
external
view
returns (uint256);
function tokenByIndex(
uint256 _index
)
external
view
returns (uint256);
function tokenOfOwnerByIndex(
address _owner,
uint256 _index
)
external
view
returns (uint256);
}
interface ERC721TokenReceiver {
function onERC721Received(
address _operator,
address _from,
uint256 _tokenId,
bytes calldata _data
)
external
returns(bytes4);
}
interface ERC165 {
function supportsInterface(
bytes4 _interfaceID
)
external
view
returns (bool);
}
contract SupportsInterface is
ERC165
{
mapping(bytes4 => bool) internal supportedInterfaces;
constructor()
public
{
supportedInterfaces[0x01ffc9a7] = true;
}
function supportsInterface(
bytes4 _interfaceID
)
external
view
returns (bool)
{
return supportedInterfaces[_interfaceID];
}
}
library AddressUtils {
function isDeployedContract(
address _addr
)
internal
view
returns (bool addressCheck)
{
uint256 size;
assembly { size := extcodesize(_addr) }
addressCheck = size > 0;
}
}
contract NFTokenMetadataEnumerable is
ERC721,
ERC721Metadata,
ERC721Enumerable,
SupportsInterface
{
using SafeMath for uint256;
using AddressUtils for address;
string constant ZERO_ADDRESS = "006001";
string constant NOT_VALID_NFT = "006002";
string constant NOT_OWNER_OR_OPERATOR = "006003";
string constant NOT_OWNER_APPROWED_OR_OPERATOR = "006004";
string constant NOT_ABLE_TO_RECEIVE_NFT = "006005";
string constant NFT_ALREADY_EXISTS = "006006";
string constant INVALID_INDEX = "006007";
bytes4 constant MAGIC_ON_ERC721_RECEIVED = 0x150b7a02;
string internal nftName;
string internal nftSymbol;
string public uriPrefix;
string public uriPostfix;
uint256[] internal tokens;
mapping(uint256 => uint256) internal idToIndex;
mapping(address => uint256[]) internal ownerToIds;
mapping(uint256 => uint256) internal idToOwnerIndex;
mapping (uint256 => address) internal idToOwner;
mapping (uint256 => address) internal idToApproval;
mapping (address => mapping (address => bool)) internal ownerToOperators;
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
);
constructor()
public
{
supportedInterfaces[0x80ac58cd] = true;
supportedInterfaces[0x5b5e139f] = true;
supportedInterfaces[0x780e9d63] = true;
}
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId,
bytes calldata _data
)
external
{
_safeTransferFrom(_from, _to, _tokenId, _data);
}
function safeTransferFrom(
address _from,
address _to,
uint256 _tokenId
)
external
{
_safeTransferFrom(_from, _to, _tokenId, "");
}
function transferFrom(
address _from,
address _to,
uint256 _tokenId
)
external
{
_transferFrom(_from, _to, _tokenId);
}
function approve(
address _approved,
uint256 _tokenId
)
external
{
address tokenOwner = idToOwner[_tokenId];
require(
tokenOwner == msg.sender || ownerToOperators[tokenOwner][msg.sender],
NOT_OWNER_OR_OPERATOR
);
idToApproval[_tokenId] = _approved;
emit Approval(tokenOwner, _approved, _tokenId);
}
function setApprovalForAll(
address _operator,
bool _approved
)
external
{
ownerToOperators[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
function balanceOf(
address _owner
)
external
view
returns (uint256)
{
require(_owner != address(0), ZERO_ADDRESS);
return ownerToIds[_owner].length;
}
function ownerOf(
uint256 _tokenId
)
external
view
returns (address _owner)
{
_owner = idToOwner[_tokenId];
require(_owner != address(0), NOT_VALID_NFT);
}
function getApproved(
uint256 _tokenId
)
external
view
returns (address)
{
require(idToOwner[_tokenId] != address(0), NOT_VALID_NFT);
return idToApproval[_tokenId];
}
function isApprovedForAll(
address _owner,
address _operator
)
external
view
returns (bool)
{
return ownerToOperators[_owner][_operator];
}
function totalSupply()
external
view
returns (uint256)
{
return tokens.length;
}
function tokenByIndex(
uint256 _index
)
external
view
returns (uint256)
{
require(_index < tokens.length, INVALID_INDEX);
return tokens[_index];
}
function tokenOfOwnerByIndex(
address _owner,
uint256 _index
)
external
view
returns (uint256)
{
require(_index < ownerToIds[_owner].length, INVALID_INDEX);
return ownerToIds[_owner][_index];
}
function name()
external
view
returns (string memory _name)
{
_name = nftName;
}
function symbol()
external
view
returns (string memory _symbol)
{
_symbol = nftSymbol;
}
function tokenURI(
uint256 _tokenId
)
external
view
returns (string memory)
{
require(idToOwner[_tokenId] != address(0), NOT_VALID_NFT);
string memory uri = "";
if (bytes(uriPrefix).length > 0)
{
uri = string(abi.encodePacked(uriPrefix, _uint2str(_tokenId)));
if (bytes(uriPostfix).length > 0)
{
uri = string(abi.encodePacked(uri, uriPostfix));
}
}
return uri;
}
function _setUri(
string memory _prefix,
string memory _postfix
)
internal
{
uriPrefix = _prefix;
uriPostfix = _postfix;
}
function _create(
address _to,
uint256 _tokenId
)
internal
{
require(_to != address(0), ZERO_ADDRESS);
require(idToOwner[_tokenId] == address(0), NFT_ALREADY_EXISTS);
idToOwner[_tokenId] = _to;
uint256 length = ownerToIds[_to].push(_tokenId);
idToOwnerIndex[_tokenId] = length - 1;
length = tokens.push(_tokenId);
idToIndex[_tokenId] = length - 1;
emit Transfer(address(0), _to, _tokenId);
}
function _destroy(
uint256 _tokenId
)
internal
{
address _owner = idToOwner[_tokenId];
require(_owner != address(0), NOT_VALID_NFT);
if (idToApproval[_tokenId] != address(0))
{
delete idToApproval[_tokenId];
}
assert(ownerToIds[_owner].length > 0);
uint256 tokenToRemoveIndex = idToOwnerIndex[_tokenId];
uint256 lastTokenIndex = ownerToIds[_owner].length - 1;
uint256 lastToken;
if (lastTokenIndex != tokenToRemoveIndex)
{
lastToken = ownerToIds[_owner][lastTokenIndex];
ownerToIds[_owner][tokenToRemoveIndex] = lastToken;
idToOwnerIndex[lastToken] = tokenToRemoveIndex;
}
delete idToOwner[_tokenId];
delete idToOwnerIndex[_tokenId];
ownerToIds[_owner].length--;
assert(tokens.length > 0);
uint256 tokenIndex = idToIndex[_tokenId];
lastTokenIndex = tokens.length - 1;
lastToken = tokens[lastTokenIndex];
tokens[tokenIndex] = lastToken;
tokens.length--;
idToIndex[lastToken] = tokenIndex;
idToIndex[_tokenId] = 0;
emit Transfer(_owner, address(0), _tokenId);
}
function _transferFrom(
address _from,
address _to,
uint256 _tokenId
)
internal
{
require(_from != address(0), ZERO_ADDRESS);
require(idToOwner[_tokenId] == _from, NOT_VALID_NFT);
require(_to != address(0), ZERO_ADDRESS);
require(
_from == msg.sender
|| idToApproval[_tokenId] == msg.sender
|| ownerToOperators[_from][msg.sender],
NOT_OWNER_APPROWED_OR_OPERATOR
);
if (idToApproval[_tokenId] != address(0))
{
delete idToApproval[_tokenId];
}
assert(ownerToIds[_from].length > 0);
uint256 tokenToRemoveIndex = idToOwnerIndex[_tokenId];
uint256 lastTokenIndex = ownerToIds[_from].length - 1;
if (lastTokenIndex != tokenToRemoveIndex)
{
uint256 lastToken = ownerToIds[_from][lastTokenIndex];
ownerToIds[_from][tokenToRemoveIndex] = lastToken;
idToOwnerIndex[lastToken] = tokenToRemoveIndex;
}
ownerToIds[_from].length--;
idToOwner[_tokenId] = _to;
uint256 length = ownerToIds[_to].push(_tokenId);
idToOwnerIndex[_tokenId] = length - 1;
emit Transfer(_from, _to, _tokenId);
}
function _safeTransferFrom(
address _from,
address _to,
uint256 _tokenId,
bytes memory _data
)
internal
{
if (_to.isDeployedContract())
{
require(
ERC721TokenReceiver(_to)
.onERC721Received(msg.sender, _from, _tokenId, _data) == MAGIC_ON_ERC721_RECEIVED,
NOT_ABLE_TO_RECEIVE_NFT
);
}
_transferFrom(_from, _to, _tokenId);
}
function _uint2str(
uint256 _i
)
internal
pure
returns (string memory str)
{
if (_i == 0)
{
return "0";
}
uint256 j = _i;
uint256 length;
while (j != 0)
{
length++;
j /= 10;
}
bytes memory bstr = new bytes(length);
uint256 k = length - 1;
j = _i;
while (j != 0)
{
bstr[k--] = byte(uint8(48 + j % 10));
j /= 10;
}
str = string(bstr);
}
}
interface ERC20 {
function name()
external
view
returns (string memory _name);
function symbol()
external
view
returns (string memory _symbol);
function decimals()
external
view
returns (uint8 _decimals);
function totalSupply()
external
view
returns (uint256 _totalSupply);
function balanceOf(
address _owner
)
external
view
returns (uint256 _balance);
function transfer(
address _to,
uint256 _value
)
external
returns (bool _success);
function transferFrom(
address _from,
address _to,
uint256 _value
)
external
returns (bool _success);
function approve(
address _spender,
uint256 _value
)
external
returns (bool _success);
function allowance(
address _owner,
address _spender
)
external
view
returns (uint256 _remaining);
event Transfer(
address indexed _from,
address indexed _to,
uint256 _value
);
event Approval(
address indexed _owner,
address indexed _spender,
uint256 _value
);
}
contract XcertToken is
Xcert,
XcertBurnable,
XcertMutable,
XcertPausable,
XcertRevokable,
NFTokenMetadataEnumerable,
Abilitable
{
uint8 constant ABILITY_CREATE_ASSET = 16;
uint8 constant ABILITY_REVOKE_ASSET = 32;
uint8 constant ABILITY_TOGGLE_TRANSFERS = 64;
uint8 constant ABILITY_UPDATE_ASSET_IMPRINT = 128;
uint16 constant ABILITY_UPDATE_URI = 256;
bytes4 constant MUTABLE = 0xbda0e852;
bytes4 constant BURNABLE = 0x9d118770;
bytes4 constant PAUSABLE = 0xbedb86fb;
bytes4 constant REVOKABLE = 0x20c5429b;
string constant CAPABILITY_NOT_SUPPORTED = "007001";
string constant TRANSFERS_DISABLED = "007002";
string constant NOT_VALID_XCERT = "007003";
string constant NOT_OWNER_OR_OPERATOR = "007004";
string constant INVALID_SIGNATURE = "007005";
string constant INVALID_SIGNATURE_KIND = "007006";
string constant CLAIM_PERFORMED = "007007";
string constant CLAIM_EXPIRED = "007008";
event IsPaused(bool isPaused);
event TokenImprintUpdate(
uint256 indexed _tokenId,
bytes32 _imprint
);
enum SignatureKind
{
eth_sign,
trezor,
no_prefix
}
struct SignatureData
{
bytes32 r;
bytes32 s;
uint8 v;
SignatureKind kind;
}
mapping(bytes32 => bool) public claimPerformed;
bytes32 internal nftSchemaId;
mapping (uint256 => bytes32) internal idToImprint;
mapping (address => bool) internal addressToAuthorized;
bool public isPaused;
constructor()
public
{
supportedInterfaces[0x4ecc17d1] = true;
}
function create(
address _to,
uint256 _id,
bytes32 _imprint
)
external
hasAbilities(ABILITY_CREATE_ASSET)
{
super._create(_to, _id);
idToImprint[_id] = _imprint;
}
function setUri(
string calldata _uriPrefix,
string calldata _uriPostfix
)
external
hasAbilities(ABILITY_UPDATE_URI)
{
super._setUri(_uriPrefix, _uriPostfix);
}
function revoke(
uint256 _tokenId
)
external
hasAbilities(ABILITY_REVOKE_ASSET)
{
require(supportedInterfaces[REVOKABLE], CAPABILITY_NOT_SUPPORTED);
super._destroy(_tokenId);
delete idToImprint[_tokenId];
}
function setPause(
bool _isPaused
)
external
hasAbilities(ABILITY_TOGGLE_TRANSFERS)
{
require(supportedInterfaces[PAUSABLE], CAPABILITY_NOT_SUPPORTED);
isPaused = _isPaused;
emit IsPaused(_isPaused);
}
function updateTokenImprint(
uint256 _tokenId,
bytes32 _imprint
)
external
hasAbilities(ABILITY_UPDATE_ASSET_IMPRINT)
{
require(supportedInterfaces[MUTABLE], CAPABILITY_NOT_SUPPORTED);
require(idToOwner[_tokenId] != address(0), NOT_VALID_XCERT);
idToImprint[_tokenId] = _imprint;
emit TokenImprintUpdate(_tokenId, _imprint);
}
function destroy(
uint256 _tokenId
)
external
{
require(supportedInterfaces[BURNABLE], CAPABILITY_NOT_SUPPORTED);
address tokenOwner = idToOwner[_tokenId];
super._destroy(_tokenId);
require(
tokenOwner == msg.sender || ownerToOperators[tokenOwner][msg.sender],
NOT_OWNER_OR_OPERATOR
);
delete idToImprint[_tokenId];
}
function setApprovalForAllWithSignature(
address _owner,
address _operator,
bool _approved,
address _feeToken,
uint256 _feeValue,
uint256 _seed,
uint256 _expiration,
SignatureData calldata _signature
)
external
{
bytes32 claim = generateClaim(
_owner,
_operator,
_approved,
_feeToken,
_feeValue,
_seed,
_expiration
);
require(
isValidSignature(
_owner,
claim,
_signature
),
INVALID_SIGNATURE
);
require(!claimPerformed[claim], CLAIM_PERFORMED);
require(_expiration >= now, CLAIM_EXPIRED);
claimPerformed[claim] = true;
ownerToOperators[_owner][_operator] = _approved;
ERC20(_feeToken).transferFrom(_owner, msg.sender, _feeValue);
emit ApprovalForAll(_owner, _operator, _approved);
}
function generateClaim(
address _owner,
address _operator,
bool _approved,
address _feeToken,
uint256 _feeValue,
uint256 _seed,
uint256 _expiration
)
public
view
returns(bytes32)
{
return keccak256(
abi.encodePacked(
address(this),
_owner,
_operator,
_approved,
_feeToken,
_feeValue,
_seed,
_expiration
)
);
}
function isValidSignature(
address _signer,
bytes32 _claim,
SignatureData memory _signature
)
public
pure
returns (bool)
{
if (_signature.kind == SignatureKind.eth_sign)
{
return _signer == ecrecover(
keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
_claim
)
),
_signature.v,
_signature.r,
_signature.s
);
} else if (_signature.kind == SignatureKind.trezor)
{
return _signer == ecrecover(
keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n\x20",
_claim
)
),
_signature.v,
_signature.r,
_signature.s
);
} else if (_signature.kind == SignatureKind.no_prefix)
{
return _signer == ecrecover(
_claim,
_signature.v,
_signature.r,
_signature.s
);
}
revert(INVALID_SIGNATURE_KIND);
}
function schemaId()
external
view
returns (bytes32 _schemaId)
{
_schemaId = nftSchemaId;
}
function tokenImprint(
uint256 _tokenId
)
external
view
returns(bytes32 imprint)
{
imprint = idToImprint[_tokenId];
}
function _transferFrom(
address _from,
address _to,
uint256 _tokenId
)
internal
{
require(!isPaused, TRANSFERS_DISABLED);
super._transferFrom(_from, _to, _tokenId);
}
}
contract XcertMock is XcertToken {
constructor(
string memory _name,
string memory _symbol,
string memory _uriPrefix,
string memory _uriPostfix,
bytes32 _schemaId,
bytes4[] memory _capabilities
)
public
{
nftName = _name;
nftSymbol = _symbol;
uriPrefix = _uriPrefix;
uriPostfix = _uriPostfix;
nftSchemaId = _schemaId;
for(uint256 i = 0; i < _capabilities.length; i++)
{
supportedInterfaces[_capabilities[i]] = true;
}
addressToAbility[msg.sender] = 2047;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment