Functions, Constructors and require
Lab deck 11: Solidity function syntax, the four visibility levels (public, private, internal, external), state-changing vs view vs pure functions, the address type, msg.sender, constructors, and require() for access control, built up through the UniversityDegree certificate contract.
Solidity functionsSearch on YouTube
A function in Solidity is a self-contained, executable block of code. It decides how a contract behaves, calculates data, or changes the blockchain's permanent state.
Every function follows a strict formula:
function <name>(<parameters>) <visibility> <state modifier> returns (<return type>) { <body> }| Part | Role |
|---|---|
function | Required keyword that declares the block |
<name> | Identifier used to call the function |
<parameters> | The incoming raw data |
<visibility> | Who is allowed to call the function |
<state modifier> | How the function interacts with blockchain storage |
returns (<type>) | The exact data type the function outputs |
<body> | The logic and computation |
Types by visibilitySearch on YouTube
Visibility modifiers are internal firewalls. They define exactly who may trigger a function.
| Visibility | Outside users / contracts | Same contract | Inheriting contract | Note |
|---|---|---|---|---|
| public | Yes | Yes | Yes | Most open level |
| private | No | Yes | No | Most restrictive; invisible outside |
| internal | No | Yes | Yes | Standard protected level |
| external | Yes | No (cannot call itself directly) | No direct call | Outward-facing only; gas-efficient for large array inputs |
public (universal access)Search on YouTube
string public latestStudent;
// An open door: anyone can trigger this function to update the state.
function issueCertificate(string memory _studentName) public {
latestStudent = _studentName;
}Anyone can call it: external wallets, other contracts and other functions in the same contract.
private (maximum restriction)Search on YouTube
// Only other functions in THIS file can trigger the hash generation.
function _generateInternalData(string memory _studentName) private pure
returns (bytes32) {
return keccak256(abi.encodePacked(_studentName, "UNIVERSITY_SECRET"));
}Only functions inside this exact contract can call it. It is invisible to the outside world and to any contract that inherits from it. Convention: prefix private function names with an underscore.
private hides a function from other contracts, not the data from people. All contract storage is readable on chain (for example with eth_getStorageAt). Never store real secrets in a contract.
internal (inheritance allowed)Search on YouTube
bool public systemActive = true;
// Hidden from the public, but can be used by an inheriting "MedicalCollege" contract.
function _validateCertificate() internal view returns (bool) {
// Reverts the transaction if the system is not active
require(systemActive == true, "System is currently paused");
return true;
}Like private, it is invisible to the outside world. But a new contract that inherits this one can use it.
external (outside access only)Search on YouTube
// Designed exclusively for employers or web portals querying the contract.
// Internal contract logic cannot trigger this check.
function verifyCertificate(uint256 _certificateId) external view returns (bool) {
// Logic to verify and return the certificate status
return true;
}Only outside users or other contracts can call it. A function inside the contract cannot call its own external function directly.
Visibility from open to closed: "Please Enter Inside Privately": Public, External, Internal, Private.
Types by state interactionSearch on YouTube
The EVM strictly tracks how a function touches permanent storage.
| State-changing (default, no modifier) | view | pure | |
|---|---|---|---|
| Access | Full read and write | Read-only | None: completely isolated |
| Can do | Change state variables, send crypto, deploy contracts | Inspect state variables, not change them | Work only on parameters and local variables (a calculator) |
| How it runs | Signed transaction, network consensus | A local node answers from current state | Local computation |
| Gas when called externally | Costs gas | Zero | Zero |
Analogy: a university degreeSearch on YouTube
| State-changing (the issuer) | view (the verifier) | pure (the utility calculator) |
|---|---|---|
| Graduation day. The registrar awards a B.Tech degree to a student's wallet address. | A student applies for a job. The employer checks the degree is real, not a forged PDF. | Before issuing, the contract needs a unique "Certificate ID" from roll number and graduation year. |
| This creates a permanent record. The registrar sends a transaction that overwrites blank state with the credentials. | The employer's portal reads the studentDegrees mapping (the master vault) for the wallet. | The function combines roll number and year and outputs a hash. |
| It uses permanent storage, so the university signs and pays gas. | It only reads, so it is instant and free. | It neither reads studentDegrees nor saves the result. Isolated from storage, it costs zero gas. |
view and pure are free only when called from outside (an eth_call). If a state-changing transaction calls them internally, their execution is part of that transaction's gas.
Constructors: giving the contract an identitySearch on YouTube
The problem with version 1Search on YouTube
function issueCertificate(string memory _studentName) public { ... }If anyone can call issueCertificate(), anyone can create a certificate. Anyone is the university. An attacker can submit a transaction that overwrites any degree with fake data. Smart contracts are open by default. This contract has no concept of identity and no memory of who created it.
The fix: teach the contract to remember the real university identity at deployment. Three tools: the address type, msg.sender, and the constructor.
The address data typeSearch on YouTube
Every Ethereum account (wallet) and contract is identified by a unique 20-byte (160-bit) value, written as 40 hex characters prefixed with 0x. Solidity has a primitive type for it: address.
// Stores the blockchain identity of IIIT Kottayam
address public university;Use address whenever the contract needs to know who something is. Who owns the contract? Who may issue certificates? Which student receives a degree? Where should payment go? The goal: the contract remembers who called a function, so later it can ask who is calling now.
msg.senderSearch on YouTube
Blockchains have no logins. A contract knows its caller through msg.sender.
msg.senderis a built-in global variable.- It evaluates to the address of the account (or contract) that started the current function call.
- Its value is set at run time for every transaction or call.
function getCaller()
public
view
returns (address)
{
return msg.sender;
}Call it from Account 1 and then Account 2 in Remix. Each call returns the address of the account selected at that moment.
address public university; | msg.sender | |
|---|---|---|
| What is it? | A state variable | A global environment variable |
| Where does it live? | Persistent contract storage on chain | Generated dynamically in the EVM call context |
| Does it change? | Stays the same until rewritten in storage | Changes with whoever sends the current transaction |
| Relationship | Who the authority is | Who is calling right now |
Why not a setter?Search on YouTube
function setUniversity(address _university) public {
university = _university;
}This creates another problem: who can call setUniversity()? Anyone, so the attacker sets themselves as the university.
The constructorSearch on YouTube
A constructor is an optional special function that runs exactly once, when the contract is deployed, and never again. It is not added to the runtime bytecode. After the deployment transaction finishes, it can never be called.
// Runs ONCE during deployment
constructor() {
university = msg.sender;
}When this contract is deployed, take the address of whoever deployed it (msg.sender) and save it permanently in university.
UniversityDegree v2Search on YouTube
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract UniversityDegree {
// Address of the university
address public university;
uint256 public certificateId;
string public studentName;
uint256 public registerNumber;
string public degree;
string public department;
uint256 public graduationYear;
bool public isValid;
// Runs once when the contract is deployed
constructor() {
university = msg.sender;
}
// Issue certificate (still public: anyone can call it)
function issueCertificate(
uint256 _certificateId,
string memory _studentName,
uint256 _registerNumber,
string memory _degree,
string memory _department,
uint256 _graduationYear
) public {
certificateId = _certificateId;
studentName = _studentName;
registerNumber = _registerNumber;
degree = _degree;
department = _department;
graduationYear = _graduationYear;
isValid = true;
}
}In Remix: deploy from Account 1 (0x5B38Da6a701c568545dCfcB03FcB875f56beddC4). The university getter returns that address. Switch to Account 2 and call it again: it still returns Account 1. The value is fixed in storage, independent of the current caller.
Now the contract knows the university's address. But the door to issueCertificate() is still wide open; it is still public.
require(): access controlSearch on YouTube
Only the university should issue a certificate. We already know two things: who is the university (university) and who is calling now (msg.sender). We need logic that compares them.
require() is a built-in check that takes a condition and an error message:
require(condition, "error message");- If the condition is true, execution continues to the next line.
- If it is false, execution stops at once and the whole transaction is reverted. Every change made so far in that call is undone, as if it never happened. The error message is returned to the caller.
require() validates things before acting:
- Is the caller authorised?
- Is the input valid? (for example, the student name is not empty)
- Does this record already exist? (prevent a duplicate certificate)
- Is the contract in the right state for this action?
UniversityDegree v3Search on YouTube
function issueCertificate(
uint256 _certificateId,
string memory _studentName,
uint256 _registerNumber,
string memory _degree,
string memory _department,
uint256 _graduationYear
) public {
require(
msg.sender == university,
"Only university can issue"
);
certificateId = _certificateId;
studentName = _studentName;
registerNumber = _registerNumber;
degree = _degree;
department = _department;
graduationYear = _graduationYear;
isValid = true;
}
function getCertificate() public view returns (
uint256, string memory, uint256, string memory, string memory, uint256, bool
) {
return (certificateId, studentName, registerNumber, degree, department, graduationYear, isValid);
}
// Return the address stored as the university
function getUniversity() public view returns (address) {
return university;
}
// Return the address of the current caller
function getCaller() public view returns (address) {
return msg.sender;
}Test in Remix:
- From Account 1 (the deployer), call
issueCertificate(1111, "Jai Ganesh", 1234, "B. Tech", "Cyber", 2026). It succeeds.getCertificatereturns1111, Jai Ganesh, 1234, B. Tech, Cyber, 2026, true. - From Account 2, call
issueCertificate(2222, "SpiderMan", 1122, ...). It fails: "The transaction has been reverted to the initial state. Reason provided by the contract: Only university can issue."
Order mattersSearch on YouTube
// Wrong: checks after writing
function issueCertificate(...) public {
certificateId = _certificateId;
studentName = _studentName;
require(msg.sender == university, "Only university can issue");
}
// Right: check first, then write
function issueCertificate(...) public {
require(msg.sender == university, "Only university can issue");
certificateId = _certificateId;
studentName = _studentName;
}Both versions block the attacker, because a failed require reverts the writes before it. But the wrong order wastes the caller's gas on writes that are thrown away. Put checks first: validate, then act.
The deck's demo code is at https://jaiganesh.notion.site/Solidity-Programs-3f0cb97603228087a92af9ed52f5a091 (short link https://shorturl.at/tjWQ3).
What to do and check in the lab:
- Write
issueCertificateas public with no checks. Show any account can overwrite the certificate. - Add
address public university;and a constructor that sets it tomsg.sender. Deploy from Account 1; readuniversityfrom Account 1 and Account 2. - Add
getCaller()and call it from different accounts to seemsg.senderchange. - Add
require(msg.sender == university, "Only university can issue");at the top ofissueCertificate. Issue from Account 1 (success) and Account 2 (reverted with the message). - Read every getter and
getCertificate()to confirm the stored values.
Quick self-testSearch on YouTube
Write the general syntax of a Solidity function and name each part.
function <name>(<parameters>) <visibility> <state modifier> returns (<type>) { <body> }. Keyword, identifier, input data, who may call, how it touches storage, output type, logic.
Viva: difference between private and internal?
Both can be called only from inside the contract. internal functions can also be used by contracts that inherit from it; private ones cannot.
Difference between public and external?
public functions can be called from outside and from inside the contract. external functions can only be called from outside; the contract cannot call its own external function directly. External is cheaper for large array inputs.
Compare state-changing, view and pure functions.
State-changing functions read and write state and need a signed transaction with gas. view reads state but cannot change it; free when called externally. pure neither reads nor writes state and works only on its inputs; also free externally.
Which modifier fits a function that hashes a roll number and year into a certificate ID?
pure. It uses only its parameters and does not read or write contract storage.
How big is an Ethereum address?
20 bytes (160 bits), shown as 40 hexadecimal characters prefixed with 0x.
Viva: what is msg.sender, and how does it differ from a stored university address?
msg.sender is a global variable holding the address that started the current call; it changes per call. university is a state variable in storage that stays the same until rewritten. One says who is calling now; the other says who the authority is.
Why use a constructor instead of a setUniversity() function?
A public setter can be called by anyone, so an attacker could make themselves the university. A constructor runs exactly once at deployment and can never be called again.
What happens when a require condition is false?
Execution stops and the whole transaction reverts. All changes made in that call are undone, and the error message is returned.
Give four uses of require() from the deck.
Check the caller is authorised. Check inputs are valid (name not empty). Check a record does not already exist (no duplicate certificate). Check the contract is in the right state.
- Function formula:
function name(params) visibility stateModifier returns (type) { body }. - Visibility:
public(everyone),external(outside only),internal(contract and children),private(this contract only). - State interaction: default state-changing (transaction, gas),
view(read-only, free externally),pure(no state, free externally). addressis a 20-byte identity type;msg.senderis the current caller, set per call.- A constructor runs once at deployment;
university = msg.senderanchors the deployer as the authority. require(condition, "message")reverts the whole transaction on failure; put checks before state changes.- The UniversityDegree contract evolves: v1 open to all, v2 remembers the university, v3 enforces "Only university can issue".