Treasury

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": {
    "contracts/l1/Treasury.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import {ERC6909} from "../../lib/solmate/src/tokens/ERC6909.sol";
import {Implementation, OwnerOnly} from "../Implementation.sol";
import {IToken} from "../interfaces/IToken.sol";

interface IDepository {
    /// @dev Calculates amounts and initiates cross-chain unstake request from specified models.
    /// @param unstakeAmount Total amount to unstake.
    /// @param chainIds Set of chain Ids with staking proxies.
    /// @param stakingProxies Set staking proxies corresponding to each chain Id.
    /// @param bridgePayloads Bridge payloads corresponding to each chain Id.
    /// @param values Value amounts for each bridge interaction, if applicable.
    /// @param sender Sender account.
    /// @return amounts Corresponding OLAS amounts for each staking proxy.
    function unstake(
        uint256 unstakeAmount,
        uint256[] memory chainIds,
        address[] memory stakingProxies,
        bytes[] memory bridgePayloads,
        uint256[] memory values,
        address sender
    ) external payable returns (uint256[] memory amounts);
}

interface IST {
    /// @dev Redeems OLAS in exchange for stOLAS tokens.
    /// @param shares stOLAS amount.
    /// @param receiver Receiver account address.
    /// @param owner Token owner account address.
    /// @return assets OLAS amount.
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

    /// @dev Gets stOLAS staked balance.
    /// @return stOLAS staked balance.
    function stakedBalance() external returns (uint256);
}

/// @dev Only `depository` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param depository Required depository address.
error DepositoryOnly(address sender, address depository);

/// @dev Zero value.
error ZeroValue();

/// @dev The contract is already initialized.
error AlreadyInitialized();

/// @dev Value overflow.
/// @param provided Overflow value.
/// @param max Maximum possible value.
error Overflow(uint256 provided, uint256 max);

/// @dev Caught reentrancy violation.
error ReentrancyGuard();

/// @title Treasury - Smart contract for treasury
contract Treasury is Implementation, ERC6909 {
    event WithdrawDelayUpdates(uint256 withdrawDelay);
    event WithdrawRequestInitiated(
        address indexed requester, uint256 indexed requestId, uint256 stAmount, uint256 olasAmount, uint256 withdrawTime
    );
    event WithdrawRequestExecuted(uint256 requestId, uint256 amount);
    event WithdrawAmountRequestedUpdated(uint256 withdrawAmountRequested);

    // Treasury version
    string public constant VERSION = "0.1.0";

    // OLAS token address
    address public immutable olas;
    // stOLAS token address
    address public immutable st;
    // Depository address
    address public immutable depository;

    // Total withdraw amount requested
    uint256 public withdrawAmountRequested;
    // Withdraw time delay
    uint256 public withdrawDelay;
    // Number of withdraw requests
    uint256 public numWithdrawRequests;

    // Reentrancy lock
    bool transient _locked;

    /// @dev Treasury constructor.
    /// @param _olas OLAS address.
    /// @param _st stOLAS address.
    /// @param _depository Depository address.
    constructor(address _olas, address _st, address _depository) {
        olas = _olas;
        st = _st;
        depository = _depository;
    }

    /// @dev Treasury initializer.
    function initialize(uint256 _withdrawDelay) external {
        // Check for already initialized
        if (owner != address(0)) {
            revert AlreadyInitialized();
        }

        withdrawDelay = _withdrawDelay;
        owner = msg.sender;
    }

    /// @dev Changes withdraw delay value.
    /// @param newWithdrawDelay New withdraw delay value in seconds.
    function changeWithdrawDelay(uint256 newWithdrawDelay) external {
        // Check for ownership
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }

        withdrawDelay = newWithdrawDelay;
        emit WithdrawDelayUpdates(newWithdrawDelay);
    }

    /// @dev Requests withdraw of OLAS in exchange of provided stOLAS.
    /// @notice Vault reserves are used first. If there is a lack of OLAS reserves, the backup amount is requested
    ///         to be unstaked from other models.
    /// @param stAmount Provided stAmount to burn in favor of OLAS tokens.
    /// @param chainIds Set of chain Ids with staking proxies.
    /// @param stakingProxies Set of staking proxies corresponding to each chain Id.
    /// @param bridgePayloads Bridge payloads corresponding to each chain Id.
    /// @param values Value amounts for each bridge interaction, if applicable.
    /// @return requestId Withdraw request ERC-1155 token.
    /// @return olasAmount Calculated OLAS amount.
    function requestToWithdraw(
        uint256 stAmount,
        uint256[] memory chainIds,
        address[] memory stakingProxies,
        bytes[] memory bridgePayloads,
        uint256[] memory values
    ) external payable returns (uint256 requestId, uint256 olasAmount) {
        // Reentrancy guard
        if (_locked) {
            revert ReentrancyGuard();
        }
        _locked = true;

        // Check for zero value
        if (stAmount == 0) {
            revert ZeroValue();
        }

        // Get current staked balance
        uint256 stakedBalanceBefore = IST(st).stakedBalance();

        // Redeem OLAS and burn stOLAS tokens
        olasAmount = IST(st).redeem(stAmount, address(this), msg.sender);

        // Get updated staked balance
        uint256 stakedBalanceAfter = IST(st).stakedBalance();

        // Calculate withdraw time
        uint256 withdrawTime = block.timestamp + withdrawDelay;

        // Get withdraw request Id
        requestId = numWithdrawRequests;
        numWithdrawRequests = requestId + 1;

        // Push a pair of key defining variables into one key: withdrawTime | requestId
        // requestId occupies first 64 bits, withdrawTime occupies next bits as they both fit well in uint256
        requestId |= withdrawTime << 64;

        // Mint request tokens
        _mint(msg.sender, requestId, olasAmount);

        // Update total withdraw amount requested
        uint256 curWithdrawAmountRequested = withdrawAmountRequested;
        curWithdrawAmountRequested += olasAmount;
        withdrawAmountRequested = curWithdrawAmountRequested;

        // If withdraw amount is bigger than the current one, need to unstake
        if (stakedBalanceBefore > stakedBalanceAfter) {
            uint256 withdrawDiff = stakedBalanceBefore - stakedBalanceAfter;

            IDepository(depository).unstake(withdrawDiff, chainIds, stakingProxies, bridgePayloads, values, msg.sender);
        }

        emit WithdrawRequestInitiated(msg.sender, requestId, stAmount, olasAmount, withdrawTime);
        emit WithdrawAmountRequestedUpdated(curWithdrawAmountRequested);
    }

    /// @dev Finalizes withdraw requests.
    /// @param requestIds Withdraw request Ids.
    /// @param amounts Token amounts corresponding to request Ids.
    function finalizeWithdrawRequests(uint256[] calldata requestIds, uint256[] calldata amounts) external {
        // Reentrancy guard
        if (_locked) {
            revert ReentrancyGuard();
        }
        _locked = true;

        uint256 totalAmount;
        // Traverse all withdraw requests
        for (uint256 i = 0; i < requestIds.length; ++i) {
            // Decode a pair of key defining variables from one key: withdrawTime | requestId
            // requestId occupies first 64 bits, withdrawTime occupies next bits as they both fit well in uint256
            uint256 requestId = requestIds[i] & type(uint64).max;

            uint256 numRequests = numWithdrawRequests;
            // This must never happen as otherwise token would not exist and none of it would be transferFrom-ed
            if (requestId >= numRequests) {
                revert Overflow(requestId, numRequests - 1);
            }

            // It is safe to just move 64 bits as there is a single withdrawTime value after that
            uint256 withdrawTime = requestIds[i] >> 64;
            // Check for earliest possible withdraw time
            if (withdrawTime > block.timestamp) {
                revert Overflow(withdrawTime, block.timestamp);
            }

            // Burn withdraw tokens
            _burn(msg.sender, requestIds[i], amounts[i]);

            totalAmount += amounts[i];

            emit WithdrawRequestExecuted(requestIds[i], amounts[i]);
        }

        // This must never happen
        uint256 curWithdrawAmountRequested = withdrawAmountRequested;
        if (totalAmount > curWithdrawAmountRequested) {
            revert Overflow(totalAmount, curWithdrawAmountRequested);
        }
        // Update total withdraw amount requested
        curWithdrawAmountRequested -= totalAmount;
        withdrawAmountRequested = curWithdrawAmountRequested;

        // Transfer total amount of OLAS
        // The transfer overflow check is not needed since balances are in sync
        // OLAS has been redeemed when withdraw request was posted
        IToken(olas).transfer(msg.sender, totalAmount);

        emit WithdrawAmountRequestedUpdated(curWithdrawAmountRequested);
    }

    /// @dev Gets withdraw request Id by request Id and withdraw time.
    /// @param requestId Withdraw request Id.
    /// @param withdrawTime Withdraw time.
    function getWithdrawRequestId(uint256 requestId, uint256 withdrawTime) external pure returns (uint256) {
        return requestId | (withdrawTime << 64);
    }

    /// @dev Gets withdraw request Id and time.
    /// @param withdrawRequestId Combined withdrawRequestId value.
    function getWithdrawIdAndTime(uint256 withdrawRequestId) external pure returns (uint256, uint256) {
        return ((withdrawRequestId & type(uint64).max), (withdrawRequestId >> 64));
    }
}
"
    },
    "lib/solmate/src/tokens/ERC6909.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Minimalist and gas efficient standard ERC6909 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC6909.sol)
abstract contract ERC6909 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OperatorSet(address indexed owner, address indexed operator, bool approved);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);

    event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                             ERC6909 STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(address => mapping(address => bool)) public isOperator;

    mapping(address => mapping(uint256 => uint256)) public balanceOf;

    mapping(address => mapping(address => mapping(uint256 => uint256))) public allowance;

    /*//////////////////////////////////////////////////////////////
                              ERC6909 LOGIC
    //////////////////////////////////////////////////////////////*/

    function transfer(
        address receiver,
        uint256 id,
        uint256 amount
    ) public virtual returns (bool) {
        balanceOf[msg.sender][id] -= amount;

        balanceOf[receiver][id] += amount;

        emit Transfer(msg.sender, msg.sender, receiver, id, amount);

        return true;
    }

    function transferFrom(
        address sender,
        address receiver,
        uint256 id,
        uint256 amount
    ) public virtual returns (bool) {
        if (msg.sender != sender && !isOperator[sender][msg.sender]) {
            uint256 allowed = allowance[sender][msg.sender][id];
            if (allowed != type(uint256).max) allowance[sender][msg.sender][id] = allowed - amount;
        }

        balanceOf[sender][id] -= amount;

        balanceOf[receiver][id] += amount;

        emit Transfer(msg.sender, sender, receiver, id, amount);

        return true;
    }

    function approve(
        address spender,
        uint256 id,
        uint256 amount
    ) public virtual returns (bool) {
        allowance[msg.sender][spender][id] = amount;

        emit Approval(msg.sender, spender, id, amount);

        return true;
    }

    function setOperator(address operator, bool approved) public virtual returns (bool) {
        isOperator[msg.sender][operator] = approved;

        emit OperatorSet(msg.sender, operator, approved);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x0f632fb3; // ERC165 Interface ID for ERC6909
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(
        address receiver,
        uint256 id,
        uint256 amount
    ) internal virtual {
        balanceOf[receiver][id] += amount;

        emit Transfer(msg.sender, address(0), receiver, id, amount);
    }

    function _burn(
        address sender,
        uint256 id,
        uint256 amount
    ) internal virtual {
        balanceOf[sender][id] -= amount;

        emit Transfer(msg.sender, sender, address(0), id, amount);
    }
}
"
    },
    "contracts/Implementation.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

/// @dev Only `owner` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param owner Required sender address as an owner.
error OwnerOnly(address sender, address owner);

/// @dev Zero address.
error ZeroAddress();

/// @title Implementation - Smart contract for default minimal implementation
contract Implementation {
    event OwnerUpdated(address indexed owner);
    event ImplementationUpdated(address indexed implementation);

    // Code position in storage is bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
    bytes32 public constant PROXY_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    // Contract owner address
    address public owner;

    /// @dev Changes contract owner address.
    /// @param newOwner Address of a new owner.
    function changeOwner(address newOwner) external {
        // Check for ownership
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }

        // Check for zero address
        if (newOwner == address(0)) {
            revert ZeroAddress();
        }

        owner = newOwner;
        emit OwnerUpdated(newOwner);
    }

    /// @dev Changes depository implementation contract address.
    /// @param newImplementation New implementation contract address.
    function changeImplementation(address newImplementation) external {
        // Check for ownership
        if (msg.sender != owner) {
            revert OwnerOnly(msg.sender, owner);
        }

        // Check for zero address
        if (newImplementation == address(0)) {
            revert ZeroAddress();
        }

        // Store depository implementation address
        assembly {
            sstore(PROXY_SLOT, newImplementation)
        }

        emit ImplementationUpdated(newImplementation);
    }
}
"
    },
    "contracts/interfaces/IToken.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

// ERC20 token interface
interface IToken {
    /// @dev Transfers the token amount.
    /// @param to Address to transfer to.
    /// @param amount The amount to transfer.
    /// @return True if the function execution is successful.
    function transfer(address to, uint256 amount) external returns (bool);

    /// @dev Transfers the token amount that was previously approved up until the maximum allowance.
    /// @param from Account address to transfer from.
    /// @param to Account address to transfer to.
    /// @param amount Amount to transfer to.
    /// @return True if the function execution is successful.
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
    /// @param spender Account address that will be able to transfer tokens on behalf of the caller.
    /// @param amount Token amount.
    /// @return True if the function execution is successful.
    function approve(address spender, uint256 amount) external returns (bool);

    /// @dev Mints tokens.
    /// @param account Account address.
    /// @param amount Token amount.
    function mint(address account, uint256 amount) external;

    /// @dev Burns tokens.
    /// @param amount Token amount.
    function burn(uint256 amount) external;

    /// @dev Gets the amount of tokens owned by a specified account.
    /// @param account Account address.
    /// @return Amount of tokens owned.
    function balanceOf(address account) external view returns (uint256);
}

// ERC721 token interface
interface INFToken {
    /// @dev Sets token `id` as the allowance of `spender` over the caller's tokens.
    /// @param spender Account address that will be able to transfer the token on behalf of the caller.
    /// @param id Token id.
    function approve(address spender, uint256 id) external;

    /// @dev Transfers a specified token Id.
    /// @param from Account address to transfer from.
    /// @param to Account address to transfer to.
    /// @param id Token id.
    function transferFrom(address from, address to, uint256 id) external;

    /// @dev Transfers a specified token Id with a callback.
    /// @param from Account address to transfer from.
    /// @param to Account address to transfer to.
    /// @param id Token id.
    function safeTransferFrom(address from, address to, uint256 id) external;
}
"
    }
  },
  "settings": {
    "remappings": [
      "@gnosis.pm/=node_modules/@gnosis.pm/",
      "@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/",
      "@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol/",
      "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
      "@registries/=lib/autonolas-registries/",
      "@solmate/=lib/solmate/",
      "autonolas-registries/=lib/autonolas-registries/",
      "devtools/=lib/devtools/packages/toolbox-foundry/src/",
      "ds-test/=lib/autonolas-registries/lib/forge-std/lib/ds-test/src/",
      "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
      "forge-std/=lib/autonolas-registries/lib/forge-std/src/",
      "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
      "layerzero-v2/=lib/layerzero-v2/",
      "openzeppelin-contracts/=lib/openzeppelin-contracts/",
      "solmate/=lib/solmate/src/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 1000000
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "prague",
    "viaIR": true
  }
}}

Tags:
ERC165, Multisig, Staking, Upgradeable, Multi-Signature, Factory|addr:0x327f1da21255d1d02cf2e7155659f8fbf12db4ee|verified:true|block:23635019|tx:0x9cdd553606fa6eaf2a349d8c482a39d2a6b25e224a91f78115e1ceedfced49c4|first_check:1761292477

Submitted on: 2025-10-24 09:54:40

Comments

Log in to comment.

No comments yet.