DACStaking

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/DACStaking.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

// Import OpenZeppelin Contracts for security management
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

// Import interfaces from the DAC ecosystem
import {DACToken} from "./DACToken.sol";
import {IDACAuthority} from "./IDACAuthority.sol";
import {DACAccessManaged} from "./DACAccessManaged.sol";
import {
    IDACStaking,
    DACStaking__TokenNotAllowed,
    DACStaking__NeedsMultiplierMoreThanOne,
    DACStaking__NeedsAmountMoreThanZero,
    DACStaking__InsufficientStakedAmount,
    DACStaking__TransferFailed,
    DACStaking__InvalidBias,
    DACStaking__BiasCooldownActive
} from "./IDACStaking.sol";

/**
 * @title DACStaking
 * @dev A staking contract for DAC ecosystem allowing users to stake, unstake, and earn rewards based on staking duration.
 */
contract DACStaking is ReentrancyGuard, DACAccessManaged, IDACStaking {
    // ================================================================
    // │                      State variables                         │
    // ================================================================

    /// @dev Multiplier scaling factor for precision
    uint256 private constant MULTIPLIER_PRECISION = 1e18;

    /// @dev Mapping from user address to their array of stakes
    mapping(address => Stake[]) private s_stakes;

    /// @dev Mapping to store each user’s political bias
    mapping(address => Bias) private s_userBias;

    /// @dev When a user last set their bias
    mapping(address => uint256) private s_lastBiasTimestamp;

    /// @dev Flag to enable/disable political bias cooldown
    bool public s_isCooldownEnabled;

    /// @dev The cooldown period in days before a user can set their bias again
    uint16 public s_cooldownPeriod;

    /// @dev The DAC token being staked
    DACToken public immutable i_dacToken;

    /// @dev Staking parameters
    bool public s_isEligibleForRewardScaling; // Flag to enable or disable reward scaling eligibility
    uint16 public s_minimumLockPeriod; // Minimum lock period in days before eligibility for rewards
    uint16 public s_warmupPeriod; // Warmup period in days for reward potential to reach 1x
    uint16 public s_hotPeriod; // Hot period in days for reward multiplier to reach maximum
    uint8 public s_maxRewardMultiplier; // Maximum reward multiplier (e.g., 10 for 10x)

    // ================================================================
    // │                        constructor                           │
    // ================================================================

    /**
     * @dev Constructor to initialize the staking contract with required parameters.
     * @param initialAuthority The address of the initial authority of the contract.
     * @param tokenAddress Address of the token to be staked.
     * @param isEligibleForRewardScaling Flag to set eligibility for reward scaling.
     * @param minimumLockPeriod Minimum lock period in days.
     * @param warmupPeriod Warmup period in days.
     * @param hotPeriod Hot period in days.
     * @param maxRewardMultiplier Maximum reward multiplier.
     */
    constructor(
        address initialAuthority,
        address tokenAddress,
        bool isEligibleForRewardScaling,
        uint16 minimumLockPeriod,
        uint16 warmupPeriod,
        uint16 hotPeriod,
        uint8 maxRewardMultiplier
    ) DACAccessManaged(IDACAuthority(initialAuthority)) {
        if (tokenAddress == address(0)) {
            revert DACStaking__TokenNotAllowed(tokenAddress);
        }
        i_dacToken = DACToken(tokenAddress);
        s_minimumLockPeriod = minimumLockPeriod;
        s_warmupPeriod = warmupPeriod;
        s_isEligibleForRewardScaling = isEligibleForRewardScaling;
        s_hotPeriod = hotPeriod;
        s_maxRewardMultiplier = maxRewardMultiplier;

        // By default, bias‐setting cooldown is disabled and set to 3 days
        s_isCooldownEnabled = false;
        s_cooldownPeriod = 3;
    }

    // ================================================================
    // │                         functions                            │
    // ================================================================

    /**
     * @inheritdoc IDACStaking
     */
    function setEligibilityForRewards(bool isEligible) external override onlyAdmin {
        s_isEligibleForRewardScaling = isEligible;
        emit EligibilityForRewardScalingUpdated(isEligible);
    }

    /**
     * @inheritdoc IDACStaking
     */
    function setMinimumLockPeriod(uint16 periodInDays) external override onlyAdmin {
        s_minimumLockPeriod = periodInDays;
        emit MinimumLockPeriodUpdated(periodInDays);
    }

    /**
     * @inheritdoc IDACStaking
     */
    function setWarmupPeriod(uint16 periodInDays) external override onlyAdmin {
        s_warmupPeriod = periodInDays;
        emit WarmupPeriodUpdated(periodInDays);
    }

    /**
     * @inheritdoc IDACStaking
     */
    function setHotPeriod(uint16 periodInDays) external override onlyAdmin {
        s_hotPeriod = periodInDays;
        emit HotPeriodUpdated(periodInDays);
    }

    /**
     * @inheritdoc IDACStaking
     */
    function setMaxRewardMultiplier(uint8 multiplier) external override onlyAdmin {
        if (multiplier <= 1) {
            revert DACStaking__NeedsMultiplierMoreThanOne();
        }
        s_maxRewardMultiplier = multiplier;
        emit MaxRewardMultiplierUpdated(multiplier);
    }

    /**
     * @notice Enables or disables the bias cooldown feature.
     * @param isEnabled New status for the bias cooldown feature.
     */
    function setCooldownEnabled(bool isEnabled) external onlyAdmin {
        s_isCooldownEnabled = isEnabled;
        emit CooldownEnabledUpdated(isEnabled);
    }

    /**
     * @notice Updates the cooldown period for setting bias.
     * @param periodInDays New cooldown period in days.
     */
    function setCooldownPeriod(uint16 periodInDays) external onlyAdmin {
        s_cooldownPeriod = periodInDays;
        emit CooldownPeriodUpdated(periodInDays);
    }

    /**
     * @inheritdoc IDACStaking
     */
    function stake(uint256 amount) external override nonReentrant {
        if (amount == 0) {
            revert DACStaking__NeedsAmountMoreThanZero();
        }

        // Create a new stake entry
        Stake memory newStake = Stake({amount: amount, timestamp: block.timestamp});

        // Add the stake to the user's array of stakes
        s_stakes[msg.sender].push(newStake);

        emit Staked(msg.sender, amount, block.timestamp);

        // Transfer tokens from the user to the contract
        bool success = i_dacToken.transferFrom(msg.sender, address(this), amount);
        if (!success) {
            revert DACStaking__TransferFailed(msg.sender, address(this), address(i_dacToken), amount);
        }
    }

    /**
     * @inheritdoc IDACStaking
     */
    function unstake(uint256 amount) external override nonReentrant {
        if (amount == 0) {
            revert DACStaking__NeedsAmountMoreThanZero();
        }

        uint256 totalStaked = getTotalStaked(msg.sender);

        if (totalStaked < amount) {
            revert DACStaking__InsufficientStakedAmount();
        }

        uint256 remaining = amount;
        uint256 len = s_stakes[msg.sender].length;

        // Iterate from the last stake (latest) to the first
        for (uint256 i = len; i > 0 && remaining > 0; i--) {
            Stake storage currentStake = s_stakes[msg.sender][i - 1];
            if (currentStake.amount <= remaining) {
                // If the current stake amount is less than or equal to the remaining amount to unstake
                uint256 currentStakeAmt = currentStake.amount;
                remaining -= currentStakeAmt;
                s_stakes[msg.sender].pop(); // Remove the stake
                emit Unstaked(msg.sender, currentStakeAmt, block.timestamp);
            } else {
                // If the current stake amount is greater than the remaining amount to unstake
                currentStake.amount -= remaining;
                emit Unstaked(msg.sender, remaining, block.timestamp);
                remaining = 0;
            }
        }

        // Transfer the unstaked tokens back to the user
        bool success = i_dacToken.transfer(msg.sender, amount);
        if (!success) {
            revert DACStaking__TransferFailed(address(this), msg.sender, address(i_dacToken), amount);
        }
    }

    /**
     * @inheritdoc IDACStaking
     */
    function setBias(Bias newBias) external override {
        //  Check if the bias is one of the enum values
        //  Neutral = 0 (default), Left = 1, Right = 2
        if ((uint8(newBias) > 2)) {
            revert DACStaking__InvalidBias();
        }

        // Prevent users with no stakes from setting a bias
        if (getTotalStaked(msg.sender) == 0) {
            revert DACStaking__InsufficientStakedAmount();
        }

        // If cooldown is enabled, ensure user waited long enough since last bias change
        uint256 lastSet = s_lastBiasTimestamp[msg.sender];
        if (s_isCooldownEnabled && lastSet != 0) {
            uint256 daysSince = (block.timestamp - lastSet) / 1 days;
            if (daysSince < s_cooldownPeriod) {
                revert DACStaking__BiasCooldownActive();
            }
        }

        Bias currentBias = s_userBias[msg.sender];
        s_userBias[msg.sender] = newBias;
        s_lastBiasTimestamp[msg.sender] = block.timestamp;

        emit BiasUpdated(msg.sender, currentBias, newBias);
    }

    /**
     * @inheritdoc IDACStaking
     */
    function getBias(address user) external view override returns (Bias) {
        return s_userBias[user];
    }

    /**
     * @inheritdoc IDACStaking
     */
    function getLastBiasTimestamp(address user) external view override returns (uint256) {
        return s_lastBiasTimestamp[user];
    }

    /**
     * @inheritdoc IDACStaking
     */
    function getTotalStakedWithMultipliers(address user) external view override returns (uint256) {
        uint256 total = 0;
        uint256 len = s_stakes[user].length;
        Stake[] storage userStakes = s_stakes[user];

        for (uint256 i = 0; i < len; i++) {
            Stake storage userStake = userStakes[i];
            uint256 multiplier = _getMultiplier(userStake.timestamp);
            total += (userStake.amount * multiplier) / MULTIPLIER_PRECISION;
        }

        return total;
    }

    /**
     * @inheritdoc IDACStaking
     */
    function getUserStakes(address user) external view override returns (Stake[] memory) {
        return s_stakes[user];
    }

    /**
     * @dev Retrieves the raw total amount staked by a user without considering reward multipliers.
     * @param user Address of the user.
     * @return totalStaked Raw total staked amount.
     */
    function getTotalStaked(address user) public view override returns (uint256) {
        uint256 total = 0;
        uint256 len = s_stakes[user].length;
        Stake[] storage userStakes = s_stakes[user];

        for (uint256 i = 0; i < len; i++) {
            total += userStakes[i].amount;
        }

        return total;
    }

    /**
     * @dev private function to calculate the current multiplier based on staking duration.
     * @param stakeTimestamp Timestamp when the stake was created.
     * @return multiplier Current reward multiplier scaled by MULTIPLIER_PRECISION for precision.
     */
    function _getMultiplier(uint256 stakeTimestamp) private view returns (uint256) {
        if (!s_isEligibleForRewardScaling) {
            return MULTIPLIER_PRECISION; // No multiplier if rewards are disabled
        }

        uint256 stakingDuration = (block.timestamp - stakeTimestamp) / 1 days;
        uint256 minimumLockPeriod = s_minimumLockPeriod;
        uint256 warmupPeriod = s_warmupPeriod;
        uint256 hotPeriod = s_hotPeriod;
        uint256 maxRewardMultiplier = s_maxRewardMultiplier;

        if (stakingDuration < minimumLockPeriod) {
            return 0; // Not eligible for rewards yet
        } else if (stakingDuration < minimumLockPeriod + warmupPeriod) {
            // Linear increase from 0 to 1x
            uint256 elapsed = stakingDuration - minimumLockPeriod;
            uint256 multiplier = (elapsed * MULTIPLIER_PRECISION) / warmupPeriod;
            return multiplier;
        } else if (stakingDuration < minimumLockPeriod + warmupPeriod + hotPeriod) {
            // Linear increase from 1x to maxMultiplier
            uint256 elapsed = stakingDuration - minimumLockPeriod - warmupPeriod;
            uint256 multiplierIncrease = (elapsed * (maxRewardMultiplier - 1) * MULTIPLIER_PRECISION) / hotPeriod;
            return MULTIPLIER_PRECISION + multiplierIncrease;
        } else {
            // Max multiplier reached
            return maxRewardMultiplier * MULTIPLIER_PRECISION;
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @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 EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * 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;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    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
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // 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;
    }
}
"
    },
    "src/DACToken.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

// Compatible with OpenZeppelin Contracts ^5.0.0
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

import {IDACAuthority} from "./IDACAuthority.sol";
import {DACAccessManaged} from "./DACAccessManaged.sol";
import {IDACToken} from "./IDACToken.sol";

//    ____    ______  ______   ____     __                                       ___                    
//   /\  _`\ /\  _  \/\__  _\ /\  _`\  /\ \                           __        /\_ \                   
//   \ \ \/\ \ \ \L\ \/_/\ \/ \ \ \/\_\\ \ \___   _ __   ___     ___ /\_\    ___\//\ \      __    ____  
//    \ \ \ \ \ \  __ \ \ \ \  \ \ \/_/_\ \  _ `\/\`'__\/ __`\ /' _ `\/\ \  /'___\\ \ \   /'__`\ /',__\ 
//     \ \ \_\ \ \ \/\ \ \_\ \__\ \ \L\ \\ \ \ \ \ \ \//\ \L\ \/\ \/\ \ \ \/\ \__/ \_\ \_/\  __//\__, `\
//      \ \____/\ \_\ \_\/\_____\\ \____/ \ \_\ \_\ \_\\ \____/\ \_\ \_\ \_\ \____\/\____\ \____\/\____/
//       \/___/  \/_/\/_/\/_____/ \/___/   \/_/\/_/\/_/ \/___/  \/_/\/_/\/_/\/____/\/____/\/____/\/___/  

/**
 * @title DACToken
 * @notice Central ERC-20 token of the Decentralized AI Chronicles Ecosystem (DAC Ecosystem).
 * @dev Implements ERC-20 with capped supply, minting, burning, and permit functionality.
 * The token is intended to be a utility token for the DAC Ecosystem,
 * enabling users to manage their NFTs and participate in the ecosystem's growth.
 */
contract DACToken is ERC20Capped, ERC20Burnable, ERC20Permit, DACAccessManaged, IDACToken {
    /**
     * @notice Deploys the DACToken contract.
     * @param initialAuthority The address of the initial authority of the contract.
     * @param maxSupply The maximum token supply, capped and cannot be exceeded.
     * @param initialSupply The initial supply of tokens to mint to the deployer.
     */
    constructor(address initialAuthority, uint256 maxSupply, uint256 initialSupply)
        ERC20("DAC Ecosystem Token", "DAC")
        ERC20Capped(maxSupply)
        ERC20Permit("DACToken")
        DACAccessManaged(IDACAuthority(initialAuthority))
    {
        _mint(msg.sender, initialSupply);
    }

    /**
     * @inheritdoc IDACToken
     */
    function mint(address to, uint256 amount) public override onlyTreasury {
        _mint(to, amount);
    }

    /**
     * @inheritdoc IDACToken
     */
    function burn(uint256 value) public override(IDACToken, ERC20Burnable) {
        super.burn(value);
    }

    /**
     * @inheritdoc ERC20Capped
     */
    function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Capped) {
        super._update(from, to, amount);
    }
}
"
    },
    "src/IDACAuthority.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

/**
 * @title IDACAuthority
 * @dev Interface for the DACAuthority contract, which defines events and functions
 * that other contracts in the DAC (Decentralized AI Chronicles) ecosystem can interact with.
 */
interface IDACAuthority {
    // ================================================================
    // │                           Events                             │
    // ================================================================

    /**
     * @dev Emitted when the admin account is updated.
     * @param previousAdmin The address of the previous admin.
     * @param newAdmin The address of the new admin.
     */
    event AdminSet(address indexed previousAdmin, address indexed newAdmin);

    /**
     * @dev Emitted when the chronicles agent account is updated.
     * @param previousChroniclesAgent The address of the previous chronicles agent.
     * @param newChroniclesAgent The address of the new chronicles agent.
     */
    event ChroniclesAgentSet(address indexed previousChroniclesAgent, address indexed newChroniclesAgent);

    /**
     * @dev Emitted when the liquidity manager agent account is updated.
     * @param previousLiquidityAgent The address of the previous liquidity manager agent.
     * @param liquidityAgent The address of the new liquidity manager agent.
     */
    event LiquidityAgentSet(address indexed previousLiquidityAgent, address indexed liquidityAgent);

    /**
     * @dev Emitted when the treasurer agent account is updated.
     * @param previousTreasurerAgent The address of the previous treasurer agent.
     * @param treasurerAgent The address of the new treasurer agent.
     */
    event TreasurerAgentSet(address indexed previousTreasurerAgent, address indexed treasurerAgent);

    /**
     * @dev Emitted when the treasury contract is updated.
     * @param previousTreasury The address of the previous treasury contract.
     * @param newTreasury The address of the new treasury contract.
     */
    event TreasurySet(address indexed previousTreasury, address indexed newTreasury);

    /**
     * @dev Emitted when a new swapper is proposed to be whitelisted.
     * @param swapper The address of the swapper proposed to be whitelisted.
     * @param activationTime The timestamp when the swapper becomes active.
     */
    event SwapperWhitelisted(address indexed swapper, uint256 activationTime);

    /**
     * @dev Emitted when a swapper is disabled.
     * @param swapper The address of the swapper that has been disabled.
     */
    event SwapperDisabled(address indexed swapper);

    // ================================================================
    // │                         Functions                            │
    // ================================================================

    /**
     * @notice Returns the address of the admin account.
     * @return The current admin address.
     */
    function admin() external view returns (address);

    /**
     * @notice Returns the address of the chronicles agent account.
     * @return The current chronicles agent address.
     */
    function chroniclesAgent() external view returns (address);

    /**
     * @notice Returns the address of the liquidity manager agent account.
     * @return The current liquidity manager agent address.
     */
    function liquidityManagerAgent() external view returns (address);

    /**
     * @notice Returns the address of the treasurer agent account.
     * @return The current treasurer agent address.
     */
    function treasurerAgent() external view returns (address);

    /**
     * @notice Returns the address of the treasury contract.
     * @return The current treasury contract address.
     */
    function treasury() external view returns (address);

    /**
     * @notice Checks if an address is an active swapper.
     * @param account The address to check.
     * @return True if the address is an active swapper, false otherwise.
     */
    function isSwapper(address account) external view returns (bool);

    /**
     * @notice Returns a list of all whitelisted swappers.
     * @return An array of swapper addresses.
     */
    function getSwappers() external view returns (address[] memory);

    /**
     * @notice Returns the activation timestamp of a swapper.
     * @param account The swapper address.
     * @return The timestamp when the swapper becomes active.
     */
    function getSwapperActivationTime(address account) external view returns (uint256);
}
"
    },
    "src/DACAccessManaged.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

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

// ================================================================
// │                           ERRORS                             │
// ================================================================

/**
 * @dev Thrown when the authority is not a authority contract (e.g., `address(0)`).
 * @param authority The invalid authority contract.
 */
error DACAuthority__InvalidAuthority(address authority);
/**
 * @dev Thrown when the caller is not authorized to perform an admin operation.
 * @param account The address of the unauthorized caller.
 */
error DACAccessManaged__AdminUnauthorizedAccount(address account);
/**
 * @dev Thrown when the caller is not authorized to perform a chronicles agent operation.
 * @param account The address of the unauthorized caller.
 */
error DACAccessManaged__ChroniclesAgentUnauthorizedAccount(address account);
/**
 * @dev Thrown when the caller is not authorized to perform a liquidity manager agent operation.
 * @param account The address of the unauthorized caller.
 */
error DACAccessManaged__LiquidityAgentUnauthorizedAccount(address account);
/**
 * @dev Thrown when the caller is not authorized to perform a treasurer agent operation.
 * @param account The address of the unauthorized caller.
 */
error DACAccessManaged__TreasurerAgentUnauthorizedAccount(address account);
/**
 * @dev Thrown when the caller is not authorized to perform a treasury operation.
 * @param treasury The address of the unauthorized caller.
 */
error DACAccessManaged__TreasuryUnauthorizedAccount(address treasury);
/**
 * @dev Thrown when the provided address is not authorized to perform a swapper operation.
 * @param account The address of the unauthorized swapper.
 */
error DACAccessManaged__SwapperUnauthorizedAccount(address account);
/**
 * @dev Thrown when a new authority is not yet proposed.
 */
error DACAuthority__NewAuthorityNotProposed();
/**
 * @dev Thrown when trying to activate an authority before the timelock has passed.
 * @param authority The authority address.
 * @param activationTime The required activation time.
 */
error DACAuthority__AuthorityTimelockNotPassed(address authority, uint256 activationTime);

/**
 * @title DACAccessManaged
 * @dev Abstract contract that provides access control mechanisms for contracts
 * in the DAC (Decentralized AI Chronicles) ecosystem. This contract acts as a
 * base for managing access permissions for different roles such as admin, chronicles agent,
 * liquidity manager agent, treasurer agent, treasury, and swapper.
 */
abstract contract DACAccessManaged {
    // ================================================================
    // │                           CONSTANTS                          │
    // ================================================================

    // Time period for which a new authority is timelocked
    uint256 public constant AUTHORITY_TIMELOCK = 7 days;

    // ================================================================
    // │                      State variables                         │
    // ================================================================

    /**
     * @dev The DACAuthority contract that manages roles and permissions for the DAC ecosystem.
     */
    IDACAuthority private s_authority;

    // Proposed authority change
    IDACAuthority private s_proposedAuthority;
    uint256 private s_authorityChangeExecutionTime;

    // ================================================================
    // │                           Events                             │
    // ================================================================

    /**
     * @dev Emitted when a proposed authority change is executed.
     * @param previousAuthority The address of the previous DACAuthority contract.
     * @param newAuthority The address of the new DACAuthority contract.
     */
    event AuthorityChangeExecuted(address indexed previousAuthority, address indexed newAuthority);

    /**
     * @dev Emitted when a new authority change is proposed.
     * @param proposedAuthority The address of the proposed new authority.
     * @param executionTime The timestamp when the authority change can be executed.
     */
    event AuthorityChangeProposed(address indexed proposedAuthority, uint256 executionTime);

    // ================================================================
    // │                         Modifiers                            │
    // ================================================================

    /**
     * @dev Modifier to restrict access to admin operations.
     * Reverts if the caller is not authorized.
     */
    modifier onlyAdmin() {
        if (s_authority.admin() != msg.sender) {
            revert DACAccessManaged__AdminUnauthorizedAccount(msg.sender);
        }
        _;
    }

    /**
     * @dev Modifier to restrict access to chronicles agent operations.
     * Reverts if the caller is not authorized.
     */
    modifier onlyChroniclesAgent() {
        if (s_authority.chroniclesAgent() != msg.sender) {
            revert DACAccessManaged__ChroniclesAgentUnauthorizedAccount(msg.sender);
        }
        _;
    }

    /**
     * @dev Modifier to restrict access to Liquidity Manager Agent operations.
     * Reverts if the caller is not authorized.
     */
    modifier onlyLiquidityAgent() {
        if (s_authority.liquidityManagerAgent() != msg.sender) {
            revert DACAccessManaged__LiquidityAgentUnauthorizedAccount(msg.sender);
        }
        _;
    }

    /**
     * @dev Modifier to restrict access to Treasurer Agent operations.
     * Reverts if the caller is not authorized.
     */
    modifier onlyTreasurerAgent() {
        if (s_authority.treasurerAgent() != msg.sender) {
            revert DACAccessManaged__TreasurerAgentUnauthorizedAccount(msg.sender);
        }
        _;
    }

    /**
     * @dev Modifier to restrict access to treasury operations.
     * Reverts if the caller is not authorized.
     */
    modifier onlyTreasury() {
        if (s_authority.treasury() != msg.sender) {
            revert DACAccessManaged__TreasuryUnauthorizedAccount(msg.sender);
        }
        _;
    }

    /**
     * @dev Modifier to restrict access to swapper operations.
     * @param swapper The address of the swapper to check.
     * Reverts if the caller is not an active swapper.
     */
    modifier onlySwapper(address swapper) {
        if (!s_authority.isSwapper(swapper)) {
            revert DACAccessManaged__SwapperUnauthorizedAccount(swapper);
        }
        _;
    }

    // ================================================================
    // │                        Constructor                           │
    // ================================================================

    /**
     * @notice Initializes the contract with the specified DACAuthority instance.
     * @param authority The address of the DACAuthority contract.
     */
    constructor(IDACAuthority authority) {
        if (address(authority) == address(0)) {
            revert DACAuthority__InvalidAuthority(address(0));
        }
        s_authority = authority;
        emit AuthorityChangeExecuted(address(0), address(authority));
    }

    // ================================================================
    // │                         Functions                            │
    // ================================================================

    /**
     * @notice Returns the address of the current DACAuthority contract.
     * @return The current DACAuthority contract.
     */
    function getAuthority() public view returns (IDACAuthority) {
        return s_authority;
    }

    /**
     * @notice Proposes a new DACAuthority contract with a 7-day timelock.
     * @dev Can only be called by the admin.
     * Emits an {AuthorityChangeProposed} event.
     * @param newAuthority The address of the new DACAuthority contract.
     */
    function proposeAuthorityChange(IDACAuthority newAuthority) external onlyAdmin {
        if (address(newAuthority) == address(0)) {
            revert DACAuthority__InvalidAuthority(address(0));
        }
        s_proposedAuthority = newAuthority;
        s_authorityChangeExecutionTime = block.timestamp + AUTHORITY_TIMELOCK;
        emit AuthorityChangeProposed(address(newAuthority), s_authorityChangeExecutionTime);
    }

    /**
     * @notice Executes the previously proposed authority change after the timelock.
     * @dev Can only be called by the admin.
     * Emits an {AuthorityChangeExecuted} event.
     */
    function executeAuthorityChange() external onlyAdmin {
        if (address(s_proposedAuthority) == address(0)) {
            revert DACAuthority__NewAuthorityNotProposed();
        }
        if (block.timestamp < s_authorityChangeExecutionTime) {
            revert DACAuthority__AuthorityTimelockNotPassed(
                address(s_proposedAuthority), s_authorityChangeExecutionTime
            );
        }
        address oldAuthority = address(s_authority);
        s_authority = s_proposedAuthority;
        s_proposedAuthority = IDACAuthority(address(0));
        s_authorityChangeExecutionTime = 0;
        emit AuthorityChangeExecuted(address(oldAuthority), address(s_authority));
    }

    /**
     * @notice Returns the address of the proposed new authority.
     * @return The proposed authority address.
     */
    function getProposedAuthority() external view returns (address) {
        return address(s_proposedAuthority);
    }

    /**
     * @notice Returns the timestamp when the authority change can be executed.
     * @return The execution timestamp.
     */
    function getAuthorityChangeExecutionTime() external view returns (uint256) {
        return s_authorityChangeExecutionTime;
    }
}
"
    },
    "src/IDACStaking.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

// ================================================================
// │                           ERRORS                             │
// ================================================================
/**
 * @dev Thrown when the token address is not allowed.
 * @param token The address of the token that is not allowed.
 */
error DACStaking__TokenNotAllowed(address token);
/**
 * @dev Thrown when the multiplier is less than or equal to 1.
 */
error DACStaking__NeedsMultiplierMoreThanOne();
/**
 * @dev Thrown when the amount is less than or equal to 0.
 */
error DACStaking__NeedsAmountMoreThanZero();
/**
 * @dev Thrown when the staked amount is less than the amount to unstake or when the user tries to set a political bias without any stake.
 */
error DACStaking__InsufficientStakedAmount();
/**
 * @dev Thrown when a transfer fails.
 * @param from The address from which the transfer failed.
 * @param to The address to which the transfer failed.
 * @param token The address of the token that failed to transfer.
 * @param amount The amount that failed to transfer.
 */
error DACStaking__TransferFailed(address from, address to, address token, uint256 amount);
/**
 * @dev Thrown when attempting to set a bias that is not one of the enum values.
 */
error DACStaking__InvalidBias();
/**
 * @dev Thrown when a user tries to set bias before cooldown expires.
 */
error DACStaking__BiasCooldownActive();

/**
 * @title IDACStaking
 * @notice Interface for interacting with the DACStaking contract, central to staking within DAC Ecosystem.
 * @dev Interface for the DACStaking contract.
 */
interface IDACStaking {
    // ================================================================
    // │                     Type declarations                        │
    // ================================================================

    /// @dev Struct representing a stake
    struct Stake {
        uint256 amount; // Amount of tokens staked
        uint256 timestamp; // Timestamp when the stake was created
    }

    /**
     * @dev Enum representing political bias levels.
     * ‒ Neutral = 0 (default)
     * ‒ Left    = 1
     * ‒ Right   = 2
     */
    enum Bias {
        Neutral,
        Left,
        Right
    }

    // ================================================================
    // │                           Events                             │
    // ================================================================

    /// @dev Emitted when a user stakes tokens
    event Staked(address indexed user, uint256 amount, uint256 timestamp);

    /// @dev Emitted when a user unstakes tokens
    event Unstaked(address indexed user, uint256 amount, uint256 timestamp);

    /// @dev Emitted when the minimum lock period is updated
    event MinimumLockPeriodUpdated(uint16 newMinimumLockPeriod);

    /// @dev Emitted when the warmup period is updated
    event WarmupPeriodUpdated(uint16 newWarmupPeriod);

    /// @dev Emitted when the eligibility for reward scaling is updated
    event EligibilityForRewardScalingUpdated(bool isEligible);

    /// @dev Emitted when the hot period is updated
    event HotPeriodUpdated(uint16 newHotPeriod);

    /// @dev Emitted when the maximum reward multiplier is updated
    event MaxRewardMultiplierUpdated(uint8 newMaxRewardMultiplier);

    /// @dev Emitted when a user updates their political bias.
    event BiasUpdated(address indexed user, Bias oldBias, Bias newBias);

    /// @dev Emitted when the cooldown feature is enabled or disabled
    event CooldownEnabledUpdated(bool isEnabled);

    /// @dev Emitted when the cooldown period is updated
    event CooldownPeriodUpdated(uint16 newCooldownPeriod);

    // ================================================================
    // │                         Functions                            │
    // ================================================================

    /**
     * @dev Allows users to stake a specific amount of the staking token.
     * @param amount Amount of tokens to stake.
     */
    function stake(uint256 amount) external;

    /**
     * @dev Allows users to unstake a specific amount of the staking token.
     * @param amount Amount of tokens to unstake.
     */
    function unstake(uint256 amount) external;

    /**
     * @dev Retrieves the raw total amount staked by a user without considering reward multipliers.
     * @param user Address of the user.
     * @return totalStaked Raw total staked amount.
     */
    function getTotalStaked(address user) external view returns (uint256);

    /**
     * @dev Retrieves the total amount staked by a user, considering current reward multipliers.
     * @param user Address of the user.
     * @return totalWithMultipliers Total staked amount with multipliers applied.
     */
    function getTotalStakedWithMultipliers(address user) external view returns (uint256);

    /**
     * @dev Retrieves all stakes of a user.
     * @param user Address of the user.
     * @return userStakes Array of Stake structs representing the user's stakes.
     */
    function getUserStakes(address user) external view returns (Stake[] memory);

    /**
     * @dev Allows the admin to update the minimum lock period.
     * @param periodInDays New minimum lock period in days.
     */
    function setMinimumLockPeriod(uint16 periodInDays) external;

    /**
     * @dev Allows the admin to update the warmup period.
     * @param periodInDays New warmup period in days.
     */
    function setWarmupPeriod(uint16 periodInDays) external;

    /**
     * @dev Allows the admin to enable or disable reward eligibility.
     * @param isEligible New eligibility status for rewards.
     */
    function setEligibilityForRewards(bool isEligible) external;

    /**
     * @dev Allows the admin to update the hot period.
     * @param periodInDays New hot period in days.
     */
    function setHotPeriod(uint16 periodInDays) external;

    /**
     * @dev Allows the admin to update the maximum reward multiplier.
     * @param multiplier New maximum reward multiplier.
     */
    function setMaxRewardMultiplier(uint8 multiplier) external;

    /**
     * @dev Allows the admin to enable or disable the bias cooldown feature.
     * @param isEnabled New status for the bias cooldown (true = enabled, false = disabled).
     */
    function setCooldownEnabled(bool isEnabled) external;

    /**
     * @dev Allows the admin to update the bias cooldown period.
     * @param periodInDays New cooldown period in days.
     */
    function setCooldownPeriod(uint16 periodInDays) external;

    /**
     * @dev Allows a user to set their political bias for all their stakes.
     * @param bias The new political bias (Neutral/Left/Right).
     */
    function setBias(Bias bias) external;

    /**
     * @dev Retrieves the political bias of a user.
     * @param user Address of the user.
     * @return bias The political bias of the user.
     */
    function getBias(address user) external view returns (Bias);

    /**
     * @dev Returns the last bias cooldown timestamp for a specific user.
     * @param user The user to check the cooldown for.
     * @return The last bias cooldown timestamp for the user.
     */
    function getLastBiasTimestamp(address user) external view returns (uint256);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC-20
 * applications.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Skips emitting an {Approval} event indicating an allowance update. This is not
     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     *
     * ```solidity
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Capped.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Capped.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Extension of {ERC20} that adds a cap to the supply of tokens.
 */
abstract contract ERC20Capped is ERC20 {
    uint256 private immutable _cap;

    /**
     * @dev Total supply cap has been exceeded.
     */
    error ERC20ExceededCap(uint256 increasedSupply, uint256 cap);

    /**
     * @dev The supplied cap is not a valid cap.
     */
    error ERC20InvalidCap(uint256 cap);

    /**
     * @dev Sets the value of the `cap`. This value is immutable, it can only be
     * set once during construction.
     */
    constructor(uint256 cap_) {
        if (cap_ == 0) {
            revert ERC20InvalidCap(0);
        }
        _cap = cap_;
    }

    /**
     * @dev Returns the cap on the token's total supply.
     */
    function cap() public view virtual returns (uint256) {
        return _cap;
    }

    /**
     * @dev See {ERC20-_update}.
     */
    function _update(address from, address to, uint256 value) internal virtual override {
        super._update(from, to, value);

        if (from == address(0)) {
            uint256 maxSupply = cap();
            uint256 supply = totalSupply();
            if (supply > maxSupply) {
                revert ERC20ExceededCap(supply, maxSupply);
            }
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/ERC20Burnable.sol)

pragma solidity ^0.8.20;

import {ERC20} from "../ERC20.sol";
import {Context} from "../../../utils/Context.sol";

/**
 * @dev Extension of {ERC20} that allows token holders to destroy both their own
 * tokens and those that they have an allowance for, in a way that can be
 * recognized off-chain (via event analysis).
 */
abstract contract ERC20Burnable is Context, ERC20 {
    /**
     * @dev Destroys a `value` amount of tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 value) public virtual {
        _burn(_msgSender(), value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, deducting from
     * the caller's allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `value`.
     */
    function burnFrom(address account, uint256 value) public virtual {
        _spendAllowance(account, _msgSender(), value);
        _burn(account, value);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/ERC20Permit.sol)

pragma solidity ^0.8.20;

import {IERC20Permit} from "./IERC20Permit.sol";
import {ERC20} from "../ERC20.sol";
import {ECDSA} from "../../../utils/cryptography/ECDSA.sol";
import {EIP712} from "../../../utils/cryptography/EIP712.sol";
import {Nonces} from "../../../utils/Nonces.sol";

/**
 * @dev Implementation of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712, Nonces {
    bytes32 private constant PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    /**
     * @dev Permit deadline has expired.
     */
    error ERC2612ExpiredSignature(uint256 deadline);

    /**
     * @dev Mismatched signature.
     */
    error ERC2612InvalidSigner(address signer, address owner);

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC-20 token name.
     */
    constructor(string memory name) EIP712(name, "1") {}

    /**
     * @inheritdoc IERC20Permit
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (block.timestamp > deadline) {
            revert ERC2612ExpiredSignature(deadline);
        }

        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        if (signer != owner) {
            revert ERC2612InvalidSigner(signer, owner);
        }

        _approve(owner, spender, value);
    }

    /**
     * @inheritdoc IERC20Permit
     */
    function nonces(address owner) public view virtual override(IERC20Permit, Nonces) returns (uint256) {
        return super.nonces(owner);
    }

    /**
     * @inheritdoc IERC20Permit
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
        return _domainSeparatorV4();
    }
}
"
    },
    "src/IDACToken.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

// Compatible with OpenZeppelin Contracts ^5.0.0
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title IDACToken
 * @notice Interface for interacting with the DACToken contract, the central ERC-20 token of the DAC Ecosystem.
 * @dev Provides a function for minting new tokens, intended to be used by the contract owner, typically representing the ecosystem's treasury.
 */
interface IDACToken is IERC20 {
    /**
     * @notice Mints new tokens to a specified address.
     * @dev
     * - This function can only be called by the contract owner.
     * - The owner is expected to be the DAC Ecosystem treasury, ensuring responsible issuance of tokens.
     * - Refer to {DACAuthority} for details on ownership and access control.
     * @param to The address that will receive the newly minted tokens.
     * @param amount The amount of tokens to mint, denominated in the smallest unit of the token.
     */
    function mint(address to, uint256 amount) external;

    /**
     * @dev Destroys a `value` amount of tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 amount) external;
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @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);
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

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);
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While thes

Tags:
ERC20, Multisig, Mintable, Burnable, Swap, Liquidity, Staking, Upgradeable, Multi-Signature, Factory|addr:0x57872e4bd7d3d4158551ea67f0b332ef09ffb705|verified:true|block:23733983|tx:0x74d118325115957b75f5092737639b739bfdb48362649d3e76ccd5023716af92|first_check:1762360090

Submitted on: 2025-11-05 17:28:12

Comments

Log in to comment.

No comments yet.