0% found this document useful (0 votes)
38 views25 pages

Uniswap V3 Core Contracts Security Analysis

Uploaded by

bbuigia76
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
38 views25 pages

Uniswap V3 Core Contracts Security Analysis

Uploaded by

bbuigia76
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Assignment 3

Uniswap V3 Core Contracts Analysis

A. Introduction
The increasing adoption of decentralized finance (DeFi) has underscored the critical need for
rigorous analysis of smart contracts. These contracts, the backbone of many DeFi
applications, govern millions of dollars in value and require meticulous scrutiny to ensure
their security and proper functionality. This report presents a detailed analysis of the
IUniswapV3Pool contract, a core component of the Uniswap V3 decentralized exchange.
Through examination of its Application Binary Interface (ABI), call graph, and inheritance
structure, we aim to provide a comprehensive understanding of its internal workings,
potential vulnerabilities, and overall design efficiency. Our analysis focuses on [mention
specific aspects you analyzed, e.g., the swap function, liquidity management, or fee
collection mechanisms] to highlight key aspects of the contract's architecture and security.

B. Project Information
1. Project Name: Uniswap V3 Core Contracts

2. Project Description:

This project analyzes the security and functionality of core smart contracts within the
Uniswap V3 decentralized exchange. The focus is on understanding the interaction between
key contracts, identifying potential vulnerabilities, and assessing the overall design in terms
of security and efficiency. Specific contracts analyzed include [List the exact names of the
contracts analyzed, e.g., IUniswapV3Pool, NonfungiblePositionManager, Quoter]. This
analysis will involve examining the Application Binary Interface (ABI), generating and
interpreting call graphs and inheritance diagrams, and conducting a manual code review of
critical functions. The goal is to provide a comprehensive assessment of the security posture
of these contracts, considering known smart contract vulnerabilities and best practices.

3. Team Information

● Bui Gia Bao: Role – Lead Analyst, responsible for ABI analysis and Call Graph
generation
● Pham Quang Minh: Role – Security Analyst, responsible for vulnerability assessment
and report writing
● Nguyen Chi Cuong: Role – Code Reviewer, responsible for manual code review and
inheritance diagram analysis

4. Version Information

Smart Contract Versions: v1.0.0

Page 1
C. Methodology
1. Tools Used:

Solidity Compiler: Used to compile the contract code and generate the ABI (Application
Binary Interface). [Specify the version used, e.g., Solidity v0.8.17].

Slither: A Solidity static analysis framework employed to generate call graphs and identify
potential vulnerabilities such as reentrancy, arithmetic overflow/underflow, and unchecked
return values. [Specify the version used].

Remix IDE: Used as a supporting tool for code review and contract interaction. [Optional:
mention specific plugins used in Remix].

2. Analysis Techniques:

ABI Analysis: The ABI for each contract was generated using the Solidity compiler. This ABI
was then used to understand the interface and functionality of each contract, identifying the
inputs, outputs, and general purpose of each function.

Static Analysis with Slither: Slither was used to analyze the contract code and generate call
graphs for each contract. These call graphs were visually inspected to identify potential
vulnerabilities and understand the flow of execution within and between contracts. Specific
analysis features within Slither, such as checks for reentrancy or arithmetic vulnerabilities,
were used. The results were reviewed for false positives and to ascertain their significance.

Manual Code Review: A manual code review was performed on critical functions within each
contract [specify which functions were reviewed, e.g., swap, mint, burn functions in
IUniswapV3Pool]. This involved a line-by-line inspection of the code to identify potential
vulnerabilities not detected by static analysis, including subtle logic flaws or potential edge
cases.

Inheritance Analysis (if applicable): If the contracts exhibit inheritance, a manual analysis
was performed to understand the inheritance hierarchy, identify overridden functions, and
assess the impact of inheritance on security and code reusability.

3. Limitations

Scope: Our analysis focuses on a specific set of Uniswap V3 core contracts, and it may not
capture vulnerabilities present in other parts of the system.

Static Analysis Limitations: Static analysis tools may not detect all vulnerabilities, especially
those that depend on runtime conditions or external factors.

Code Complexity: The inherent complexity of the Uniswap V3 contracts may limit the depth
of the analysis within the given timeframe.

Page 2
D. Results and Analysis

1. ABI illustrations

UniswapV3PoolDeployer contract:

[
{
"inputs": [],
"name": "parameters",
"outputs": [
{
"internalType": "address",
"name": "factory",
"type": "address"
},
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"stateMutability": "view",

Page 3
"type": "function"
}
]

The parameters() function is a view function, meaning it's read-only and doesn't modify the
contract's state. It returns the parameters required for deploying a Uniswap V3 pool:

● factory (address): The address of the Uniswap V3 factory contract. This contract is
responsible for creating and managing pools. The security of this address is critical; if
compromised, it could impact the security of all pools deployed through it.

● token0 (address): The address of the first token in the pool (determined by
lexicographical ordering of addresses). The security and functionality of this token
are implicit dependencies for the pool's security. A vulnerable or malicious token
could compromise the pool.

● token1 (address): The address of the second token in the pool. Similar to token0, its
security and functionality are essential.

● fee (uint24): The trading fee collected by the pool, denominated in hundredths of a
bip (basis points). A misconfigured fee could lead to unexpected financial results or
vulnerabilities.

● tickSpacing (int24): The spacing between ticks, which determines the granularity of
price ranges in the pool's concentrated liquidity mechanism. An incorrect tickSpacing
can negatively impact liquidity management and efficient price discovery.

Function Descriptions:

● constructor(): Initializes the contract. Likely sets the parameters state variable to
default values or initializes it to null. The exact initialization would need to be
examined further.

● parameters(): A getter function. It returns the current values stored in the parameters
state variable. This is a read-only function, and it does not modify the contract's state.
It's crucial for external entities to retrieve the deployment parameters for the pool.

● deploy(address factory, address token0, address token1, uint24 fee, int24


tickSpacing): This is the core function of the contract. It orchestrates the deployment
of a new UniswapV3Pool contract. The steps are:

● Set Parameters: It temporarily sets the parameters state variable to the


values provided as input.

● Deploy Pool (CREATE2): It uses the CREATE2 opcode to deploy a new


UniswapV3Pool contract. The salt parameter ensures predictable address
generation for the pool, given the provided token0, token1, and fee. This is

Page 4
deterministic and a security feature that can increase auditing visibility and
prevent accidental pool duplications.

● Clear Parameters: After deployment, it clears the parameters state variable to


prevent accidental reuse or exposure of sensitive deployment information.
This is a security measure.

Code Style Assessment:

● Readability and Structure: The code is relatively well-structured and easy to read.
The use of a Parameters struct improves code organization. The comments are
clear, helpful, and assist in understanding the function's purpose and logic.

● Use of Comments and Documentation: The code includes concise and informative
comments. The /// @inheritdoc directive also indicates that the parameters variable is
inheriting documentation from an interface, which is good practice for maintaining
consistency. This helps make it clear for developers who might interact with or
integrate with the deployment contract.

● Elaboration on deploy Function: The deploy function is carefully designed to use


CREATE2 for predictable pool address generation. This is a key security feature. The
temporary setting and subsequent clearing of the parameters state variable are also
important security measures, as they prevent accidental disclosure or manipulation of
those parameters. The use of delete is appropriate for releasing that storage space.

Security Analysis

● Lack of Explicit Access Control: The ABI doesn't reveal any access control
mechanisms for the parameters() function. This raises concerns about who can
access and potentially manipulate the returned parameters. If an attacker could
influence the parameters returned by this function, they could create pools with
vulnerabilities or manipulate the pools' properties. Further code inspection is needed
to determine whether there are implicit controls (e.g., using a modifier).

● Parameter Integrity: The security of deployed Uniswap V3 pools depends on the


correct values of the parameters. While the parameters() function merely returns
these parameters, the setting of these parameters within the contract itself (likely
through a constructor or a setter function) must be secured against manipulation. Any
vulnerability in the parameter setting mechanism could compromise the pools
created.

● Reentrancy Vulnerability (Indirect): Although not directly visible in the ABI, there's a
potential reentrancy vulnerability in the deploy() function (which is internal and not
exposed in the ABI). If the deploy() function interacts with external contracts before
securely setting the pool's state, a malicious contract could potentially re-enter the

Page 5
deploy() function, potentially leading to a state manipulation attack. Careful review of
the deploy() function's implementation is required to rule out this risk.

● Dependence on External Contracts: The contract's security implicitly depends on the


factory contract (factory), token0, and token1. Vulnerabilities in these external
contracts could affect the security of the pools. A thorough security audit must also
include analysis of these external contracts.

Uniswap V3 Factory contract

[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "FeeAmountEnabled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldOwner",
"type": "address"

Page 6
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"

Page 7
}
],
"name": "PoolCreated",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
}
],
"name": "createPool",
"outputs": [
{
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",

Page 8
"name": "tickSpacing",
"type": "int24"
}
],
"name": "enableFeeAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"name": "feeAmountTickSpacing",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint24",
"name": "",

Page 9
"type": "uint24"
}
],
"name": "getPool",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "parameters",
"outputs": [
{
"internalType": "address",
"name": "factory",
"type": "address"
},
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",

Page 10
"name": "token1",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

FeeAmountEnabled(uint24 fee, int24 tickSpacing): Signals that a new fee tier has been
enabled. This is important for monitoring changes in the factory's configuration and for
auditing purposes. Analyzing the emitted events can reveal the evolution of fee structures
over time. It also provides a way to verify that fees are correctly set.

OwnerChanged(address oldOwner, address newOwner): Tracks changes in the factory's


ownership. This is crucial for governance and security. Monitoring these events helps identify
potential issues such as unauthorized ownership transfers.

Page 11
PoolCreated(address token0, address token1, uint24 fee, int24 tickSpacing, address pool):
Indicates the creation of a new pool. This event logs essential information about each pool,
allowing for verification and post-deployment analysis. It enables tracking of pool creation
and can be used for identifying patterns or potential misuse of the factory.

Functions:

● constructor(): The constructor initializes the factory owner to the deployer's address,
emits an OwnerChanged event, and sets up three predefined fee amounts (500,
3000, 10000) with their respective tick spacings.

● owner(): A simple getter function returning the current owner of the factory contract.

● feeAmountTickSpacing(uint24 fee): A getter function that retrieves the tick spacing


associated with a particular fee tier.

● getPool(address tokenA, address tokenB, uint24 fee): This getter function returns the
address of a pool given the two tokens and the fee tier.

● createPool(address tokenA, address tokenB, uint24 fee): This core function creates a
new Uniswap V3 pool. It performs the following steps:

1. Input Validation: Checks that the two tokens are different, neither token is the
zero address, and the fee is valid.

2. Token Ordering: Orders tokens lexicographically to ensure consistency


(token0 < token1).

3. Fee Validation: Retrieves the tick spacing for the given fee, ensuring it exists.

4. Uniqueness Check: Verifies that no pool exists for the given token pair and
fee.

5. Pool Deployment: Deploys the pool using the deploy function inherited from
UniswapV3PoolDeployer. The factory address is passed as the deployer.

6. Mapping Population: Populates the getPool mapping in both directions


(token0, token1) and (token1, token0).

7. Event Emission: Emits a PoolCreated event.

● setOwner(address _owner): Allows the current owner to transfer ownership of the


factory contract. It requires the caller to be the current owner.

● enableFeeAmount(uint24 fee, int24 tickSpacing): This function allows the owner to


add new fee tiers with their associated tick spacings. It includes input validation to
ensure the fee and tick spacing are within reasonable limits.

Page 12
Code Style Assessment:
● Readability and Structure: The code is well-structured and readable. The use of
comments and the @inheritdoc tag makes it easy to understand the contract's
purpose and functionality.

● Comments and Documentation: The code is adequately commented, explaining the


purpose of different parts. The @inheritdoc tag is particularly helpful.

● Function createPool Elaboration: The createPool function is well-designed,


implementing thorough checks to prevent creating duplicate pools, invalid pools with
zero-addresses, or pools with unsupported fees.

Security Analysis

● Access Control: Robust access control is essential to protect functions such as


enableFeeAmount and setOwner.

● Input Validation: Thorough validation of all inputs across all functions is mandatory to
prevent the creation of invalid or malicious pools and to prevent unexpected
behavior.

● Reentrancy Protection: The createPool function, especially, needs to be protected


against reentrancy attacks, which is a common vulnerability in smart contracts.

● Error Handling: The contract should handle errors gracefully to prevent unexpected
behavior or the exposure of sensitive information.

● Upgradability (if applicable): If the factory is designed to be upgradable, the upgrade


mechanism should be extremely carefully designed to avoid vulnerabilities.

● Gas Optimization: Optimizing gas usage is crucial, especially for frequently called
functions like getPool.

NoDelegateCall Contract

Functions:

● constructor(): The constructor initializes the original state variable to the address of
the contract instance. This immutable variable is used for the reentrancy check.

● checkNotDelegateCall(): This private function is the core logic for preventing


delegatecalls. It checks if the current contract address (address(this)) matches the
address stored in the original. If they don't match, it means the function is being
called via a delegatecall, and the required statement will revert the transaction.

Code Style Assessment:

Page 13
● Readability and Structure: The code is concise and easy to understand. The
comments clearly explain the purpose and functionality. The separation of the core
logic into the checkNotDelegateCall function improves readability and reduces code
duplication in inheriting contracts, as the modifier only needs to make a call to the
helper function.

● Comments and Documentation: Good use of NatSpec comments to document the


contract's purpose, the modifier's function, and the reasoning behind using a private
function instead of inlining the check within the modifier. This improves maintainability
and readability.

● Efficiency Consideration: The use of an immutable variable in checkNotDelegateCall


avoids additional storage access during every call of the function, as the address
bytes are directly copied into the bytecode of the inheriting contracts.

Security Analysis

● Reentrancy: By preventing delegatecalls, the contract helps prevent reentrancy


attacks, a very common type of vulnerability. In a reentrancy attack, a malicious
contract calls back into the calling contract's function, manipulating its state before
the original function completes. This contract helps to prevent this by limiting the
execution context.

● Unintended State Modification: Delegatecalls can lead to unintended state changes if


the called contract has unintended side effects or bugs. This contract prevents those
by restricting access to its internal functions and state.

● Data Corruption: Malicious contracts could potentially use delegatecalls to corrupt


data within an inheriting contract if security measures weren't implemented in the
inheriting contract.

● Malicious Code Execution: This mitigates the risk that the called contract might
contain malicious code.

● Correct Implementation: The effectiveness of this contract depends entirely on the


correctness of its implementation in preventing delegatecalls. A flaw in its
implementation would negate its security benefits. A thorough code review is
necessary to validate its functionality.

● Inheritance: The security benefits only apply to contracts that correctly inherit from
NoDelegateCall and use the noDelegateCall modifier. Incorrect usage of the modifier,
or missing the modifier entirely in the inheriting contract, would completely undermine
the security goals of this contract.

Page 14
● Gas Costs: The noDelegateCall modifier will likely increase the gas cost of the
functions in which it's used. This trade-off between security and efficiency needs to
be considered when deciding on its usage.

Uniswap V3 Pool Contract

Events:

● Burn(address owner, int24 tickLower, int24 tickUpper, uint128 amount, uint256


amount0, uint256 amount1): Records the removal of liquidity from a position. The
owner, tickLower, and tickUpper parameters identify the specific position. amount is
the liquidity removed. amount0 and amount1 represent the amounts of token0 and
token1 returned to the owner. Security concerns revolve around the accuracy of the
amount0 and amount1 calculations.

● Collect(address owner, address recipient, int24 tickLower, int24 tickUpper, uint128


amount0, uint128 amount1): Logs the collection of fees by a position owner. The
accuracy of amount0 and amount1 calculations is crucial. The recipient allows
tracking of fee recipients.

● CollectProtocol(address sender, address recipient, uint128 amount0, uint128


amount1): Records the collection of protocol fees by the factory owner. Monitoring
this event is important for tracking protocol revenue.

● Flash(address sender, address recipient, uint256 amount0, uint256 amount1, uint256


paid0, uint256 paid1): This event is critical for auditing flash loans. It logs the
amounts borrowed (amount0, amount1), and the amounts repaid (paid0, paid1).
Discrepancies between borrowed and repaid amounts could indicate vulnerabilities.

● IncreaseObservationCardinalityNext(uint16 observationCardinalityNextOld, uint16


observationCardinalityNextNew): Records changes to the maximum number of
observations. This helps in understanding the pool's capacity for price/liquidity data.

● Initialize(uint160 sqrtPriceX96, int24 tick): This is emitted only once during the pool's
initialization, providing its starting price (sqrtPriceX96) and tick.

● Mint(address sender, address owner, int24 tickLower, int24 tickUpper, uint128


amount, uint256 amount0, uint256 amount1): Logs the addition of liquidity to a
position, providing similar information to the Burn event.

● SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8


feeProtocol0New, uint8 feeProtocol1New): Tracks changes to the protocol fee
parameters.

● Swap(address sender, address recipient, int256 amount0, int256 amount1, uint160


sqrtPriceX96, uint128 liquidity, int24 tick): This event is extremely important, providing
a complete record of each swap. Analysis of this event is crucial for detecting
anomalies, identifying potential manipulations, and detecting vulnerabilities.

Page 15
Functions:

● burn: Removes liquidity. Requires checking for authorization (only the position owner)
and accurate calculation of returned tokens.

● collect: Collects fees. Requires authorization (only the position owner) and accurate
fee calculations.

● collectProtocol: Collects protocol fees. Requires strict access control (likely only the
factory owner).

● flash: Supports flash loans. Extremely high security scrutiny is needed to prevent
exploits. It relies on a callback mechanism, which is a common source of
vulnerabilities.

● initialize: Initializes the pool. This function is usually only called once and needs to
correctly set the initial price and other crucial parameters.

● mint: Adds liquidity to a position. Similar to burn, it needs to accurately calculate the
amounts of tokens required and returned. Authorization is necessary.

● observe: Retrieves historical price and liquidity data. The correctness of this function
is vital for accurate off-chain analytics.

● swap: Executes a token swap. This function is very complex and therefore prone to
exploits:

● Price Manipulation: Preventing price manipulation is crucial.

● Front-running: The order of operations should prevent front-running attacks


(where an attacker observes a swap and places their order ahead, profiting
from the price movement).

● Reentrancy: Protecting this function from reentrancy attacks is very important.

● Underflow/Overflow: The calculations should have robust checks against


arithmetic errors.

● Slippage: The swap should handle slippage appropriately.

● Getter functions: (factory, fee, feeGrowthGlobal0X128, feeGrowthGlobal1X128,


liquidity, maxLiquidityPerTick, observations, positions, protocolFees, slot0,
tickBitmap, tickSpacing, ticks, token0, token1) These are generally less vulnerable
but should still be reviewed to ensure correctness.

Code Style Assessment:

Page 16
● Readability and Structure: The code is highly complex, making readability
challenging. The use of structs (Slot0, ProtocolFees, ModifyPositionParams,
SwapCache, SwapState, StepComputations) and comments helps manage this.
However, some functions are exceptionally long and would benefit from better
decomposition into smaller, more focused functions.

● Comments and Documentation: The contract has good NatSpec documentation, but
it's not enough to fully clarify some complex internal operations. More comments
within complex calculations would improve comprehension.

● Gas Optimization: The comments indicate various gas optimizations (e.g., using
staticcall, avoiding redundant checks)

Security Assessment

● Reentrancy: The swap, mint, and flash functions are particularly vulnerable to
reentrancy attacks due to their external calls. The lock modifier provides some
protection, but thorough analysis of the interaction with callbacks is critical.

● Arithmetic Overflow/Underflow: Given the extensive use of arithmetic operations, this


is a major concern. While LowGasSafeMath is used, it's important to double-check all
calculations, especially in edge cases, as vulnerabilities may still exist. A thorough
analysis of FullMath and other libraries is crucial to assess whether there are subtle
issues in the mathematical calculations that the SafeMath library does not address.

● Price Manipulation: The swap function needs careful scrutiny to ensure that it is
resistant to price manipulation attacks.

● Input Validation: Many require statements to protect against invalid inputs, but an
exhaustive check is needed.

● Callback Attacks: The callback functions are potential attack vectors. Malicious
callbacks could manipulate the contract's state in unexpected ways. The checks
made after the callbacks are important for verifying that the contract's state has not
been unexpectedly changed. These checks are vital and need thorough testing.

● Data Corruption: With such extensive data structures in memory, thorough


consideration is needed to ensure that the data in memory is correctly initialized, and
also correctly used throughout.

Page 17
2. Call Graph Discussion

Overview
The graph depicts a complex ecosystem of smart contracts, libraries, and interfaces
for a contract system, from UniswapV3. It organizes them into clusters (subgraphs),
representing individual contracts, interfaces, or reusable libraries. These are linked by call
relationships, either internal or external, forming a comprehensive blueprint of the system’s
architecture.

Graph
Since the graph is too big, please refer to this link:

[Link]

Graph Structure
● Graph Settings:
○ ratio = "auto", bgcolor = "#2e3e56": Specifies layout and background color.
○ Nodes and edges are styled for a visually distinct display with custom colors
and fonts.
● Node Appearance:
○ Nodes use different shapes, colors, and labels to denote various roles (e.g.,
constructors, methods, library functions).
○ Highlighted in colors such as red-pink for constructor, green for special
methods, and others.
● Edge Appearance:
○ Edges are color-coded:
■ Green for internal calls.
■ White for external calls.

Subgraphs

The graph is divided into clusters (subgraphs), each representing a smart contract, interface,
or library:

1. Contracts:
○ NoDelegateCall:
■ Includes methods like <Constructor> and checkNotDelegateCall.
○ UniswapV3Factory:
■ Implements methods like createPool, setOwner, and deploy.
○ UniswapV3Pool:
■ A key contract with multiple methods for managing liquidity, swaps,
and balances (mint, swap, burn, etc.).
2. Interfaces (iface):

Page 18
○ Represent contract interfaces like IUniswapV3Factory, IUniswapV3Pool, and
others.
○ Define expected methods like owner, feeAmountTickSpacing, and getPool.
3. Libraries (lib):
○ Examples include TickMath, SqrtPriceMath, and FullMath.
○ Provide utility functions for mathematical operations, like getTickAtSqrtRatio,
mulDiv, and addDelta.
4. Supporting Structures:
○ Other clusters represent helper libraries, utility functions, or shared resources
like observations, ticks, and positions.

Relationships

● Edges connect methods, indicating call hierarchies and interactions.


● Internal calls stay within a single contract or library, while external calls cross contract
boundaries.
● Shared dependencies are visualized (e.g., observations and positions).

In-depth

1. Contracts

Contracts define the core business logic and operational mechanisms of the protocol. They
contain methods and constructors represented as nodes. Notable contracts include:

a. NoDelegateCall

● Purpose: Prevents certain types of exploits where delegatecall can be abused.


● Methods:
○ Constructor: Initializes the contract (highlighted in red).
○ checkNotDelegateCall: A validation method.
○ noDelegateCall: Core functionality of the contract.

b. UniswapV3Factory

● Purpose: Factory pattern contract for deploying and managing pools.


● Methods:
○ Constructor: Initializes the factory.
○ createPool: Deploys a new pool.
○ setOwner: Updates the contract owner.
○ enableFeeAmount: Configures fee parameters for pools.
○ deploy: Actual deployment logic for pools.

c. UniswapV3Pool

● Purpose: Core pool logic for managing liquidity, swaps, and fees.
● Methods:

Page 19
○ Constructor: Initializes the pool with configuration.
○ mint, burn, swap: Core operations for liquidity providers and traders.
○ flash: Executes flash loans.
○ initialize: Configures initial pool state.
○ _modifyPosition, _updatePosition: Internal methods for managing liquidity
positions.
○ observe, snapshotCumulativesInside: Time-weighted average price (TWAP)
and cumulative data management.
○ balance0, balance1: Tracks token balances in the pool.
○ checkTicks: Ensures tick boundaries are valid.

2. Interfaces

Interfaces define standard methods that contracts are expected to implement. They
represent dependencies or externally called contracts. Examples include:

a. IUniswapV3Factory

● Contains methods like owner, createPool, setOwner, and enableFeeAmount.

b. IUniswapV3Pool

● Includes core pool methods like mint, swap, burn, and TWAP-related methods
(observe, snapshotCumulativesInside).

c. IUniswapV3PoolState

● Exposes state-related functions like slot0, feeGrowthGlobal, and liquidity.

d. IERC20Minimal

● Represents ERC20 token interactions with functions like transfer, approve, and
transferFrom.

3. Libraries

Libraries provide reusable logic for calculations, validations, or transformations. These are
stateless and serve as utility modules.

a. TickMath

● Core library for managing ticks in the pool.


● Functions include:
○ getTickAtSqrtRatio: Maps a price to a tick.
○ getSqrtRatioAtTick: Converts a tick to a price.

b. SqrtPriceMath

Page 20
● Calculates changes in token amounts and prices during swaps.
● Functions include:
○ getNextSqrtPriceFromInput and getNextSqrtPriceFromOutput: Predicts new
prices based on trades.
○ getAmount0Delta and getAmount1Delta: Computes token amounts for swaps.

c. FullMath

● Implements precise mathematical operations.


● Functions include:
○ mulDiv: Multiplies two numbers and divides the result.
○ mulDivRoundingUp: Rounding version of mulDiv.

d. UnsafeMath

● Provides optimized but less safe math operations like divRoundingUp.

e. Other Libraries:

● LiquidityMath: Adjusts liquidity values with methods like addDelta.


● Oracle: Manages price observations and TWAP.
● TransferHelper: Safely transfers tokens.
● BitMath: Computes significant bits for efficient indexing.

4. Supporting Structures

These clusters represent reusable components within the protocol:

Observations

● Manages historical price and liquidity data.


● Functions include initialize, write, observe, and grow.

Ticks

● Represents ranges within the pool.


● Functions include update, clear, cross, and getFeeGrowthInside.

Positions

● Tracks liquidity positions in the pool.


● Functions include get and update.

Balances

● Tracks token balances before and after operations.

Page 21
Call Relationships

● Internal Calls (green): Represent function calls within the same contract or library.
● External Calls (white): Represent cross-contract or interface method calls.
● Examples:
○ [Link] -> [Link] (Internal).
○ [Link] -> [Link] (External).

3. Inheritance Graph Discussion


Overview:
Inheritance is a way of extending the functionality of a program, used to separate the code,
reduces the dependency, and increases the re-usability of the existing code. Solidity
supports inheritance between smart contracts, where multiple contracts can be inherited into
a single contract. The contract from which other contracts inherit features is known as a
base contract, while the contract which inherits the features is called a derived contract.
Simply, they are referred to as parent-child contracts. In the Uniswap V3 Core contracts,
inheritance can be seen in managing and organizing various functionalities across different
contracts.

Graph:

[Link]

Key Contracts and Their Relationships:

1. Core Uniswap Contracts:


○ Pool Contract: Inherits from base liquidity and math libraries. It facilitates
liquidity provision, swaps, and fee management.
○ Factory Contract: Inherits from base factory functionalities. It is responsible
for deploying new pools and managing ownership rights.

Explanation of Key Relationships:

● UniswapV3Pool → NoDelegateCall: UniswapV3Pool inherits from NoDelegateCall


to mitigate risks related to reentrancy attacks or unintended execution of external
code. This modifier ensures that certain functions in the pool cannot be executed via
delegatecall, which could alter the contract’s state unexpectedly.
● UniswapV3Pool → IUniswapV3Pool: IUniswapV3Pool is an interface that defines
the required methods for interacting with a Uniswap V3 pool. By inheriting from this
interface, UniswapV3Pool ensures that it conforms to the expected structure and
provides essential pool functionalities such as swap execution, liquidity management,
and fee accrual.
● UniswapV3Factory → UniswapV3PoolDeployer: The UniswapV3PoolDeployer
contract is responsible for deploying new liquidity pool contracts. By inheriting from it,

Page 22
UniswapV3Factory leverages the deployment logic to create new instances of
UniswapV3Pool.
● UniswapV3Factory → NoDelegateCall: NoDelegateCall is a security mechanism
that prevents function calls from being executed through delegatecall. This is crucial
for protecting sensitive operations and preventing reentrancy attacks.
● UniswapV3Factory → IUniswapV3Factory: IUniswapV3Factory defines the
standard interface that UniswapV3Factory must implement. It specifies the core
functionalities required for factory operations, such as pool deployment and retrieval.

Code Reusability:

Through inheritance Solidity allows developers to reuse existing code, such example
can be seen in the UniswapV3:

● The UniswapV3Factory inherits functionality from UniswapV3PoolDeployer to


deploy new liquidity pools.
● The UniswapV3Pool inherits from IUniswapV3Pool, ensuring it follows the
required interface, allowing for predictable interactions.

Through the usage of Solidity libraries code can be reused in situations where
multiple contracts require the same logic. An example of this is the Tickmath library
used to handle tick-related calculations for pool management which provide a
reusable, standardized way to handle these operations.

Code reusability can bring many benefits such as easier maintenance, scalability and
reduce the coding time.

Function and State Variable Overriding:

Function overriding occurs when a derived contract provides a new implementation


of a function that was already defined in a parent contract or interface. In Solidity, a
derived contract can override the function’s behavior by using the override
keyword. This is essential for customizing functionality while preserving the inherited
structure.

This function overriding can be seen in plenty in UniswapV3Factory. An example:

function setOwner(address _owner) external override {

require([Link] == owner);

emit OwnerChanged(owner, _owner);

owner = _owner;

Page 23
State variable overriding occurs when a derived contract modifies or extends a state
variable that was declared in a parent contract. In Solidity, derived contracts can
redefine state variables or add new ones to extend the contract's functionality, often
for the specific context in which they are used.

By overriding the state variable you allow for customizations in functionality such as
pool-specific parameters or fee structures.

The ability to override is important because it allows for better customization, clear
separation of concerns and also provides flexibility and upgradeability.

Function Visibility and Access Control:

Function visibility and access control plays a key role in ensuring the security and
functionality of smart contracts. They help determine which functions can be
accessed and by whom, directly impacting the contract's behavior and safety.

There are 4 different types of function visibility: public, private, internal and external.
These can help ensure no critical private or internal functions are exposed. Access
control is a mechanism that helps control access to functions, enhancing the security
of smart contracts.

There are many benefits to function visibility and access control such as:

● Improving security
● Separating operational control
● Help improve code understanding
● Serves as clear documentation

Modifier Application:

Modifiers in Solidity are reusable code blocks that can change or restrict the behavior
of functions. They play a crucial role in enforcing access control, validating
conditions, and ensuring security within smart contracts.

Modifiers can be inherited from parent contracts and applied to functions in child
contracts. An inheritance diagram can illustrate:

1. Modifier Inheritance:
○ Shows which modifiers are inherited and reused in derived contracts.
○ Helps track the flow of access control logic across multiple contracts.
2. Modifier Overrides:
○ Indicates if modifiers have been overridden or customized in derived
contracts.
○ Ensures that critical security checks are not bypassed or altered
unintentionally.
3. Affected Functions:
○ Highlights which functions are protected by specific modifiers.
○ Allows developers and auditors to verify that sensitive functions are
appropriately secured.

Page 24
Page 25

You might also like