// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import { OrderParameters, Order, OrderComponents, OrderStatus } from "./ConsiderationStructs.sol"; import "./ConsiderationConstants.sol"; import { Executor } from "./Executor.sol"; import { Shadow } from "./Shadow.sol"; contract OrderValidator is Executor, Shadow { mapping(bytes32 => OrderStatus) private _orderStatus; constructor(address conduitController, address shadowToken) Executor(conduitController) Shadow(shadowToken) {} function _validateOrderAndUpdateStatus( Order calldata order, bool revertOnInvalid ) internal returns ( bytes32 orderHash, bool valid, uint256 shadowId ) { OrderParameters calldata orderParameters = order.parameters; if ( !_verifyTime( orderParameters.startTime, orderParameters.endTime, revertOnInvalid ) ) { return (bytes32(0), false, 0); } if (orderParameters.periods < 2) { if (revertOnInvalid) { revert InvalidOrderParameters(); } return (bytes32(0), false, 0); } orderHash = _deriveOrderHash( orderParameters, _getCounter(orderParameters.offerer) ); OrderStatus storage orderStatus = _orderStatus[orderHash]; if ( !_verifyOrderStatus( orderHash, orderStatus, true, revertOnInvalid ) ) { return (orderHash, false, 0); } if (!orderStatus.isValidated) { _verifySignature( orderParameters.offerer, orderHash, order.signature ); } shadowId = _mintToken( msg.sender, orderParameters.token, orderParameters.identifier, orderParameters.duration ); orderStatus.isValidated = true; orderStatus.isCancelled = false; orderStatus.isBroken = false; orderStatus.fulfiller = msg.sender; orderStatus.startedAt = block.timestamp; orderStatus.shadowId = shadowId; orderStatus.paidTimes = 1; valid = true; } function _validateOrderAndUpdateRepayStatus( OrderParameters calldata parameters, uint256 payTimes, bool revertOnInvalid ) internal returns ( bytes32 orderHash, address fulfiller, bool valid, bool isFinalized ) { orderHash = _deriveOrderHash( parameters, _getCounter(parameters.offerer) ); OrderStatus storage orderStatus = _orderStatus[orderHash]; if (!orderStatus.isValidated) { if (revertOnInvalid) { revert OrderNotValidated(orderHash); } return (orderHash, address(0), false, false); } if ( !_verifyOrderStatus( orderHash, orderStatus, false, revertOnInvalid ) ) { return (orderHash, address(0), false, false); } if (orderStatus.paidTimes + payTimes > parameters.periods || payTimes < 1) { if (revertOnInvalid) { revert OrderInvalidRepayParameters(orderHash); } return (orderHash, address(0), false, false); } if (orderStatus.startedAt + orderStatus.paidTimes * parameters.duration < block.timestamp) { if (revertOnInvalid) { revert OrderExpired(orderHash); } return (orderHash, address(0), false, false); } orderStatus.paidTimes += payTimes; if (orderStatus.paidTimes == parameters.periods) { orderStatus.isFinalized = true; isFinalized = true; _burnToken(orderStatus.shadowId); } else { _extendToken( orderStatus.fulfiller, orderStatus.shadowId, orderStatus.startedAt + orderStatus.paidTimes * parameters.duration ); } valid = true; fulfiller = orderStatus.fulfiller; } function _validateOrderAndUpdateBreakStatus( OrderParameters calldata parameters, bool revertOnInvalid ) internal returns ( bytes32 orderHash, uint256 paidTimes, bool valid ) { orderHash = _deriveOrderHash( parameters, _getCounter(parameters.offerer) ); OrderStatus storage orderStatus = _orderStatus[orderHash]; if (!orderStatus.isValidated) { if (revertOnInvalid) { revert OrderNotValidated(orderHash); } return (orderHash, paidTimes, false); } paidTimes = orderStatus.paidTimes; if ( !_verifyOrderStatus( orderHash, orderStatus, false, revertOnInvalid ) ) { return (orderHash, paidTimes, false); } if (orderStatus.startedAt + paidTimes * parameters.duration > block.timestamp) { if (revertOnInvalid) { revert OrderNotExpired(orderHash); } return (orderHash, paidTimes, false); } _burnToken(orderStatus.shadowId); orderStatus.isFinalized = true; orderStatus.isBroken = true; valid = true; } function _cancel(OrderComponents[] calldata orders) internal returns (bool cancelled) { // Ensure that the reentrancy guard is not currently set. _assertNonReentrant(); // Declare variables outside of the loop. OrderStatus storage orderStatus; address offerer; // Skip overflow check as for loop is indexed starting at zero. unchecked { // Read length of the orders array from memory and place on stack. uint256 totalOrders = orders.length; // Iterate over each order. for (uint256 i = 0; i < totalOrders; ) { // Retrieve the order. OrderComponents calldata order = orders[i]; offerer = order.offerer; if (msg.sender != offerer) { revert InvalidCanceller(); } // Derive order hash using the order parameters and the counter. bytes32 orderHash = _deriveOrderHash( OrderParameters( offerer, order.token, order.identifier, order.currency, order.artist, order.platform, order.startTime, order.endTime, order.duration, order.periods, order.amount, order.ratio, order.royalty, order.fee, order.withdrawFee, order.salt, order.conduitKey ), order.counter ); // Retrieve the order status using the derived order hash. orderStatus = _orderStatus[orderHash]; if (orderStatus.startedAt > 0) { revert OrderAlreadyStarted(orderHash); } // Update the order status as not valid and cancelled. orderStatus.isValidated = false; orderStatus.isCancelled = true; // Emit an event signifying that the order has been cancelled. emit OrderCancelled(orderHash, offerer); // Increment counter inside body of loop for gas efficiency. ++i; } } // Return a boolean indicating that orders were successfully cancelled. cancelled = true; } function _validate(Order[] calldata orders) internal returns (bool validated) { // Ensure that the reentrancy guard is not currently set. _assertNonReentrant(); // Declare variables outside of the loop. OrderStatus storage orderStatus; bytes32 orderHash; address offerer; // Skip overflow check as for loop is indexed starting at zero. unchecked { // Read length of the orders array from memory and place on stack. uint256 totalOrders = orders.length; // Iterate over each order. for (uint256 i = 0; i < totalOrders; ) { // Retrieve the order. Order calldata order = orders[i]; // Retrieve the order parameters. OrderParameters calldata orderParameters = order.parameters; // Move offerer from memory to the stack. offerer = orderParameters.offerer; // Get current counter & use it w/ params to derive order hash. orderHash = _deriveOrderHash( OrderParameters( offerer, orderParameters.token, orderParameters.identifier, orderParameters.currency, orderParameters.artist, orderParameters.platform, orderParameters.startTime, orderParameters.endTime, orderParameters.duration, orderParameters.periods, orderParameters.amount, orderParameters.ratio, orderParameters.royalty, orderParameters.fee, orderParameters.withdrawFee, orderParameters.salt, orderParameters.conduitKey ), _getCounter(orderParameters.offerer) ); // Retrieve the order status using the derived order hash. orderStatus = _orderStatus[orderHash]; // Ensure order is fillable and retrieve the filled amount. _verifyOrderStatus( orderHash, orderStatus, true, // Signifies that partially filled orders are valid. true // Signifies to revert if the order is invalid. ); // If the order has not already been validated... if (!orderStatus.isValidated) { // Verify the supplied signature. _verifySignature(offerer, orderHash, order.signature); // Update order status to mark the order as valid. orderStatus.isValidated = true; // Emit an event signifying the order has been validated. emit OrderValidated( orderHash, offerer ); } // Increment counter inside body of the loop for gas efficiency. ++i; } } // Return a boolean indicating that orders were successfully validated. validated = true; } function _getOrderStatus(bytes32 orderHash) internal view returns ( bool isValidated, bool isCancelled, bool isFinalized, bool isBroken, address fulfiller, uint256 startedAt, uint256 shadowId, uint256 paidTimes ) { OrderStatus storage orderStatus = _orderStatus[orderHash]; return ( orderStatus.isValidated, orderStatus.isCancelled, orderStatus.isFinalized, orderStatus.isBroken, orderStatus.fulfiller, orderStatus.startedAt, orderStatus.shadowId, orderStatus.paidTimes ); } }