Nipol / Governance

Codename, Coffee HouseπŸ•
GNU Lesser General Public License v3.0
2 stars 0 forks source link

Move to Proxy Vote Module #5

Open Nipol opened 2 years ago

Nipol commented 2 years ago

For now version of the Governance, aiming to bean the DAO not Universal. but in the future codebase aiming to Universal.

Nipol commented 2 years ago

Snapshot Module

/**
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
pragma solidity ^0.8.0;

import "@beandao/contracts/interfaces/IERC20.sol";
import "@beandao/contracts/interfaces/IERC165.sol";
import "./IModule.sol";

error NotEnoughVotes();
error NotAllowedAddress(address delegatee);
contract SnapshotModule is IModule, IERC165 {
    struct Checkpoint {
        uint32 fromBlock;
        uint224 votes;
    }

    struct Storage {
        mapping(address => uint256) balances;
        mapping(address => address) delegates;
        mapping(address => Checkpoint[]) checkpoints;
        Checkpoint[] totalCheckpoints;
    }

    bytes32 constant POSITION = keccak256("eth.dao.bean.stakemodule.snapshot");
    address public immutable council;
    address public immutable token;

    event Delegate(address to, uint256 prevVotes, uint256 nextVotes);

    modifier onlyDelegateCouncil() {
        if (address(this) != council) revert();
        _;
    }

    /**
     * @param tokenAddr μΈμ½”λ”©λœ νˆ¬ν‘œκΆŒμœΌλ‘œ μ‚¬μš©ν•  토큰 μ»¨νŠΈλž™νŠΈ μ£Όμ†Œ
     */
    constructor(address councilAddr, address tokenAddr) {
        council = councilAddr;
        token = tokenAddr;
    }

    /**
     * @notice ν•΄λ‹Ή λͺ¨λ“ˆμ— μ§€μ •λœ 토큰을 μž…λ ₯된 μˆ˜λŸ‰λ§ŒνΌ μ˜ˆμΉ˜ν•˜μ—¬ νˆ¬ν‘œκΆŒμœΌλ‘œ λ°”κΏ”λ‘‘λ‹ˆλ‹€.
     */
    function stake(uint256 amount) external {
        if (!(amount != 0)) revert();
        Storage storage s = moduleStorage();
        address currentDelegatee = s.delegates[msg.sender];

        safeTransferFrom(token, msg.sender, address(this), amount);
        unchecked {
            s.balances[msg.sender] += amount;
        }

        // λˆ„κ΅°κ°€μ—κ²Œ μœ„μž„μ„ ν–ˆλ‹€λ©΄,
        if (currentDelegatee != msg.sender && currentDelegatee != address(0)) {
            // μΆ”κ°€λœ μˆ˜λŸ‰λ§ŒνΌ κΈ°μ‘΄ μœ„μž„μžμ—κ²Œ μœ„μž„ μˆ˜λŸ‰ 증가.
            delegateVotes(address(0), currentDelegatee, amount);
        } else {
            delegateVotes(address(0), msg.sender, amount);
            s.delegates[msg.sender] = msg.sender;
        }

        writeCheckpoint(s.totalCheckpoints, _add, amount);
    }

    /**
     * @notice ν•΄λ‹Ή λͺ¨λ“ˆμ— μ§€μ •λœ 토큰을 μž…λ ₯된 μˆ˜λŸ‰λ§ŒνΌ μ˜ˆμΉ˜ν•˜μ—¬ νˆ¬ν‘œκΆŒμœΌλ‘œ λ³€κ²½ν•˜κ³ , νˆ¬ν‘œκΆŒμ„ λ‹€λ₯Έ μ£Όμ†Œμ—κ²Œ μœ„μž„ν•©λ‹ˆλ‹€.
     */
    function stakeWithDelegate(uint256 amount, address delegatee) external {
        if (!(amount != 0)) revert();
        if (delegatee == msg.sender || delegatee == address(0)) revert();
        Storage storage s = moduleStorage();
        (address currentDelegatee, uint256 latestBalance) = (s.delegates[msg.sender], s.balances[msg.sender]);

        // μΆ”κ°€λ˜λŠ” νˆ¬ν‘œκΆŒ 카운슬둜 전솑
        safeTransferFrom(token, msg.sender, address(this), amount);

        // μΆ”κ°€λ˜λŠ” 만큼 밸런슀 μ—…λ°μ΄νŠΈ
        unchecked {
            s.balances[msg.sender] += amount;
        }

        // μœ„μž„ λŒ€μƒμ΄ κΈ°μ‘΄κ³Ό λ™μΌν•˜λ‹€λ©΄ μΆ”κ°€ κΈˆμ•‘λ§Œ μœ„μž„.
        if (delegatee == currentDelegatee) {
            delegateVotes(address(0), delegatee, amount);
        } else {
            // λ‹€λ₯Έ μœ„μž„ λŒ€μƒμ΄λΌλ©΄ 이전 μœ„μž„μ„ μ·¨μ†Œν•˜μ—¬ νˆ¬ν‘œκΆŒμ„ μƒˆλ‘œμš΄ λŒ€μƒμœΌλ‘œ λ³€κ²½
            delegateVotes(currentDelegatee, delegatee, latestBalance);
            // μƒˆλ‘œμš΄ νˆ¬ν‘œκΆŒμ„ μƒˆλ‘œμš΄ delegateeμ—κ²Œ μœ„μž„
            delegateVotes(address(0), delegatee, amount);
            // λˆ„κ΅¬μ—κ²Œ μœ„μž„ν•˜κ³  μžˆλŠ”μ§€ 정보 λ³€κ²½,
            s.delegates[msg.sender] = delegatee;
        }

        // 총 μœ„μž„λŸ‰ μ—…λ°μ΄νŠΈ
        writeCheckpoint(s.totalCheckpoints, _add, amount);
    }

    /**
     * @notice 예치된 νˆ¬ν‘œκΆŒμ„ ν† ν°μœΌλ‘œ λ³€ν™˜ν•˜μ—¬ μΆœκΈˆν•©λ‹ˆλ‹€.
     */
    function unstake(uint256 amount) external {
        // μˆ˜λŸ‰ 0이 λ“€μ–΄μ˜€λŠ” 경우 μ·¨μ†Œλ©λ‹ˆλ‹€.
        if (!(amount != 0)) revert();

        Storage storage s = moduleStorage();
        (address currentDelegatee, uint256 latestBalance) = (s.delegates[msg.sender], s.balances[msg.sender]);

        // ν˜„μž¬ μœ„μž„λœ μˆ˜λŸ‰ 해지.
        delegateVotes(currentDelegatee, address(0), amount);
        unchecked {
            // μž”μ•‘μ΄ 0이라면 κΈ°μ‘΄ 밸런슀 λͺ¨λ‘ μ‚­μ œ.
            if (latestBalance - amount == 0) {
                delete s.balances[msg.sender];
                delete s.delegates[msg.sender];
            } else {
                // μž”μ•‘μ΄ λ‚¨μ•˜λ‹€λ©΄ 차감만 함
                s.balances[msg.sender] -= amount;
            }
        }

        // 총 μœ„μž„λŸ‰ μ—…λ°μ΄νŠΈ
        writeCheckpoint(s.totalCheckpoints, _sub, amount);

        safeTransfer(token, msg.sender, amount);
    }

    /**
     * @notice 예치된 νˆ¬ν‘œκΆŒμ„ νŠΉμ • μ£Όμ†Œλ‘œ μœ„μž„ν•©λ‹ˆλ‹€.
     */
    function delegate(address delegatee) external {
        if (delegatee == address(0)) revert NotAllowedAddress(delegatee);
        Storage storage s = moduleStorage();
        (address currentDelegate, uint256 latestBalance) = (s.delegates[msg.sender], s.balances[msg.sender]);

        if (latestBalance == 0) revert NotEnoughVotes();

        if (currentDelegate != delegatee) {
            delegateVotes(currentDelegate, delegatee, latestBalance);
            s.delegates[msg.sender] = delegatee;
        }
    }

    /**
     * @notice νŠΉμ • μ£Όμ†Œμ˜ 리비전에 λ”°λ₯Έ νˆ¬ν‘œκΆŒ 정보λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function checkpoints(address account, uint32 pos) public view returns (Checkpoint memory) {
        Storage storage s = moduleStorage();
        return s.checkpoints[account][pos];
    }

    /**
     * @notice λˆ„μ λœ νŠΉμ • μ£Όμ†Œμ˜ νˆ¬ν‘œκΆŒ 정보 개수λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€.
     */
    function numCheckpoints(address account) public view returns (uint32) {
        Storage storage s = moduleStorage();
        return uint32(s.checkpoints[account].length);
    }

    /**
     * @notice μž…λ ₯된 블둝을 κΈ°μ€€ν•˜μ—¬ μ£Όμ†Œμ˜ μ •λŸ‰μ μΈ νˆ¬ν‘œκΆŒμ„ κ°€μ Έμ˜΅λ‹ˆλ‹€
     * @param account λŒ€μƒμ΄ λ˜λŠ” μ£Όμ†Œ
     * @param blockNumber 기반이 λ˜λŠ” 블둝 숫자
     * @return votes νˆ¬ν‘œ κΆŒν•œ
     */
    function getPriorVotes(address account, uint256 blockNumber) external view returns (uint256 votes) {
        if (blockNumber > block.number) revert();
        Storage storage s = moduleStorage();
        votes = _checkpointsLookup(s.checkpoints[account], blockNumber);
    }

    /**
     * @notice μž…λ ₯된 블둝을 κΈ°μ€€ν•˜μ—¬, μ£Όμ†Œμ˜ μ •λŸ‰μ μΈ νˆ¬ν‘œκΆŒμ„ λΉ„μœ¨ν™”ν•˜μ—¬ κ°€μ Έμ˜΅λ‹ˆλ‹€.
     * @param account λŒ€μƒμ΄ λ˜λŠ” μ£Όμ†Œ
     * @param blockNumber 기반이 λ˜λŠ” 블둝 숫자
     * @return rate λΉ„μœ¨
     */
    function getPriorRate(address account, uint256 blockNumber) external view returns (uint256 rate) {
        if (blockNumber > block.number) revert();
        Storage storage s = moduleStorage();

        rate =
            (_checkpointsLookup(s.checkpoints[account], blockNumber) * 1e4) /
            _checkpointsLookup(s.totalCheckpoints, blockNumber);
    }

    /**
     * @notice μž…λ ₯된 블둝을 κΈ°μ€€ν•˜μ—¬, νŠΉμ • 수치의 νˆ¬ν‘œκΆŒμ„ 총 νˆ¬ν‘œκΆŒμ˜ λΉ„μœ¨λ‘œ κ³„μ‚°ν•˜λŠ” ν•¨μˆ˜
     * @param votes κ³„μ‚°ν•˜κ³ μž ν•˜λŠ” νˆ¬ν‘œκΆŒν•œ
     * @param blockNumber 기반이 λ˜λŠ” 블둝 숫자
     * @return rate λΉ„μœ¨
     */
    function getVotesToRate(uint256 votes, uint256 blockNumber) external view returns (uint256 rate) {
        if (blockNumber > block.number) revert();
        Storage storage s = moduleStorage();
        rate = (votes * 1e4) / _checkpointsLookup(s.totalCheckpoints, blockNumber);
    }

    /**
     * @notice ν•΄λ‹Ή λ˜λŠ” 블둝 숫자λ₯Ό κΈ°μ€€ν•˜μ—¬ 총 νˆ¬ν‘œκΆŒ 숫자λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function getPriorTotalSupply(uint256 blockNumber) external view returns (uint256 totalVotes) {
        if (blockNumber > block.number) revert();
        Storage storage s = moduleStorage();
        totalVotes = _checkpointsLookup(s.totalCheckpoints, blockNumber);
    }

    /**
     * @notice νŠΉμ • μ£Όμ†Œμ˜ 총 예치 μˆ˜λŸ‰μ„ λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function balanceOf(address target) public view returns (uint256 balance) {
        Storage storage s = moduleStorage();
        balance = s.balances[target];
    }

    /**
     * @notice νŠΉμ • μ£Όμ†Œμ˜ 총 νˆ¬ν‘œκΆŒμ„ λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function voteOf(address target) public view returns (uint256 votes) {
        Storage storage s = moduleStorage();
        uint256 length = s.checkpoints[target].length;
        unchecked {
            votes = length != 0 ? s.checkpoints[target][length - 1].votes : 0;
        }
    }

    /**
     * @notice νŠΉμ • μ£Όμ†Œκ°€ νˆ¬ν‘œκΆŒμ„ μœ„μž„ν•˜κ³  μžˆλŠ” μ£Όμ†Œλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function getDelegate(address target) public view returns (address delegatee) {
        Storage storage s = moduleStorage();
        delegatee = s.delegates[target];
    }

    /**
     * @notice ν˜„μž¬ 총 νˆ¬ν‘œκΆŒμ„ λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function totalSupply() public view returns (uint256 amount) {
        Storage storage s = moduleStorage();
        unchecked {
            uint256 length = s.totalCheckpoints.length;
            amount = length != 0 ? s.totalCheckpoints[length - 1].votes : 0;
        }
    }

    /**
     * @notice ν•΄λ‹Ή λͺ¨λ“ˆμ΄ μ‚¬μš©ν•˜λŠ” 토큰 μ£Όμ†Œλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function getToken() public view returns (address) {
        return token;
    }

    /**
     * @notice amount μˆ˜λŸ‰λ§ŒνΌ, from으둜 λΆ€ν„° to둜 μ΄κ΄€ν•©λ‹ˆλ‹€.
     * @dev from이 Zero Address라면, μƒˆλ‘œμš΄ amountλ₯Ό λ“±λ‘ν•˜λŠ” 것이며, toκ°€ Zero Address라면 기쑴에 있던 amountλ₯Ό κ°μ†Œμ‹œν‚΅λ‹ˆλ‹€.
     * @param from μœ„μž„μ„ λΆ€μ—¬ν•  λŒ€μƒ
     * @param to μœ„μž„μ΄ 이전될 λŒ€μƒ
     * @param amount μœ„μž„ μˆ˜λŸ‰
     */
    function delegateVotes(
        address from,
        address to,
        uint256 amount
    ) internal {
        Storage storage s = moduleStorage();

        if (from != to && amount != 0) {
            if (from != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = writeCheckpoint(s.checkpoints[from], _sub, amount);
                emit Delegate(from, oldWeight, newWeight);
            }

            if (to != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = writeCheckpoint(s.checkpoints[to], _add, amount);
                emit Delegate(to, oldWeight, newWeight);
            }
        }
    }

    function writeCheckpoint(
        Checkpoint[] storage ckpts,
        function(uint256, uint256) view returns (uint256) op,
        uint256 delta
    ) internal returns (uint256 oldWeight, uint256 newWeight) {
        uint256 length = ckpts.length;
        oldWeight = length != 0 ? ckpts[length - 1].votes : 0;
        newWeight = op(oldWeight, delta);

        if (length > 0 && ckpts[length - 1].fromBlock == block.number) {
            ckpts[length - 1].votes = uint224(newWeight);
        } else {
            ckpts.push(Checkpoint({fromBlock: uint32(block.number), votes: uint224(newWeight)}));
        }
    }

    function safeTransferFrom(
        address tokenAddr,
        address from,
        address to,
        uint256 amount
    ) internal returns (bool success) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let freePointer := mload(0x40)
            mstore(freePointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freePointer, 4), from)
            mstore(add(freePointer, 36), to)
            mstore(add(freePointer, 68), amount)

            let callStatus := call(gas(), tokenAddr, 0, freePointer, 100, 0, 0)

            let returnDataSize := returndatasize()
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }
            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }

    function safeTransfer(
        address tokenAddr,
        address to,
        uint256 amount
    ) internal returns (bool success) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let freePointer := mload(0x40)
            mstore(freePointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freePointer, 4), to)
            mstore(add(freePointer, 36), amount)

            let callStatus := call(gas(), tokenAddr, 0, freePointer, 68, 0, 0)

            let returnDataSize := returndatasize()
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }
            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }

    function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256 votes) {
        uint256 high = ckpts.length;
        uint256 low = 0;
        uint256 mid;
        while (low < high) {
            unchecked {
                mid = ((low & high) + (low ^ high) / 2);
            }
            if (ckpts[mid].fromBlock > blockNumber) {
                high = mid;
            } else {
                unchecked {
                    low = mid + 1;
                }
            }
        }

        unchecked {
            votes = high != 0 ? ckpts[high - 1].votes : 0;
        }
    }

    function _add(uint256 a, uint256 b) private pure returns (uint256) {
        return a + b;
    }

    function _sub(uint256 a, uint256 b) private pure returns (uint256) {
        return a - b;
    }

    function supportsInterface(bytes4 interfaceID) external pure returns (bool) {
        return interfaceID == type(IModule).interfaceId || interfaceID == type(IERC165).interfaceId;
    }

    function moduleStorage() internal pure returns (Storage storage s) {
        bytes32 position = POSITION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            s.slot := position
        }
    }
}

Uniswap Module

/**
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */
pragma solidity ^0.8.0;

import "UniswapV3Pack/v3-core/interfaces/IUniswapV3Pool.sol";
import "UniswapV3Pack/v3-core/interfaces/IUniswapV3Factory.sol";
import "UniswapV3Pack/v3-core/interfaces/callback/IUniswapV3MintCallback.sol";
import "UniswapV3Pack/v3-core/libraries/TickMath.sol";
import "UniswapV3Pack/v3-periphery/interfaces/ISwapRouter.sol";
import "UniswapV3Pack/v3-periphery/interfaces/IQuoterV2.sol";
import {PositionKey} from "UniswapV3Pack/v3-periphery/libraries/PositionKey.sol";
import {LiquidityAmounts} from "UniswapV3Pack/v3-periphery/libraries/LiquidityAmounts.sol";
import "@beandao/contracts/interfaces/IERC165.sol";

import "./Math.sol";
import "./IModule.sol";

error UnorderedTick();
error NotEnoughVotes();
error NotAllowedAddress(address delegatee);

/**
 * @author  yoonsung.eth
 * @title   UniswapModule
 * @notice  νˆ¬ν‘œκΆŒμ„ λ“±λ‘ν•˜λŠ” 방식을 μœ λ‹ˆμŠ€μ™‘μ— μœ λ™μ„±μ„ κ³΅κΈ‰ν•˜λŠ” λ°©λ²•μœΌλ‘œ μˆ˜ν–‰ν•©λ‹ˆλ‹€. μ§€μ •ν•œ 토큰과, κ°€μΉ˜λ₯Ό 보μž₯ν•˜κΈ° μœ„ν•œ νŽ˜μ–΄ 토큰을 μ§€μ •ν•˜μ—¬ μœ λ™μ„± 풀을
 *          λ§Œλ“€μ–΄ ν•΄λ‹Ή λͺ¨λ“ˆμ„ 톡해 κ³΅κΈ‰λœ μœ λ™μ„±μ€ νˆ¬ν‘œκΆŒμœΌλ‘œ κ³„μ‚°λ©λ‹ˆλ‹€. ν•΄λ‹Ή λͺ¨λ“ˆμ΄ μ΄ˆκΈ°ν™” 될 λ•Œ μœ λ™μ„±μ˜ 가격 λ²”μœ„λ₯Ό 지정할 수 있으며, ν•΄λ‹Ή μ˜μ—­μ—
 *          λŒ€ν•˜μ—¬ μœ λ™μ„±μ΄ μΆ”κ°€λ©λ‹ˆλ‹€.
 * @dev     λͺ¨λ“  토큰은 ν•΄λ‹Ή λͺ¨λ“ˆμ„ μ‚¬μš©ν•˜λŠ” Council둜 μ „μ†‘λ˜μ–΄ Pool둜 μ „μ†‘λ˜λŠ” 과정을 κ±°μΉ©λ‹ˆλ‹€.
 * TODO: FEE Policy, Optimization
 */
contract UniswapModule is IModule, IERC165, IUniswapV3MintCallback {
    struct Checkpoint {
        uint32 fromBlock;
        uint224 votes;
    }

    struct Storage {
        mapping(address => uint256) balances;
        mapping(address => address) delegates;
        mapping(address => Checkpoint[]) checkpoints;
        Checkpoint[] totalCheckpoints;
    }

    struct StakeParam {
        uint256 amount0Desired;
        uint256 amount1Desired;
        uint256 amount0Min;
        uint256 amount1Min;
        uint256 deadline;
    }

    struct StakeSingleParam {
        uint256 amountIn;
        uint256 amountInForSwap;
        uint256 amountOutMin;
        bool isAmountIn0;
        uint256 deadline;
    }

    // address public constant WETH = ;
    bytes32 constant POSITION = keccak256("eth.dao.bean.stakemodule.uniswapv3");
    address public constant UNIV3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
    address public constant UNIV3_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
    address public constant UNIV3_QUOTOR_V2 = 0x61fFE014bA17989E743c5F6cB21bF9697530B21e;
    uint256 internal constant DUST_THRESHOLD = 1e6;

    address public immutable council;
    address public immutable token0;
    address public immutable token1;
    address public immutable pool;
    int24 public immutable lowerTick;
    int24 public immutable upperTick;
    int24 public immutable tickSpacing;
    uint24 public immutable fee;
    uint32 public immutable undelegatePeriod;

    modifier onlyCouncil() {
        if (address(this) != council) revert();
        _;
    }

    modifier checkDeadline(uint256 deadline) {
        if (block.timestamp > deadline) revert();
        _;
    }

    event Delegate(address to, uint256 prevVotes, uint256 nextVotes);

    /**
     * @notice ν•΄λ‹Ή λͺ¨λ“ˆμ„ μ΄ˆκΈ°ν™” ν•˜λŠ”λ° μ‚¬μš©ν•˜λŠ” ν•¨μˆ˜, ν•΄λ‹Ή ν•¨μˆ˜λŠ” Council을 ν†΅ν•΄μ„œ
     */
    constructor(
        address councilAddr,
        address _token0,
        address _token1,
        uint24 _fee,
        uint160 _sqrtPriceX96,
        int24 _lowerTick,
        int24 _upperTick,
        uint32 _undelegatePeriod
    ) {
        (council, fee, undelegatePeriod) = (councilAddr, _fee, _undelegatePeriod);

        address tmppool = IUniswapV3Factory(UNIV3_FACTORY).getPool(_token0, _token1, _fee);
        pool = tmppool == address(0) ? IUniswapV3Factory(UNIV3_FACTORY).createPool(_token0, _token1, _fee) : tmppool;
        (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(pool).slot0();
        if (sqrtPriceX96 == 0) {
            IUniswapV3Pool(pool).initialize(_sqrtPriceX96);
        }
        tickSpacing = IUniswapV3Pool(pool).tickSpacing();

        unchecked {
            // validate tick
            lowerTick = (_lowerTick % tickSpacing) != 0
                ? _lowerTick - (_lowerTick % tickSpacing) + (_lowerTick < 0 ? -tickSpacing : tickSpacing)
                : _lowerTick;
            upperTick = (_upperTick % tickSpacing) != 0
                ? _upperTick - (_upperTick % tickSpacing) + (_upperTick < 0 ? -tickSpacing : tickSpacing)
                : _upperTick;
        }
        if (upperTick <= lowerTick) revert UnorderedTick();
        (token0, token1) = _token0 > _token1 ? (_token1, _token0) : (_token0, _token1);
    }

    /**
     * @notice  두 개의 토큰을 μ΄μš©ν•˜μ—¬, νˆ¬ν‘œκΆŒμœΌλ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€.
     * @dev     μΆ”κ°€λ˜μ–΄μ•Ό ν•  μˆ˜λŸ‰κ°’μ€ κΈ‰κ²©ν•˜κ²Œ 가격이 λ³€λ™ν•˜λŠ” 경우λ₯Ό λŒ€λΉ„ν•œ 값이 μž…λ ₯λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
     * @param   params token0κ³Ό token1의 μˆ˜λŸ‰κ³Ό μ΅œμ†Œν•œ μΆ”κ°€λ˜μ–΄μ•Ό ν•  μˆ˜λŸ‰ κ°’
     */
    function stake(StakeParam calldata params) external checkDeadline(params.deadline) onlyCouncil {
        // λ‘˜ λ‹€ 0으둜 λ“€μ–΄μ˜€λŠ” 경우 μ‹€νŒ¨
        if (params.amount0Desired == 0 && params.amount1Desired == 0) revert();
        Storage storage s = moduleStorage();

        address currentDelegatee = s.delegates[msg.sender];
        bytes32 positionKey = PositionKey.compute(address(this), lowerTick, upperTick);

        // ν˜„μž¬ ν¬μ§€μ…˜μ— μžˆλŠ” μœ λ™μ„±
        (uint128 existingLiquidity, , , , ) = IUniswapV3Pool(pool).positions(positionKey);
        // ν˜„μž¬ Pool의 가격
        (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(pool).slot0();

        // Pool에 더해야 ν•˜λŠ” μœ λ™μ„± 계산
        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
            sqrtPriceX96,
            TickMath.getSqrtRatioAtTick(lowerTick),
            TickMath.getSqrtRatioAtTick(upperTick),
            params.amount0Desired,
            params.amount1Desired
        );

        if (liquidity == 0) revert();

        // ν•΄λ‹Ή μ‹œμ μ—μ„œ, Council이 가지고 μžˆλŠ” 토큰을 등둝함
        (uint256 amount0, uint256 amount1) = IUniswapV3Pool(pool).mint(
            address(this),
            lowerTick,
            upperTick,
            liquidity,
            abi.encode(msg.sender)
        );

        // μ‹€μ œλ‘œ μΆ”κ°€λœ 토큰 μˆ˜λŸ‰ 체크
        if (amount0 < params.amount0Min && amount1 < params.amount1Min) revert();

        // added totalShare
        uint256 existingShareSupply;
        unchecked {
            uint256 length = s.totalCheckpoints.length;
            existingShareSupply = length != 0 ? s.totalCheckpoints[length - 1].votes : 0;
        }

        uint256 shares;

        if (existingShareSupply == 0) {
            shares = liquidity;
        } else {
            shares = Math.mulDiv(existingShareSupply, liquidity, existingLiquidity);
        }

        unchecked {
            s.balances[msg.sender] += shares;
        }

        // λˆ„κ΅°κ°€μ—κ²Œ μœ„μž„μ„ ν–ˆλ‹€λ©΄,
        if (currentDelegatee != msg.sender && currentDelegatee != address(0)) {
            // μΆ”κ°€λœ μˆ˜λŸ‰λ§ŒνΌ κΈ°μ‘΄ μœ„μž„μžμ—κ²Œ μœ„μž„ μˆ˜λŸ‰ 증가.
            delegateVotes(address(0), currentDelegatee, shares);
        } else {
            delegateVotes(address(0), msg.sender, shares);
            s.delegates[msg.sender] = msg.sender;
        }

        // 총 μœ„μž„λŸ‰ μ—…λ°μ΄νŠΈ
        writeCheckpoint(s.totalCheckpoints, _add, shares);
    }

    /**
     * @notice  ν•˜λ‚˜μ˜ ν† ν°λ§Œ μ˜ˆμΉ˜ν•˜μ—¬, swap을 톡해 ν¬μ„ν•œ λ‹€μŒ νˆ¬ν‘œκΆŒμœΌλ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€.
     * @dev     μΆ”κ°€λ˜μ–΄μ•Ό ν•  μˆ˜λŸ‰κ°’μ€ κΈ‰κ²©ν•˜κ²Œ 가격이 λ³€λ™ν•˜λŠ” 경우λ₯Ό λŒ€λΉ„ν•œ 값이 μž…λ ₯λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€. param에 μ‚¬μš©λ  값은 `getSingleSidedAmount`
     *          ν•¨μˆ˜λ‘œ 미리 κ³„μ‚°λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.
     * @param   params μΆ”κ°€ν•  총 토큰 μˆ˜λŸ‰, κ΅ν™˜ν•  토큰 μˆ˜λŸ‰, μ΅œμ†Œλ‘œ κ΅ν™˜λœ 토큰 μˆ˜λŸ‰, μž…λ ₯λ˜λŠ” 토큰이
     * TODO: Deadline.
     */
    function stake(StakeSingleParam calldata params) external checkDeadline(params.deadline) onlyCouncil {
        if (params.amountInForSwap > params.amountIn) revert();
        Storage storage s = moduleStorage();

        address currentDelegatee = s.delegates[msg.sender];
        (address tokenIn, address tokenOut) = params.isAmountIn0 ? (token0, token1) : (token1, token0);

        safeTransferFrom(tokenIn, msg.sender, address(this), params.amountIn);

        safeApprove(tokenIn, UNIV3_ROUTER, params.amountInForSwap);

        uint256 amountOut = ISwapRouter(UNIV3_ROUTER).exactInput(
            ISwapRouter.ExactInputParams({
                path: abi.encodePacked(tokenIn, fee, tokenOut),
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: params.amountInForSwap,
                amountOutMinimum: params.amountOutMin
            })
        );

        bytes32 positionKey = PositionKey.compute(address(this), lowerTick, upperTick);

        (uint128 existingLiquidity, , , , ) = IUniswapV3Pool(pool).positions(positionKey);
        (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(pool).slot0();

        (uint256 amount0, uint256 amount1) = params.isAmountIn0
            ? (params.amountIn - params.amountInForSwap, amountOut)
            : (amountOut, params.amountIn - params.amountInForSwap);

        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
            sqrtPriceX96,
            TickMath.getSqrtRatioAtTick(lowerTick),
            TickMath.getSqrtRatioAtTick(upperTick),
            amount0,
            amount1
        );

        if (liquidity == 0) revert();

        // this stage for token transfered
        (amount0, amount1) = IUniswapV3Pool(pool).mint(
            address(this),
            lowerTick,
            upperTick,
            liquidity,
            abi.encode(this)
        );

        // added totalShare
        uint256 existingShareSupply;
        unchecked {
            uint256 length = s.totalCheckpoints.length;
            existingShareSupply = length != 0 ? s.totalCheckpoints[length - 1].votes : 0;
        }
        uint256 shares;

        if (existingShareSupply == 0) {
            shares = liquidity;
        } else {
            shares = Math.mulDiv(existingShareSupply, liquidity, existingLiquidity);
        }

        unchecked {
            s.balances[msg.sender] += shares;
        }

        // λˆ„κ΅°κ°€μ—κ²Œ μœ„μž„μ„ ν–ˆλ‹€λ©΄,
        if (currentDelegatee != msg.sender && currentDelegatee != address(0)) {
            // μΆ”κ°€λœ μˆ˜λŸ‰λ§ŒνΌ κΈ°μ‘΄ μœ„μž„μžμ—κ²Œ μœ„μž„ μˆ˜λŸ‰ 증가.
            delegateVotes(address(0), currentDelegatee, shares);
        } else {
            delegateVotes(address(0), msg.sender, shares);
            s.delegates[msg.sender] = msg.sender;
        }

        // 총 μœ„μž„λŸ‰ μ—…λ°μ΄νŠΈ
        writeCheckpoint(s.totalCheckpoints, _add, shares);

        // λ‚¨μ•„μžˆλŠ” dust 전솑
        {
            (bool success, bytes memory data) = token0.staticcall(
                abi.encodeWithSignature("balanceOf(address)", address(this))
            );
            require(success && data.length >= 32);
            uint256 dust0 = abi.decode(data, (uint256));

            (success, data) = token1.staticcall(abi.encodeWithSignature("balanceOf(address)", address(this)));
            require(success && data.length >= 32);
            uint256 dust1 = abi.decode(data, (uint256));

            if (dust0 != 0) safeTransfer(token0, msg.sender, dust0);
            if (dust1 != 0) safeTransfer(token1, msg.sender, dust1);
        }
    }

    function unstake(uint256 shares) external onlyCouncil {
        if (shares == 0) revert();
        Storage storage s = moduleStorage();

        s.balances[msg.sender] -= shares;

        (address currentDelegate, uint256 latestBalance) = (s.delegates[msg.sender], s.balances[msg.sender]);

        uint256 currentTotalSupply;
        unchecked {
            uint256 length = s.totalCheckpoints.length;
            currentTotalSupply = length != 0 ? s.totalCheckpoints[length - 1].votes : 0;
        }

        bytes32 positionKey = PositionKey.compute(address(this), lowerTick, upperTick);

        (uint128 existingLiquidity, , , , ) = IUniswapV3Pool(pool).positions(positionKey);
        uint128 removedLiquidity = uint128(Math.mulDiv(existingLiquidity, shares, currentTotalSupply));
        // μœ λ™μ„± ν•΄μ œ,
        (uint256 amount0, uint256 amount1) = IUniswapV3Pool(pool).burn(lowerTick, upperTick, removedLiquidity);
        // ν•΄μ œλœ μœ λ™μ„± 전솑
        (amount0, amount1) = IUniswapV3Pool(pool).collect(
            msg.sender,
            lowerTick,
            upperTick,
            uint128(amount0),
            uint128(amount1)
        );

        // ν˜„μž¬ μœ„μž„μ—μ„œ share 만큼 삭감
        delegateVotes(currentDelegate, address(0), shares);
        unchecked {
            // μž”μ•‘μ΄ 0이라면 κΈ°μ‘΄ 밸런슀 λͺ¨λ‘ μ‚­μ œ.
            if (latestBalance - shares == 0) {
                delete s.balances[msg.sender];
                delete s.delegates[msg.sender];
            } else {
                // μž”μ•‘μ΄ λ‚¨μ•˜λ‹€λ©΄ 차감만 함
                s.balances[msg.sender] -= shares;
            }
        }

        writeCheckpoint(s.totalCheckpoints, _sub, shares);
    }

    /**
     * @notice 예치된 νˆ¬ν‘œκΆŒμ„ νŠΉμ • μ£Όμ†Œλ‘œ μœ„μž„ν•©λ‹ˆλ‹€.
     */
    function delegate(address delegatee) external onlyCouncil {
        if (delegatee == address(0)) revert NotAllowedAddress(delegatee);
        Storage storage s = moduleStorage();

        (address currentDelegate, uint256 latestBalance) = (s.delegates[msg.sender], s.balances[msg.sender]);

        if (latestBalance == 0) revert NotEnoughVotes();

        if (currentDelegate != delegatee) {
            delegateVotes(currentDelegate, delegatee, latestBalance);
            s.delegates[msg.sender] = delegatee;
        }
    }

    function getSingleSidedAmount(uint256 amountIn, bool isAmountIn0)
        external
        onlyCouncil
        returns (uint128 liquidity, uint256 amountForSwap)
    {
        (uint160 lowerSqrtPrice, uint160 upperSqrtPrice) = (
            TickMath.getSqrtRatioAtTick(lowerTick),
            TickMath.getSqrtRatioAtTick(upperTick)
        );

        (address tokenIn, address tokenOut) = isAmountIn0 ? (token0, token1) : (token1, token0);

        amountForSwap = amountIn / 2;
        uint256 i; // Cur binary search iteration
        (uint256 low, uint256 high) = (0, amountIn);
        uint256 amountOutRecv;
        uint160 sqrtRatioX96; // current price
        uint256 leftoverAmount0;
        uint256 leftoverAmount1;

        while (i != 128) {
            (amountOutRecv, sqrtRatioX96, , ) = IQuoterV2(UNIV3_QUOTOR_V2).quoteExactInputSingle(
                IQuoterV2.QuoteExactInputSingleParams({
                    tokenIn: tokenIn,
                    tokenOut: tokenOut,
                    amountIn: amountForSwap,
                    fee: fee,
                    sqrtPriceLimitX96: 0
                })
            );

            uint256 amountInPostSwap = amountIn - amountForSwap;

            liquidity = LiquidityAmounts.getLiquidityForAmounts(
                sqrtRatioX96,
                lowerSqrtPrice,
                upperSqrtPrice,
                isAmountIn0 ? amountInPostSwap : amountOutRecv,
                isAmountIn0 ? amountOutRecv : amountInPostSwap
            );

            // Get the amounts needed for post swap end sqrt ratio end state
            (uint256 lpAmount0, uint256 lpAmount1) = LiquidityAmounts.getAmountsForLiquidity(
                sqrtRatioX96,
                lowerSqrtPrice,
                upperSqrtPrice,
                liquidity
            );

            // Calculate leftover amounts with Trimming some dust
            if (isAmountIn0) {
                leftoverAmount0 = ((amountInPostSwap - lpAmount0) / 100) * 100;
                leftoverAmount1 = ((amountOutRecv - lpAmount1) / 100) * 100;
            } else {
                leftoverAmount0 = ((amountOutRecv - lpAmount0) / 100) * 100;
                leftoverAmount1 = ((amountInPostSwap - lpAmount1) / 100) * 100;
            }

            // Termination condition, we approximated enough
            if (leftoverAmount0 <= DUST_THRESHOLD && leftoverAmount1 <= DUST_THRESHOLD) {
                break;
            }

            if (isAmountIn0) {
                if (leftoverAmount0 > 0) {
                    (low, amountForSwap, high) = (amountForSwap, (high + amountForSwap) / 2, high);
                } else if (leftoverAmount1 > 0) {
                    (low, amountForSwap, high) = (low, (low + amountForSwap) / 2, amountForSwap);
                } else {
                    break;
                }
            } else {
                if (leftoverAmount1 > 0) {
                    (low, amountForSwap, high) = (amountForSwap, (high + amountForSwap) / 2, high);
                } else if (leftoverAmount0 > 0) {
                    (low, amountForSwap, high) = (low, (low + amountForSwap) / 2, amountForSwap);
                } else {
                    break;
                }
            }

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice λˆ„μ λœ νŠΉμ • μ£Όμ†Œμ˜ νˆ¬ν‘œκΆŒ 정보 개수λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€.
     */
    function numCheckpoints(address account) public view onlyCouncil returns (uint32) {
        Storage storage s = moduleStorage();
        return uint32(s.checkpoints[account].length);
    }

    /**
     * @notice BlockNumberλ₯Ό κΈ°μ€€μœΌλ‘œ, target의 μ •λŸ‰μ μΈ νˆ¬ν‘œκΆŒμ„ κ°€μ Έμ˜΅λ‹ˆλ‹€.
     * @param target λŒ€μƒμ΄ λ˜λŠ” μ£Όμ†Œ
     * @param blockNumber 기반이 λ˜λŠ” 블둝 숫자
     * @return votes νˆ¬ν‘œ κΆŒν•œ
     */
    function getPriorVotes(address target, uint256 blockNumber) external view onlyCouncil returns (uint256 votes) {
        if (blockNumber > block.number) revert();
        Storage storage s = moduleStorage();
        votes = _checkpointsLookup(s.checkpoints[target], blockNumber);
    }

    /**
     * @notice BlockNumberλ₯Ό κΈ°μ€€μœΌλ‘œ, target의 νˆ¬ν‘œκΆŒμ„ λΉ„μœ¨ν™” ν•˜μ—¬ κ°€μ Έμ˜΅λ‹ˆλ‹€.
     * @param target λŒ€μƒμ΄ λ˜λŠ” μ£Όμ†Œ
     * @param blockNumber 기반이 λ˜λŠ” 블둝 숫자
     * @return rate λΉ„μœ¨
     */
    function getPriorRate(address target, uint256 blockNumber) external view onlyCouncil returns (uint256 rate) {
        if (blockNumber > block.number) revert();
        Storage storage s = moduleStorage();
        rate =
            (_checkpointsLookup(s.checkpoints[target], blockNumber) * 1e4) /
            _checkpointsLookup(s.totalCheckpoints, blockNumber);
    }

    /**
     * @notice BlockNumberλ₯Ό κΈ°μ€€μœΌλ‘œ, νŠΉμ • 수치의 νˆ¬ν‘œκΆŒμ„ 총 νˆ¬ν‘œκΆŒμ˜ λΉ„μœ¨λ‘œ κ³„μ‚°ν•˜λŠ” ν•¨μˆ˜
     * @param votes κ³„μ‚°ν•˜κ³ μž ν•˜λŠ” νˆ¬ν‘œκΆŒν•œ
     * @param blockNumber 기반이 λ˜λŠ” 블둝 숫자
     */
    function getVotesToRate(uint256 votes, uint256 blockNumber) external view onlyCouncil returns (uint256 rate) {
        if (blockNumber > block.number) revert();
        Storage storage s = moduleStorage();
        rate = (votes * 1e4) / _checkpointsLookup(s.totalCheckpoints, blockNumber);
    }

    /**
     * @notice μž…λ ₯된 블둝을 κΈ°μ€€ν•˜μ—¬, 총 νˆ¬ν‘œκΆŒμ„ λ°˜ν™˜ν•©λ‹ˆλ‹€.
     * @param blockNumber 기반이 λ˜λŠ” 블둝 숫자
     * @return totalVotes 총 νˆ¬ν‘œκΆŒ
     */
    function getPriorTotalSupply(uint256 blockNumber) external view onlyCouncil returns (uint256 totalVotes) {
        if (blockNumber > block.number) revert();
        Storage storage s = moduleStorage();
        totalVotes = _checkpointsLookup(s.totalCheckpoints, blockNumber);
    }

    /**
     * @notice νŠΉμ • μ£Όμ†Œμ˜ 총 예치 μˆ˜λŸ‰μ„ λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function balanceOf(address target) public view onlyCouncil returns (uint256 balance) {
        Storage storage s = moduleStorage();
        balance = s.balances[target];
    }

    /**
     * @notice νŠΉμ • μ£Όμ†Œμ˜ 총 νˆ¬ν‘œκΆŒμ„ λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function voteOf(address target) public view onlyCouncil returns (uint256 votes) {
        Storage storage s = moduleStorage();
        uint256 length = s.checkpoints[target].length;
        unchecked {
            votes = length != 0 ? s.checkpoints[target][length - 1].votes : 0;
        }
    }

    /**
     * @notice νŠΉμ • μ£Όμ†Œκ°€ νˆ¬ν‘œκΆŒμ„ μœ„μž„ν•˜κ³  μžˆλŠ” μ£Όμ†Œλ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
     */
    function getDelegate(address target) public view onlyCouncil returns (address delegatee) {
        Storage storage s = moduleStorage();
        delegatee = s.delegates[target];
    }

    // 총 νˆ¬ν‘œκΆŒ μˆ˜λŸ‰
    function totalSupply() external view onlyCouncil returns (uint256 amount) {
        Storage storage s = moduleStorage();
        unchecked {
            uint256 length = s.totalCheckpoints.length;
            amount = length != 0 ? s.totalCheckpoints[length - 1].votes : 0;
        }
    }

    function getTokens() public view onlyCouncil returns (address, address) {
        return (token0, token1);
    }

    // Poolμ—μ„œ fallback으둜 ν˜ΈμΆœλ˜λŠ” ν•¨μˆ˜
    function uniswapV3MintCallback(
        uint256 amount0Owed,
        uint256 amount1Owed,
        bytes calldata data
    ) external onlyCouncil {
        address from = abi.decode(data, (address));
        if (from != address(this)) {
            if (amount0Owed != 0) safeTransferFrom(token0, from, pool, amount0Owed);
            if (amount1Owed != 0) safeTransferFrom(token1, from, pool, amount1Owed);
        } else if (from == address(this)) {
            if (amount0Owed != 0) safeTransfer(token0, pool, amount0Owed);
            if (amount1Owed != 0) safeTransfer(token1, pool, amount1Owed);
        }
    }

    function supportsInterface(bytes4 interfaceID) external pure returns (bool) {
        return interfaceID == type(IModule).interfaceId || interfaceID == type(IERC165).interfaceId;
    }

    /**
     * @notice amount μˆ˜λŸ‰λ§ŒνΌ, from으둜 λΆ€ν„° to둜 μ΄κ΄€ν•©λ‹ˆλ‹€.
     * @dev from이 Zero Address라면, μƒˆλ‘œμš΄ amountλ₯Ό λ“±λ‘ν•˜λŠ” 것이며, toκ°€ Zero Address라면 기쑴에 있던 amountλ₯Ό κ°μ†Œμ‹œν‚΅λ‹ˆλ‹€.
     * @param from μœ„μž„μ„ λΆ€μ—¬ν•  λŒ€μƒ
     * @param to μœ„μž„μ΄ 이전될 λŒ€μƒ
     * @param amount μœ„μž„ μˆ˜λŸ‰
     */
    function delegateVotes(
        address from,
        address to,
        uint256 amount
    ) internal {
        Storage storage s = moduleStorage();

        if (from != to && amount != 0) {
            if (from != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = writeCheckpoint(s.checkpoints[from], _sub, amount);
                emit Delegate(from, oldWeight, newWeight);
            }

            if (to != address(0)) {
                (uint256 oldWeight, uint256 newWeight) = writeCheckpoint(s.checkpoints[to], _add, amount);
                emit Delegate(to, oldWeight, newWeight);
            }
        }
    }

    function writeCheckpoint(
        Checkpoint[] storage ckpts,
        function(uint256, uint256) view returns (uint256) op,
        uint256 delta
    ) internal returns (uint256 oldWeight, uint256 newWeight) {
        uint256 length = ckpts.length;
        oldWeight = length != 0 ? ckpts[length - 1].votes : 0;
        newWeight = op(oldWeight, delta);

        if (length > 0 && ckpts[length - 1].fromBlock == block.number) {
            ckpts[length - 1].votes = uint224(newWeight);
        } else {
            ckpts.push(Checkpoint({fromBlock: uint32(block.number), votes: uint224(newWeight)}));
        }
    }

    function _checkpointsLookup(Checkpoint[] storage ckpts, uint256 blockNumber) private view returns (uint256 votes) {
        uint256 high = ckpts.length;
        uint256 low = 0;
        uint256 mid;
        while (low < high) {
            unchecked {
                mid = ((low & high) + (low ^ high) / 2);
            }
            if (ckpts[mid].fromBlock > blockNumber) {
                high = mid;
            } else {
                unchecked {
                    low = mid + 1;
                }
            }
        }

        unchecked {
            votes = high != 0 ? ckpts[high - 1].votes : 0;
        }
    }

    /// @notice Modified from Gnosis
    /// (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol)
    function safeTransferFrom(
        address tokenAddr,
        address from,
        address to,
        uint256 amount
    ) internal returns (bool success) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let freePointer := mload(0x40)
            mstore(freePointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freePointer, 4), from)
            mstore(add(freePointer, 36), to)
            mstore(add(freePointer, 68), amount)

            let callStatus := call(gas(), tokenAddr, 0, freePointer, 100, 0, 0)

            let returnDataSize := returndatasize()
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }
            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }

    function safeTransfer(
        address tokenAddr,
        address to,
        uint256 amount
    ) internal returns (bool success) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let freePointer := mload(0x40)
            mstore(freePointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freePointer, 4), to)
            mstore(add(freePointer, 36), amount)

            let callStatus := call(gas(), tokenAddr, 0, freePointer, 68, 0, 0)

            let returnDataSize := returndatasize()
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }
            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }

    function safeApprove(
        address tokenAddr,
        address to,
        uint256 amount
    ) internal returns (bool success) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let freePointer := mload(0x40)

            mstore(freePointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freePointer, 4), to)
            mstore(add(freePointer, 36), amount)

            let callStatus := call(gas(), tokenAddr, 0, freePointer, 68, 0, 0)

            let returnDataSize := returndatasize()
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }
            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }

    function _add(uint256 a, uint256 b) private pure returns (uint256) {
        return a + b;
    }

    function _sub(uint256 a, uint256 b) private pure returns (uint256) {
        return a - b;
    }

    function moduleStorage() internal pure returns (Storage storage s) {
        bytes32 position = POSITION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            s.slot := position
        }
    }
}