Giter Club home page Giter Club logo

Comments (1)

Nipol avatar Nipol commented on September 25, 2024

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
        }
    }
}

from governance.

Related Issues (1)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.