Introduction
Smart contracts are digital agreements that exist on the blockchain and have changed how we conduct transactions. They provide trustless implementation of agreements by operating without middlemen. Ethereum, a central blockchain platform, is home to a thriving ecosystem of these Solidity-written, self-executing contracts.
Deploying a contract from a wallet address is easy, but in time one may wonder if it is possible to deploy a smart contract from another smart contract. Yes, it is possible.
In this article, we'll be looking into three techniques that enable the creation of complex, interconnected decentralized applications.
Let's dive into the core of blockchain development, where contract development takes place.
Prerequisites
Before starting, we need some prerequisites that are important for you to move forward with this article.
- Solidity Basics: Familiarity with Solidity is essential. It's the programming language used to write smart contracts on the Ethereum blockchain. If you're new to Solidity, consider reviewing introductory tutorials or courses. You can read Introduction to Solidity. This article will provide a basic understanding for you to start your solidity journey.
- Ethereum Familiarity: A foundational understanding of Ethereum and its blockchain ecosystem is assumed. This includes knowledge of Ethereum's purpose, how transactions work, and the role of smart contracts.
- Basic Deployment Knowledge: A grasp of basic smart contract deployment is recommended. This includes knowing how to deploy a standalone smart contract to the Ethereum blockchain. If you're unfamiliar, a quick primer on deploying contracts will be beneficial. Feel free to read my previous article on how to deploy smart contracts using Remix to get some basic knowledge on deployment.
Now we are ready to start with our journey of how to deploy a smart contract from another smart contract.
Understanding Contract Deployment
Smart contract deployment is the process of uploading a self-executing digital contract onto the Ethereum blockchain. This allows the people across the network to access, execute, and interact with it. The deployment process involves specifying the contract's code, initial parameters, and the address where it will reside on the blockchain.
Introduction to Bytecode
Bytecode is the machine-readable, low-level version of a smart contract's code. Unlike high-level programming languages, which are designed for human understanding, bytecode is what the Ethereum Virtual Machine (EVM) reads and executes. It is made up of a set of hexadecimal instructions that indicate the operations that the EVM will perform.
Understanding bytecode is crucial for developers, as it enables them to optimize and debug their smart contracts. It also provides insights into the efficiency and gas costs associated with executing specific operations.
The Role of Ethereum Virtual Machine (EVM)
The Ethereum Virtual Machine (EVM) is the decentralized computer that processes and executes code on the Ethereum network. It's a crucial component that maintains network consensus among all members. Each node in the Ethereum network runs its own instance of the EVM, which collectively maintains the integrity and security of the blockchain.
The EVM is capable of executing smart contracts by interpreting and processing their bytecode instructions. This enables trustless and automated contractual agreement execution without the use of middlemen.
You'll be well-prepared to investigate advanced strategies for deploying contracts from within other contracts if you understand the fundamentals of contract deployment, bytecode, and the EVM. Let us continue on our way.
Three ways of deploying a smart contract from another smart contract
There are a total of three ways in which we can deploy a smart contract from inside another smart contract. These ways are.
- The Factory Pattern
- Using new keyword
- Using CREATE2 Opcode
Let's discuss them one by one.
1. The Factory Pattern Method
The Factory Pattern is a fundamental design pattern in smart contract development. It provides a structured way to create multiple instances of a contract with predefined parameters. Consider it a blueprint or template for creating new contracts. This pattern is very important since it increases code reusability and efficiency.
Developers can use the Factory Pattern to design a single smart contract, referred to as the "factory contract," that is responsible for creating new instances of a target contract. This target contract can have certain qualities or settings that can be customized with each new instance. This method makes it easier to deploy similar contracts several times.
Let's consider a scenario where we want to create a decentralized marketplace with multiple vendors. For this first, we need to create a vendor contract. Create a simple vendor contract with the constructor.
// Vendor Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Vendor {
string public name;
uint256 public balance;
constructor(string memory _name, uint256 _initialBalance) {
name = _name;
balance = _initialBalance;
}
}
Here, we have created a Vendor contract with two state variables, name and balance which are being initialized by the contract deployer during the time of contract deployment using the constructor.
Now the Factory Pattern allows us to deploy a single Vendor Contract with the necessary functions and data structures. The factory contract can then be used to create distinct instances for each vendor. Here's a simplified Solidity code snippet for the Factory Pattern concept:
// Factory Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract VendorFactory {
address[] public vendors;
function createVendor(string memory name, uint256 initialBalance) public {
address newVendor = address(new Vendor(name, initialBalance));
vendors.push(newVendor);
}
}
Here, we have created a function createVendor which takes two parameters name and initialBalance. In this function, we are creating an instance of the Vendor contract using the name and initial balance. This way, we can deploy multiple vendors using the same function and store the address of the newly deployed vendor contract in the vendor array.
Remember to keep the contracts in the same file, or if you are using different files, then you can import the vendor contract file into the factory contract file.
2. Using new keyword
The new keyword is a critical tool in the Solidity programming language that enables dynamic contract creation. It allows a smart contract to deploy new instances of other contracts during its execution. This allows developers to build complex systems where contracts can create and interact with one another.
The new keyword in Solidity causes a new contract instance to be created on the Ethereum network. This happens during runtime, which means it happens during contract execution rather than upon deployment. It enables dynamic contract formation depending on conditions, parameters, or user inputs, providing a strong framework for decentralized application development.
Example
Consider a scenario where we are building a decentralized crowdfunding platform. We want project creators to be able to launch their campaigns as separate contracts. The new keyword facilitates this dynamic contract creation. Here's a simplified Solidity code snippet to illustrate this concept:
//Campaign Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Campaign {
string public name;
uint256 public goal;
address public creator;
constructor(string memory _name, uint256 _goal, address _creator) {
name = _name;
goal = _goal;
creator = _creator;
}
}
Here, We are creating a template Campaign Contract which will be deployed every time a new campaign is created. This way, we can manage multiple campaigns as different smart contracts.
Now we will be writing our main contract crowdfunding contract. This will be the main contract which will deploy separate contracts every time on different campaigns.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "./Campaign.sol";
contract CrowdfundingPlatform {
mapping (string => address) private projectAddress;
// Create a new campaign
function createCampaign(string memory projectName, uint256 fundingGoal) public {
address newCampaign = address(new Campaign(projectName, fundingGoal, msg.sender));
// Further logic to manage the campaign
projectAddress[projectName] = newCampaign;
}
function getCampaignAddress(string memory projectName) public view returns(address) {
return projectAddress[projectName];
}
}
Here, we are creating a crowdfunding contract that has two functions. The first one, createCampaign, takes two parameters, projectName, and fundingGoal, to create a campaign by deploying a smart contract. This function employs the new
keyword to create a new instance of the Campaign contract, customizing it with the project name, funding goal, and the address of the creator. The other function is to view the address of the campaign using the campaign name.
Through the use of the new keyword, developers can dynamically spawn new contracts, opening up a world of possibilities for creating sophisticated decentralized applications.
With this knowledge, we're equipped to explore further techniques for deploying contracts from within other contracts. Let's continue our journey.
3. Using CREATE2 Opcode
The CREATE2 opcode is a powerful feature in Ethereum that allows for deterministic contract creation. Unlike traditional contract deployment methods, CREATE2 enables developers to deploy contracts at a specific address based on a given set of parameters. This provides a level of predictability and control not achievable with other methods.
CREATE2 calculates the ultimate address of the freshly deployed contract using a mix of elements such as the address of the deploying contract, a salt value, and the contract bytecode. This is extremely useful for applications that need the creation of customized contracts in a predictable and secure way.
Example.
Consider a scenario where we are creating a Multisig contract with a feature of deploying contracts. This type of Multisig wallet will help in deploying contracts more securely, as to execute any transaction from a Multisig wallet, there is always a need for more than one approval of the transaction. In this tutorial, I will be more focussed on explaining how we can deploy any contract from the contract. If you want to know how to create a multi-sig wallet, read my article Creating MultiSig Wallet Contract
To deploy a contract from another contract, we need the bytecode of the compiled contract that we want to deploy. The bytecode should contain the constructor arguments, if there are any.
//Multisig Wallet Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract Multisig {
mapping(uint256 => address) contractAddress;
function DeployContract(bytes memory bytecode, uint256 salt)
public
returns (address)
{
address createdAddr;
assembly {
createdAddr := create2(
0,
add(bytecode, 0x20),
mload(bytecode),
salt
)
}
return createdAddr;
}
}
Here, In this contract, we are creating a function to deploy a contract using its bytecode. We can get the bytecode of the contract to be deployed after compiling the contract on any compiler. The bytecode should contain the constructor arguments if there are requirements for any. Salt is a random number which is provided by the user.
Using this CREATE2 Opcode, we can deploy any smart contract, all we need is to have the bytecode of the contract.
Security Considerations
A developer should always be mindful of the security of the code that is being provided. The world of blockchain and crypto is both fascinating and risky at the same time. There are certain risks that always follow the tracks of the developers while smart contract development
Potential Security Risks
Deploying contracts from contracts introduces certain security considerations that developers must be aware of
- Reentrancy Attacks: Contracts calling other contracts can be vulnerable to reentrancy attacks, where an external contract exploits a callback mechanism to re-enter the original contract and manipulate state variables.
- Uninitialized or Incomplete Initialization: When deploying contracts from within contracts, ensure that all necessary variables are properly initialized to prevent unexpected behavior.
- Gas Limitations: Deploying contracts consumes gas. It's essential to consider the gas costs, especially when deploying multiple contracts from a single contract.
Tips for Secure Development and Best Practices
To mitigate the above-discussed risks, consider the following tips and best practices:
- Use Checks-Effects-Interactions Pattern: Ensure that all state changes are made before interacting with external contracts. This helps prevent reentrancy attacks.
- Careful Use of External Calls: Exercise caution when making external calls within a contract. Be aware of potential reentrancy vulnerabilities and use mechanisms like checks and balances before making calls.
- Comprehensive Testing: Thoroughly test contracts that deploy other contracts, covering different scenarios and edge cases to identify and fix potential vulnerabilities.
- Code Auditing: Have experienced developers review the code, looking specifically for security vulnerabilities. External audits can provide valuable insights and recommendations.
- Avoid Hardcoding Addresses: Instead of hardcoding addresses, use patterns like the Factory Pattern or CREATE2 opcode to ensure flexibility and security in contract deployment.
Conclusion
In this article, we explored three powerful techniques for deploying smart contracts from other smart contracts: the Factory Pattern, new keyword, and CREATE2 opcode. These methods offer unique advantages for creating complex decentralized applications. Remember to prioritize security, follow best practices, and happy coding!
FAQs
Q 1. What are the key techniques for deploying smart contracts from other contracts?
Ans. The key techniques for deploying smart contracts from other contracts are the Factory Pattern, the 'new' keyword, and the CREATE2 opcode.
Q 2. How can developers ensure security when deploying contracts from contracts?
Ans. Developers can ensure security when deploying contracts from contracts by following best practices such as using the Checks-Effects-Interactions pattern, being cautious with external calls, conducting comprehensive testing, and seeking code auditing from experienced developers.
Q 3. What are the benefits of using the Factory Pattern in smart contract development?
Ans. The Factory Pattern in smart contract development offers benefits such as increased code reusability, efficiency, and structured creation of multiple instances of a contract with predefined parameters. This pattern serves as a blueprint or template for creating new contracts, making it easier to deploy similar contracts multiple times.