Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions contracts/lens/SwapSimulator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity ^0.8.4;

import { IUniswapV3Pool } from '@uniswap/v3-core-0.8-support/contracts/interfaces/IUniswapV3Pool.sol';
import { TickMath } from '@uniswap/v3-core-0.8-support/contracts/libraries/TickMath.sol';
import { Simulate as SimulateUniswap } from '@uniswap/v3-core-0.8-support/contracts/libraries/Simulate.sol';

import { IClearingHouse } from '../interfaces/IClearingHouse.sol';
import { IClearingHouseStructures } from '../interfaces/clearinghouse/IClearingHouseStructures.sol';
Expand All @@ -13,6 +12,7 @@ import { IVToken } from '../interfaces/IVToken.sol';

import { ClearingHouseExtsload } from '../extsloads/ClearingHouseExtsload.sol';
import { SimulateSwap } from '../libraries/SimulateSwap.sol';
import { SimulateSwapUniswap } from '../libraries/SimulateSwapUniswap.sol';
import { SwapMath } from '../libraries/SwapMath.sol';
import { UniswapV3PoolHelper } from '../libraries/UniswapV3PoolHelper.sol';

Expand Down Expand Up @@ -186,12 +186,12 @@ contract SwapSimulator {
);

// simulate swap and ignore tick crosses
(swapResult.vTokenIn, swapResult.vQuoteIn) = SimulateUniswap.simulateSwap(
vPool,
swapVTokenForVQuote,
swapResult.amountSpecified,
sqrtPriceLimitX96
);
(
swapResult.vTokenIn,
swapResult.vQuoteIn,
swapResult.sqrtPriceX96Start,
swapResult.sqrtPriceX96End
) = SimulateSwapUniswap.simulateSwap(vPool, swapVTokenForVQuote, swapResult.amountSpecified, sqrtPriceLimitX96);

SwapMath.afterSwap(exactIn, swapVTokenForVQuote, uniswapFeePips, liquidityFeePips, protocolFeePips, swapResult);
}
Expand Down
215 changes: 215 additions & 0 deletions contracts/libraries/SimulateSwapUniswap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import { SwapMath } from '@uniswap/v3-core-0.8-support/contracts/libraries/SwapMath.sol';
import { SafeCast } from '@uniswap/v3-core-0.8-support/contracts/libraries/SafeCast.sol';
import { TickMath } from '@uniswap/v3-core-0.8-support/contracts/libraries/TickMath.sol';
import { TickBitmap } from '@uniswap/v3-core-0.8-support/contracts/libraries/TickBitmap.sol';
import { BitMath } from '@uniswap/v3-core-0.8-support/contracts/libraries/BitMath.sol';

import { IUniswapV3Pool } from '@uniswap/v3-core-0.8-support/contracts/interfaces/IUniswapV3Pool.sol';

/// @title Library for simulating swaps.
/// @notice By fully replicating the swap logic, we can make a static call to get a quote.
/// This is a copy of Simulate lib from Uniswap repo which additionally exports start and end prices.
library SimulateSwapUniswap {
using SafeCast for uint256;

struct Cache {
// price at the beginning of the swap
uint160 sqrtPriceX96Start;
// tick at the beginning of the swap
int24 tickStart;
// liquidity at the beginning of the swap
uint128 liquidityStart;
// the lp fee of the pool
uint24 fee;
// the tick spacing of the pool
int24 tickSpacing;
}

struct State {
// the amount remaining to be swapped in/out of the input/output asset
int256 amountSpecifiedRemaining;
// the amount already swapped out/in of the output/input asset
int256 amountCalculated;
// current sqrt(price)
uint160 sqrtPriceX96;
// the tick associated with the current price
int24 tick;
// the current liquidity in range
uint128 liquidity;
}

// copied from UniswapV3Pool to avoid pragma issues associated with importing it
struct StepComputations {
// the price at the beginning of the step
uint160 sqrtPriceStartX96;
// the next tick to swap to from the current tick in the swap direction
int24 tickNext;
// whether tickNext is initialized or not
bool initialized;
// sqrt(price) for the next tick (1/0)
uint160 sqrtPriceNextX96;
// how much is being swapped in in this step
uint256 amountIn;
// how much is being swapped out
uint256 amountOut;
// how much fee is being paid in
uint256 feeAmount;
}

function simulateSwap(
IUniswapV3Pool pool,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96
)
internal
view
returns (
int256 amount0,
int256 amount1,
uint160 sqrtPriceX96Start,
uint160 sqrtPriceX96End
)
{
require(amountSpecified != 0, 'AS');

(uint160 sqrtPriceX96, int24 tick, , , , , ) = pool.slot0();

require(
zeroForOne
? sqrtPriceLimitX96 < sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO
: sqrtPriceLimitX96 > sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO,
'SPL'
);

Cache memory cache = Cache({
sqrtPriceX96Start: (sqrtPriceX96Start = sqrtPriceX96),
tickStart: tick,
liquidityStart: pool.liquidity(),
fee: pool.fee(),
tickSpacing: pool.tickSpacing()
});

bool exactInput = amountSpecified > 0;

State memory state = State({
amountSpecifiedRemaining: amountSpecified,
amountCalculated: 0,
sqrtPriceX96: cache.sqrtPriceX96Start,
tick: cache.tickStart,
liquidity: cache.liquidityStart
});

while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
StepComputations memory step;

step.sqrtPriceStartX96 = state.sqrtPriceX96;

(step.tickNext, step.initialized) = nextInitializedTickWithinOneWord(
pool.tickBitmap,
state.tick,
cache.tickSpacing,
zeroForOne
);

if (step.tickNext < TickMath.MIN_TICK) {
step.tickNext = TickMath.MIN_TICK;
} else if (step.tickNext > TickMath.MAX_TICK) {
step.tickNext = TickMath.MAX_TICK;
}

step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext);

(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep(
state.sqrtPriceX96,
(zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96)
? sqrtPriceLimitX96
: step.sqrtPriceNextX96,
state.liquidity,
state.amountSpecifiedRemaining,
cache.fee
);

if (exactInput) {
unchecked {
state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256();
}
state.amountCalculated -= step.amountOut.toInt256();
} else {
unchecked {
state.amountSpecifiedRemaining += step.amountOut.toInt256();
}
state.amountCalculated += (step.amountIn + step.feeAmount).toInt256();
}

if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
if (step.initialized) {
(, int128 liquidityNet, , , , , , ) = pool.ticks(step.tickNext);
unchecked {
if (zeroForOne) liquidityNet = -liquidityNet;
}

state.liquidity = liquidityNet < 0
? state.liquidity - uint128(-liquidityNet)
: state.liquidity + uint128(liquidityNet);
}

unchecked {
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
}
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96);
}
}

sqrtPriceX96End = state.sqrtPriceX96;
(amount0, amount1) = zeroForOne == exactInput
? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated)
: (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining);
}

// This function replicates TickBitmap, but accepts a function pointer argument.
// It's private because it's messy, and shouldn't be re-used.
function nextInitializedTickWithinOneWord(
function(int16) external view returns (uint256) self,
int24 tick,
int24 tickSpacing,
bool lte
) private view returns (int24 next, bool initialized) {
unchecked {
int24 compressed = tick / tickSpacing;
if (tick < 0 && tick % tickSpacing != 0) compressed--; // round towards negative infinity

if (lte) {
(int16 wordPos, uint8 bitPos) = TickBitmap.position(compressed);
// all the 1s at or to the right of the current bitPos
uint256 mask = (1 << bitPos) - 1 + (1 << bitPos);
uint256 masked = self(wordPos) & mask;

// if there are no initialized ticks to the right of or at the current tick, return rightmost in the word
initialized = masked != 0;
// overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick
next = initialized
? (compressed - int24(uint24(bitPos - BitMath.mostSignificantBit(masked)))) * tickSpacing
: (compressed - int24(uint24(bitPos))) * tickSpacing;
} else {
// start from the word of the next tick, since the current tick state doesn't matter
(int16 wordPos, uint8 bitPos) = TickBitmap.position(compressed + 1);
// all the 1s at or to the left of the bitPos
uint256 mask = ~((1 << bitPos) - 1);
uint256 masked = self(wordPos) & mask;

// if there are no initialized ticks to the left of the current tick, return leftmost in the word
initialized = masked != 0;
// overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick
next = initialized
? (compressed + 1 + int24(uint24(BitMath.leastSignificantBit(masked) - bitPos))) * tickSpacing
: (compressed + 1 + int24(uint24(type(uint8).max - bitPos))) * tickSpacing;
}
}
}
}
19 changes: 12 additions & 7 deletions deploy/RageTradeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
// initialize protocol settings
await execute(
'ClearingHouse',
{ from: deployer, waitConfirmations },
{ from: deployer, waitConfirmations, log: true },
'updateProtocolSettings',
{
rangeLiquidationFeeFraction: 1500,
Expand Down Expand Up @@ -83,24 +83,29 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const timelock = await get('TimelockController');
await execute(
'RageTradeFactory',
{ from: deployer, waitConfirmations },
{ from: deployer, waitConfirmations, log: true },
'initiateGovernanceTransfer',
timelock.address,
);
await execute('ClearingHouse', { from: deployer, waitConfirmations }, 'initiateGovernanceTransfer', timelock.address);
await execute('ProxyAdmin', { from: deployer, waitConfirmations }, 'transferOwnership', timelock.address);
await execute(
'ClearingHouse',
{ from: deployer, waitConfirmations, log: true },
'initiateGovernanceTransfer',
timelock.address,
);
await execute('ProxyAdmin', { from: deployer, waitConfirmations, log: true }, 'transferOwnership', timelock.address);

// transfer teamMultisig to multisig address
const { multisigAddress } = getNetworkInfo(hre.network.config.chainId);
const { multisigAddress } = getNetworkInfo();
await execute(
'RageTradeFactory',
{ from: deployer, waitConfirmations },
{ from: deployer, waitConfirmations, log: true },
'initiateTeamMultisigTransfer',
multisigAddress,
);
await execute(
'ClearingHouse',
{ from: deployer, waitConfirmations },
{ from: deployer, waitConfirmations, log: true },
'initiateTeamMultisigTransfer',
multisigAddress,
);
Expand Down
4 changes: 2 additions & 2 deletions deploy/SettlementToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {

const { deployer } = await getNamedAccounts();

const { SETTLEMENT_TOKEN_ADDRESS } = getNetworkInfo(hre.network.config.chainId);
const { SETTLEMENT_TOKEN_ADDRESS } = getNetworkInfo();

// if SETTLEMENT_TOKEN_ADDRESS is not provided, then deploy a dummy ERC20 contract
if (SETTLEMENT_TOKEN_ADDRESS === undefined) {
Expand All @@ -28,7 +28,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
// mint dummy tokens to the deployer
await execute(
'SettlementToken',
{ from: deployer, waitConfirmations },
{ from: deployer, waitConfirmations, log: true },
'mint',
deployer,
parseUnits('1000000000', 6),
Expand Down
14 changes: 10 additions & 4 deletions deploy/TimelockController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {

const { deployer } = await getNamedAccounts();

const { multisigAddress, timelockMinDelay } = getNetworkInfo(hre.network.config.chainId);
const { multisigAddress, timelockMinDelay } = getNetworkInfo();

const isMultisigAddressProvided = multisigAddress && isAddress(multisigAddress);
if (isMultisigAddressProvided) {
Expand All @@ -31,14 +31,20 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
waitConfirmations,
});

if (isMultisigAddressProvided) {
if (isMultisigAddressProvided && multisigAddress.toLowerCase() !== deployer.toLowerCase()) {
const TIMELOCK_ADMIN_ROLE = await read('TimelockController', 'TIMELOCK_ADMIN_ROLE');

// make the governance contract the admin of Timelock
await execute('TimelockController', { from: deployer }, 'grantRole', TIMELOCK_ADMIN_ROLE, multisigAddress);
await execute(
'TimelockController',
{ from: deployer, log: true },
'grantRole',
TIMELOCK_ADMIN_ROLE,
multisigAddress,
);

// renounce admin control from deployer
await execute('TimelockController', { from: deployer }, 'renounceRole', TIMELOCK_ADMIN_ROLE, deployer);
await execute('TimelockController', { from: deployer, log: true }, 'renounceRole', TIMELOCK_ADMIN_ROLE, deployer);
}
};

Expand Down
Loading