Open Nipol opened 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
}
}
}
For now version of the
Governance
, aiming tobean the DAO
not Universal. but in the future codebase aiming to Universal.