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.
Multiple 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.
Github Imports
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");
_;
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import "./Ownable.sol";
contract Token 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;
}
}
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!