Skip to content

Deploying Smart Contracts: Recommendations and Security

Deploying a smart contract to mainnet is the apex of its development, marking a crucial milestone in its lifecycle. This significant phase, however, requires careful planning and thorough preparation. Developers can heighten their confidence by methodically traversing through various preliminary stages, each offering opportunities for refining performance and enhancing security. This guide details these stages and the associated best practices for a secure and efficient deployment process. Correctly executed, this process minimizes security risks, bolsters the contract's reliability, and ultimately instills user confidence.

flowchart LR
    A[Private Testnet Deployment]
    B[Public Testnet Deployment]
    C[Mainnet Beta Deployment]
    D[Mainnet Release]
    A --> B
    B --> C
    C --> D
    A -- Test Suite --> A
    B -- Internal QA --> B
    C -- User Testing --> C

Every stage in the deployment flow diagram is intrinsically associated with progressive implementation adjustments. These include resolving bugs, incorporating quality-of-life features, and implementing performance optimizations. Each adjustment iteratively refines the smart contract system's functionality and prepares it for the next stage. This iterative refinement cycle ensures the system's readiness for the final mainnet deployment step and all user interactions beyond.

Local Testnet

Using Hardhat, developers can fork a private testnet from the mainnet state, replicating the actual conditions expected in the final deployment environment. This need not be an isolated step but can be seamlessly integrated within the test suite, ensuring the smart contract is continuously validated against realistic conditions. Such repeated deployments instill confidence by asserting no on-chain state anomalies exist, which might disrupt the real-world mainnet deployment in the future.

Developers can simulate various failure conditions and system faults by tweaking the forked chain's parameters and state. Tools such as Foundry cheat codes provide the flexibility to create these simulated conditions. Combined with a test-driven development (TDD) approach, checks for private testnet deployments can be implemented from day one. This combination of robust testing strategies and early-stage implementation checks forms a low-hanging fruit that considerably bolsters the integrity of the smart contract system before mainnet deployment.

More information on concrete testing tools and strategies can be found in the testing section. At this stage, it is also recommended to start preparing for a security audit and threat modeling.

Public Testnet

The public testnet deployment marks the initial phase where the smart contract system becomes available to the public, consequently exposing it to a broad range of interactions within this more unpredictable environment. This step allows developers, the internal quality assurance team, and the community to interact with and test the system.

The public testnet phase is an opportune moment to launch a bug bounty program, incentivizing the discovery of vulnerabilities by security researchers. It also serves as an appropriate juncture for testing monitoring tools to ensure the seamless operation of the off-chain infrastructure and that event notifications function as intended.

The degree of community involvement in the testing process largely hinges on the extent of prior community development efforts. The potential for many users engaging in system testing underlines the importance of establishing an open communication channel, like Discord or a beta tester Telegram channel, to gather feedback and identify potential bugs. Additionally, a dedicated person should be responsible for moderating the communication channels and gathering concrete feedback from user testers.

In appreciation of their participation, beta testers can be incentivized through mainnet airdrops to their addresses, encouraging user testing efforts.

Mainnet (Beta)

Following successful local and public testnet deployments, the smart contract system can commence a soft launch on the mainnet. This initial phase typically restricts the volume of assets users can deposit. Before this step, users should be informed about the extensive efforts undertaken to secure the system, including security audits and thorough testing.

Users must also understand the inherent risks of beta testing on the mainnet. They must acknowledge the possibility of losing their invested funds due to system vulnerabilities. A mainnet beta deployment marks the first real exposure of the system to a potentially hostile environment where attackers may be economically incentivized to exploit any weak spots.

The smart contract system's deployment should be a routine, if not entirely automated, operation by this stage. A functional bug bounty program and monitoring software stack should be firmly established, with the team already having gained valuable operational experience. Additionally, this stage offers a reasonable opportunity to consider incident response procedures.

To prevent an indefinite beta phase, it's essential to set a definitive timeline for this phase, such as six months. The risk of prolonged beta testing is the potential complacency that may deter the transition toward a fully-functional production-grade deployment.

A practical strategy to enforce this timeline is incorporating automatic deprecation into the smart contract system. This feature would disallow new investments while ensuring users can withdraw their funds, thereby maintaining recoverability. Consequently, users seeking ongoing interaction with the contract must transition to the final production deployment.

Below is an example of how you can implement automatic deprecation in your Solidity contract:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
modifier isActive() {
 require(block.number <= SOME_BLOCK_NUMBER);
 _;
}

function deposit() public isActive {
 //Some code
}

function withdraw() public {
 //Some code
}

In this code snippet, the isActive modifier is used to check if the current block number is less than or equal to a predefined block number (SOME_BLOCK_NUMBER). If not, the require statement will fail, and all functions using the isActive modifier will be deactivated. This effectively results in the contract deprecating after a certain number of blocks.

Secure Initialization

The deployment of upgradeable smart contracts necessitates specific security considerations. The constructor cannot be utilized with custom logic because these contracts are deployed behind a proxy, which means their constructors don't have access to the proxy contract's storage slots. An initialization function, such as provided by OpenZeppelin's Initializable, is a suitable alternative in this scenario.

However, caution must be exercised when implementing initializer functions within an inheritance hierarchy. Developers must ensure no parent initializer function is invoked twice, which could cause an error and revert the smart contract system deployment.

Furthermore, security considerations dictate that all implementation contracts deployed behind a proxy should be initialized during deployment. The initialization process must occur within the same transaction context as the deployment to prevent a frontrunning attacker from executing the initialization function immediately after the deployment transaction has been processed.

To further secure the system, it is recommended to prevent the direct usage of the implementation contract and its initialization functions. A widely accepted security best practice involves defining a constructor that invokes the _disableInitializers function. This action effectively prohibits any attacker from interacting directly with the implementation.