// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _HEX_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) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @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] = _HEX_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);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {ERC721} from "solady/src/tokens/ERC721.sol";
import {Ownable} from "solady/src/auth/Ownable.sol";
import "./Strings.sol";
interface TradFiRendererAnimation {
function renderFromSeed(uint256[] memory seedsAndInfo) external view returns(string memory svg);
}
interface TradFiLines {
function ownerOf(uint256 tokenId) external view returns(address);
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function tokenIdToSeed(uint256 seed) external view returns(uint256);
function tokenIdToCount(uint256 seed) external view returns(uint256);
}
interface TradFiLinesColor {
function ownerOf(uint256 tokenId) external view returns(address);
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function tokenIdToSeed(uint256 tokenId) external view returns(uint256);
function tokenIdToCount(uint256 tokenId) external view returns(uint256);
function tokenIdToPlacement(uint256 tokenId) external view returns(uint256);
function colorTokenIdToSeed(uint256 tokenId) external view returns(uint256);
}
interface CircuitBreaker {
function isHalted() external view returns(bool halted);
function revertOnHalted() external view;
}
interface BokkyPooBahsDateTimeContract {
function timestampToDateTime(uint timestamp) external returns (uint year, uint month, uint day, uint hour, uint minute, uint second);
function timestampFromDateTime(uint year, uint month, uint day, uint hour, uint minute, uint second) external returns (uint timestamp);
function isWeekEnd(uint timestamp) external returns (bool weekEnd);
function getMonth(uint timestamp) external returns (uint month);
function getDay(uint timestamp) external returns (uint day);
function subHours(uint timestamp, uint _hours) external returns (uint newTimestamp);
function getDayOfWeek(uint timestamp) external returns (uint dayOfWeek);
}
contract TradFiLinesAnimation is ERC721, Ownable {
mapping (uint => uint) public tokenIdToCount;
mapping (uint => uint) public tokenIdToSeed;
mapping (uint => uint) public tokenIdToSeedAnimation;
mapping (uint => uint) public tokenIdToPlacement;
mapping (uint => uint) public dayToPlace;
bool public prizesActive = true;
bool public renderersLocked;
TradFiRendererAnimation public tfrm;
TradFiLinesColor public tflColor;
TradFiLines public tfl;
BokkyPooBahsDateTimeContract public bpbdtc;
CircuitBreaker public cb;
bool public circuitBreakerActive = true;
string public _name = "TradFiLines-A";
string public _symbol = "TFL-A";
constructor(address tradFiRenderer, address tradFiLines, address tradFiLinesColor, address circuitBreakerAddress, address bokkyPooBahsDateTimeContractAddress) {
_initializeOwner(msg.sender);
tfrm = TradFiRendererAnimation(tradFiRenderer);
tflColor = TradFiLinesColor(tradFiLinesColor);
tfl = TradFiLines(tradFiLines);
cb = CircuitBreaker(circuitBreakerAddress);
bpbdtc = BokkyPooBahsDateTimeContract(bokkyPooBahsDateTimeContractAddress);
}
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual returns (bytes4) {
return this.onERC721Received.selector;
}
function setPrizes(bool prizesBool) external onlyOwner {
prizesActive = prizesBool;
}
function setCircuitBreaker(bool cbBool) external onlyOwner {
circuitBreakerActive = cbBool;
}
bool public isDST = true;
uint public hoursToSub = 4;
function adjustDST() external {
uint timestamp = bpbdtc.subHours(block.timestamp, hoursToSub);
uint month = bpbdtc.getMonth(timestamp);
if(month > 3 && month < 11) {
isDST = true;
} else {
uint day = bpbdtc.getDayOfWeek(timestamp);
uint dayOfMonth = bpbdtc.getDay(timestamp);
if(month == 3) {
if(dayOfMonth > 14) {
isDST = true;
} else if(dayOfMonth < 8) {
isDST = false;
} else {
if((dayOfMonth - day - 7) < 1) {
isDST = false;
} else {
isDST = true;
}
}
}
if(month == 11) {
if(dayOfMonth > 7) {
isDST = false;
} else {
if((dayOfMonth - day) < 1) {
isDST = true;
} else {
isDST = false;
}
}
}
}
if(isDST) {
hoursToSub = 4;
} else {
hoursToSub = 5;
}
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721) {
require(isWithinOpeningHours(), "Outside regular trading hours");
if(circuitBreakerActive) {
require(!cb.isHalted(), "Circuit breaker triggered: Transfer functionality has been halted.");
}
tokenIdToCount[tokenId] += 1;
super._beforeTokenTransfer(from, to, tokenId);
}
function swapTFR(address tradFiRenderer) public onlyOwner {
require(!renderersLocked, "Can't swap renderers anymore");
tfrm = TradFiRendererAnimation(tradFiRenderer);
}
function lockRenderers() public onlyOwner {
renderersLocked = true;
}
function getAdjustedTimestamp() public returns(uint timestamp) {
return bpbdtc.subHours(block.timestamp, hoursToSub);
}
function isTradingOpen() public returns(bool){
return isWithinOpeningHours() && !cb.isHalted();
}
function isWithinOpeningHours() public returns(bool){
uint timestamp = getAdjustedTimestamp();
bool weekend = bpbdtc.isWeekEnd(timestamp);
uint hour;
uint year;
uint month;
uint day;
uint minute;
(year, month, day, hour, minute, ) = bpbdtc.timestampToDateTime(timestamp);
if(weekend) {
return false;
}
if(hour < 9 || hour > 15) {
return false;
}
if(hour == 9 && minute < 30) {
return false;
}
return true;
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721) returns (bool) {
return super.supportsInterface(interfaceId);
}
receive() external payable {}
function wrap(uint tokenToWrap) external {
uint timestamp = getAdjustedTimestamp();
uint year;
uint month;
uint day;
(year, month, day, , , ) = bpbdtc.timestampToDateTime(timestamp);
tokenIdToCount[tokenToWrap] = tflColor.tokenIdToCount(tokenToWrap);
tflColor.safeTransferFrom(msg.sender, address(this), tokenToWrap);
if(tokenIdToSeedAnimation[tokenToWrap] == 0 ) {
tokenIdToSeedAnimation[tokenToWrap] = uint(keccak256(abi.encodePacked(blockhash(block.number-1),tokenToWrap)));
}
if(tokenIdToPlacement[tokenToWrap] == 0) {
uint place = dayToPlace[day + month * 100 + year * 10000] += 1;
tokenIdToPlacement[tokenToWrap] = place;
if(prizesActive) {
uint prize = 0;
if(place == 1) {
prize = 0.10 ether;
} else if(place == 2) {
prize = 0.05 ether;
} else {
prize = 0.015 ether;
}
if(prize > 0 && address(this).balance >= prize) {
(bool sent, bytes memory data) = payable(msg.sender).call{value: prize}("");
require(sent, "Failed to send Ether");
}
}
}
_mint(msg.sender, tokenToWrap);
}
function tokenURI(uint256 _tokenId)
public
view
override
returns (string memory)
{
require(
_exists(_tokenId),
"ERC721Metadata: URI query for nonexistent token"
);
uint256 seed = tfl.tokenIdToSeed(_tokenId);
uint256 colorSeed = tflColor.colorTokenIdToSeed(_tokenId);
uint256[] memory seedsAndInfo = new uint[](7);
seedsAndInfo[0] = seed;
seedsAndInfo[1] = colorSeed;
seedsAndInfo[2] = tokenIdToSeedAnimation[_tokenId];
seedsAndInfo[3] = tokenIdToCount[_tokenId];
seedsAndInfo[4] = _tokenId;
seedsAndInfo[5] = tflColor.tokenIdToPlacement(_tokenId);
seedsAndInfo[6] = tokenIdToPlacement[_tokenId];
return
string(
abi.encodePacked(
tfrm.renderFromSeed(seedsAndInfo)
)
);
}
function withdraw() public onlyOwner {
uint balance = address(this).balance;
payable(msg.sender).call{value: balance}("");
}
}