In programming, a function is a block of code that encapsulates a specific task or operation. By using functions, code reusability and maintainability are enhanced, enabling efficient programming.
Functions
A function in a smart contract executes specific actions or processes. Functions are defined as follows: they take arguments (inputs), perform some processing, and return an output. Additionally, as we will discuss later, modifiers can be added to specify the characteristics of a function.
function functionName(<arguments>) <modifier> returns (<returnType>) {
<process>
...
return returnValue;
}
An actual example of a function is as follows. ‘addressToPerson’, mentioned on day 9, is a mapping that links an address type to a Person type. Here, we are returning a uint256 type balance. The modifiers ‘public’ and ‘view’ used here add specific characteristics to the function.
contract PersonStorage {
...
function getBalanceOf(address userAddress) public view returns (uint256) {
uint256 balance = addressToPerson[userAddress].balance;
return balance;
}
...
}
Modifiers
Modifiers are descriptions that provide additional functionality or behavior to elements like functions or variables. Below are some common modifiers used with functions or variables.
Visibility Modifiers
Visibility modifiers control the access scope of functions or variables.
- public: Accessible both from inside and outside the contract (including other contracts and GUIs like Remix).
- private: Accessible only from within the contract where the function or variable is defined.
- internal: Accessible only from within the contract where it is defined or its derived contracts.
- external: Exclusively for external calls and accessible only from outside the contract.
The previously mentioned ‘getBalanceOf’ function, with the public modifier, can be called from outside the contract. For example, public is used when you want to check the result of a function execution from the GUI in Remix.
State Modifiers
State modifiers are a type of modifier that specifies how a function affects the state of the smart contract.
- view: Indicates that the function does not change the state of the smart contract, i.e., it does not modify the values of variables defined within the smart contract. Functions that do not change the state tend to have lower gas costs.
- pure: Like view, it indicates that the function does not change the state of the smart contract and additionally cannot reference external data (variables within the smart contract). Used for pure mathematical operations or calculations. Functions that do not change the state tend to have lower gas costs.
- payable: Indicates that the function allows the receipt of Ether. Explicitly specifying payable is required for operations involving the transfer of Ether, such as sending and receiving currency. The ‘getBalanceOf’ function mentioned earlier is suitable for the view modifier as it references but does not change any variables in the smart contract.
An example of payable is shown next. In this example, the ‘deposit’ function has the payable modifier. This function records the person (msg.sender) who called the function and the amount of Ether sent (msg.value) in the ‘balances’ mapping variable.
mapping(address => uint) balances;
function deposit() payable external {
balances[msg.sender] += msg.value;
}
‘msg.sender’ and ‘msg.value’ are special variables with the following meanings:
- msg.sender: The address of the person or contract that called the ‘deposit’ function.
- msg.value: The amount of Ether sent when calling the ‘deposit’ function.
An example of calling the ‘deposit’ function in Remix is as follows. Enter the amount of Ether to be sent in the ‘Value’ field and press the ‘deposit’ button to call the function from the external source (Remix). When the function is called, a transaction is issued that calls the ‘deposit’ function, including the ‘msg.sender’ address (0x5B3…) and ‘msg.value’ of 1000000000000000000 Wei (1 ETH).
Function Modifiers
Modifiers are used to add specific actions or behaviors to functions. They enable the definition of common processes between functions, enhancing their reusability.
In the following example, a modifier named ‘onlyOwner’ is defined, and it is used with the function ‘changeOwner’. This ensures that at the beginning of ‘changeOwner’, it checks if the person calling the function is the current owner. If not the owner, an error message is displayed, and the function does not execute (this action of stopping the transaction is called ‘revert’). If the caller is the owner, the address specified in the argument becomes the new owner.
contract PersonStorage {
address public owner = 0x551af3f6A226a62BCb27f08B42401bd5629f2E44;
...
modifier onlyOwner() {
// Error message means "Only the contract owner can call this function."
require(msg.sender == owner, "Only contract owner can call this function");
_; // Placeholder (where the function is executed)
}
function changeOwner(address newOwner) public onlyOwner {
owner = newOwner;
}
}
Other Modifiers
Constants are variables that always represent a fixed value. There are two modifiers for defining constants: ‘constant’ and ‘immutable’.
- constant: Once set, its value cannot be changed and will always have the same value at runtime. The value must be set when the variable is defined.
- immutable: Similar to constant, but its value must be set either when the variable is defined or within a special function called ‘constructor’, which is called during the contract’s initialization.
In the next example, the constant variable ‘FEE’ and the immutable variable ‘i_owner’ are defined. ‘FEE’ is set as 0.1 ETH and cannot be changed. ‘i_owner’ is for setting the owner’s address, but unlike constant, its value is set only once during the contract’s initialization (deployment) in the constructor function. The contract deployer’s address is set as ‘i_owner’.
As a convention, constant variables are often defined in uppercase, and immutable variables start with ‘i_’.
contract PersonStorage {
uint256 public constant FEE = 1e17; // 0.1ETH
address public immutable i_owner;
...
constructor() {
i_owner = msg.sender;
}
}
```"
Comments