The Basics of Smart Contracts 4: Constructors and SelfDestruct
- andy1265
- Jun 30, 2022
- 4 min read
In this installment we will be covering instantiating and destroying smart contracts. This will largely be focussed on how constructors work in Solidity and the selfdestruct operation which can be performed by smart contracts. We have used constructors in a basic manner in previous installments of this series however we will be covering them in more detail here.
We will be briefly covering inheritance in this article however we will only be covering it at a basic level and will be doing a more in-depth look at this functionality in a later installment of this series.
The steps necessary to setup a development environment to run through the code in this tutorial can be found in previous of this series both here and here.
Constructors
In Solidity constructors are used the first time a contract is deployed to the blockchain. As such they are only run once. This negates the need for multiple constructors and each contract can only have one constructor. A default constructor is created by the compiler is no constructor is defined.
We used a basic constructor in the previous 2 installments of this series. The code for the constructor used in our third article can be seen below:
constructor() {
setInConstructor = "This is set in the constructor.";
}
Unlike in other languages where the constructor has the same name as the class in solidity a constructor is aptly named "constructor". Also constructors access modifiers are ignored unless declared as abstract.
As mentioned above if a constructor is not defined then the compiler will define a default constructor. However if the contract inherits from another contract where a constructor is defined and requires some parameters then the child contract will need to provide the required parameters to the constructor. Otherwise if the child does not pass any parameters to the constructor then the child contract will become an abstract contract.
// SPDX-License-Identifier: MIT
pragma solidity >= 0.8.11;
contract parent {
string private message;
constructor (string memory _message) {
message = _message;
}
function getMessage() public view returns (string memory) {
return message;
}
}
contract child is parent {
constructor (string memory _message) parent(_message) {}
}
contract call {
child c = new child("This is provided to the constructor");
function getMessage() public view returns (string memory) {
return c.getMessage();
}
}
The above smart contract code contains three contracts. Child, which inherits from Parent and Call which creates an instance of child and retrieves the inherited message parameter. You can deploy this contract to a local block chain and call it in the same way we did in the first installment of this series. Change the main function in the deployment script to the code below and you should be good to go:
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
const call = await ethers.getContractFactory("call");
const Call = await call.deploy();
console.log("Call address:", Call.address);
}
Everything in this code should be reasonably straight forward however we have two important parts to cover which we have not covered before. These are the use of the default constructor in the call contract and the use of the inherited constructor in the child contract. First lets look at the inherited constructor:
contract child is parent {
constructor (string memory _message) parent(_message) {}
}
In this example we pass a string to the child constructor which then utilizes that string in the parent constructor. If we do not pass the required arguments to the base classes constructor then the inheriting class will need to be marked as abstract.
contract call {
child c = new child("This is provided to the constructor");
function getMessage() public view returns (string memory) {
return c.getMessage();
}
}
Next in the call contract we declare an instance of the child contract and provide an argument to the constructor. This is not done inside call's constructor as call does not have a constructor. Instead the default constructor is utilized and run when the contract is first deployed.
SelfDestruct
Now let's look at the selfdestruct method. This is a method that can be used on smart contracts to send all contained Eth to a provided address and destroy the contract. Once you call destruct all Eth held by the contract will be sent to the provided address and the address of the Smart Contract will contain no more code. You can still send transactions to the address and transfer Ether there, but there won't be any code that could send you the Ether back.
Lets change the call contract to look like this:
contract call {
address payable private owner;
child c;
constructor(){
c = new child("This is provided to the constructor");
owner = payable(msg.sender);
}
function getMessage() public view returns (string memory) {
return c.getMessage();
}
function destruct() public {
selfdestruct(owner);
}
}
Now if we call destruct followed by getMessage we will get an error as the contract has been destroyed.
Challenges
Implement all of these contracts and call the methods.
If you have a contract that holds Eth and a public selfdestruct method what can happen?
Implement selfdestruct in a way that it can only be executed by a specific address.
Implement selfdestruct so it can only be run by the address that deployed the contract.
Comments