Skip to content

Commit

Permalink
Merge pull request #11 from ronin-chain/feature/upgradeable-beacon-proxy
Browse files Browse the repository at this point in the history
feat: upgradeable beacon proxy
  • Loading branch information
thaixuandang authored Aug 26, 2024
2 parents 3d17556 + 5baf0db commit ff771e7
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 84 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ docs/
node_modules/
yarn-error.log
.yarn
.yarnrc.yml
.yarnrc.yml

.vscode/
23 changes: 15 additions & 8 deletions src/core/KatanaV3Factory.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;

import "@openzeppelin/contracts/proxy/UpgradeableBeacon.sol";

import "./interfaces/IKatanaV3Factory.sol";

import "./KatanaV3PoolDeployer.sol";
import "./NoDelegateCall.sol";

import "./KatanaV3Pool.sol";

/// @title Canonical Katana V3 factory
/// @notice Deploys Katana V3 pools and manages ownership and control over pool protocol fees
contract KatanaV3Factory is IKatanaV3Factory, KatanaV3PoolDeployer, NoDelegateCall {
contract KatanaV3Factory is IKatanaV3Factory, KatanaV3PoolDeployer {
/// @inheritdoc IKatanaV3Factory
address public immutable override beacon;

/// @inheritdoc IKatanaV3Factory
address public override owner;

Expand All @@ -20,6 +24,9 @@ contract KatanaV3Factory is IKatanaV3Factory, KatanaV3PoolDeployer, NoDelegateCa
mapping(address => mapping(address => mapping(uint24 => address))) public override getPool;

constructor() {
address poolImplementation = address(new KatanaV3Pool());
beacon = address(new UpgradeableBeacon(poolImplementation));

owner = msg.sender;
emit OwnerChanged(address(0), msg.sender);

Expand All @@ -31,13 +38,13 @@ contract KatanaV3Factory is IKatanaV3Factory, KatanaV3PoolDeployer, NoDelegateCa
emit FeeAmountEnabled(10000, 200);
}

function upgradeBeacon(address newImplementation) external {
require(msg.sender == owner);
UpgradeableBeacon(beacon).upgradeTo(newImplementation);
}

/// @inheritdoc IKatanaV3Factory
function createPool(address tokenA, address tokenB, uint24 fee)
external
override
noDelegateCall
returns (address pool)
{
function createPool(address tokenA, address tokenB, uint24 fee) external override returns (address pool) {
require(tokenA != tokenB);
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0));
Expand Down
71 changes: 36 additions & 35 deletions src/core/KatanaV3Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ pragma solidity =0.7.6;

import "./interfaces/IKatanaV3Pool.sol";

import "./NoDelegateCall.sol";

import "./libraries/LowGasSafeMath.sol";
import "./libraries/SafeCast.sol";
import "./libraries/Tick.sol";
Expand All @@ -27,7 +25,7 @@ import "./interfaces/callback/IKatanaV3MintCallback.sol";
import "./interfaces/callback/IKatanaV3SwapCallback.sol";
import "./interfaces/callback/IKatanaV3FlashCallback.sol";

contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
contract KatanaV3Pool is IKatanaV3Pool {
using LowGasSafeMath for uint256;
using LowGasSafeMath for int256;
using SafeCast for uint256;
Expand All @@ -38,21 +36,6 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
using Position for Position.Info;
using Oracle for Oracle.Observation[65535];

/// @inheritdoc IKatanaV3PoolImmutables
address public immutable override factory;
/// @inheritdoc IKatanaV3PoolImmutables
address public immutable override token0;
/// @inheritdoc IKatanaV3PoolImmutables
address public immutable override token1;
/// @inheritdoc IKatanaV3PoolImmutables
uint24 public immutable override fee;

/// @inheritdoc IKatanaV3PoolImmutables
int24 public immutable override tickSpacing;

/// @inheritdoc IKatanaV3PoolImmutables
uint128 public immutable override maxLiquidityPerTick;

struct Slot0 {
// the current price
uint160 sqrtPriceX96;
Expand Down Expand Up @@ -100,6 +83,25 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
/// @inheritdoc IKatanaV3PoolState
Oracle.Observation[65535] public override observations;

// These immutable constants are set when deploy the beacon proxy of the pool and cannot be changed unless upgrading.
/// @inheritdoc IKatanaV3PoolImmutables
address public override factory;
/// @inheritdoc IKatanaV3PoolImmutables
address public override token0;
/// @inheritdoc IKatanaV3PoolImmutables
address public override token1;
/// @inheritdoc IKatanaV3PoolImmutables
uint24 public override fee;

/// @inheritdoc IKatanaV3PoolImmutables
int24 public override tickSpacing;

/// @inheritdoc IKatanaV3PoolImmutables
uint128 public override maxLiquidityPerTick;

/// @dev The contract is initialized with the immutable parameters
bool private _immutablesInitialized;

/// @dev Mutually exclusive reentrancy protection into the pool to/from a method. This method also prevents entrance
/// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because
/// we use balance checks to determine the payment status of interactions such as mint, swap and flash.
Expand All @@ -117,11 +119,20 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
}

constructor() {
int24 _tickSpacing;
(factory, token0, token1, fee, _tickSpacing) = IKatanaV3PoolDeployer(msg.sender).parameters();
tickSpacing = _tickSpacing;
// disable immutables initialization
_immutablesInitialized = true;
}

function initializeImmutables(address factory_, address token0_, address token1_, uint24 fee_, int24 tickSpacing_)
public
{
require(!_immutablesInitialized);

(factory, token0, token1, fee, tickSpacing) = (factory_, token0_, token1_, fee_, tickSpacing_);

maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);
maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing_);

_immutablesInitialized = true;
}

/// @dev Common checks for valid tick inputs.
Expand Down Expand Up @@ -161,7 +172,6 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
external
view
override
noDelegateCall
returns (int56 tickCumulativeInside, uint160 secondsPerLiquidityInsideX128, uint32 secondsInside)
{
checkTicks(tickLower, tickUpper);
Expand Down Expand Up @@ -219,7 +229,6 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
external
view
override
noDelegateCall
returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s)
{
return observations.observe(
Expand All @@ -228,7 +237,7 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
}

/// @inheritdoc IKatanaV3PoolActions
function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external override lock noDelegateCall {
function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external override lock {
uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event
uint16 observationCardinalityNextNew = observations.grow(observationCardinalityNextOld, observationCardinalityNext);
slot0.observationCardinalityNext = observationCardinalityNextNew;
Expand Down Expand Up @@ -276,7 +285,6 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
/// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient
function _modifyPosition(ModifyPositionParams memory params)
private
noDelegateCall
returns (Position.Info storage position, int256 amount0, int256 amount1)
{
checkTicks(params.tickLower, params.tickUpper);
Expand Down Expand Up @@ -400,7 +408,6 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
}

/// @inheritdoc IKatanaV3PoolActions
/// @dev noDelegateCall is applied indirectly via _modifyPosition
function mint(address recipient, int24 tickLower, int24 tickUpper, uint128 amount, bytes calldata data)
external
override
Expand Down Expand Up @@ -458,7 +465,6 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
}

/// @inheritdoc IKatanaV3PoolActions
/// @dev noDelegateCall is applied indirectly via _modifyPosition
function burn(int24 tickLower, int24 tickUpper, uint128 amount)
external
override
Expand Down Expand Up @@ -542,7 +548,7 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external override noDelegateCall returns (int256 amount0, int256 amount1) {
) external override returns (int256 amount0, int256 amount1) {
require(amountSpecified != 0, "AS");

Slot0 memory slot0Start = slot0;
Expand Down Expand Up @@ -721,12 +727,7 @@ contract KatanaV3Pool is IKatanaV3Pool, NoDelegateCall {
}

/// @inheritdoc IKatanaV3PoolActions
function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data)
external
override
lock
noDelegateCall
{
function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external override lock {
uint128 _liquidity = liquidity;
require(_liquidity > 0, "L");

Expand Down
4 changes: 2 additions & 2 deletions src/core/KatanaV3PoolDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity =0.7.6;

import "./interfaces/IKatanaV3PoolDeployer.sol";

import "./KatanaV3Pool.sol";
import "./KatanaV3PoolProxy.sol";

contract KatanaV3PoolDeployer is IKatanaV3PoolDeployer {
struct Parameters {
Expand All @@ -29,7 +29,7 @@ contract KatanaV3PoolDeployer is IKatanaV3PoolDeployer {
returns (address pool)
{
parameters = Parameters({ factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing });
pool = address(new KatanaV3Pool{ salt: keccak256(abi.encode(token0, token1, fee)) }());
pool = address(new KatanaV3PoolProxy{ salt: keccak256(abi.encode(token0, token1, fee)) }());
delete parameters;
}
}
24 changes: 24 additions & 0 deletions src/core/KatanaV3PoolProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;

import "@openzeppelin/contracts/proxy/BeaconProxy.sol";

import "./interfaces/IKatanaV3PoolDeployer.sol";
import "./interfaces/IKatanaV3Factory.sol";

import "./KatanaV3Pool.sol";

contract KatanaV3PoolProxy is BeaconProxy {
constructor() BeaconProxy(address(0), "") { }

/// @dev This function will be called with zero arguments but modified to include the pool's immutable parameters.
function _setBeacon(address beacon, bytes memory data) internal virtual override {
(address factory, address token0, address token1, uint24 fee, int24 tickSpacing) =
IKatanaV3PoolDeployer(msg.sender).parameters();

beacon = IKatanaV3Factory(factory).beacon();
data = abi.encodeWithSelector(KatanaV3Pool.initializeImmutables.selector, factory, token0, token1, fee, tickSpacing);

super._setBeacon(beacon, data);
}
}
27 changes: 0 additions & 27 deletions src/core/NoDelegateCall.sol

This file was deleted.

4 changes: 4 additions & 0 deletions src/core/interfaces/IKatanaV3Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ interface IKatanaV3Factory {
/// @param tickSpacing The minimum number of ticks between initialized ticks for pools created with the given fee
event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing);

/// @notice Returns the beacon used for creating new pools
/// @return The beacon contract address
function beacon() external view returns (address);

/// @notice Returns the current owner of the factory
/// @dev Can be changed by the current owner via setOwner
/// @return The address of the factory owner
Expand Down
11 changes: 0 additions & 11 deletions test/Dummy.t.sol

This file was deleted.

31 changes: 31 additions & 0 deletions test/core/KatanaV3Factory.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.7.6;
pragma abicoder v2;

import { Test, console } from "forge-std/Test.sol";

import { KatanaV3Factory } from "@katana/v3-contracts/core/KatanaV3Factory.sol";
import { IKatanaV3Pool } from "@katana/v3-contracts/core/interfaces/IKatanaV3Pool.sol";

contract KatanaV3FactoryTest is Test {
KatanaV3Factory factory;

function setUp() public {
factory = new KatanaV3Factory();
}

function test_createPool() public {
address token0 = makeAddr("token0");
address token1 = makeAddr("token1");
uint24 fee = 3000; // 0.3%
int24 tickSpacing = 60;

address pool = factory.createPool(token0, token1, fee);

assertEq(IKatanaV3Pool(pool).factory(), address(factory));
assertEq(IKatanaV3Pool(pool).token0(), token0);
assertEq(IKatanaV3Pool(pool).token1(), token1);
assertEq(uint256(IKatanaV3Pool(pool).fee()), uint256(fee));
assertEq(uint256(IKatanaV3Pool(pool).tickSpacing()), uint256(tickSpacing));
}
}

0 comments on commit ff771e7

Please sign in to comment.