Last updated: December 19, 2024
SCWE-004: Circular Dependencies
Content in BETA
This content is in beta and still under active development, so it is subject to change any time (e.g. structure, IDs, content, URLs, etc.).
Send Feedback
Relationships
Description
Circular dependencies occur when two or more components depend on each other directly or indirectly, creating a loop. In smart contracts, this can lead to issues such as stack overflows, infinite loops, or other unexpected behaviors. Circular dependencies make contracts harder to upgrade, test, and maintain. It can also cause issues during contract initialization, leading to failures when interacting with external systems or other contracts.
- Refactor Dependencies: Break the circular dependencies by refactoring the code, ensuring that contracts depend on simpler, independent modules.
- Use Interfaces: Instead of direct dependencies, use interfaces to interact with external contracts, which reduces coupling and prevents circular relationships.
- Separate Responsibilities: Separate contract logic into distinct modules to avoid interdependencies. Ensure that contracts are focused on one specific responsibility.
- Upgrade Design: If upgrading contracts, be mindful of how dependencies between contracts interact. Implement upgradeable proxies to maintain contract state and prevent circular dependency issues.
Samples
Example of Circular Dependency:
pragma solidity ^0.4.0;
contract ContractA {
ContractB public contractB;
function setContractB(address _contractB) public {
contractB = ContractB(_contractB);
}
function callContractB() public {
contractB.doSomething();
}
}
contract ContractB {
ContractA public contractA;
function setContractA(address _contractA) public {
contractA = ContractA(_contractA);
}
function doSomething() public {
contractA.callContractB();
}
}
In the above example, ContractA
and ContractB
depend on each other, causing a circular dependency.
Refactored to Avoid Circular Dependency:
pragma solidity ^0.4.0;
contract ContractA {
address public contractB;
function setContractB(address _contractB) public {
contractB = _contractB;
}
function callContractB() public {
// Interact with ContractB via an interface
IContractB(contractB).doSomething();
}
}
contract ContractB {
function doSomething() public {
// Perform logic
}
}
interface IContractB {
function doSomething() external;
}
In this refactored example, ContractA
interacts with ContractB
via an interface, eliminating the circular dependency.