Inheritance and Modifiers
Letās start with a simple token-like Smart Contract. Mind, this is not an ERC20 Token contract. Its much simpler than that, and this code sample is only here to demonstrate how modifiers and inheritance works!
Copy the following contract over to Remix to follow along
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
contract InheritanceModifierExample {
mapping(address => uint) public tokenBalance;
address owner;
uint tokenPrice = 1 ether;
constructor() {
owner = msg.sender;
tokenBalance[owner] = 100;
}
function createNewToken() public {
require(msg.sender == owner, "You are not allowed");
tokenBalance[owner]++;
}
function burnToken() public {
require(msg.sender == owner, "You are not allowed");
tokenBalance[owner]--;
}
function purchaseToken() public payable {
require((tokenBalance[owner] * tokenPrice) / msg.value > 0, "not enough tokens");
tokenBalance[owner] -= msg.value / tokenPrice;
tokenBalance[msg.sender] += msg.value / tokenPrice;
}
function sendToken(address _to, uint _amount) public {
require(tokenBalance[msg.sender] >= _amount, "Not enough tokens");
tokenBalance[msg.sender] -= _amount;
tokenBalance[_to] += _amount;
}
}
Try the Smart Contract
What does the contract do? Letās give it a try! Head over to the Deploy and Run Transactions tab.
- Select Account#1 from the Accounts Dropdown
- Deploy the Contract
Buy Tokens
- Switch over to Account #2
- Enter 1 into the value field
- Select āEtherā from the Dropdown
- Buy 1 Token for 1 Ether by hitting the purchase button
Get the Token Balance
Now lets look if you have the right balance. Copy your address, fill it into the input field for ātokenBalanceā and see if the balance is 1:
Burn Tokens
Now lets see what happens if you stay on Account#2 and try to burn tokens:
- Try to burn with Account #2
- Observe the Error message
- Which is coming from the require statement
Problem
Right now we have several similar require statements. They are all testing if a specific address called the smart contract. To avoid code duplication and make it easier to change this from a single place, we can use modifiers:
//other code
modifier onlyOnwer {
require(msg.sender == owner, "You are not allowed");
_;
}
//...
Letās add this to the contractā¦
Adding A Simple Solidity Modifier
Now that you know whats the purpose of a modifier, letās add a simple modifier and remove the code duplication:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
contract InheritanceModifierExample {
mapping(address => uint) public tokenBalance;
address owner;
uint tokenPrice = 1 ether;
constructor() {
owner = msg.sender;
tokenBalance[owner] = 100;
}
modifier onlyOwner() {
require(msg.sender == owner, "You are not allowed");
_;
}
function createNewToken() public onlyOwner {
tokenBalance[owner]++;
}
function burnToken() public onlyOwner {
tokenBalance[owner]--;
}
function purchaseToken() public payable {
require((tokenBalance[owner] * tokenPrice) / msg.value > 0, "not enough tokens");
tokenBalance[owner] -= msg.value / tokenPrice;
tokenBalance[msg.sender] += msg.value / tokenPrice;
}
function sendToken(address _to, uint _amount) public {
require(tokenBalance[msg.sender] >= _amount, "Not enough tokens");
tokenBalance[msg.sender] -= _amount;
tokenBalance[_to] += _amount;
}
}
Re-Deploy the Smart Contract and Test
- Deploy the Contract with Account#1
- Purchase Tokens with Account#2
- Try to Burn with Account#2
It should error out.
Problem
Right now, weāre doing two things in one Smart Contract:
The token logic and the owner functionality.
We can further simplify the Smart Contract by using an Owner Smart Contract and then extending the Functionality. Letās do that nextā¦
Inhertiance with Solidity
Now its time to split up our Smart Contract into smaller pieces. We are going to do one Smart Contract that manages the ownership, and another one that does the Token logic:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
contract Owned {
address owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "You are not allowed");
_;
}
}
contract InheritanceModifierExample is Owned {
mapping(address => uint) public tokenBalance;
uint tokenPrice = 1 ether;
constructor() {
tokenBalance[owner] = 100;
}
function createNewToken() public onlyOwner {
tokenBalance[owner]++;
}
function burnToken() public onlyOwner {
tokenBalance[owner]--;
}
function purchaseToken() public payable {
require((tokenBalance[owner] * tokenPrice) / msg.value > 0, "not enough tokens");
tokenBalance[owner] -= msg.value / tokenPrice;
tokenBalance[msg.sender] += msg.value / tokenPrice;
}
function sendToken(address _to, uint _amount) public {
require(tokenBalance[msg.sender] >= _amount, "Not enough tokens");
assert(tokenBalance[_to] + _amount >= tokenBalance[_to]);
assert(tokenBalance[msg.sender] - _amount <= tokenBalance[msg.sender]);
tokenBalance[msg.sender] -= _amount;
tokenBalance[_to] += _amount;
}
}
You see, now we have two contracts in one file.
Be careful when compiling and deploying the contract to select the correct one from the dropdown:
Of course, its not ideal to have multiple contracts in one file. Thatās why we can also separate them and import contracts. Letās do that next!
Importing Solidity Contracts
One great feature of Solidity is the ability to import contracts into other contracts.
Did you know? In Remix you can also directly import contracts from github repositories
Letās take the previous contract and split it up into two separate files:
Ownable.sol Token.sol
Create these two files in Remix and copy the following content into the files:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
contract Owned {
address owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "You are not allowed");
_;
}
}
Letās test this and head over to the Run Tab. You will see again several contracts in the dropdown.
If you select āTokenā from the dropdown and deploy it, youāll get the same contract as in the beginning, but better:
- The Contracts are split up into smaller logical units
- With modifiers we have re-usable small components we can also use in contracts extending the base contract
- With separate files we created a better organization of code
Now, letās have a look what happens here behind the scenes!