Contract Name:
MetaAnchorFactory
Contract Source Code:
File 1 of 1 : MetaAnchorFactory
// SPDX-License-Identifier: MIT AND UNLICENSED AND CC0-1.0
//
// OpenZeppelin and ERC-6956 are licensed under MIT
// Note ERC-6956 is authored by us (authenticvision.com)
// IERC6454 is licensed under CC0-1.0
//
// All other contracts are UNLICENSED, visit metaanchor.io for licensing information
//
// Meta Anchor (TM), Authentic Vision (TM) and Digital Soul (TM) are Registered Trademarks
// and will be denoted as MetaAnchor, AuthenticVision and DigitalSoul subsequently.
// Sources flattened with hardhat v2.12.6 https://hardhat.org
// File @openzeppelin/contracts/access/[email protected]
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IAccessControl {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
// File @openzeppelin/contracts/utils/[email protected]
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// File @openzeppelin/contracts/utils/introspection/[email protected]
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// File @openzeppelin/contracts/utils/introspection/[email protected]
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// File @openzeppelin/contracts/utils/math/[email protected]
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}
// File @openzeppelin/contracts/utils/[email protected]
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
// File @openzeppelin/contracts/access/[email protected]
// OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with a standardized message including the required role.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*
* _Available since v4.1._
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
return _roles[role].members[account];
}
/**
* @dev Revert with a standard message if `_msgSender()` is missing `role`.
* Overriding this function changes the behavior of the {onlyRole} modifier.
*
* Format of the revert message is described in {_checkRole}.
*
* _Available since v4.6._
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Revert with a standard message if `account` is missing `role`.
*
* The format of the revert reason is given by the following regular expression:
*
* /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(account),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event. Note that unlike {grantRole}, this function doesn't perform any
* checks on the calling account.
*
* May emit a {RoleGranted} event.
*
* [WARNING]
* ====
* This function should only be called from the constructor when setting
* up the initial roles for the system.
*
* Using this function in any other way is effectively circumventing the admin
* system imposed by {AccessControl}.
* ====
*
* NOTE: This function is deprecated in favor of {_grantRole}.
*/
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
// File @openzeppelin/contracts/access/[email protected]
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// File @openzeppelin/contracts/security/[email protected]
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// File @openzeppelin/contracts/token/ERC721/[email protected]
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool _approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// File @openzeppelin/contracts/token/ERC721/extensions/[email protected]
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.0;
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// File @openzeppelin/contracts/token/ERC721/[email protected]
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// File @openzeppelin/contracts/utils/[email protected]
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// File @openzeppelin/contracts/token/ERC721/[email protected]
// OpenZeppelin Contracts (last updated v4.8.2) (token/ERC721/ERC721.sol)
pragma solidity ^0.8.0;
/**
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
* the Metadata extension, but not including the Enumerable extension, which is available separately as
* {ERC721Enumerable}.
*/
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
using Address for address;
using Strings for uint256;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to owner address
mapping(uint256 => address) private _owners;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721-balanceOf}.
*/
function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: address zero is not a valid owner");
return _balances[owner];
}
/**
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _ownerOf(tokenId);
require(owner != address(0), "ERC721: invalid token ID");
return owner;
}
/**
* @dev See {IERC721Metadata-name}.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev See {IERC721Metadata-symbol}.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
_requireMinted(tokenId);
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overridden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev See {IERC721-approve}.
*/
function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not token owner or approved for all"
);
_approve(to, tokenId);
}
/**
* @dev See {IERC721-getApproved}.
*/
function getApproved(uint256 tokenId) public view virtual override returns (address) {
_requireMinted(tokenId);
return _tokenApprovals[tokenId];
}
/**
* @dev See {IERC721-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC721-isApprovedForAll}.
*/
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
}
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* `data` is additional data, it has no specified format and it is sent in call to `to`.
*
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
* implement alternative mechanisms to perform token transfer, such as signature-based.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
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");
}
/**
* @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
*/
function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
return _owners[tokenId];
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted (`_mint`),
* and stop existing when they are burned (`_burn`).
*/
function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _ownerOf(tokenId) != address(0);
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
}
/**
* @dev Safely mints `tokenId` and transfers it to `to`.
*
* Requirements:
*
* - `tokenId` must not exist.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
/**
* @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
*/
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"
);
}
/**
* @dev Mints `tokenId` and transfers it to `to`.
*
* WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
*
* Requirements:
*
* - `tokenId` must not exist.
* - `to` cannot be the zero address.
*
* Emits a {Transfer} event.
*/
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");
_beforeTokenTransfer(address(0), to, tokenId, 1);
// Check that tokenId was not minted by `_beforeTokenTransfer` hook
require(!_exists(tokenId), "ERC721: token already minted");
unchecked {
// Will not overflow unless all 2**256 token ids are minted to the same owner.
// Given that tokens are minted one by one, it is impossible in practice that
// this ever happens. Might change if we allow batch minting.
// The ERC fails to describe this case.
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId, 1);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
* This is an internal function that does not check if the sender is authorized to operate on the token.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
address owner = ERC721.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId, 1);
// Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
owner = ERC721.ownerOf(tokenId);
// Clear approvals
delete _tokenApprovals[tokenId];
unchecked {
// Cannot overflow, as that would require more tokens to be burned/transferred
// out than the owner initially received through minting and transferring in.
_balances[owner] -= 1;
}
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId, 1);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(
address from,
address to,
uint256 tokenId
) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId, 1);
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
// Clear approvals from the previous owner
delete _tokenApprovals[tokenId];
unchecked {
// `_balances[from]` cannot overflow for the same reason as described in `_burn`:
// `from`'s balance is the number of token held, which is at least one before the current
// transfer.
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
_balances[from] -= 1;
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId, 1);
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* Emits an {Approval} event.
*/
function _approve(address to, uint256 tokenId) internal virtual {
_tokenApprovals[tokenId] = to;
emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}
/**
* @dev Approve `operator` to operate on all of `owner` tokens
*
* Emits an {ApprovalForAll} event.
*/
function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
/**
* @dev Reverts if the `tokenId` has not been minted yet.
*/
function _requireMinted(uint256 tokenId) internal view virtual {
require(_exists(tokenId), "ERC721: invalid token ID");
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
* 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 bool whether the call correctly returned the expected magic value
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool) {
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
/**
* @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
* - When `from` is zero, the tokens will be minted for `to`.
* - When `to` is zero, ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual {}
/**
* @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
* used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
* - When `from` is zero, the tokens were minted for `to`.
* - When `to` is zero, ``from``'s tokens were burned.
* - `from` and `to` are never both zero.
* - `batchSize` is non-zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual {}
/**
* @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
*
* WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
* being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
* that `ownerOf(tokenId)` is `a`.
*/
// solhint-disable-next-line func-name-mixedcase
function __unsafe_increaseBalance(address account, uint256 amount) internal {
_balances[account] += amount;
}
}
// File @openzeppelin/contracts/token/ERC721/extensions/[email protected]
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Burnable.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 Burnable Token
* @dev ERC721 Token that can be burned (destroyed).
*/
abstract contract ERC721Burnable is Context, ERC721 {
/**
* @dev Burns `tokenId`. See {ERC721-_burn}.
*
* Requirements:
*
* - The caller must own `tokenId` or be an approved operator.
*/
function burn(uint256 tokenId) public virtual {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_burn(tokenId);
}
}
// File @openzeppelin/contracts/token/ERC721/extensions/[email protected]
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC721/extensions/IERC721Enumerable.sol)
pragma solidity ^0.8.0;
/**
* @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Enumerable is IERC721 {
/**
* @dev Returns the total amount of tokens stored by the contract.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns a token ID owned by `owner` at a given `index` of its token list.
* Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
/**
* @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
* Use along with {totalSupply} to enumerate all tokens.
*/
function tokenByIndex(uint256 index) external view returns (uint256);
}
// File @openzeppelin/contracts/token/ERC721/extensions/[email protected]
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Enumerable.sol)
pragma solidity ^0.8.0;
/**
* @dev This implements an optional extension of {ERC721} defined in the EIP that adds
* enumerability of all the token ids in the contract as well as all token ids owned by each
* account.
*/
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
// Mapping from owner to list of owned token IDs
mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
// Mapping from token ID to index of the owner tokens list
mapping(uint256 => uint256) private _ownedTokensIndex;
// Array with all token ids, used for enumeration
uint256[] private _allTokens;
// Mapping from token id to position in the allTokens array
mapping(uint256 => uint256) private _allTokensIndex;
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
*/
function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
/**
* @dev See {IERC721Enumerable-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _allTokens.length;
}
/**
* @dev See {IERC721Enumerable-tokenByIndex}.
*/
function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
/**
* @dev See {ERC721-_beforeTokenTransfer}.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 firstTokenId,
uint256 batchSize
) internal virtual override {
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
if (batchSize > 1) {
// Will only trigger during construction. Batch transferring (minting) is not available afterwards.
revert("ERC721Enumerable: consecutive transfers not supported");
}
uint256 tokenId = firstTokenId;
if (from == address(0)) {
_addTokenToAllTokensEnumeration(tokenId);
} else if (from != to) {
_removeTokenFromOwnerEnumeration(from, tokenId);
}
if (to == address(0)) {
_removeTokenFromAllTokensEnumeration(tokenId);
} else if (to != from) {
_addTokenToOwnerEnumeration(to, tokenId);
}
}
/**
* @dev Private function to add a token to this extension's ownership-tracking data structures.
* @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 _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
uint256 length = ERC721.balanceOf(to);
_ownedTokens[to][length] = tokenId;
_ownedTokensIndex[tokenId] = length;
}
/**
* @dev Private function to add a token to this extension's token tracking data structures.
* @param tokenId uint256 ID of the token to be added to the tokens list
*/
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
/**
* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
* while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
* This has O(1) time complexity, but alters the order of the _ownedTokens array.
* @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 _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
uint256 tokenIndex = _ownedTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
}
// This also deletes the contents at the last position of the array
delete _ownedTokensIndex[tokenId];
delete _ownedTokens[from][lastTokenIndex];
}
/**
* @dev Private function to remove a token from this extension's token tracking data structures.
* This has O(1) time complexity, but alters the order of the _allTokens array.
* @param tokenId uint256 ID of the token to be removed from the tokens list
*/
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
// then delete the last slot (swap and pop).
uint256 lastTokenIndex = _allTokens.length - 1;
uint256 tokenIndex = _allTokensIndex[tokenId];
// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
// This also deletes the contents at the last position of the array
delete _allTokensIndex[tokenId];
_allTokens.pop();
}
}
// File @openzeppelin/contracts/utils/introspection/[email protected]
// OpenZeppelin Contracts (last updated v4.8.2) (utils/introspection/ERC165Checker.sol)
pragma solidity ^0.8.0;
/**
* @dev Library used to query support of an interface declared via {IERC165}.
*
* Note that these functions return the actual result of the query: they do not
* `revert` if an interface is not supported. It is up to the caller to decide
* what to do in these cases.
*/
library ERC165Checker {
// As per the EIP-165 spec, no interface should ever match 0xffffffff
bytes4 private constant _INTERFACE_ID_INVALID = 0xffffffff;
/**
* @dev Returns true if `account` supports the {IERC165} interface.
*/
function supportsERC165(address account) internal view returns (bool) {
// Any contract that implements ERC165 must explicitly indicate support of
// InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
return
supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
!supportsERC165InterfaceUnchecked(account, _INTERFACE_ID_INVALID);
}
/**
* @dev Returns true if `account` supports the interface defined by
* `interfaceId`. Support for {IERC165} itself is queried automatically.
*
* See {IERC165-supportsInterface}.
*/
function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
// query support of both ERC165 as per the spec and support of _interfaceId
return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
}
/**
* @dev Returns a boolean array where each value corresponds to the
* interfaces passed in and whether they're supported or not. This allows
* you to batch check interfaces for a contract where your expectation
* is that some interfaces may not be supported.
*
* See {IERC165-supportsInterface}.
*
* _Available since v3.4._
*/
function getSupportedInterfaces(address account, bytes4[] memory interfaceIds)
internal
view
returns (bool[] memory)
{
// an array of booleans corresponding to interfaceIds and whether they're supported or not
bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);
// query support of ERC165 itself
if (supportsERC165(account)) {
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
}
}
return interfaceIdsSupported;
}
/**
* @dev Returns true if `account` supports all the interfaces defined in
* `interfaceIds`. Support for {IERC165} itself is queried automatically.
*
* Batch-querying can lead to gas savings by skipping repeated checks for
* {IERC165} support.
*
* See {IERC165-supportsInterface}.
*/
function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
// query support of ERC165 itself
if (!supportsERC165(account)) {
return false;
}
// query support of each interface in interfaceIds
for (uint256 i = 0; i < interfaceIds.length; i++) {
if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
return false;
}
}
// all interfaces supported
return true;
}
/**
* @notice Query if a contract implements an interface, does not check ERC165 support
* @param account The address of the contract to query for support of an interface
* @param interfaceId The interface identifier, as specified in ERC-165
* @return true if the contract at account indicates support of the interface with
* identifier interfaceId, false otherwise
* @dev Assumes that account contains a contract that supports ERC165, otherwise
* the behavior of this method is undefined. This precondition can be checked
* with {supportsERC165}.
*
* Some precompiled contracts will falsely indicate support for a given interface, so caution
* should be exercised when using this function.
*
* Interface identification is specified in ERC-165.
*/
function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
// prepare call
bytes memory encodedParams = abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId);
// perform static call
bool success;
uint256 returnSize;
uint256 returnValue;
assembly {
success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
returnSize := returndatasize()
returnValue := mload(0x00)
}
return success && returnSize >= 0x20 && returnValue > 0;
}
}
// File contracts/DeployerContract.sol
pragma solidity ^0.8.18;
/**
* @title Interface for contracts featuring cascade-verification of the deployment origination
* @author [email protected]
* @notice Allows cascade-verification of a deployment origination across multiple DeployerContracts
* @dev Contracts implementing this interface must take as the first constructor-parameter the address of the
* `DeployerContract`
*/
interface IDeployedContract {
/**
* @notice Indicates whether addr has been directly or indirectly deployed by this contract
* @dev Indirect deployment means e.g. by deploying through a contract that has been deployed by this contract
*
* @param addr Address of the deployed contract requesting initArgs
* @return hasDeployedAddr abi-encoded init args
*/
function hasDeployed(address addr) external view returns (bool hasDeployedAddr);
/**
* Returns the deployer of a particular contract. Can be EOA or Contract Account
*/
function deployedBy() external view returns (address deployer);
}
/**
* @title Predictable-Deployment contract of origin-verifyable contracts
* @author [email protected]
* @notice Deploys contracts implementing IDeployedContract based on passed bytecode and constructorArgs and allows to trace their origin
* across multiple Deployercontracts
*
* @dev Has a static deployment salt, which shall only be changed in absolute emergencies.
* The root contract is typically deployed by the Nonce=0 of an account on different blockchains.
* This ensures that all contracts can be cascade-verified to originate from one well-known and trusted
* source, e.g. an AppHub for a company.
*
*/
abstract contract DeployerContract is IDeployedContract {
mapping (address => address) private _deployedContractsWithOperator;
IDeployedContract[] public deployedContracts;
address public deployedBy;
bytes32 private _salt;
/**
* @notice Emits when a contract is deployed through `deploy()`
* @param deployedAddress Address of the just deployed contract
* @param operator The operator initiating the deployment
*/
event ContractDeployed(address deployedAddress, address operator);
/**
* @notice Emits (in emergencies), when salt is updated.
* @param newSalt The new salt used for new deployments
* @param oldSalt The old salt, has been used for previous deployments
* @param maintainer Initiator of the salt update
*/
event DeploymentSaltUpdate(bytes32 newSalt, bytes32 oldSalt, address maintainer);
/**
* @notice Indicates whether `addr` can use the `deploy()` function.
* @dev To be overwritten by extending contracts, typically by only authorizing a specific role.
* @param addr The address in question
*/
function canDeploy(address addr) public virtual returns (bool);
modifier onlyDeployer() {
require(canDeploy(msg.sender), "msg.sender must be deployer");
_;
}
/**
* Returns the predicted address (with the current `_salt`) for a provided bytecode and constructorArgs
* @param bytecode The bytecode to be deployed
* @param constructorArgs abi-encoded constructor args, accepted by the constructor of the contract in bytecode
*/
function getAddress(
bytes memory bytecode,
bytes memory constructorArgs
) public view returns (address) {
uint actualSalt = uint(_salt);
bytes32 hash = keccak256(
abi.encodePacked(bytes1(0xff), address(this), actualSalt, keccak256(_assembleByteCodeAndArgs(bytecode, constructorArgs)))
);
// NOTE: cast last 20 bytes of hash to address
return address(uint160(uint(hash)));
}
/**
* @notice Like `getAddress(bytes,bytes)`, but for constructors not taking additional arguments (additional to deployer address)
* @param bytecode Bytecode to be deployed
*/
function getAddress(
bytes memory bytecode
) public view returns (address) {
return getAddress(bytecode, abi.encode(address(this)));
}
/**
* @notice Indicates whether a contract at `addr` directly or indirectly has been deployed through this contract
* @param addr Address of the contract in question
* @dev This function is typically cascade-called from parent DeployerContracts
*/
function hasDeployed(address addr) public view returns (bool hasDeployedAddr) {
if(_deployedContractsWithOperator[addr] != address(0)) {
return true;
}
for(uint i=0; i<deployedContracts.length; i++) {
if(deployedContracts[i].hasDeployed(addr)) {
return true;
}
}
return false;
}
/**
* @dev Internhal helper to pack bytecode and constructor args. Ensurs the first constructor argument is the deployer, i.e. address(this)
* @param byteCode Bytecode excl constructor args
* @param constructorArgs ABI-encoded, expected to have the address of this contract encoded as first argument
*/
function _assembleByteCodeAndArgs(bytes memory byteCode, bytes memory constructorArgs) internal view returns (bytes memory bytecodeWithArgs) {
(address deployerAddress) = abi.decode(constructorArgs, (address));
require(deployerAddress == address(this), "First constructor arg must be address of this contract");
return abi.encodePacked(byteCode, constructorArgs);
}
/**
* @notice Deploys bytecode of IDeployedContract-implementing contract with constructor args
* @param byteCode Bytecode of contract implementing IDeployedContract
* @param constructorArgs ABI-encoded constructor args, first argument must be address of this contract
* @dev Emits ContractDeployed
* Throws if bytecode does not implement IDeployedContract
* Throws if `deployedBy()` of the deployed contract does not indicate this contract as deployer,
* hence `hasDeployed()` mechanism would fail
*/
function deploy(bytes memory byteCode, bytes memory constructorArgs) public onlyDeployer() {
// verify first argument of constructorArgs is address(this)
address addr = _deployBinary(_assembleByteCodeAndArgs(byteCode, constructorArgs), _salt);
// verify the contract implements IDeployedContract
require(ERC165Checker.supportsInterface(addr, type(IDeployedContract).interfaceId), "Can only deploy contracts implementing IDeployedContract interface");
// verify contract claims this contract as deployer
require(IDeployedContract(addr).deployedBy() == address(this), "Deployed contract must return this contract in getDeployer()");
emit ContractDeployed(addr, msg.sender);
_deployedContractsWithOperator[addr] = msg.sender;
deployedContracts.push(IDeployedContract(addr));
}
/**
* @notice Like deploy(bytes,bytes), but adds address(this) as only constructor argument
* @param byteCode Bytecode of contract implementing IDeployedContract
*/
function deploy(bytes memory byteCode) public onlyDeployer() {
deploy(byteCode, abi.encode(address(this)));
}
/**
* @notice Updates the deployment salt - do only use in absolut emergencies!
* @dev This shall not be used at all and is just for emergencies and major fuckups. As soon
* as the salt is updated, it can happen that the same contract / same version / same constructorArgs
* can be re-deployed to a different address.
*/
function updateDeploymentSalt(bytes32 newSalt) public onlyDeployer() {
emit DeploymentSaltUpdate(_salt, newSalt, msg.sender);
_salt = newSalt;
}
/**
* @param bytecode Bytecode + packed constructorArgs of contract implementing IDeployedContract
* @param salt The deployment salt.
* @dev Isolated function to actually deploy contracts (can be used by extending contracts)
* inspired by https://solidity-by-example.org/app/create2/
* Salt is taken as parameter to also allow deploying contracts with an "old" salt in case of
* emergency salt-upgrade.
*/
function _deployBinary(bytes memory bytecode, bytes32 salt) internal virtual onlyDeployer() returns (address) {
address addr;
uint actualSalt = uint(salt);
/*
NOTE: How to call create2
create2(v, p, n, s)
create new contract with code at memory p to p + n
and send v wei
and return the new address
where new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n)))
s = big-endian 256-bit value
*/
assembly {
addr := create2(
callvalue(), // wei sent with current call
// Actual code starts after skipping the first 32 bytes
add(bytecode, 0x20),
mload(bytecode), // Load the size of code contained in the first 32 bytes
actualSalt // Salt from function arguments
)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
return addr;
}
constructor() {
deployedBy = msg.sender;
}
}
// File contracts/AppHub.sol
pragma solidity ^0.8.18;
/**
* @title AuthenticVision MetaAnchor AppHub
* @author [email protected]
* @notice Used to manage roles and verify a deployed contract originates directly or indirectly from this AppHub.
* @dev This can be seen as the "AuthenticVision root certificate". All contracts deployed by Authentic Vision
* will have `hasDeployed(address)==true`. Only these contracts originate from Authentic Vision.
*
* This AppHub will be deployed at the same address in all Blockchains we support.
*
* Visit authenticvision.com for contact and further information
*/
contract AppHub is AccessControl, DeployerContract {
/**
* @notice DEPLOYER_ROLE can deploy new MetaAnchor-Contracts from MetaAnchorFactory
* @return Role hash, as should be passed to hasRole(), grantRole()
*/
bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE");
/**
* @notice FACTORY_DEPLOYER_ROLE can deploy factories (via AppHub)
*/
bytes32 public constant FACTORY_DEPLOYER_ROLE = keccak256("FACTORY_DEPLOYER_ROLE");
/**
* @notice FACTORY_MAINTAINER_ROLE can maintain factories, e.g. add providers, remove registrations, ..
*/
bytes32 public constant FACTORY_MAINTAINER_ROLE = keccak256("FACTORY_MAINTAINER_ROLE");
/**
* @notice MAINTAINER_ROLE can maintain MetaAnchor-Contracts, e.g. updateValidAnchors(), configurations, owners, etc.
*/
bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");
/**
* @notice Signatures for ORACLE_ROLE will be accepted for ERC-6956 attestations
*/
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
/**
* @notice PAUSER_ROLE has permission to pause contracts
*/
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/**
* @notice REGISTRAR_ROLE can register (and unregister their own) contracts for deployment
*/
bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE");
/**
* @notice Overrides authorization functionality from `DeployerContract` to allow only FACTORY_DEPLOYER_ROLE accounts
* @param addr Account address in equestion
* @return addrIsDeployer true indicates this account can deploy contracts via `deploy()` method
*/
function canDeploy(address addr) public view override(DeployerContract) returns (bool addrIsDeployer) {
return hasRole(FACTORY_DEPLOYER_ROLE, addr);
}
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
}
// File contracts/AppContract.sol
pragma solidity ^0.8.18;
/**
* @title AppContract for access control. Base-class for actual contracts controlled by AppHub
* @author [email protected]
*/
contract AppContract is AccessControl {
AppHub internal _hub;
constructor(address hub) {
_hub = AppHub(hub);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
* @notice Check whether a specific account has a certain role. This role is also set in all linked contracts.
*/
function hasRole(bytes32 role, address account) public view virtual override(AccessControl) returns (bool) {
return (super.hasRole(role, account) || _hub.hasRole(role, account));
}
/**
* @dev Updates the address of app_hub.
* @param hub Address of the app-hub
*/
function updateAppHub(address hub) public onlyRole(DEFAULT_ADMIN_ROLE) {
address prevHubAddr = address(_hub);
_hub = AppHub(hub);
// after update, I still need to have the default admin role from appHub
require(ERC165Checker.supportsInterface(hub, type(IDeployedContract).interfaceId), "AppHub must implement IDeployedContract");
require(_hub.hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Account has no admin role in AppHub");
emit AppHubUpdate(hub, prevHubAddr, msg.sender);
}
event AppHubUpdate(address hub, address oldHub, address maintainer);
// The following functions are overrides required by Solidity, EIP-165.
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(AccessControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
// File @openzeppelin/contracts/utils/[email protected]
// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
pragma solidity ^0.8.0;
/**
* @title Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*
* Include with `using Counters for Counters.Counter;`
*/
library Counters {
struct Counter {
// This variable should never be directly accessed by users of the library: interactions must be restricted to
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
// this feature: see https://github.com/ethereum/solidity/issues/4637
uint256 _value; // default: 0
}
function current(Counter storage counter) internal view returns (uint256) {
return counter._value;
}
function increment(Counter storage counter) internal {
unchecked {
counter._value += 1;
}
}
function decrement(Counter storage counter) internal {
uint256 value = counter._value;
require(value > 0, "Counter: decrement overflow");
unchecked {
counter._value = value - 1;
}
}
function reset(Counter storage counter) internal {
counter._value = 0;
}
}
// File contracts/eip-6956/IERC6956.sol
pragma solidity ^0.8.18;
/**
* @title IERC6956 Asset-Bound Non-Fungible Tokens
* @notice Asset-bound Non-Fungible Tokens anchor a token 1:1 to a (physical or digital) asset and token transfers are authorized through attestation of control over the asset
* @dev See https://eips.ethereum.org/EIPS/eip-6956
* Note: The ERC-165 identifier for this interface is 0xa9cf7635
*/
interface IERC6956 {
/** @dev Authorization, typically mapped to authorizationMaps, where each bit indicates whether a particular ERC6956Role is authorized
* Typically used in constructor (hardcoded or params) to set burnAuthorization and approveAuthorization
* Also used in optional updateBurnAuthorization, updateApproveAuthorization, I
*/
enum Authorization {
NONE, // = 0, // None of the above
OWNER, // = (1<<OWNER), // The owner of the token, i.e. the digital representation
ISSUER, // = (1<<ISSUER), // The issuer of the tokens, i.e. this smart contract
ASSET, // = (1<<ASSET), // The asset, i.e. via attestation
OWNER_AND_ISSUER, // = (1<<OWNER) | (1<<ISSUER),
OWNER_AND_ASSET, // = (1<<OWNER) | (1<<ASSET),
ASSET_AND_ISSUER, // = (1<<ASSET) | (1<<ISSUER),
ALL // = (1<<OWNER) | (1<<ISSUER) | (1<<ASSET) // Owner + Issuer + Asset
}
/**
* @notice This emits when approved address for an anchored tokenId is changed or reaffirmed via attestation
* @dev This emits when approveAnchor() is called and corresponds to ERC-721 behavior
* @param owner The owner of the anchored tokenId
* @param approved The approved address, address(0) indicates there is no approved address
* @param anchor The anchor, for which approval has been chagned
* @param tokenId ID (>0) of the anchored token
*/
event AnchorApproval(address indexed owner, address approved, bytes32 indexed anchor, uint256 tokenId);
/**
* @notice This emits when the ownership of any anchored NFT changes by any mechanism
* @dev This emits together with tokenId-based ERC-721.Transfer and provides an anchor-perspective on transfers
* @param from The previous owner, address(0) indicate there was none.
* @param to The new owner, address(0) indicates the token is burned
* @param anchor The anchor which is bound to tokenId
* @param tokenId ID (>0) of the anchored token
*/
event AnchorTransfer(address indexed from, address indexed to, bytes32 indexed anchor, uint256 tokenId);
/**
* @notice This emits when an attestation has been used indicating no second attestation with the same attestationHash will be accepted
* @param to The to address specified in the attestation
* @param anchor The anchor specificed in the attestation
* @param attestationHash The hash of the attestation, see ERC-6956 for details
* @param totalUsedAttestationsForAnchor The total number of attestations already used for the particular anchor
*/
event AttestationUse(address indexed to, bytes32 indexed anchor, bytes32 indexed attestationHash, uint256 totalUsedAttestationsForAnchor);
/**
* @notice This emits when the trust-status of an oracle changes.
* @dev Trusted oracles must explicitely be specified.
* If the last event for a particular oracle-address indicates it's trusted, attestations from this oracle are valid.
* @param oracle Address of the oracle signing attestations
* @param trusted indicating whether this address is trusted (true). Use (false) to no longer trust from an oracle.
*/
event OracleUpdate(address indexed oracle, bool indexed trusted);
/**
* @notice Returns the 1:1 mapped anchor for a tokenId
* @param tokenId ID (>0) of the anchored token
* @return anchor The anchor bound to tokenId, 0x0 if tokenId does not represent an anchor
*/
function anchorByToken(uint256 tokenId) external view returns (bytes32 anchor);
/**
* @notice Returns the ID of the 1:1 mapped token of an anchor.
* @param anchor The anchor (>0x0)
* @return tokenId ID of the anchored token, 0 if no anchored token exists
*/
function tokenByAnchor(bytes32 anchor) external view returns (uint256 tokenId);
/**
* @notice The number of attestations already used to modify the state of an anchor or its bound tokens
* @param anchor The anchor(>0)
* @return attestationUses The number of attestation uses for a particular anchor, 0 if anchor is invalid.
*/
function attestationsUsedByAnchor(bytes32 anchor) view external returns (uint256 attestationUses);
/**
* @notice Decodes and returns to-address, anchor and the attestation hash, if the attestation is valid
* @dev MUST throw when
* - Attestation has already been used (an AttestationUse-Event with matching attestationHash was emitted)
* - Attestation is not signed by trusted oracle (the last OracleUpdate-Event for the signer-address does not indicate trust)
* - Attestation is not valid yet or expired
* - [if IERC6956AttestationLimited is implemented] attestationUsagesLeft(attestation.anchor) <= 0
* - [if IERC6956ValidAnchors is implemented] validAnchors(data) does not return true.
* @param attestation The attestation subject to the format specified in ERC-6956
* @param data Optional additional data, may contain proof as the first abi-encoded argument when IERC6956ValidAnchors is implemented
* @return to Address where the ownership of an anchored token or approval shall be changed to
* @return anchor The anchor (>0)
* @return attestationHash The attestation hash computed on-chain as `keccak256(attestation)`
*/
function decodeAttestationIfValid(bytes memory attestation, bytes memory data) external view returns (address to, bytes32 anchor, bytes32 attestationHash);
/**
* @notice Indicates whether any of ASSET, OWNER, ISSUER is authorized to burn
*/
function burnAuthorization() external view returns(Authorization burnAuth);
/**
* @notice Indicates whether any of ASSET, OWNER, ISSUER is authorized to approve
*/
function approveAuthorization() external view returns(Authorization approveAuth);
/**
* @notice Corresponds to transferAnchor(bytes,bytes) without additional data
* @param attestation Attestation, refer ERC-6956 for details
*/
function transferAnchor(bytes memory attestation) external;
/**
* @notice Changes the ownership of an NFT mapped to attestation.anchor to attestation.to address.
* @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation.
* - Uses decodeAttestationIfValid()
* - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited.
* - Matches the behavior of ERC-721.safeTransferFrom(ownerOf[tokenByAnchor(attestation.anchor)], attestation.to, tokenByAnchor(attestation.anchor), ..) and mint an NFT if `tokenByAnchor(anchor)==0`.
* - Throws when attestation.to == ownerOf(tokenByAnchor(attestation.anchor))
* - Emits AnchorTransfer
*
* @param attestation Attestation, refer EIP-6956 for details
* @param data Additional data, may be used for additional transfer-conditions, may be sent partly or in full in a call to safeTransferFrom
*
*/
function transferAnchor(bytes memory attestation, bytes memory data) external;
/**
* @notice Corresponds to approveAnchor(bytes,bytes) without additional data
* @param attestation Attestation, refer ERC-6956 for details
*/
function approveAnchor(bytes memory attestation) external;
/**
* @notice Approves attestation.to the token bound to attestation.anchor. .
* @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation.
* - Uses decodeAttestationIfValid()
* - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited.
* - Matches the behavior of ERC-721.approve(attestation.to, tokenByAnchor(attestation.anchor)).
* - Throws when ASSET is not authorized to approve.
*
* @param attestation Attestation, refer EIP-6956 for details
*/
function approveAnchor(bytes memory attestation, bytes memory data) external;
/**
* @notice Corresponds to burnAnchor(bytes,bytes) without additional data
* @param attestation Attestation, refer ERC-6956 for details
*/
function burnAnchor(bytes memory attestation) external;
/**
* @notice Burns the token mapped to attestation.anchor. Uses ERC-721._burn.
* @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation.
* - Uses decodeAttestationIfValid()
* - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited.
* - Throws when ASSET is not authorized to burn
*
* @param attestation Attestation, refer EIP-6956 for details
*/
function burnAnchor(bytes memory attestation, bytes memory data) external;
}
// File contracts/eip-6956/ERC6956.sol
pragma solidity ^0.8.18;
/** Used for several authorization mechansims, e.g. who can burn, who can set approval, ...
* @dev Specifying the role in the ecosystem. Used in conjunction with IERC6956.Authorization
*/
enum Role {
OWNER, // =0, The owner of the digital token
ISSUER, // =1, The issuer (contract) of the tokens, typically represented through a MAINTAINER_ROLE, the contract owner etc.
ASSET, // =2, The asset identified by the anchor
INVALID // =3, Reserved, do not use.
}
/**
* @title ASSET-BOUND NFT minimal reference implementation
* @author Thomas Bergmueller (@tbergmueller)
*
* @dev Error messages
* ```
* ERROR | Message
* ------|-------------------------------------------------------------------
* E1 | Only maintainer allowed
* E2 | No permission to burn
* E3 | Token does not exist, call transferAnchor first to mint
* E4 | batchSize must be 1
* E5 | Token not transferable
* E6 | Token already owned
* E7 | Not authorized based on ERC6956Authorization
* E8 | Attestation not signed by trusted oracle
* E9 | Attestation already used
* E10 | Attestation not valid yet
* E11 | Attestation expired
* E12 | Attestation expired (contract limit)
* E13 | Invalid signature length
* E14-20| Reserved for future use
* ```
*/
contract ERC6956 is
ERC721,
ERC721Enumerable,
ERC721Burnable,
IERC6956
{
using Counters for Counters.Counter;
mapping(bytes32 => bool) internal _anchorIsReleased; // currently released anchors. Per default, all anchors are dropped, i.e. 1:1 bound
mapping(address => bool) public maintainers;
/// @notice Resolves tokenID to anchor. Inverse of tokenByAnchor
mapping(uint256 => bytes32) public anchorByToken;
/// @notice Resolves Anchor to tokenID. Inverse of anchorByToken
mapping(bytes32 => uint256) public tokenByAnchor;
mapping(address => bool) private _trustedOracles;
/// @dev stores the anchors for each attestation
mapping(bytes32 => bytes32) private _anchorByUsedAttestation;
/// @dev stores handed-back tokens (via burn)
mapping (bytes32 => uint256) private _burnedTokensByAnchor;
/**
* @dev Counter to keep track of issued tokens
*/
Counters.Counter private _tokenIdCounter;
/// @dev Default validity timespan of attestation. In validateAttestation the attestationTime is checked for MIN(defaultAttestationvalidity, attestation.expiry)
uint256 public maxAttestationExpireTime = 5*60; // 5min valid per default
Authorization public burnAuthorization;
Authorization public approveAuthorization;
/// @dev Records the number of transfers done for each attestation
mapping(bytes32 => uint256) public attestationsUsedByAnchor;
modifier onlyMaintainer() {
require(isMaintainer(msg.sender), "ERC6956-E1");
_;
}
/**
* @notice Behaves like ERC721 burn() for wallet-cleaning purposes. Note only the tokenId (as a wrapper) is burned, not the ASSET represented by the ANCHOR.
* @dev
* - tokenId is remembered for the anchor, to ensure a later transferAnchor(), which would mint, assigns the same tokenId. This ensures strict 1:1 relation
* - For burning, the anchor needs to be released. This forced release FOR BURNING ONLY is allowed for owner() or approvedOwner().
*
* @param tokenId The token that shall be burned
*/
function burn(uint256 tokenId) public override
{
// remember the tokenId of burned tokens, s.t. one can issue the token with the same number again
bytes32 anchor = anchorByToken[tokenId];
require(_roleBasedAuthorization(anchor, createAuthorizationMap(burnAuthorization)), "ERC6956-E2");
_burn(tokenId);
}
function burnAnchor(bytes memory attestation, bytes memory data) public virtual
authorized(Role.ASSET, createAuthorizationMap(burnAuthorization))
{
address to;
bytes32 anchor;
bytes32 attestationHash;
(to, anchor, attestationHash) = decodeAttestationIfValid(attestation, data);
_commitAttestation(to, anchor, attestationHash);
uint256 tokenId = tokenByAnchor[anchor];
// remember the tokenId of burned tokens, s.t. one can issue the token with the same number again
_burn(tokenId);
}
function burnAnchor(bytes memory attestation) public virtual {
return burnAnchor(attestation, "");
}
function approveAnchor(bytes memory attestation, bytes memory data) public virtual
authorized(Role.ASSET, createAuthorizationMap(approveAuthorization))
{
address to;
bytes32 anchor;
bytes32 attestationHash;
(to, anchor, attestationHash) = decodeAttestationIfValid(attestation, data);
_commitAttestation(to, anchor, attestationHash);
require(tokenByAnchor[anchor]>0, "ERC6956-E3");
_approve(to, tokenByAnchor[anchor]);
}
// approveAuth == ISSUER does not really make sense.. so no separate implementation, since ERC-721.approve already implies owner...
function approve(address to, uint256 tokenId) public virtual override(ERC721,IERC721)
authorized(Role.OWNER, createAuthorizationMap(approveAuthorization))
{
super.approve(to, tokenId);
}
function approveAnchor(bytes memory attestation) public virtual {
return approveAnchor(attestation, "");
}
/**
* @notice Adds or removes a trusted oracle, used when verifying signatures in `decodeAttestationIfValid()`
* @dev Emits OracleUpdate
* @param oracle address of oracle
* @param doTrust true to add, false to remove
*/
function updateOracle(address oracle, bool doTrust) public
onlyMaintainer()
{
_trustedOracles[oracle] = doTrust;
emit OracleUpdate(oracle, doTrust);
}
/**
* @dev A very simple function wich MUST return false, when `a` is not a maintainer
* When derived contracts extend ERC6956 contract, this function may be overridden
* e.g. by using AccessControl, onlyOwner or other common mechanisms
*
* Having this simple mechanism in the reference implementation ensures that the reference
* implementation is fully ERC-6956 compatible
*/
function isMaintainer(address a) public virtual view returns (bool) {
return maintainers[a];
}
function createAuthorizationMap(Authorization _auth) public pure returns (uint256) {
uint256 authMap = 0;
if(_auth == Authorization.OWNER
|| _auth == Authorization.OWNER_AND_ASSET
|| _auth == Authorization.OWNER_AND_ISSUER
|| _auth == Authorization.ALL) {
authMap |= uint256(1<<uint256(Role.OWNER));
}
if(_auth == Authorization.ISSUER
|| _auth == Authorization.ASSET_AND_ISSUER
|| _auth == Authorization.OWNER_AND_ISSUER
|| _auth == Authorization.ALL) {
authMap |= uint256(1<<uint256(Role.ISSUER));
}
if(_auth == Authorization.ASSET
|| _auth == Authorization.ASSET_AND_ISSUER
|| _auth == Authorization.OWNER_AND_ASSET
|| _auth == Authorization.ALL) {
authMap |= uint256(1<<uint256(Role.ASSET));
}
return authMap;
}
function _roleBasedAuthorization(bytes32 anchor, uint256 authorizationMap) internal view returns (bool) {
uint256 tokenId = tokenByAnchor[anchor];
Role myRole = Role.INVALID;
Role alternateRole = Role.INVALID;
if(_isApprovedOrOwner(_msgSender(), tokenId)) {
myRole = Role.OWNER;
}
if(isMaintainer(msg.sender)) {
alternateRole = Role.ISSUER;
}
return hasAuthorization(myRole, authorizationMap)
|| hasAuthorization(alternateRole, authorizationMap);
}
///@dev Hook executed before decodeAttestationIfValid returns. Override in derived contracts
function _beforeAttestationUse(bytes32 anchor, address to, bytes memory data) internal view virtual {}
function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
internal virtual
override(ERC721, ERC721Enumerable)
{
require(batchSize == 1, "ERC6956-E4");
bytes32 anchor = anchorByToken[tokenId];
emit AnchorTransfer(from, to, anchor, tokenId);
if(to == address(0)) {
// we are burning, ensure the mapping is deleted BEFORE the transfer
// to avoid reentrant-attacks
_burnedTokensByAnchor[anchor] = tokenId; // Remember tokenId for a potential re-mint
delete tokenByAnchor[anchor];
delete anchorByToken[tokenId];
}
else {
require(_anchorIsReleased[anchor], "ERC6956-E5");
}
delete _anchorIsReleased[anchor]; // make sure anchor is non-released after the transfer again
}
/// @dev hook called after an anchor is minted
function _afterAnchorMint(address to, bytes32 anchor, uint256 tokenId) internal virtual {}
/**
* @notice Add (_add=true) or remove (_add=false) a maintainer
* @dev Note this is a trivial implementation, which can leave the contract without a maintainer.
* Since the function is access-controlled via onlyMaintainer, this results in the contract
* becoming unmaintainable.
* This may be desired behavior, for example if the contract shall become immutable until
* all eternity, therefore making a project truly trustless.
*/
function updateMaintainer(address _maintainer, bool _add) public onlyMaintainer() {
maintainers[_maintainer] = _add;
}
/// @dev Verifies a anchor is valid and mints a token to the target address.
/// Internal function to be called whenever minting is needed.
/// Parameters:
/// @param to Beneficiary account address
/// @param anchor The anchor (from Merkle tree)
function _safeMint(address to, bytes32 anchor) internal virtual {
assert(tokenByAnchor[anchor] <= 0); // saftey for contract-internal errors
uint256 tokenId = _burnedTokensByAnchor[anchor];
if(tokenId < 1) {
_tokenIdCounter.increment();
tokenId = _tokenIdCounter.current();
}
assert(anchorByToken[tokenId] <= 0); // saftey for contract-internal errors
anchorByToken[tokenId] = anchor;
tokenByAnchor[anchor] = tokenId;
super._safeMint(to, tokenId);
_afterAnchorMint(to, anchor, tokenId);
}
function _commitAttestation(address to, bytes32 anchor, bytes32 attestationHash) internal {
_anchorByUsedAttestation[attestationHash] = anchor;
uint256 totalAttestationsByAnchor = attestationsUsedByAnchor[anchor] +1;
attestationsUsedByAnchor[anchor] = totalAttestationsByAnchor;
emit AttestationUse(to, anchor, attestationHash, totalAttestationsByAnchor );
}
function transferAnchor(bytes memory attestation, bytes memory data) public virtual
{
bytes32 anchor;
address to;
bytes32 attestationHash;
(to, anchor, attestationHash) = decodeAttestationIfValid(attestation, data);
_commitAttestation(to, anchor, attestationHash); // commit already here, will be reverted in error case anyway
uint256 fromToken = tokenByAnchor[anchor]; // tokenID, null if not exists
address from = address(0); // owneraddress or 0x00, if not exists
_anchorIsReleased[anchor] = true; // Attestation always temporarily releases the anchor
if(fromToken > 0) {
from = ownerOf(fromToken);
require(from != to, "ERC6956-E6");
_safeTransfer(from, to, fromToken, "");
} else {
_safeMint(to, anchor);
}
}
function transferAnchor(bytes memory attestation) public virtual {
return transferAnchor(attestation, "");
}
function hasAuthorization(Role _role, uint256 _auth ) public pure returns (bool) {
uint256 result = uint256(_auth & (1 << uint256(_role)));
return result > 0;
}
modifier authorized(Role _role, uint256 _authMap) {
require(hasAuthorization(_role, _authMap), "ERC6956-E7");
_;
}
// The following functions are overrides required by Solidity, EIP-165.
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721, ERC721Enumerable)
returns (bool)
{
return
interfaceId == type(IERC6956).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @notice Returns whether a certain address is registered as trusted oracle, i.e. attestations signed by this address are accepted in `decodeAttestationIfValid`
* @dev This function may be overwritten when extending ERC-6956, e.g. when other oracle-registration mechanics are used
* @param oracleAddress Address of the oracle in question
* @return isTrusted True, if oracle is trusted
*/
function isTrustedOracle(address oracleAddress) public virtual view returns (bool isTrusted) {
return _trustedOracles[oracleAddress];
}
function decodeAttestationIfValid(bytes memory attestation, bytes memory data) public view returns (address to, bytes32 anchor, bytes32 attestationHash) {
uint256 attestationTime;
uint256 validStartTime;
uint256 validEndTime;
bytes memory signature;
bytes32[] memory proof;
attestationHash = keccak256(attestation);
(to, anchor, attestationTime, validStartTime, validEndTime, signature) = abi.decode(attestation, (address, bytes32, uint256, uint256, uint256, bytes));
bytes32 messageHash = keccak256(abi.encodePacked(to, anchor, attestationTime, validStartTime, validEndTime, proof));
address signer = _extractSigner(messageHash, signature);
// Check if from trusted oracle
require(isTrustedOracle(signer), "ERC6956-E8");
require(_anchorByUsedAttestation[attestationHash] <= 0, "ERC6956-E9");
// Check expiry
uint256 timestamp = block.timestamp;
require(timestamp > validStartTime, "ERC6956-E10");
require(attestationTime + maxAttestationExpireTime > block.timestamp, "ERC6956-E11");
require(validEndTime > block.timestamp, "ERC6956-E112");
// Calling hook!
_beforeAttestationUse(anchor, to, data);
return(to, anchor, attestationHash);
}
/// @notice Compatible with ERC721.tokenURI(). Returns {baseURI}{anchor}
/// @dev Returns when called for tokenId=5, baseURI=https://myurl.com/collection/ and anchorByToken[5] = 0x12345
/// Example: https://myurl.com/collection/0x12345
/// Works for non-burned tokens / active-Anchors only.
/// Anchor-based tokenURIs are needed as an anchor's corresponding tokenId is only known after mint.
/// @param tokenId TokenID
/// @return tokenURI Returns the Uniform Resource Identifier (URI) for `tokenId` token.
function tokenURI(uint256 tokenId) public view override returns (string memory) {
bytes32 anchor = anchorByToken[tokenId];
string memory anchorString = Strings.toHexString(uint256(anchor));
return bytes(_baseURI()).length > 0 ? string(abi.encodePacked(_baseURI(), anchorString)) : "";
}
function _baseURI() internal view virtual override(ERC721) returns (string memory) {
return _baseUri;
}
/**
* @dev Base URI, MUST end with a slash. Will be used as `{baseURI}{tokenId}` in tokenURI() function
*/
string internal _baseUri = ""; // needs to end with '/'
/// @notice Set a new BaseURI. Can be used with dynamic NFTs that have server APIs, IPFS-buckets
/// or any other suitable system. Refer tokenURI(tokenId) for anchor-based or tokenId-based format.
/// @param tokenBaseURI The token base-URI. Must end with slash '/'.
function updateBaseURI(string calldata tokenBaseURI) public onlyMaintainer() {
_baseUri = tokenBaseURI;
}
event BurnAuthorizationChange(Authorization burnAuth, address indexed maintainer);
function updateBurnAuthorization(Authorization burnAuth) public onlyMaintainer() {
burnAuthorization = burnAuth;
emit BurnAuthorizationChange(burnAuth, msg.sender);
// TODO event
}
event ApproveAuthorizationChange(Authorization approveAuth, address indexed maintainer);
function updateApproveAuthorization(Authorization approveAuth) public onlyMaintainer() {
approveAuthorization = approveAuth;
emit ApproveAuthorizationChange(approveAuth, msg.sender);
// TODO event
}
constructor(string memory _name, string memory _symbol)
ERC721(_name, _symbol) {
maintainers[msg.sender] = true; // deployer is automatically maintainer
// Indicates general float-ability, i.e. whether anchors can be digitally dropped and released
// OWNER and ASSET shall normally be in sync anyway, so this is reasonable default
// authorization for approve and burn, as it mimicks ERC-721 behavior
burnAuthorization = Authorization.OWNER_AND_ASSET;
approveAuthorization = Authorization.OWNER_AND_ASSET;
}
/*
########################## SIGNATURE MAGIC,
########################## adapted from https://solidity-by-example.org/signature/
*/
/**
* Returns the signer of a message.
*
* OFF-CHAIN:
* const [alice] = ethers.getSigners(); // = 0x3c44...
* const messageHash = ethers.utils.solidityKeccak256(["address", "bytes32"], [a, b]);
const sig = await alice.signMessage(ethers.utils.arrayify(messageHash));
ONCHAIN In this contract, call from
```
function (address a, bytes32 b, bytes memory sig) {
messageHash = keccak256(abi.encodePacked(to, b));
signer = extractSigner(messageHash, sig); // signer will be 0x3c44...
}
``` *
* @param messageHash A keccak25(abi.encodePacked(...)) hash
* @param sig Signature (length 65 bytes)
*
* @return The signer
*/
function _extractSigner(bytes32 messageHash, bytes memory sig) internal pure returns (address) {
require(sig.length == 65, "ERC6956-E13");
/*
Signature is produced by signing a keccak256 hash with the following format:
"\x19Ethereum Signed Message\n" + len(msg) + msg
*/
bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
bytes32 r;
bytes32 s;
uint8 v;
// Extract the r, s, and v parameters from the signature
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
// Ensure the v parameter is either 27 or 28
// TODO is this needed?
if (v < 27) {
v += 27;
}
// Recover the public key from the signature and message hash
// and convert it to an address
address signer = ecrecover(ethSignedMessageHash, v, r, s);
return signer;
}
}
// File @openzeppelin/contracts/utils/cryptography/[email protected]
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates merkle trees that are safe
* against this attack out of the box.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/
function verifyCalldata(
bytes32[] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuild the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proof.length - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value for the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i] ? leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++] : proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
return hashes[totalHashes - 1];
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// File contracts/eip-6956/IERC6956AttestationLimited.sol
pragma solidity ^0.8.18;
/**
* @title Attestation-limited Asset-Bound NFT
* @dev See https://eips.ethereum.org/EIPS/eip-6956
* Note: The ERC-165 identifier for this interface is 0x75a2e933
*/
interface IERC6956AttestationLimited is IERC6956 {
enum AttestationLimitPolicy {
IMMUTABLE,
INCREASE_ONLY,
DECREASE_ONLY,
FLEXIBLE
}
/// @notice Returns the attestation limit for a particular anchor
/// @dev MUST return the global attestation limit per default
/// and override the global attestation limit in case an anchor-based limit is set
function attestationLimit(bytes32 anchor) external view returns (uint256 limit);
/// @notice Returns number of attestations left for a particular anchor
/// @dev Is computed by comparing the attestationsUsedByAnchor(anchor) and the current attestation limit
/// (current limited emitted via GlobalAttestationLimitUpdate or AttestationLimt events)
function attestationUsagesLeft(bytes32 anchor) external view returns (uint256 nrTransfersLeft);
/// @notice Indicates the policy, in which direction attestation limits can be updated (globally or per anchor)
function attestationLimitPolicy() external view returns (AttestationLimitPolicy policy);
/// @notice This emits when the global attestation limt is updated
event GlobalAttestationLimitUpdate(uint256 indexed transferLimit, address updatedBy);
/// @notice This emits when an anchor-specific attestation limit is updated
event AttestationLimitUpdate(bytes32 indexed anchor, uint256 indexed tokenId, uint256 indexed transferLimit, address updatedBy);
/// @dev This emits in the transaction, where attestationUsagesLeft becomes 0
event AttestationLimitReached(bytes32 indexed anchor, uint256 indexed tokenId, uint256 indexed transferLimit);
}
// File contracts/eip-6956/IERC6956Floatable.sol
pragma solidity ^0.8.18;
/**
* @title Floatable Asset-Bound NFT
* @notice A floatable Asset-Bound NFT can (temporarily) be transferred without attestation
* @dev See https://eips.ethereum.org/EIPS/eip-6956
* Note: The ERC-165 identifier for this interface is 0xf82773f7
*/
interface IERC6956Floatable is IERC6956 {
enum FloatState {
Default, // 0, inherits from floatAll
Floating, // 1
Anchored // 2
}
/// @notice Indicates that an anchor-specific floating state changed
event FloatingStateChange(bytes32 indexed anchor, uint256 indexed tokenId, FloatState isFloating, address operator);
/// @notice Emits when FloatingAuthorization is changed.
event FloatingAuthorizationChange(Authorization startAuthorization, Authorization stopAuthorization, address maintainer);
/// @notice Emits, when the default floating state is changed
event FloatingAllStateChange(bool areFloating, address operator);
/// @notice Indicates whether an anchored token is floating, namely can be transferred without attestation
function floating(bytes32 anchor) external view returns (bool);
/// @notice Indicates whether any of OWNER, ISSUER, (ASSET) is allowed to start floating
function floatStartAuthorization() external view returns (Authorization canStartFloating);
/// @notice Indicates whether any of OWNER, ISSUER, (ASSET) is allowed to stop floating
function floatStopAuthorization() external view returns (Authorization canStartFloating);
/**
* @notice Allows to override or reset to floatAll-behavior per anchor
* @dev Must throw when newState == Floating and floatStartAuthorization does not authorize msg.sender
* @dev Must throw when newState == Anchored and floatStopAuthorization does not authorize msg.sender
* @param anchor The anchor, whose anchored token shall override default behavior
* @param newState Override-State. If set to Default, the anchor will behave like floatAll
*/
function float(bytes32 anchor, FloatState newState) external;
}
// File contracts/eip-6956/IERC6956ValidAnchors.sol
pragma solidity ^0.8.18;
/**
* @title Anchor-validating Asset-Bound NFT
* @dev See https://eips.ethereum.org/EIPS/eip-6956
* Note: The ERC-165 identifier for this interface is 0x051c9bd8
*/
interface IERC6956ValidAnchors is IERC6956 {
/**
* @notice Emits when the valid anchors for the contract are updated.
* @param validAnchorHash Hash representing all valid anchors. Typically Root of MerkleTree
* @param maintainer msg.sender updating the hash
*/
event ValidAnchorsUpdate(bytes32 indexed validAnchorHash, address indexed maintainer);
/**
* @notice Indicates whether an anchor is valid in the present contract
* @dev Typically implemented via MerkleTrees, where proof is used to verify anchor is part of the MerkleTree
* MUST return false when no ValidAnchorsUpdate-event has been emitted yet
* @param anchor The anchor in question
* @param proof Proof that the anchor is valid, typically MerkleProof
* @return isValid True, when anchor and proof can be verified against validAnchorHash (emitted via ValidAnchorsUpdate-event)
*/
function anchorValid(bytes32 anchor, bytes32[] memory proof) external view returns (bool isValid);
}
// File contracts/eip-6956/ERC6956Full.sol
pragma solidity ^0.8.18;
/**
* @title ASSET-BOUND NFT implementation with all interfaces
* @author Thomas Bergmueller (@tbergmueller)
* @notice Extends ERC6956.sol with additional interfaces and functionality
*
* @dev Error-codes
* ERROR | Message
* ------|-------------------------------------------------------------------
* E1-20 | See ERC6956.sol
* E21 | No permission to start floating
* E22 | No permission to stop floating
* E23 | allowFloating can only be called when changing floating state
* E24 | No attested transfers left
* E25 | data must contain merkle-proof
* E26 | Anchor not valid
* E27 | Updating attestedTransferLimit violates policy
*/
contract ERC6956Full is ERC6956, IERC6956AttestationLimited, IERC6956Floatable, IERC6956ValidAnchors {
Authorization public floatStartAuthorization;
Authorization public floatStopAuthorization;
/// ###############################################################################################################################
/// ############################################################################################## IERC6956AttestedTransferLimited
/// ###############################################################################################################################
mapping(bytes32 => uint256) public attestedTransferLimitByAnchor;
mapping(bytes32 => FloatState) public floatingStateByAnchor;
uint256 public globalAttestedTransferLimitByAnchor;
AttestationLimitPolicy public attestationLimitPolicy;
bool public allFloating;
/// @dev The merkle-tree root node, where proof is validated against. Update via updateValidAnchors(). Use salt-leafs in merkle-trees!
bytes32 private _validAnchorsMerkleRoot;
function _requireValidLimitUpdate(uint256 oldValue, uint256 newValue) internal view {
if(newValue > oldValue) {
require(attestationLimitPolicy == AttestationLimitPolicy.FLEXIBLE || attestationLimitPolicy == AttestationLimitPolicy.INCREASE_ONLY, "ERC6956-E27");
} else {
require(attestationLimitPolicy == AttestationLimitPolicy.FLEXIBLE || attestationLimitPolicy == AttestationLimitPolicy.DECREASE_ONLY, "ERC6956-E27");
}
}
function updateGlobalAttestationLimit(uint256 _nrTransfers)
public
onlyMaintainer()
{
_requireValidLimitUpdate(globalAttestedTransferLimitByAnchor, _nrTransfers);
globalAttestedTransferLimitByAnchor = _nrTransfers;
emit GlobalAttestationLimitUpdate(_nrTransfers, msg.sender);
}
function updateAttestationLimit(bytes32 anchor, uint256 _nrTransfers)
public
onlyMaintainer()
{
uint256 currentLimit = attestationLimit(anchor);
_requireValidLimitUpdate(currentLimit, _nrTransfers);
attestedTransferLimitByAnchor[anchor] = _nrTransfers;
emit AttestationLimitUpdate(anchor, tokenByAnchor[anchor], _nrTransfers, msg.sender);
}
function attestationLimit(bytes32 anchor) public view returns (uint256 limit) {
if(attestedTransferLimitByAnchor[anchor] > 0) { // Per anchor overwrites always, even if smaller than globalAttestedTransferLimit
return attestedTransferLimitByAnchor[anchor];
}
return globalAttestedTransferLimitByAnchor;
}
function attestationUsagesLeft(bytes32 anchor) public view returns (uint256 nrTransfersLeft) {
// FIXME panics when attestationsUsedByAnchor > attestedTransferLimit
// since this should never happen, maybe ok?
return attestationLimit(anchor) - attestationsUsedByAnchor[anchor];
}
/// ###############################################################################################################################
/// ############################################################################################## FLOATABILITY
/// ###############################################################################################################################
function updateFloatingAuthorization(Authorization startAuthorization, Authorization stopAuthorization) public
onlyMaintainer() {
floatStartAuthorization = startAuthorization;
floatStopAuthorization = stopAuthorization;
emit FloatingAuthorizationChange(startAuthorization, stopAuthorization, msg.sender);
}
function floatAll(bool doFloatAll) public onlyMaintainer() {
require(doFloatAll != allFloating, "ERC6956-E23");
allFloating = doFloatAll;
emit FloatingAllStateChange(doFloatAll, msg.sender);
}
function _floating(bool defaultFloatState, FloatState anchorFloatState) internal pure returns (bool floats) {
if(anchorFloatState == FloatState.Default) {
return defaultFloatState;
}
return anchorFloatState == FloatState.Floating;
}
function float(bytes32 anchor, FloatState newFloatState) public
{
bool currentFloatState = floating(anchor);
bool willFloat = _floating(allFloating, newFloatState);
require(willFloat != currentFloatState, "ERC6956-E23");
if(willFloat) {
require(_roleBasedAuthorization(anchor, createAuthorizationMap(floatStartAuthorization)), "ERC6956-E21");
} else {
require(_roleBasedAuthorization(anchor, createAuthorizationMap(floatStopAuthorization)), "ERC6956-E22");
}
floatingStateByAnchor[anchor] = newFloatState;
emit FloatingStateChange(anchor, tokenByAnchor[anchor], newFloatState, msg.sender);
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
internal virtual
override(ERC6956) {
bytes32 anchor = anchorByToken[tokenId];
if(!_anchorIsReleased[anchor]) {
// Only write when not already released - this saves gas, as memory-write is quite expensive compared to IF
if(floating(anchor)) {
_anchorIsReleased[anchor] = true; // FIXME OPTIMIZATION, we do not need
}
}
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
function _beforeAttestationUse(bytes32 anchor, address to, bytes memory data) internal view virtual override(ERC6956) {
// empty, can be overwritten by derived conctracts.
require(attestationUsagesLeft(anchor) > 0, "ERC6956-E24");
// IERC6956ValidAnchors check anchor is indeed valid in contract
require(data.length > 0, "ERC6956-E25");
bytes32[] memory proof;
(proof) = abi.decode(data, (bytes32[])); // Decode it with potentially more data following. If there is more data, this may be passed on to safeTransfer
require(anchorValid(anchor, proof), "ERC6956-E26");
super._beforeAttestationUse(anchor, to, data);
}
/// @notice Update the Merkle root containing the valid anchors. Consider salt-leaves!
/// @dev Proof (transferAnchor) needs to be provided from this tree.
/// @dev The merkle-tree needs to contain at least one "salt leaf" in order to not publish the complete merkle-tree when all anchors should have been dropped at least once.
/// @param merkleRootNode The root, containing all anchors we want validated.
function updateValidAnchors(bytes32 merkleRootNode) public onlyMaintainer() {
_validAnchorsMerkleRoot = merkleRootNode;
emit ValidAnchorsUpdate(merkleRootNode, msg.sender);
}
function anchorValid(bytes32 anchor, bytes32[] memory proof) public virtual view returns (bool) {
return MerkleProof.verify(
proof,
_validAnchorsMerkleRoot,
keccak256(bytes.concat(keccak256(abi.encode(anchor)))));
}
function floating(bytes32 anchor) public view returns (bool){
return _floating(allFloating, floatingStateByAnchor[anchor]);
}
constructor(
string memory _name,
string memory _symbol,
AttestationLimitPolicy _limitUpdatePolicy)
ERC6956(_name, _symbol) {
attestationLimitPolicy = _limitUpdatePolicy;
// Note per default no-one change floatability. canStartFloating and canStopFloating needs to be configured first!
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC6956)
returns (bool)
{
return
interfaceId == type(IERC6956AttestationLimited).interfaceId ||
interfaceId == type(IERC6956Floatable).interfaceId ||
interfaceId == type(IERC6956ValidAnchors).interfaceId ||
super.supportsInterface(interfaceId);
}
}
// File contracts/factory/BaseProvider.sol
pragma solidity ^0.8.18;
/**
* @title InitArgsProvider, typically implemented by factories
* @author [email protected]
* @dev Provides initArgs for a deployed contract's address, that can be passed to that deployed contract's `initialize()` function.
* The deployed contract typically implements `ProvideableContract`. The constructor of `ProvideableContract` checks for this interface
* and calls usually calls `getInitArgs(address(this))`
*/
interface IInitArgsProvider {
/**
* @notice Provides initArgs (typically from a factory) for a particular deployed smart-contract address.
* @dev is called from `ProvidableContract`-constructor at deploy-time. Main reason for this implementation
* is that Contracts are deployed via create2, so any constructor-parameter is address defining. This allows
* at deploy time to set additional parameters, which are not address-defining.
* @param deployedContract Address of the deployed contract requesting initArgs
* @return initArgs abi-encoded init args
*/
function getInitArgs(address deployedContract) external view returns (bytes memory initArgs);
}
/**
* @title ProvidableContract (to be extended by actual contracts)
* @author [email protected]
* @notice Abstract ProvidableContract defining the `initialize()` function, which is called at deploy-time, if the deployer implements IInitArgsProvider
* @dev Any contract deployed through SlimFactory must extend `ProvidableContract`.
*/
abstract contract ProvidableContract {
/**
* @notice Indicates the deployer of this contract.
*/
address public deployedBy;
/**
* @notice Returns the Version in format X.Y.Z
* @dev Needs to match `BaseProvider.getVersion()`
*/
function getVersion() public virtual pure returns (string memory);
/**
* @notice Initializer called at deploytime or manually afterwards.
* @dev MUST ensure it can only be executed once, typically by setting a notInitialized bool and saying require(notInitialized)
* @param initArgs abi-encoded init args provided by `IInitArgsProvider`
*/
function initialize(bytes memory initArgs) public virtual;
/**
* @dev Stores the deployer-address and - if the deployer supports the IInitArgsProvider interface - requests initArgs and calls `initialize()`
*/
constructor() {
// saftey feature, lets people easily verifiy it has been deployed through a trusted factory
deployedBy = msg.sender;
// Callback to the factory
if(ERC165Checker.supportsInterface(deployedBy, type(IInitArgsProvider).interfaceId)) {
IInitArgsProvider factory = IInitArgsProvider(msg.sender);
initialize(factory.getInitArgs(address(this)));
}
}
}
/**
* @title BaseProvider (to be extended by actual providers)
* @author [email protected]
* @notice Abstract BaseProvider contract to deliver a `ProvidableContract`, must be extended by actual SlimFactory-Providers
* @dev A provider is a contract, which provides the bytecode of a particular `ProvidableContract`, a function to encode deployArgs (constructorArgs) and a function to encode initArgs
*/
abstract contract BaseProvider {
/**
* Returns bytecode with constructor args. Needs to be implemented by actual provider contract.abi
* Typical implementation:
* ```
* bytes memory bytecode = type(DemoContract).creationCode; // DemoContract is ProvidableContract
* return abi.encodePacked(bytecode, args);
* ```
*/
function getBytecode(bytes memory args) public virtual pure returns (bytes memory);
/**
* Returns the version as string in format X.Y.Z
* Needs to match `BaseProvider.getVersion()`
*/
function getVersion() public virtual pure returns (string memory);
function getVersionHash() public pure returns (bytes32) {
return keccak256(abi.encode(getVersion()));
}
function getDefaultInitArgs() public virtual view returns (bytes memory);
function getDefaultArgs(address appHub, string memory name, string memory symbol) public virtual view returns (bytes memory);
// ############################### NOT sure whether to keep the below (currently not used)
// TODO or remove
function getBytecodeHash(bytes memory bytecode) public pure returns (bytes32) {
return keccak256(abi.encode(bytecode, getVersionHash()));
}
/**
* @dev Verifies via hashing that the bytecode is indeed suitable to be deployed with this contract
* @param byteCode The bytecode, typically the creationCode + args
* @param byteCodeHash The bytecode hash, computed typically by calling getBytecodeHash() at some point
*/
function verifyByteCode(bytes memory byteCode, bytes32 byteCodeHash) public virtual pure returns (bool) {
return byteCodeHash == getBytecodeHash(byteCode);
}
}
// File contracts/factory/SlimFactory.sol
pragma solidity ^0.8.18;
/**
* @title A slim Register+Deploy factory for arbitrary, `ProvidableContract`-extending contracts using create2 deploy mechanism
* @author [email protected]
* @notice Two-step factory with create2 requesting bytecode from deployed `BaseProvider`-extending, versioned and potentially incompatible contract templates.
* @dev In order to deploy a contract follow these steps
* 1. Write the `MyContract is ProvidableContract`, lets say with version 1.2.3
* 2. Write a `MyContractProvider is BaseProvider`, which serves the bytecode of `MyContract` and offers getDeployArgs / getInitArgs functions
* 3. Deploy `MyContractProvider` with any arbitrary wallet and to any arbitrary address. Note its deployed `providerAddr`
* 4. Call `SlimFactory.updateProvider(providerAddr)`. The version will be requested from `providerAddr` onchain.
* 5. Register
* 5.1. [OPTIONAL] Obtain from MyContractProvider optionally deployArgs = getDeployArgs() and initArgs = getInitArgs()
* 5.2. Call SlimFactory.register("1.2.3", deployArgs, initArgs). If deployArgs and/or initArgs are not needed, pass empty `bytes memory`.
* 5.3. Record the registeredAddress, which is returned from register() as well as emitted via ContractRegistered event.
* 6. Deploy a previously registered contract via SlimFactory.deploy(registeredAddress)
*/
contract SlimFactory is ERC165, IInitArgsProvider {
event ContractDeployed(address indexed addr, string version, address deployer);
event ContractRegistered(address indexed addr, string version, address registrar);
event ContractUnregistered(address indexed addr, address maintainer);
event ProviderUpdate(address indexed addr, string indexed version, address maintainer);
event RegistrarUpdate(address to, bool added, address maintainer);
event MaintainerUpdate(address to, bool added, address maintainer);
event DeployerUpdate(address to, bool added, address maintainer);
event DeploymentSaltUpdate(bytes32 newSalt, bytes32 oldSalt, address maintainer);
mapping (address => bytes) public deployArgsByAddress;
mapping (address => address) public providerForAddress; // provider by deployment!
mapping (address => bytes32) public bytecodeHash;
mapping (bytes32 => address) public addressByBytecodeHash;
mapping (address => address) public registrarByContract;
mapping (address => bytes) public initArgsByAddress;
mapping (string => address) public providerByVersion;
mapping (bytes32 => string) public versionByVersionHash;
address public appHubAddress;
/**
* Keeps a list of all deployed contracts (in deployment order)
*/
address[] public deployedContracts;
/**
* @notice Backlog of contracts that have been registered but not deployed yet
*/
mapping (address => bool) public pendingDeployments;
mapping (address => bool) private _authorizedDeployer;
mapping (address => bool) private _authorizedMaintainer;
mapping (address => bool) private _authorizedRegistrar;
bytes32 private _staticSalt = keccak256("slimFactory");
// https://solidity-by-example.org/app/create2/
// 2. Compute the address of the contract to be deployed
// NOTE: _salt is a random number used to create an address
function getAddress(
bytes memory bytecode,
bytes32 _salt
) public view returns (address) {
uint actualSalt = uint(_salt);
bytes32 hash = keccak256(
abi.encodePacked(bytes1(0xff), address(this), actualSalt, keccak256(bytecode))
);
// NOTE: cast last 20 bytes of hash to address
return address(uint160(uint(hash)));
}
/**
* @param bytecode Bytecode + packed constructorArgs of contract implementing IDeployedContract
* @param salt The deployment salt.
* @dev Isolated function to actually deploy contracts (can be used by extending contracts)
* inspired by https://solidity-by-example.org/app/create2/
* Salt is taken as parameter to also allow deploying contracts with an "old" salt in case of
* emergency salt-upgrade.
*/
function _deployBinary(bytes memory bytecode, bytes32 salt) internal whenOperational() returns (address) {
address addr;
uint actualSalt = uint(salt);
/*
NOTE: How to call create2
create2(v, p, n, s)
create new contract with code at memory p to p + n
and send v wei
and return the new address
where new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n)))
s = big-endian 256-bit value
*/
assembly {
addr := create2(
callvalue(), // wei sent with current call
// Actual code starts after skipping the first 32 bytes
add(bytecode, 0x20),
mload(bytecode), // Load the size of code contained in the first 32 bytes
actualSalt // Salt from function arguments
)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
return addr;
}
/**
* @notice Updates the deployment salt - do only use in absolut emergencies!
* @dev This shall not be used at all and is just for emergencies and major fuckups. As soon
* as the salt is updated, it can happen that the same contract / same version / same constructorArgs
* can be re-deployed to a different address.
*/
function updateDeploymentSalt(bytes32 newSalt) public onlyMaintainer() whenOperational() {
emit DeploymentSaltUpdate(_staticSalt, newSalt, msg.sender);
_staticSalt = newSalt;
}
/**
* @dev Hook called after registration of a new contract. Typically overwritten in extending implementations
* @param addr Address of the just registered contract
* @param contractVersion Version of the just registered contract as indicated by provider in format X.Y.Z
*/
function _afterRegistration(address addr, string memory contractVersion) internal virtual {}
/**
* @dev Hook called after deployment of of a registered contract. Typically overwritten in extending implementations
* @param addr Address of the just registered contract
* @param contractVersion Version of the just registered contract as indicated by provider in format X.Y.Z
*/
function _afterDeployment(address addr, string memory contractVersion) internal virtual {}
/**
* @notice Unregisters undeployed contracts (undo register(), as long as not deployed)
* @param toUnregister The address of the contract to unregister
* @dev Can be called by maintainer or the registrar who registered the particular contract at `toUnregister`-address
*/
function unregister(address toUnregister) public maintainerOrRegistrar() {
require(pendingDeployments[toUnregister], "Only registered, not-deployed contracts can be unregistered");
if(!isAuthorizedMaintainer(msg.sender)) {
require(registrarByContract[toUnregister] == msg.sender, "Can only unregister own registered contracts");
}
_beforeUnregister(toUnregister);
delete addressByBytecodeHash[bytecodeHash[toUnregister]];
delete bytecodeHash[toUnregister];
delete deployArgsByAddress[toUnregister];
delete initArgsByAddress[toUnregister];
delete pendingDeployments[toUnregister];
delete registrarByContract[toUnregister];
delete providerForAddress[toUnregister];
emit ContractUnregistered(toUnregister, msg.sender);
}
/**
* @dev Hook called before unregistering a contract - permission to unregister has been verified before calling this function
*/
function _beforeUnregister(address addrToUnregister) internal virtual {}
/**
* @notice Registers a new Contract deployment of contract with version X.Y.Z. Can only be executed by authorized registrars.
* @dev The returned address is then typically passed into `deploy()` to execute the deployment
* @param contractVersion The version in format X.Y.Z which should be deployed
* @param deployArgs DeployArgs, typically obtained from the BaseProvider-extending contract getDeployArgs(...arbitraryParams ...)
* @param initArgs InitArgs requestable through getInitArgs(), typically obtained from BaseProvider-extending contract getInitArgs(...) or getDefaultInitArgs(...)
* @return registeredAddress The predicted address, where the contract will be deployed to via `deploy(registeredAddress)`
*/
function register(string memory contractVersion, bytes memory deployArgs, bytes memory initArgs) public onlyRegistrar() whenOperational() returns (address registeredAddress) {
address _provider = providerByVersion[contractVersion];
require(_provider != address(0), "No provider found");
BaseProvider p = BaseProvider(_provider);
registeredAddress = getAddress(p.getBytecode(deployArgs), _staticSalt);
require(providerForAddress[registeredAddress] == address(0), "Contract already registered");
registrarByContract[registeredAddress] = msg.sender;
providerForAddress[registeredAddress] = _provider;
_beforeRegister(registeredAddress, contractVersion, deployArgs, initArgs);
deployArgsByAddress[registeredAddress] = deployArgs;
bytes memory byteCodeForDeployment = p.getBytecode(deployArgs);
bytes32 bcHash = p.getBytecodeHash(byteCodeForDeployment);
require(addressByBytecodeHash[bcHash] == address(0), "Identical bytecode already registered");
bytecodeHash[registeredAddress] = bcHash;
addressByBytecodeHash[bcHash] = registeredAddress;
initArgsByAddress[registeredAddress] = initArgs;
pendingDeployments[registeredAddress] = true;
emit ContractRegistered(registeredAddress, contractVersion, msg.sender);
_afterRegistration(registeredAddress,contractVersion);
return registeredAddress;
}
/**
* @notice Hook called before registering a contract, typically overridden by extending contracts
*/
function _beforeRegister(address /*_deployment*/, string memory /*_version*/, bytes memory /*_deployArgs*/, bytes memory /*_initArgs*/) internal virtual {}
/**
* @notice Actually deploys the previously registered contract. Can only be executed by authorized deployers.
* @dev Note that if you get an "Error: Transaction reverted without a reason string",
* the most likely reason is corrupt init data. Verify init-data is correct!
* Also a common error is that the factory is lacking permissions to run initialize()
* on the just deployed contract.
* Verifies bytecode-integrity to account for potential corroptions between registration and deployment.
* This also accounts for cases, where the _staticSalt has changed between registration and deployment.
* Further does regression-testing by verifying deployed address matches registered address
* @param registeredAddress The address as returned from `register()`
*/
function deploy(address registeredAddress) public virtual
onlyDeployer()
returns (address)
{
BaseProvider p = BaseProvider(providerForAddress[registeredAddress]);
bytes memory argsForDeployment = deployArgsByAddress[registeredAddress];
bytes memory byteCodeForDeployment = p.getBytecode(argsForDeployment);
require(p.verifyByteCode(byteCodeForDeployment, bytecodeHash[registeredAddress]), "Bytecode verification failed, version mismatch?");
address deployedAddress = _deployBinary(byteCodeForDeployment, _staticSalt);
require(deployedAddress == registeredAddress, "Predicted and deployed address mismatch");
ProvidableContract pc = ProvidableContract(deployedAddress);
delete pendingDeployments[registeredAddress];
emit ContractDeployed(deployedAddress, pc.getVersion(), msg.sender);
_afterDeployment(deployedAddress, pc.getVersion());
return deployedAddress;
}
/**
* @notice Adds or updates a provider contract. Can only be used by authorized maintainers.
* @dev Note updating should be avoided, rather increase the the Revision Z in X.Y.Z versioning.
* @dev The version is determined by calling BaseProvider(_providerAddress).getVersion()
* @param providerAddress Address of the deployed ProviderContract (which extends BaseProvider)
*/
function updateProvider(address providerAddress) public onlyMaintainer() {
BaseProvider bp = BaseProvider(providerAddress);
string memory _version = bp.getVersion();
require(providerByVersion[_version] != providerAddress, "No change required");
emit ProviderUpdate(providerAddress, _version, msg.sender );
providerByVersion[_version] = providerAddress;
}
/**
* @notice Removes the provider of `version`, i.e. stop supporting this version
*/
function removeVersion(string memory version) public onlyMaintainer() {
address provider = providerByVersion[version];
require(provider != address(0), "Version unknown");
emit ProviderUpdate(address(0), version, msg.sender);
delete providerByVersion[version];
}
/**
* @notice Indicates whether a certain version is supported / can be registered and deployed
* @param version in Format x.y.z
*/
function versionSupported(string memory version) public view returns (bool) {
return providerByVersion[version] != address(0);
}
/**
* @notice Returns the init args stored for a contract deployed to `deployedAddress`
* @dev Typically called (exactly once) from the deployedContract's constructor via BaseProvider-Constructor.
*/
function getInitArgs(address deployedAddress) public view returns (bytes memory) {
return initArgsByAddress[deployedAddress];
}
// ####################################################### SIMPLE ACCESS CONTROL
// Overwrite isAuthorized* in extending contracts, e.g. if you want to use
// Role-based AccessControl etc.
/**
* @notice Indicates that `àccount` is authorized to deploy.
* @dev Typically overridden in extending contracts when role-based access control shall be used
*/
function isAuthorizedDeployer(address account) public virtual view returns (bool) {
return _authorizedDeployer[account];
}
/**
* @notice Indicates that `account` is authorized to register and unregister (own) contracts.
* @dev Typically overridden in extending contracts when role-based access control shall be used
*/
function isAuthorizedRegistrar(address account) public virtual view returns (bool) {
return _authorizedRegistrar[account];
}
/**
* @notice Indicates that `account` is authorized to maintain this factory.
* @dev Typically overridden in extending contracts when role-based access control shall be used
*/
function isAuthorizedMaintainer(address account) public virtual view returns (bool) {
return _authorizedMaintainer[account];
}
modifier onlyRegistrar() {
require(isAuthorizedRegistrar(msg.sender), "Authorized Registrar only");
_;
}
modifier onlyDeployer() {
require(isAuthorizedDeployer(msg.sender), "Authorized Deployer only");
_;
}
modifier onlyMaintainer() {
require(isAuthorizedMaintainer(msg.sender), "Authorized Maintainer only");
_;
}
modifier maintainerOrRegistrar() {
require(isAuthorizedMaintainer(msg.sender) || isAuthorizedRegistrar(msg.sender), "msg.sender must be maintainer or registrar");
_;
}
/**
* This function is typically overwritten when e.g. implementing pausable interface
*/
function isOperational() public virtual view returns (bool) {
return true;
}
/**
* @notice Allows to `add` or remove an `account` as an authorized registrar.
*/
function updateAuthorizedRegistrar(address account, bool add) public onlyMaintainer() {
require(_authorizedRegistrar[account] != add, "No change required");
emit RegistrarUpdate(account, add, msg.sender);
_authorizedRegistrar[account] = add;
}
/**
* @notice Allows to `add` or remove an `account` as an authorized deployer.
*/
function updateAuthorizedDeployer(address account, bool add) public onlyMaintainer() {
require(_authorizedDeployer[account] != add, "No change required");
emit DeployerUpdate(account, add, msg.sender);
_authorizedDeployer[account] = add;
}
/**
* @notice Allows to `add` or remove an `account` as an authorized maintainer.
*/
function updateAuthorizedMaintainer(address account, bool add) public onlyMaintainer() {
require(_authorizedMaintainer[account] != add, "No change required");
emit MaintainerUpdate(account, add, msg.sender);
_authorizedMaintainer[account] = add;
}
modifier whenOperational() {
require(isOperational(), "Not operational");
_;
}
// ####################################################### ERC165
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) {
return interfaceId == type(IInitArgsProvider).interfaceId
|| super.supportsInterface(interfaceId);
}
constructor(address _appHub) {
_authorizedMaintainer[msg.sender] = true;
emit MaintainerUpdate(msg.sender, true, msg.sender);
appHubAddress = _appHub;
}
}
// File contracts/IERC6454.sol
/// @title EIP-6454 Minimalistic Non-Transferable interface for NFTs
/// @dev See https://eips.ethereum.org/EIPS/eip-6454
/// @dev Note: the ERC-165 identifier for this interface is 0x91a6262f.
pragma solidity ^0.8.18;
interface IERC6454 /* is IERC165 */ {
/**
* @notice Used to check whether the given token is transferable or not.
* @dev If this function returns `false`, the transfer of the token MUST revert execution.
* @dev If the tokenId does not exist, this method MUST revert execution, unless the token is being checked for
* minting.
* @dev The `from` parameter MAY be used to also validate the approval of the token for transfer, but anyone
* interacting with this function SHOULD NOT rely on it as it is not mandated by the proposal.
* @param tokenId ID of the token being checked
* @param from Address from which the token is being transferred
* @param to Address to which the token is being transferred
* @return Boolean value indicating whether the given token is transferable
*/
function isTransferable(uint256 tokenId, address from, address to) external view returns (bool);
}
// File contracts/MetaAnchor.sol
pragma solidity ^0.8.18;
/**
* @title Meta Anchor (TM) Digital Soul (TM) contract
* @author metaanchor.io
* @notice This contract anchors DigitalSoul-NFTs to Physical Objects. They're called
* DigitalSoul-NFTs because as they anchor 1:1 and inseperably to a specific physical object
* over its entire lifecycle. Whoever posesses/controls the physical object has the
* authorization to control the corresponding DigitalSoul-NFT, e.g. can transfer or mint.
* Note that neither from- nor to-account need to sign transfers, authorization is solely
* provided through access to the physical object!
*
* ~~ OWNER OF THIS CONTRACT ~~
* The owner is indicated through `owner()`. The owner issues the collection and physical objects,
* manages the contract and it's appearance on marketplaces/platforms and
* is the issuer of any meta data, assets or perks associated with the DigitalSoul-NFTs.
*
* ~~ SYSTEM DESCRIPTION ~~
* A Physical Object is equipped with a uniquely identifiable MetaAnchor security tag ("anchor").
* A user optically authenticates the MetaAnchor security tag with a smartphone and specifies the
* to`-account, which shall receive the DigitalSoul-NFT.
* Upon the authenticated presence of the Physical Object through the MetaAnchor technology,
* `transferAnchor` is invoked. The DigitalSoul-NFT gets minted or transfered to the
* `to`-account, irregardless whether an eventual previous owner approves or not.
* Using the well-known `tokenURI()`, the DigitalSoul (metadata) of the Physical Object can be resolved.
* Metadata is stored off-chain (IPFS, centralized server, ...) by the owner of this contract, hence the
* producer/issuer of the Physical Objects.
* Metadata is linked to the anchor (not the tokenID!), hence anchored 1:1 to the physical object.
*
* ~~ SMART CONTRACT TLDR ~~
* a) DigitalSoul-NFTs are technically ERC-6956 Asset-Bound NFTs, fully compatible with ERC-721.
* b) When an account owns or has owned the DigitalSoul-NFT this proves this account has benefitted from
* having access to the Physical Object corresponding to the specific anchor.
* c) `ownerOf[tokenByAnchor(anchor)]` indicates the current owner of the DigitalSoul-NFT, which
* corresponds to the particular physical object identified through `anchor`.
* d) `isFloating(anchor)` respectively `isFloating(anchorByToken(token))` indicates whether a DigitalSoul-Token
* can also be transferred without having access to the physical object.
* This is useful for e.g. pre-sale activity or temporary lending of DigitalSoul-Tokens to other accounts.
* e) When `isFloating(anchor)==true`, a DigitalSoul-Token acts like an "ordinary" ERC-721 NFT with the caveat,
* that it can be transferred from anywhere as soon as the physical object is used to authorize transferAnchor.
* f) DigitalSoul-NFTs have a limited amount of `transferAnchor()` calls. Use `attestationUsagesLeft(anchor)` to see how
* often a DigitalSoul-NFT can still be transferred.
*
* ~~ SECURITY CONSIDERATIONS ~~
* If you do not control / restrict other people's access to the physical object, a DigitalSoul-NFT may
* be transferred from your wallet without your consent anytime. Protect the physical object from
* unauthorized access or theft like you would protect any other "ordinary" physical thing.
*
* On the bright side, if your DigitalSoul-NFT should get stolen, you can recover it anytime
* through using the physical object and `transferAnchor()`.
*
* ~~ Legal disclaimer ~~
* metaanchor.io licenses this contract to the `owner()` without warrenty of any kind, express or implied,
* including but not limited to the warranties of merchantability, fitness for particular purpose and noninfringement.
* In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether
* in an action of contract, tort or otherwise, arising from, out of or in connection with this software
* or the use or other dealings in the software.
*
* @dev This contract extends [ERC-6956, also authored by us](https://eips.ethereum.org/EIPS/eip-6956) and is therefore
* fully compatible with ERC-721 [especially when a token `floating`].
* TokenIds are assigned to anchors in the order of minting. Once a tokenId <> anchor relation is established,
* it is immutable over the complete lifecycle.
*/
contract MetaAnchor is ERC6956Full, Pausable, Ownable, AppContract, ProvidableContract, IERC6454{
/// @notice ORACLE_ROLE is an accepted signer for attestations.
/// @return Role hash, as should be passed to hasRole(), grantRole()
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
/// @notice MINTER_ROLE can call dropAnchor and safeMint(), the latter is not recommended to use directly
/// @return Role hash, as should be passed to hasRole(), grantRole()
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// @notice MINTER_ROLE can call dropAnchor and safeMint(), the latter is not recommended to use directly
/// @return Role hash, as should be passed to hasRole(), grantRole()
bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");
bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE");
string internal _contractUri = "";
bool private _isInitialized;
/// @notice Contract version string
function getVersion() public virtual pure override returns (string memory) {
return "0.3.1";
}
/// @notice Takes identifiying _name and _symbol parameters. updateBaseURI() and maxDropsPerAnchor() shall directly after deployment.
/// @dev Typically used via MetaAnchorFactory, which ensures to set default parameters.
/// @param _hub The address of the AppHub for role management
/// @param _name The Name of the Contract, usually also the Collection-Name
/// @param _symbol The symbol for tokens of this contract. In MetaAnchor-language often referred to as CSN (contract short name)
constructor(address _hub, string memory _name, string memory _symbol, IERC6956AttestationLimited.AttestationLimitPolicy _limitUpdatePolicy)
ERC6956Full(_name, _symbol, _limitUpdatePolicy) AppContract(_hub) {
// Better safe than sorry - remove the Deployer (factory) from ERC6956.
// This is possible, because this constructor is run after all dependent constructors, so also
// after ERC6956Full-constructor as well as the ProvidableContract-constructor (calling initialize)
delete maintainers[msg.sender];
}
/**
* @notice Decodes init-args, typically generated by the ProviderContract
* @param initArgs abi-encoded init-args
* @return _burnAuthorization Indicates which authorization is needed to burn tokens
* @return _approveAuthorizaion Indicates which authorization is needed to approve other accounts for a token
* @return _canStartFloatingAuthorization Indicates which authorization is needed to start floating of an anchor
* @return _canStopFloatingAuthorization Indicates which authorization is needed to stop floating of an anchor
* @return _attestationLimit The initial attestation limit. This is the only way to define the attestationLimit if AttestationLimitPolicy is IMMUTABLE.
*/
function decodeInitArgs(bytes memory initArgs) public pure returns
(
IERC6956.Authorization _burnAuthorization,
IERC6956.Authorization _approveAuthorizaion,
// IERC6956Floatable
IERC6956.Authorization _canStartFloatingAuthorization,
IERC6956.Authorization _canStopFloatingAuthorization,
// IERC6956AttestationLimited
uint256 _attestationLimit
) {
(_burnAuthorization, _approveAuthorizaion,
_canStartFloatingAuthorization, _canStopFloatingAuthorization,
_attestationLimit
) = abi.decode(
initArgs, (
IERC6956.Authorization, IERC6956.Authorization,
IERC6956.Authorization, IERC6956.Authorization,
uint256
)
);
// implicitely return values
}
/**
* @notice Initializes the contract with well-defined init args (refer `decodeInitArgs` for definition)
* @dev This is the only way how attestationLimits can be defined when AttestationLimitPolicy == IMMUTABLE.
* @param initArgs abi-encoded, typically requested from MetaAnchorFactory and generated by MetaAnchorProvider
*/
function initialize(bytes memory initArgs) public virtual override onlyMaintainer() {
require(!_isInitialized, "initialize() can only be called once");
_isInitialized = true;
(
IERC6956.Authorization _burnAuthorization,
IERC6956.Authorization _approveAuthorizaion,
// IERC6956Floatable
IERC6956.Authorization _canStartFloatingAuthorization,
IERC6956.Authorization _canStopFloatingAuthorization,
// IERC6956AttestationLimited
uint256 _attestationLimit
) = decodeInitArgs(initArgs);
updateBurnAuthorization(_burnAuthorization);
updateApproveAuthorization(_approveAuthorizaion);
updateFloatingAuthorization(_canStartFloatingAuthorization, _canStopFloatingAuthorization);
updateGlobalAttestationLimit(_attestationLimit);
}
// ####################################### ERC6956-Overrides for permissions
function isMaintainer(address a) public virtual view override(ERC6956) returns (bool) {
// explicitly override the permission system of ERC6956 with AppHub
return hasRole(MAINTAINER_ROLE, a) || super.isMaintainer(a);
}
/**
* @notice Declares `oracleAddress` trusted, if it has the `ORACLE_ROLE`
* @dev Overrides ERC6956 oracle logic, does NOT emit OracleUpdate events, when ORACLE_ROLE is granted via grantRole()
* @param oracleAddress Oracle address in question
* @return isTrusted true, if oracleAddress has ORACLE_ROLE
*/
function isTrustedOracle(address oracleAddress) public view override(ERC6956) returns (bool isTrusted) {
return hasRole(ORACLE_ROLE, oracleAddress);
}
/**
* @notice When calling ContractURI, typically json-metadata is returned. Refer e.g. the OpenSea format suggestion
* @return ContractURI pointing to the json
*/
function contractURI() public view returns (string memory) {
return _contractUri;
}
/**
* @notice Pauses the contract. This means among other things that drops/releases are no longer possible.
*/
function pause() public onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* Unpauses the contract / reverts pause().
*/
function unpause() public onlyRole(PAUSER_ROLE) {
_unpause();
}
// ####################################### IERC6454
/**
* @notice Refer IERC6454.
* @param tokenId TokenId in question
* @param from current owner address or approved address
* @param to target address, where tokenId shall be transfered to.
* @dev Indicates general
*/
function isTransferable(uint256 tokenId, address from, address to) external view returns (bool transferable) {
if(from == address(0)) {
// Minting can only be done through transferAnchor()
return false;
}
bytes32 anchor = anchorByToken[tokenId];
require(anchor != 0x0, "Token does not exist"); // A non-existent tokenId will resolve to the invalid anchor 0x0. ERC-6454 requires this throw
if (from == address(0) && to == address(0)) {
// Indicate the token can be transferred in general, as tokens can be oracle-transferred anytime - as long as limit is not reached
// but even if the limit is reached, a floating token could be transfered by "traditional" ways.
return attestationUsagesLeft(anchor) > 0 || floating(anchor);
}
return floating(anchor) && _isApprovedOrOwner(from, tokenId);
}
// ####################################### ER165
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC6956Full, AppContract)
returns (bool)
{
return super.supportsInterface(interfaceId) || interfaceId == type(IERC6454).interfaceId;
}
/// @notice Set a new contractURI. Refer `contractURI` for details.
/// @param contractUri The token base-URI. Must end with slash '/'.
function updateContractURI(string calldata contractUri) public onlyRole(MAINTAINER_ROLE) whenNotPaused() {
_contractUri = contractUri;
}
/// @notice Transfers ownership over the contract. The owner has no updating power in this contract, but is returned when calling owner().
/// This allows the owner to act/sign accordinlgy external software applications / marketplaces etc.
/// The owner can be changed by MAINTAINER_ROLE or the current owner.
/// @dev Overrides Ownable's function. For MetaAnchor, the MAINTAINER_ROLE can also change ownership.
/// Do NOT use onlyOwner modifier in this contract, as this would give power to the owner, which is not desired in our use-case.
/// @param newOwner The new owner
function transferOwnership(address newOwner) override public virtual
ownerOrMaintainer() whenNotPaused() {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner); // Calls the Ownable implementation
}
modifier ownerOrMaintainer() {
require(hasRole(MAINTAINER_ROLE, msg.sender) || msg.sender == owner(), "Caller must be maintainer or owner");
_;
}
// ################################## PAUSABLE function wrappers
function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
internal virtual
override(ERC6956Full)
whenNotPaused()
{
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
function anchorValid(bytes32 anchor, bytes32[] memory proof) public virtual view override(ERC6956Full) whenNotPaused() returns (bool isValid) {
return super.anchorValid(anchor, proof);
}
function _beforeAttestationUse(bytes32 anchor, address to, bytes memory data) internal view virtual override(ERC6956Full) whenNotPaused() {
super._beforeAttestationUse(anchor, to, data);
}
function _approve(address to, uint256 tokenId) internal virtual override(ERC721) whenNotPaused(){
super._approve(to, tokenId);
}
function _setApprovalForAll(address owner, address operator, bool approved) internal virtual override(ERC721) whenNotPaused() {
super._setApprovalForAll(owner, operator, approved);
}
}
// File contracts/MetaAnchorFactory.sol
pragma solidity ^0.8.18;
/**
* @title MetaAnchor Factory
* @author [email protected]
* @notice Registers and deploys MetaAnchor contracts and lets you verify via `hasDeployed()`
* whether contract is an original MetaAnchor contract. Corresponding deployed contracts will have the same address on all EVM-blockchains.
* @dev A Meta Anchor (TM) contract is identified solely by its symbol. This factory ensures max. one contract per symbol exists.
* Contracts will deploy to a deterministic address on all EVM-chains, depending solely on (version, deployArgs).
* Each MetaAnchor-contract needs to have the following mandatory constructor arguments (in that order):
* (address appHub, string name, string symbol)
* - The latter two are passed to ERC-721.
* - The factory ensures at most one registered (and consequently deployed) contract per symbol exists
* In order to ensure deterministic deployment cross-chain
* - The provider registered for a version needs to return the same byteCode on all chains
* - deployArgs when registering deployment of a certain version need to be identical
* - This factory needs to be deployed to the same address on all chains
* Also features a mechanism to grant finite numbers of registrations and deployments to arbitrary accounts
*
* Refer documentation of `SlimFactory` or details on the registration/deployment process
*/
contract MetaAnchorFactory is AppContract, SlimFactory, Pausable, IDeployedContract {
/**
* Emits when new registrations are granted to an `account`
* @param account Account, which has been granted `nrAdded` additional registrations
* @param nrAdded Indicates how many additional registrations have been added to the account
* @param registrationsLeft Registrations left for the account
* @param registrationLimit New registration limit for account after adding `nrAdded`
*/
event RegistrationsGranted(address indexed account, uint256 nrAdded, uint256 registrationsLeft, uint256 registrationLimit);
/**
* Emits when new deployments are granted to an `account`
* @param account Account, which has been granted `nrAdded` additional deployments
* @param nrAdded Indicates how many additional deployments have been added to the account
* @param deploymentsLeft Deployments left for the account
* @param deploymentLimit New deployment limit for account after adding `nrAdded`
*/
event DeploymentsGranted(address indexed account, uint256 nrAdded, uint256 deploymentsLeft, uint256 deploymentLimit);
/**
* @notice DEPLOYER_ROLE is permitted to deploy new contracts
*/
bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE");
/**
* @notice MAINTAINER_ROLE corresponds to the permissions of SlimFactory.isMaintainer(),
* which can maintain the factory and unregister contracts
*/
bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");
/**
* @notice Can add providers, remove registrations etc
*/
bytes32 public constant FACTORY_MAINTAINER_ROLE = keccak256("FACTORY_MAINTAINER_ROLE");
/**
* @notice Can pause the contract
*/
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/**
* @notice REGISTRAR_ROLE can register new contracts and unregister own earlier registrations
*/
bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE");
/**
* @notice Version of this contract
*/
string public constant version = "0.3.2";
/**
* @notice Registration limits per account. Applies only to accounts not granted the REGISTRAR_ROLE
*/
mapping (address => uint256) public registrationLimit;
/**
* @notice Deployment limits per account. Applies only to accounts not granted the DEPLOYER_ROLE
*/
mapping (address => uint256) public deploymentLimit;
/**
* @notice Records the number of active registrations per contract
*/
mapping (address => uint256) public registrationsByAccount;
/**
* @notice Records the number of active deployments per contract
*/
mapping (address => uint256) public deploymentsByAccount;
/**
* @notice Records the deployer account of a specific deployment.
*/
mapping (address => address) public deployerOf;
/**
* @notice Resolves a symbol to the registered contract address 1:1
*/
mapping (string => address) public contractBySymbol;
/**
* @notice Resolves a contract address to the symbol 1:1
*/
mapping (address => string) public symbolByContract;
/**
* @notice IDeployedContract interface, indicates the deployer of this contract
*/
address public deployedBy;
/**
* @notice IDeployedContract interface, indicates whether a contract at `addr` was deployed through this factory
* @param addr Address of contract in question, typically a MetaAnchor-contract
*/
function hasDeployed(address addr) public view returns (bool hasDeployedAddr) {
return deployerOf[addr] != address(0);
}
// ################################ Use AppHub-Permissions to overwrite SlimFactory-Authorization mechanics
/**
* Indicates that accounts with `FACTORY_MAINTAINER_ROLE` are the only maintainers
* @param addr Account in question
*/
function isAuthorizedMaintainer(address addr) public view override returns (bool) {
return hasRole(FACTORY_MAINTAINER_ROLE, addr);
}
/**
* @notice Indicates whether an account `addr` currently has permissions/capacity to register a contract
* @param addr Account-address
*/
function isAuthorizedRegistrar(address addr) public view override returns (bool) {
return hasRole(REGISTRAR_ROLE, addr) || (registrationsLeft(addr) > 0);
}
/**
* @notice Indicates whether an account `addr` currently has permissions/capacity to deploy a contract
* @param addr Account-address
*/
function isAuthorizedDeployer(address addr) public view override returns (bool) {
return hasRole(DEPLOYER_ROLE, addr) || (deploymentsLeft(addr) > 0);
}
/**
* @dev Hook from Slimfactory. Verifies before registration that the symbol is still available and if App-Hub address matches the one of this factory
* @param deploymentAddress Address of the registered contract
* @param _deployArgs ABI-encoded deployment arguments, where the first 3 parameters are (appHubAddress, contractName, contractSymbol)
*/
function _beforeRegister(address deploymentAddress, string memory /*_version*/, bytes memory _deployArgs, bytes memory /*_initArgs*/) internal virtual override(SlimFactory) {
(address appHub, , string memory symbol ) = abi.decode(_deployArgs, (address, string, string));
require(contractBySymbol[symbol] == address(0), "Symbol already taken");
require(appHub == appHubAddress, "AppHubAddress mismatch");
registrationsByAccount[msg.sender] += 1;
symbolByContract[deploymentAddress] = symbol;
contractBySymbol[symbol] = deploymentAddress;
}
/**
* @dev Hook from SlimFactory. Rollback of state-changes made in `_beforeRegister`
* @param toUnregister Contract that will be unregistered
*/
function _beforeUnregister(address toUnregister) internal override(SlimFactory) {
registrationsByAccount[registrarByContract[toUnregister]] -= 1;
delete contractBySymbol[symbolByContract[toUnregister]];
delete symbolByContract[toUnregister];
super._beforeUnregister(toUnregister);
}
// ################################## UTILITY for finite-time deployments of foreign accounts
/**
* @notice Allows FACTORY_MAINTAINER_ROLEs to grant additional `nrRegistrations` to `account`
*/
function grantRegistrations(address account, uint256 nrRegistrations) public onlyRole(FACTORY_MAINTAINER_ROLE) {
registrationLimit[account] += nrRegistrations;
emit RegistrationsGranted(account, nrRegistrations, registrationsLeft(account), registrationLimit[account]);
}
/**
* @notice Allows FACTORY_MAINTAINER_ROLEs to grant additional `nrDeployments` to `account`
*/
function grantDeployments(address account, uint256 nrDeployments) public onlyRole(FACTORY_MAINTAINER_ROLE) {
deploymentLimit[account] += nrDeployments;
emit DeploymentsGranted(account, nrDeployments, deploymentsLeft(account), deploymentLimit[account]);
}
/**
* @notice Indicates how many deployments are left for `account`.
* @dev Note if account has DEPLOYER_ROLE, it can even deploy registered contracts if this method returns 0
*/
function deploymentsLeft(address account) public view returns(uint256 nrDeploymentsLeft) {
uint256 limit = deploymentLimit[account];
uint256 used = deploymentsByAccount[account];
if(used > limit) {
return 0;
}
return limit - used; }
/**
* @notice Indicates how many registrations are left for `account`.
* @dev Note if account has REGISTRAR_ROLE, it can even register new contracts if this method returns 0
*/
function registrationsLeft(address account) public view returns(uint256 nrRegistrationsLeft) {
uint256 limit = registrationLimit[account];
uint256 used = registrationsByAccount[account];
if(used > limit) {
return 0;
}
return limit - used;
}
/**
* @dev Tracks deployment details for stats and deployment permissions
* @param _deploymentAddr Address of the just-deployed contract
*/
function _afterDeployment(address _deploymentAddr, string memory /*version*/) internal override(SlimFactory) {
deploymentsByAccount[msg.sender] += 1;
deployerOf[_deploymentAddr] = msg.sender;
}
/**
* @notice Registers a new MetaAnchor contract with `version` using default settings. Refer `SlimFactory.deploy()` for further details.
* @param name Name of the contract (immutable)
* @param symbol Symbol of the contract (immutable), identifying the MetaAnchor-contract unambigiously
* @param contractVersion The contract version, which shall be deployed in format X.Y.Z
*/
function registerWithDefaults(string memory name, string memory symbol, string memory contractVersion) public {
require(versionSupported(contractVersion), "Requested version not supported");
BaseProvider bp = BaseProvider(providerByVersion[contractVersion]);
bytes memory constructorArgs = bp.getDefaultArgs(appHubAddress, name, symbol);
bytes memory initArgs = bp.getDefaultInitArgs();
register(contractVersion, constructorArgs, initArgs);
}
// ################################### ERC-165
function supportsInterface(bytes4 interfaceId) public view virtual override(AppContract, SlimFactory) returns (bool) {
return interfaceId == type(IDeployedContract).interfaceId
|| super.supportsInterface(interfaceId);
}
// ########################### PAUSABLE
/**
* @notice Pauses the contract.
* @dev This means among other things that deployments and registrations are no longer possible (via `isOperational`).
*/
function pause() public onlyRole(PAUSER_ROLE) {
_pause();
}
/**
* Unpauses the contract / reverts pause().
*/
function unpause() public onlyRole(PAUSER_ROLE) {
_unpause();
}
/**
* Only operational, when contract is not paused
*/
function isOperational() public virtual view override(SlimFactory) returns (bool) {
return !paused();
}
constructor(address _hub) AppContract(_hub) SlimFactory(_hub) {
// Set default parameters for a higher chance that behavior across chains is equal!
deployedBy = msg.sender;
}
}