// SPDX-License-Identifier: MIT
// 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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @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: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
*
* 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);
}
//SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface ITraitRegistry {
function addressCanModifyTrait(address, uint16) external view returns (bool);
function isAllowed(bytes32 role, address user) external view returns (bool);
}
abstract contract UTGenericTimedController {
bytes32 public constant TRAIT_REGISTRY_ADMIN = keccak256("TRAIT_REGISTRY_ADMIN");
IERC721 public erc721; // NFT ToolBox
ITraitRegistry public registry; // Trait registry
bool public locked = false;
uint256 public startTime;
uint256 public endTime;
// Errors
error UTGenericTimedControllerNotAuthorised();
error UTGenericTimedControllerLocked();
error UTGenericTimedControllerBeforeValidity();
error UTGenericTimedControllerAfterValidity();
constructor(
address _erc721,
address _registry,
uint256 _startTime,
uint256 _endTime
) {
erc721 = IERC721(_erc721);
registry = ITraitRegistry(_registry);
startTime = _startTime;
endTime = _endTime == 0 ? 9999999999 : _endTime;
}
function getTimestamp() public view virtual returns (uint256) {
return block.timestamp;
}
struct contractInfo {
address erc721;
address registry;
bool locked;
uint256 startTime;
uint256 endTime;
bool available;
}
function tellEverything() external view returns (contractInfo memory) {
return contractInfo(
address(erc721),
address(registry),
locked,
startTime,
endTime,
(getTimestamp() >= startTime && getTimestamp() <= endTime && !locked )
);
}
/*
* Admin Stuff - For controlling the toggleLock() funtion
*/
function toggleLock() public {
if(!registry.isAllowed(TRAIT_REGISTRY_ADMIN, msg.sender)) {
revert UTGenericTimedControllerNotAuthorised();
}
locked = !locked;
}
/*
* Generic validation of controller availability
*/
modifier checkValidity() {
if(locked) {
revert UTGenericTimedControllerLocked();
}
if(getTimestamp() < startTime) {
revert UTGenericTimedControllerBeforeValidity();
}
if(getTimestamp() > endTime) {
revert UTGenericTimedControllerAfterValidity();
}
_;
}
}
//SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.13;
/*
* ██████ ██ ██ ██ ██ ███████ ██ ██████ █████ ██ ██████ ███████ ██████ ███████ ███ ███ ██████ ████████ ██ ██████ ███ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ████ ██
* ██████ ███████ ████ ███████ ██ ██ ███████ ██ ██████ █████ ██ ██ █████ ██ ████ ██ ██████ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██ ██ ███████ ██ ██████ ██ ██ ███████ ██ ██ ███████ ██████ ███████ ██ ██ ██ ██ ██ ██████ ██ ████
*
*
* ██████ ██████ ███ ██ ████████ ██████ ██████ ██ ██ ███████ ██████
* ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██████ ██ ██ ██ ██ █████ ██████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██████ ██ ████ ██ ██ ██ ██████ ███████ ███████ ███████ ██ ██
* *
*
* GALAXIS - Physical Redemption Trait (logic) controller
*
* This contract is used for changing trait values in a UTPhysicalRedeemStorage contract
*
*/
import "./UTGenericTimedController.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
interface ITraitStorage {
function setValue(uint16 _tokenId, uint8 _value) external;
function getValues(uint16[] memory _tokenIds) external view returns (uint8[] memory);
}
// Status:
// 1 - Open to redeem
// 2 - Redeemed by user
// 3 - Item posted (optional)
contract UTPhysicalRedeemController is UTGenericTimedController {
uint256 public constant version = 20230619;
uint8 public constant TRAIT_TYPE = 3; // Physical redemption
string public constant APP = "redeemown"; // Application in the claim URL
uint8 constant TRAIT_OPEN_VALUE = 1;
uint8 constant TRAIT_REDEEMED_VALUE = 2;
// Errors
error UTPhysicalRedeemControllerNoToken();
error UTPhysicalRedeemControllerNotOwnerOfToken(address);
error UTPhysicalRedeemControllerInvalidState(uint8);
constructor(
address _erc721,
address _registry,
uint256 _startTime,
uint256 _endTime
) UTGenericTimedController(_erc721, _registry, _startTime, _endTime) {
}
function setValue(uint16[] memory tokenId, uint8 value, address traitStorage) public checkValidity {
if(tokenId.length == 0) {
revert UTPhysicalRedeemControllerNoToken();
}
// Read all current states
uint8[] memory values = ITraitStorage(traitStorage).getValues(tokenId);
for(uint8 i = 0; i < tokenId.length; i++) {
uint16 currentTokenId = tokenId[i];
uint8 currentValue = values[i];
// Business logic (simple)
if(currentValue == 1 && value == 2) {
// From state 1 - "Open to redeem" to state 2 - "Redeemed by user" - Only owner of the NFT can change it
if(erc721.ownerOf(currentTokenId) != msg.sender) {
revert UTPhysicalRedeemControllerNotOwnerOfToken(msg.sender);
}
ITraitStorage(traitStorage).setValue(currentTokenId, 2);
} else {
revert UTPhysicalRedeemControllerInvalidState(value);
}
}
}
}
{
"compilationTarget": {
"contracts/traits/implementers/UTPhysicalRedeemController.sol": "UTPhysicalRedeemController"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_erc721","type":"address"},{"internalType":"address","name":"_registry","type":"address"},{"internalType":"uint256","name":"_startTime","type":"uint256"},{"internalType":"uint256","name":"_endTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"UTGenericTimedControllerAfterValidity","type":"error"},{"inputs":[],"name":"UTGenericTimedControllerBeforeValidity","type":"error"},{"inputs":[],"name":"UTGenericTimedControllerLocked","type":"error"},{"inputs":[],"name":"UTGenericTimedControllerNotAuthorised","type":"error"},{"inputs":[{"internalType":"uint8","name":"","type":"uint8"}],"name":"UTPhysicalRedeemControllerInvalidState","type":"error"},{"inputs":[],"name":"UTPhysicalRedeemControllerNoToken","type":"error"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"UTPhysicalRedeemControllerNotOwnerOfToken","type":"error"},{"inputs":[],"name":"APP","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRAIT_REGISTRY_ADMIN","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRAIT_TYPE","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"erc721","outputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"locked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"registry","outputs":[{"internalType":"contract ITraitRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16[]","name":"tokenId","type":"uint16[]"},{"internalType":"uint8","name":"value","type":"uint8"},{"internalType":"address","name":"traitStorage","type":"address"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tellEverything","outputs":[{"components":[{"internalType":"address","name":"erc721","type":"address"},{"internalType":"address","name":"registry","type":"address"},{"internalType":"bool","name":"locked","type":"bool"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bool","name":"available","type":"bool"}],"internalType":"struct UTGenericTimedController.contractInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"toggleLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]