Trecena

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/Trecena.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// MENE, MENE, TEKEL, UPHARSIN

import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

contract Trecena is ReentrancyGuard, EIP712 {
    using SafeERC20 for IERC20;

    enum Pool { None, Sun, Moon }

    uint public constant TARGET_DURATION_SECONDS = 86400; 
    uint public constant AVG_BLOCK_TIME_SECONDS = 12; 
    uint256 public constant BLOCKS_PER_CYCLE = TARGET_DURATION_SECONDS / AVG_BLOCK_TIME_SECONDS; 
    uint256 public constant BLOCKS_BETWEEN_SAMPLES = 280; 
    uint256 public constant APPROVAL_CYCLE_COUNT = 13; 
    uint256 public constant CHECKPOINT_INTERVAL = 1; 

    uint256 public constant MIN_OFFERING_USDC = 20 * 1e6;
    uint16 public constant PLATFORM_FEE_BASIS_POINTS = 100;
    uint8 public constant USDC_DECIMALS = 6;

    IERC20 public immutable usdcToken;
    address payable public immutable TemploTrecena;
    address public immutable TemploMayor;
    uint256 public immutable Nahui_Ollin; 

    uint256 public lastSampleTimestamp;
    uint256 public lastSampleBlock;
    uint256 public observedBlockTimeAverage = AVG_BLOCK_TIME_SECONDS; 
    mapping(uint256 => uint256) public cycleLengths; 
    mapping(uint256 => uint256) public cycleStartBlockCheckpoints; 

    bytes32 internal constant READY_KNIFE_TYPEHASH = keccak256(
        "ReadyObsidianKnife(address caller,address warrior,uint256 trecenaCycleId,uint256 expiryBlock)"
    );
    bytes32 internal constant DISARM_KNIFE_TYPEHASH = keccak256(
        "DisarmObsidianKnife(address caller,address warrior,uint256 trecenaCycleId,uint256 expiryBlock)"
    );
    bytes32 internal constant REARM_KNIFE_TYPEHASH = keccak256(
        "RearmObsidianKnife(address caller,address warrior,uint256 trecenaCycleId,uint256 expiryBlock)"
    );

    struct TrecenaCycleData {
        mapping(Pool => uint256) poolTotals;
        mapping(address => bool) isApproved;
        mapping(address => WarriorOfferings) individualWarriorOfferings;
        Pool winningPool;
        bool finalized;
    }

    struct WarriorOfferings {
        mapping(Pool => uint256) offerings;
        uint256 totalOfferings;
        bool hasWithdrawn;
    }

    struct WithdrawalData {
        uint256 trecenaCycleId;
    }

    mapping(uint256 => TrecenaCycleData) public trecenaCycles;
    mapping(bytes32 => bool) private usedSignatures;
    mapping(uint256 => mapping(address => bool)) public isRevoked;
    mapping(address => uint256) public warriorTotalClaimedWinnings;
    uint256 public totalHistoricalVolume;

    error WarriorNotApprovedForTrecenaCycle();
    error InsufficientOffering();
    error AlreadyRewarded();
    error NoOfferings();
    error InvalidTemploMayorAddress();
    error SignatureUsed();
    error InvalidSignature();
    error ExpiryBlockPassed();
    error WarriorRevoked();
    error TrecenaCycleEnded();
    error TrecenaCycleNotActive();
    error InvalidTemploTrecenaAddress();
    error InvalidTokenDecimals(uint8 expected, uint8 actual);
    error OnlyEOA();
    error CycleNotYetEnded();
    error CycleAlreadyFinalized();
    error InvalidCycleId();
    error OnlyCurrentCycleAllowed();
    error CycleCalculationOverflow();
    error RunawayCycleCalculation();
    error CycleNotFinalized();

    event OfferingMade(
        uint256 indexed trecenaCycleId,
        address indexed warriorAddress,
        Pool indexed pool,
        uint256 grossAmount,
        uint256 netAmount,
        uint256 blockNumber
    );
    event WarriorArmed(uint256 indexed trecenaCycleId, address indexed warrior);
    event WarriorDisarmed(uint256 indexed trecenaCycleId, address indexed warrior);
    event WarriorRearmed(uint256 indexed trecenaCycleId, address indexed warrior);
    event WarriorRewarded(uint256 indexed trecenaCycleId, address indexed warrior, uint256 amount);
    event CycleFinalized(uint256 indexed trecenaCycleId, Pool winningPool);

    modifier blockNotExpired(uint256 expiryBlock) {
        if (block.number > expiryBlock) {
            revert ExpiryBlockPassed();
        }
        _;
    }

    modifier onlyCurrentCycle(uint256 trecenaCycleId) {
        if (trecenaCycleId != getCurrentCycleId()) {
            revert OnlyCurrentCycleAllowed();
        }
        _;
    }

    constructor(
        IERC20 _usdcToken,
        address payable _TemploTrecena,
        address _TemploMayor
    ) EIP712("Trecena", "1") {
        uint8 actual = IERC20Metadata(address(_usdcToken)).decimals();
        if (actual != USDC_DECIMALS) {
            revert InvalidTokenDecimals(USDC_DECIMALS, actual);
        }
        if (_TemploTrecena == address(0)) {
            revert InvalidTemploTrecenaAddress();
        }
        if (_TemploMayor == address(0)) {
            revert InvalidTemploMayorAddress();
        }

        usdcToken = _usdcToken;
        TemploTrecena = _TemploTrecena;
        TemploMayor = _TemploMayor;
        Nahui_Ollin = block.number; 
        
        lastSampleTimestamp = block.timestamp;
        lastSampleBlock = block.number;
        observedBlockTimeAverage = AVG_BLOCK_TIME_SECONDS;
        
        cycleLengths[0] = BLOCKS_PER_CYCLE;
        cycleStartBlockCheckpoints[0] = Nahui_Ollin; 
    }
    

    function _maybeUpdateBlockTimeEstimate() internal {
        if (block.number % 13 == 0) {
            if (block.number - lastSampleBlock >= BLOCKS_BETWEEN_SAMPLES) {
                _updateBlockTimeEstimate();
            }
        }
    }
    
    function _updateBlockTimeEstimate() internal {
        uint256 blocksDelta = block.number - lastSampleBlock;
        uint256 timeDelta = block.timestamp - lastSampleTimestamp;
        
        bool updatedAverage = false;
        if (blocksDelta > 0 && timeDelta / blocksDelta >= 1 && timeDelta / blocksDelta <= 60) {
            uint256 newAverage = timeDelta / blocksDelta;
            uint256 oldAverage = observedBlockTimeAverage;
            
            observedBlockTimeAverage = (oldAverage * 80 + newAverage * 20) / 100;
            
            uint256 nextCycleId = getCurrentCycleId() + 1; 
            uint256 nextCycleLength = TARGET_DURATION_SECONDS / observedBlockTimeAverage;
            cycleLengths[nextCycleId] = nextCycleLength;
            
            if (nextCycleId % CHECKPOINT_INTERVAL == 0) {
                uint256 prevCheckpointId = nextCycleId - CHECKPOINT_INTERVAL;
                uint256 prevCheckpointStartBlock = cycleStartBlockCheckpoints[prevCheckpointId];
                
                if (prevCheckpointStartBlock == 0 && prevCheckpointId != 0) {
                    prevCheckpointStartBlock = _calculateStartBlockFromScratch(prevCheckpointId);
                }
                
                uint256 currentCheckpointStartBlock = prevCheckpointStartBlock;
                for (uint256 i = prevCheckpointId; i < nextCycleId; i++) {
                    uint256 len = cycleLengths[i]; 
                    if (len == 0) len = BLOCKS_PER_CYCLE;
                    currentCheckpointStartBlock += len;
                }
                cycleStartBlockCheckpoints[nextCycleId] = currentCheckpointStartBlock;
            }
            
            updatedAverage = true;
        }
        
        lastSampleTimestamp = block.timestamp;
        lastSampleBlock = block.number;
    }

    function _getCycleLength(uint256 cycleId) internal view returns (uint256 length) {
        length = cycleLengths[cycleId];
        if (length == 0) {

            uint256 avg = observedBlockTimeAverage;
            if (avg == 0) { 
                avg = AVG_BLOCK_TIME_SECONDS;
            }
            length = TARGET_DURATION_SECONDS / avg;
            if (length == 0) { 
                 length = BLOCKS_PER_CYCLE; 
            }
        }
    }

    function _calculateStartBlockUsingCheckpoints(uint256 trecenaCycleId) internal view returns (uint256 startBlock) {
        if (trecenaCycleId == 0) return Nahui_Ollin;

        uint256 lastCheckpointId = (trecenaCycleId / CHECKPOINT_INTERVAL) * CHECKPOINT_INTERVAL;
        startBlock = cycleStartBlockCheckpoints[lastCheckpointId];

        if (startBlock == 0 && lastCheckpointId != 0) {
             startBlock = _calculateStartBlockFromScratch(lastCheckpointId);
        }
         if (startBlock == 0 && lastCheckpointId == 0) { 
            startBlock = Nahui_Ollin;
        }

        for (uint256 i = lastCheckpointId; i < trecenaCycleId; i++) {
            startBlock += _getCycleLength(i); 
        }
    }

    function _calculateStartBlockFromScratch(uint256 trecenaCycleId) internal view returns (uint256 startBlock) {
        startBlock = Nahui_Ollin;
        for (uint256 i = 0; i < trecenaCycleId; i++) {
            startBlock += _getCycleLength(i);
        }
    }

    function getCurrentCycleId() public view returns (uint256 cycleId) {
        if (block.number < Nahui_Ollin) return 0; 
        
        uint256 currentBlock = block.number;
        
        uint256 avgLen = observedBlockTimeAverage > 0 ? TARGET_DURATION_SECONDS / observedBlockTimeAverage : BLOCKS_PER_CYCLE;
        uint256 estimatedCycle = (currentBlock - Nahui_Ollin) / avgLen;
        uint256 startSearchCheckpointId = (estimatedCycle / CHECKPOINT_INTERVAL) * CHECKPOINT_INTERVAL;
        
        uint256 blockAccum = cycleStartBlockCheckpoints[startSearchCheckpointId];
        cycleId = startSearchCheckpointId;

        if (blockAccum == 0 || blockAccum > currentBlock) { 
            blockAccum = Nahui_Ollin;
            cycleId = 0;
        }
        
        while (true) {
            uint256 cycleLength = _getCycleLength(cycleId);
            
            uint256 nextCycleStartBlock = blockAccum + cycleLength;
            if (nextCycleStartBlock < blockAccum) { 
                revert CycleCalculationOverflow();
            }

            if (currentBlock < nextCycleStartBlock) {
                return cycleId;
            }
            
            blockAccum = nextCycleStartBlock;
            cycleId++;

            if (cycleId > estimatedCycle + CHECKPOINT_INTERVAL * 2) { 
                 revert RunawayCycleCalculation();
            }
        }
    }

    function getTrecenaCycleBlockRange(uint256 trecenaCycleId) public view returns (uint256 startBlock, uint256 endBlock) {
        startBlock = _calculateStartBlockUsingCheckpoints(trecenaCycleId);
        uint256 length = _getCycleLength(trecenaCycleId);
        endBlock = startBlock + length;
        if (endBlock < startBlock) revert CycleCalculationOverflow();
    }

    function getCycleIdForBlock(uint256 blockNumber) public view returns (uint256 cycleId) {
        if (blockNumber < Nahui_Ollin) revert InvalidCycleId();
        
        uint256 avgLen = observedBlockTimeAverage > 0 ? TARGET_DURATION_SECONDS / observedBlockTimeAverage : BLOCKS_PER_CYCLE;
        uint256 estimatedCycle = (blockNumber - Nahui_Ollin) / avgLen;
        uint256 startSearchCheckpointId = (estimatedCycle / CHECKPOINT_INTERVAL) * CHECKPOINT_INTERVAL;
        
        uint256 blockAccum = cycleStartBlockCheckpoints[startSearchCheckpointId];
        cycleId = startSearchCheckpointId;

        if (blockAccum == 0 || blockAccum > blockNumber) { 
            blockAccum = Nahui_Ollin;
            cycleId = 0;
        }
        
        while (true) {
            uint256 cycleLength = _getCycleLength(cycleId);
            
            uint256 nextCycleStartBlock = blockAccum + cycleLength;
            if (nextCycleStartBlock < blockAccum) revert CycleCalculationOverflow();

            if (blockNumber < nextCycleStartBlock) {
                return cycleId;
            }
            
            blockAccum = nextCycleStartBlock;
            cycleId++;

            if (cycleId > estimatedCycle + CHECKPOINT_INTERVAL * 2) { 
                 revert RunawayCycleCalculation();
            }
        }
    }

    function isCycleActive(uint256 cycleId) public view returns (bool) {
        return cycleId == getCurrentCycleId();
    }

    function getTrecenaCycleStatus(uint256 trecenaCycleId) public view returns (uint8 status) {
        uint256 currentCycleId = getCurrentCycleId();
        
        if (trecenaCycleId > currentCycleId) {
            return 0;
        }
        else if (trecenaCycleId == currentCycleId) {
            return 1;
        }
        else {
            return trecenaCycles[trecenaCycleId].finalized ? 3 : 2;
        }
    }

    function ReadyObsidianKnife(
        uint256 trecenaCycleId,
        address kycApprovedAddress,
        uint256 expiryBlock,
        bytes memory signature
    ) external blockNotExpired(expiryBlock) onlyCurrentCycle(trecenaCycleId) {
        _maybeUpdateBlockTimeEstimate();
        
        bytes32 structHash = keccak256(abi.encode(
            READY_KNIFE_TYPEHASH,
            msg.sender,
            kycApprovedAddress,
            trecenaCycleId,
            expiryBlock
        ));
        bytes32 digest = _hashTypedDataV4(structHash);

        if (usedSignatures[digest]) {
            revert SignatureUsed();
        }

        address signer = ECDSA.recover(digest, signature);
        if (signer != TemploMayor) {
            revert InvalidSignature();
        }

        usedSignatures[digest] = true;
        for (uint256 i = 0; i < APPROVAL_CYCLE_COUNT; i++) {
            trecenaCycles[trecenaCycleId + i].isApproved[kycApprovedAddress] = true;
        }
        emit WarriorArmed(trecenaCycleId, kycApprovedAddress);
    }

    function DisarmObsidianKnife(
        uint256 trecenaCycleId,
        address warrior,
        uint256 expiryBlock,
        bytes memory signature
    ) external blockNotExpired(expiryBlock) onlyCurrentCycle(trecenaCycleId) {
        _maybeUpdateBlockTimeEstimate();
        
        bytes32 structHash = keccak256(abi.encode(
            DISARM_KNIFE_TYPEHASH,
            msg.sender,
            warrior,
            trecenaCycleId,
            expiryBlock
        ));
        bytes32 digest = _hashTypedDataV4(structHash);

        if (usedSignatures[digest]) {
            revert SignatureUsed();
        }

        address signer = ECDSA.recover(digest, signature);
        if (signer != TemploMayor) {
            revert InvalidSignature();
        }

        usedSignatures[digest] = true;
        for (uint256 i = 0; i < APPROVAL_CYCLE_COUNT; i++) {
            isRevoked[trecenaCycleId + i][warrior] = true;
            trecenaCycles[trecenaCycleId + i].isApproved[warrior] = false;
        }
        emit WarriorDisarmed(trecenaCycleId, warrior);
    }

    function RearmObsidianKnife(
        uint256 trecenaCycleId,
        address warrior,
        uint256 expiryBlock,
        bytes memory signature
    ) external blockNotExpired(expiryBlock) onlyCurrentCycle(trecenaCycleId) {
        _maybeUpdateBlockTimeEstimate();
        
        bytes32 structHash = keccak256(abi.encode(
            REARM_KNIFE_TYPEHASH,
            msg.sender,
            warrior,
            trecenaCycleId,
            expiryBlock
        ));
        bytes32 digest = _hashTypedDataV4(structHash);

        if (usedSignatures[digest]) {
            revert SignatureUsed();
        }

        address signer = ECDSA.recover(digest, signature);
        if (signer != TemploMayor) {
            revert InvalidSignature();
        }

        usedSignatures[digest] = true;
        for (uint256 i = 0; i < APPROVAL_CYCLE_COUNT; i++) {
            isRevoked[trecenaCycleId + i][warrior] = false;
            trecenaCycles[trecenaCycleId + i].isApproved[warrior] = true;
        }
        emit WarriorRearmed(trecenaCycleId, warrior);
    }

    function SunOffering(uint256 _amount) external nonReentrant {
        if (msg.sender != tx.origin) revert OnlyEOA();
        
        _maybeUpdateBlockTimeEstimate();
        
        uint256 currentCycleId = getCurrentCycleId();
        _executeOffering(currentCycleId, Pool.Sun, _amount, msg.sender);
    }

    function MoonOffering(uint256 _amount) external nonReentrant {
        if (msg.sender != tx.origin) revert OnlyEOA();
        
        _maybeUpdateBlockTimeEstimate();
        
        uint256 currentCycleId = getCurrentCycleId();
        _executeOffering(currentCycleId, Pool.Moon, _amount, msg.sender);
    }

    function _executeOffering(
        uint256 trecenaCycleId,
        Pool _pool,
        uint256 _amount,
        address warriorAddress
    ) internal {
        if (!isCycleActive(trecenaCycleId)) revert TrecenaCycleNotActive();

        if (isRevoked[trecenaCycleId][warriorAddress]) revert WarriorRevoked();
        if (!trecenaCycles[trecenaCycleId].isApproved[warriorAddress]) revert WarriorNotApprovedForTrecenaCycle();
        if (_amount < MIN_OFFERING_USDC) revert InsufficientOffering();

        uint256 fee = (_amount * PLATFORM_FEE_BASIS_POINTS) / 10000;
        uint256 netOffering = _amount - fee;

        _updateStateAndTransfer(
            trecenaCycles[trecenaCycleId],
            trecenaCycleId,
            warriorAddress,
            _pool,
            _amount,
            netOffering,
            fee
        );
    }

    function _updateStateAndTransfer(
        TrecenaCycleData storage trecenaCycle,
        uint256 trecenaCycleId,
        address warriorAddress,
        Pool pool,
        uint256 grossAmount,
        uint256 netOffering,
        uint256 fee
    ) internal {
        WarriorOfferings storage warriorOfferings = trecenaCycle.individualWarriorOfferings[warriorAddress];

        trecenaCycle.poolTotals[pool] += netOffering;
        warriorOfferings.offerings[pool] += netOffering;
        warriorOfferings.totalOfferings += netOffering;

        totalHistoricalVolume += netOffering;

        usdcToken.safeTransferFrom(warriorAddress, address(this), grossAmount);
        if (fee > 0) {
            usdcToken.safeTransfer(TemploTrecena, fee);
        }

        emit OfferingMade(trecenaCycleId, warriorAddress, pool, grossAmount, netOffering, block.number);
    }

    function ReceiveRewards(
        WithdrawalData calldata withdrawalData
    ) external nonReentrant {
        _maybeUpdateBlockTimeEstimate();
        
        uint256 trecenaCycleId = withdrawalData.trecenaCycleId;
        uint256 currentCycleId = getCurrentCycleId();

        if (trecenaCycleId >= currentCycleId) {
            revert CycleNotYetEnded();
        }

        TrecenaCycleData storage trecenaCycle = trecenaCycles[trecenaCycleId];

        address warriorAddress = msg.sender;
        WarriorOfferings storage warriorOfferings = trecenaCycle.individualWarriorOfferings[warriorAddress];
        if (warriorOfferings.hasWithdrawn) {
            revert AlreadyRewarded();
        }

        uint256 warriorSunOffering = warriorOfferings.offerings[Pool.Sun];
        uint256 warriorMoonOffering = warriorOfferings.offerings[Pool.Moon];
        if (warriorSunOffering == 0 && warriorMoonOffering == 0) {
            revert NoOfferings();
        }

        if (!trecenaCycle.finalized) {
            _finalizeTrecenaCycle(trecenaCycleId);
        }
        if (!trecenaCycle.finalized) revert CycleNotFinalized();

        uint256 reward = _calculateReward(trecenaCycle, warriorSunOffering, warriorMoonOffering);
        warriorOfferings.hasWithdrawn = true;

        warriorTotalClaimedWinnings[warriorAddress] += reward;
        
        if (reward > 0) {
            usdcToken.safeTransfer(warriorAddress, reward);
            emit WarriorRewarded(trecenaCycleId, warriorAddress, reward);
        }
    }

    function _finalizeTrecenaCycle(uint256 cycleId) internal {
         TrecenaCycleData storage cycle = trecenaCycles[cycleId];
         if (cycle.finalized) {
              return;
         }
         uint256 currentCycleId = getCurrentCycleId();
         if (cycleId >= currentCycleId) {
              revert CycleNotYetEnded();
         }

         uint256 sunPoolTotal = cycle.poolTotals[Pool.Sun];
         uint256 moonPoolTotal = cycle.poolTotals[Pool.Moon];

         cycle.winningPool = _determineWinningPool(sunPoolTotal, moonPoolTotal);
         cycle.finalized = true;
         emit CycleFinalized(cycleId, cycle.winningPool);
    }

    function _calculateReward(
        TrecenaCycleData storage trecenaCycle,
        uint256 warriorSunOffering,
        uint256 warriorMoonOffering
    ) internal view returns (uint256) {
         Pool winningPool = trecenaCycle.winningPool;
        if (winningPool == Pool.None) {
            return warriorSunOffering + warriorMoonOffering;
        }

        uint256 sunTotal = trecenaCycle.poolTotals[Pool.Sun];
        uint256 moonTotal = trecenaCycle.poolTotals[Pool.Moon];

        if (winningPool == Pool.Sun) {
            if (warriorSunOffering == 0) return 0;
            if (sunTotal == 0) return warriorSunOffering;
            uint256 sunBonus = (moonTotal * warriorSunOffering) / sunTotal;
            return warriorSunOffering + sunBonus;
        }

        if (warriorMoonOffering == 0) return 0;
        if (moonTotal == 0) return warriorMoonOffering;
        uint256 moonBonus = (sunTotal * warriorMoonOffering) / moonTotal;
        return warriorMoonOffering + moonBonus;
    }

    function getObservedBlockTimeAverage() external view returns (uint256) {
        return observedBlockTimeAverage;
    }
    
    function getCycleLength(uint256 cycleId) public view returns (uint256) {
        return _getCycleLength(cycleId);
    }
    
    function getEstimatedCycleDurationSeconds(uint256 cycleId) external view returns (uint256) {
        uint256 blocks = _getCycleLength(cycleId);
        uint256 avg = observedBlockTimeAverage;
        if (avg == 0) avg = AVG_BLOCK_TIME_SECONDS;
        return blocks * avg;
    }

    function getPoolTotal(uint256 trecenaCycleId, Pool pool) external view returns (uint256) {
        return trecenaCycles[trecenaCycleId].poolTotals[pool];
    }

    function getWarriorOffering(uint256 trecenaCycleId, address warrior, Pool pool) external view returns (uint256) {
        return trecenaCycles[trecenaCycleId].individualWarriorOfferings[warrior].offerings[pool];
    }

    function getWarriorTotalOffering(uint256 trecenaCycleId, address warrior) external view returns (uint256) {
        return trecenaCycles[trecenaCycleId].individualWarriorOfferings[warrior].totalOfferings;
    }

    function hasWithdrawn(uint256 trecenaCycleId, address warrior) external view returns (bool) {
        return trecenaCycles[trecenaCycleId].individualWarriorOfferings[warrior].hasWithdrawn;
    }

    function isTrecenaCycleOpenForOfferings(uint256 trecenaCycleId) external view returns (bool) {
        return isCycleActive(trecenaCycleId);
    }

    function isTrecenaCycleFinalized(uint256 trecenaCycleId) external view returns (bool) {
        return trecenaCycles[trecenaCycleId].finalized;
    }

    function getWinningPool(uint256 trecenaCycleId) public view returns (Pool) {
        uint256 currentCycleId = getCurrentCycleId();
        TrecenaCycleData storage trecenaCycle = trecenaCycles[trecenaCycleId];

        if (trecenaCycle.finalized) {
            return trecenaCycle.winningPool;
        }

        if (trecenaCycleId >= currentCycleId) {
            return Pool.None;
        }

        uint256 sunPoolTotal = trecenaCycle.poolTotals[Pool.Sun];
        uint256 moonPoolTotal = trecenaCycle.poolTotals[Pool.Moon];
        return _determineWinningPool(sunPoolTotal, moonPoolTotal);
    }

    function isApprovedForTrecenaCycle(uint256 trecenaCycleId, address warrior) external view returns (bool) {
        return trecenaCycles[trecenaCycleId].isApproved[warrior];
    }

    function calculatePotentialReward(
        uint256 trecenaCycleId,
        address warrior
    ) external view returns (uint256) {
        TrecenaCycleData storage trecenaCycle = trecenaCycles[trecenaCycleId];
        WarriorOfferings storage warriorOfferings = trecenaCycle.individualWarriorOfferings[warrior];
        
        if (warriorOfferings.hasWithdrawn || warriorOfferings.totalOfferings == 0) {
            return 0;
        }
        
        uint256 warriorSunOffering = warriorOfferings.offerings[Pool.Sun];
        uint256 warriorMoonOffering = warriorOfferings.offerings[Pool.Moon];

        Pool winner = getWinningPool(trecenaCycleId); 

        if (winner == Pool.None) {
            return warriorSunOffering + warriorMoonOffering;
        }

        uint256 sunTotal = trecenaCycle.poolTotals[Pool.Sun];
        uint256 moonTotal = trecenaCycle.poolTotals[Pool.Moon];

        if (winner == Pool.Sun) {
            if (warriorSunOffering == 0) return 0;
            if (sunTotal == 0) return warriorSunOffering;
            uint256 sunBonus = (moonTotal * warriorSunOffering) / sunTotal;
            return warriorSunOffering + sunBonus;
        }
        if (warriorMoonOffering == 0) return 0;
        if (moonTotal == 0) return warriorMoonOffering;
        uint256 moonBonus = (sunTotal * warriorMoonOffering) / moonTotal;
        return warriorMoonOffering + moonBonus;
    }

    function getTrecenaCycleTotalOfferings(uint256 trecenaCycleId) external view returns (uint256) {
        TrecenaCycleData storage trecenaCycle = trecenaCycles[trecenaCycleId];
        return trecenaCycle.poolTotals[Pool.Sun] + trecenaCycle.poolTotals[Pool.Moon];
    }

    function getPoolRatio(uint256 trecenaCycleId) external view returns (uint256 sunRatio, uint256 moonRatio) {
        TrecenaCycleData storage trecenaCycle = trecenaCycles[trecenaCycleId];
        uint256 sunTotal = trecenaCycle.poolTotals[Pool.Sun];
        uint256 moonTotal = trecenaCycle.poolTotals[Pool.Moon];
        uint256 total = sunTotal + moonTotal;
        
        if (total == 0) {
            return (0, 0);
        }
        
        sunRatio = (sunTotal * 10000) / total;
        moonRatio = 10000 - sunRatio;
        return (sunRatio, moonRatio);
    }

    function getWarriorTrecenaCycleHistory(address warrior, uint256 trecenaCycleId) external view returns (
        uint256 sunOffering,
        uint256 moonOffering,
        uint256 totalOffering,
        bool hasWarriorWithdrawn,
        uint256 reward
    ) {
        TrecenaCycleData storage trecenaCycle = trecenaCycles[trecenaCycleId];
        WarriorOfferings storage warriorOfferings = trecenaCycle.individualWarriorOfferings[warrior];

        sunOffering = warriorOfferings.offerings[Pool.Sun];
        moonOffering = warriorOfferings.offerings[Pool.Moon];
        totalOffering = warriorOfferings.totalOfferings;
        hasWarriorWithdrawn = warriorOfferings.hasWithdrawn;

        uint256 currentCycleId = getCurrentCycleId();
        if (trecenaCycleId < currentCycleId && totalOffering > 0) {
            Pool winner = getWinningPool(trecenaCycleId);
            if (winner == Pool.None) {
                reward = sunOffering + moonOffering;
            } else if (winner == Pool.Sun) {
                if (sunOffering == 0) reward = 0;
                else {
                    uint256 sunTotal = trecenaCycle.poolTotals[Pool.Sun];
                    if (sunTotal == 0) reward = sunOffering;
                    else reward = sunOffering + (trecenaCycle.poolTotals[Pool.Moon] * sunOffering) / sunTotal;
                }
            } else { 
                if (moonOffering == 0) reward = 0;
                else {
                    uint256 moonTotal = trecenaCycle.poolTotals[Pool.Moon];
                    if (moonTotal == 0) reward = moonOffering;
                    else reward = moonOffering + (trecenaCycle.poolTotals[Pool.Sun] * moonOffering) / moonTotal;
                }
            }
        } else {
            reward = 0;
        }
        
        return (sunOffering, moonOffering, totalOffering, hasWarriorWithdrawn, reward);
    }

    function getWarriorTotalWinnings(address warrior) external view returns (uint256) {
        return warriorTotalClaimedWinnings[warrior];
    }

    function isEligibleForRefund(uint256 trecenaCycleId, address warrior) external view returns (bool) {
        uint256 currentCycleId = getCurrentCycleId();
        if (trecenaCycleId >= currentCycleId) {
            return false;
        }
        
        TrecenaCycleData storage trecenaCycle = trecenaCycles[trecenaCycleId];
        WarriorOfferings storage warriorOfferings = trecenaCycle.individualWarriorOfferings[warrior];

        if (warriorOfferings.hasWithdrawn || warriorOfferings.totalOfferings == 0) {
            return false; 
        }
        
        Pool winner = getWinningPool(trecenaCycleId);
        return winner == Pool.None;
    }
    
    function getTotalHistoricalVolume() external view returns (uint256) {
        return totalHistoricalVolume;
    }
    
    function getMostPopularPool(uint256 trecenaCycleId) external view returns (Pool) {
        return getWinningPool(trecenaCycleId);
    }

    function isWarriorRevoked(uint256 trecenaCycleId, address warrior) external view returns (bool) {
        return isRevoked[trecenaCycleId][warrior];
    }

    function hasWarriorMadeOfferingToPool(uint256 trecenaCycleId, address warrior, Pool pool) external view returns (bool) {
        return trecenaCycles[trecenaCycleId].individualWarriorOfferings[warrior].offerings[pool] > 0;
    }

    function getCurrentTrecenaCycleDetails() external view returns (
        uint256 cycleId,
        uint256 startBlock,
        uint256 endBlock,
        uint8 status,
        uint256 sunTotal,
        uint256 moonTotal,
        Pool projectedWinner
    ) {
        cycleId = getCurrentCycleId();
        (startBlock, endBlock) = getTrecenaCycleBlockRange(cycleId);
        status = getTrecenaCycleStatus(cycleId);
        
        TrecenaCycleData storage trecenaCycle = trecenaCycles[cycleId];
        sunTotal = trecenaCycle.poolTotals[Pool.Sun];
        moonTotal = trecenaCycle.poolTotals[Pool.Moon];
        projectedWinner = getWinningPool(cycleId);
        
        return (cycleId, startBlock, endBlock, status, sunTotal, moonTotal, projectedWinner);
    }

    function _determineWinningPool(uint256 sunTotal, uint256 moonTotal) internal pure returns (Pool) {
        if (sunTotal == moonTotal) {
            return Pool.None; 
        } else if (sunTotal > moonTotal) {
            return Pool.Sun;
        } else {
            return Pool.Moon;
        }
    }
}
"
    },
    "@openzeppelin/contracts/utils/cryptography/EIP712.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/cryptography/EIP712.sol)

pragma solidity ^0.8.20;

import {MessageHashUtils} from "./MessageHashUtils.sol";
import {ShortStrings, ShortString} from "../ShortStrings.sol";
import {IERC5267} from "../../interfaces/IERC5267.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
 * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
 * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
 * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
 * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
 * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
 *
 * @custom:oz-upgrades-unsafe-allow state-variable-immutable
 */
abstract contract EIP712 is IERC5267 {
    using ShortStrings for *;

    bytes32 private constant TYPE_HASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");

    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
    // invalidate the cached domain separator if the chain id changes.
    bytes32 private immutable _cachedDomainSeparator;
    uint256 private immutable _cachedChainId;
    address private immutable _cachedThis;

    bytes32 private immutable _hashedName;
    bytes32 private immutable _hashedVersion;

    ShortString private immutable _name;
    ShortString private immutable _version;
    // slither-disable-next-line constable-states
    string private _nameFallback;
    // slither-disable-next-line constable-states
    string private _versionFallback;

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        _name = name.toShortStringWithFallback(_nameFallback);
        _version = version.toShortStringWithFallback(_versionFallback);
        _hashedName = keccak256(bytes(name));
        _hashedVersion = keccak256(bytes(version));

        _cachedChainId = block.chainid;
        _cachedDomainSeparator = _buildDomainSeparator();
        _cachedThis = address(this);
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
            return _cachedDomainSeparator;
        } else {
            return _buildDomainSeparator();
        }
    }

    function _buildDomainSeparator() private view returns (bytes32) {
        return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
    }

    /// @inheritdoc IERC5267
    function eip712Domain()
        public
        view
        virtual
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        )
    {
        return (
            hex"0f", // 01111
            _EIP712Name(),
            _EIP712Version(),
            block.chainid,
            address(this),
            bytes32(0),
            new uint256[](0)
        );
    }

    /**
     * @dev The name parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _name which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Name() internal view returns (string memory) {
        return _name.toStringWithFallback(_nameFallback);
    }

    /**
     * @dev The version parameter for the EIP712 domain.
     *
     * NOTE: By default this function reads _version which is an immutable value.
     * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).
     */
    // solhint-disable-next-line func-name-mixedcase
    function _EIP712Version() internal view returns (string memory) {
        return _version.toStringWithFallback(_versionFallback);
    }
}
"
    },
    "@openzeppelin/contracts/utils/cryptography/ECDSA.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS
    }

    /**
     * @dev The signature derives the `address(0)`.
     */
    error ECDSAInvalidSignature();

    /**
     * @dev The signature has an invalid length.
     */
    error ECDSAInvalidSignatureLength(uint256 length);

    /**
     * @dev The signature has an S value that is in the upper half order.
     */
    error ECDSAInvalidSignatureS(bytes32 s);

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
     * return address(0) without also returning an error description. Errors are documented using an enum (error type)
     * and a bytes32 providing additional information about the error.
     *
     * If no error is returned, then the address can be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     */
    function tryRecover(
        bytes32 hash,
        bytes memory signature
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            assembly ("memory-safe") {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        unchecked {
            bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            // We do not check for an overflow here since the shift operation results in 0 or 1.
            uint8 v = uint8((uint256(vs) >> 255) + 27);
            return tryRecover(hash, v, r, s);
        }
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS, s);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature, bytes32(0));
        }

        return (signer, RecoverError.NoError, bytes32(0));
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
        _throwError(error, errorArg);
        return recovered;
    }

    /**
     * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
     */
    function _throwError(RecoverError error, bytes32 errorArg) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert ECDSAInvalidSignature();
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert ECDSAInvalidSignatureLength(uint256(errorArg));
        } else if (error == RecoverError.InvalidSignatureS) {
            revert ECDSAInvalidSignatureS(errorArg);
        }
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity >=0.6.2;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
"
    },
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
    },
    "@openzeppelin/contracts/security/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}
"
    },
    "@openzeppelin/contracts/interfaces/IERC5267.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC5267.sol)

pragma solidity >=0.4.16;

interface IERC5267 {
    /**
     * @dev MAY be emitted to signal that the domain could have changed.
     */
    event EIP712DomainChanged();

    /**
     * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712
     * signature.
     */
    function eip712Domain()
        external
        view
        returns (
            bytes1 fields,
            string memory name,
            string memory version,
            uint256 chainId,
            address verifyingContract,
            bytes32 salt,
            uint256[] memory extensions
        );
}
"
    },
    "@openzeppelin/contracts/utils/ShortStrings.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/ShortStrings.sol)

pragma solidity ^0.8.20;

import {StorageSlot} from "./StorageSlot.sol";

// | string  | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA   |
// | length  | 0x                                                              BB |
type ShortString is bytes32;

/**
 * @dev This library provides functions to convert short memory strings
 * into a `ShortString` type that can be used as 

Tags:
ERC20, ERC165, Proxy, Upgradeable, Factory|addr:0x7bdd0e44f9e8ac6a620da5968c11d244d0a241bb|verified:true|block:23626440|tx:0x5967089f30491d6c7a58a84c77c6b0786fbfa9db4c86e9a57075b7d996269ce3|first_check:1761056521

Submitted on: 2025-10-21 16:22:03

Comments

Log in to comment.

No comments yet.