Functions are the primary way to interact with contracts.
function functionName(Type1 param1, Type2 param2)
visibility
stateMutability
modifiers
returns (ReturnType)
{
// function body
}Callable from anywhere:
function transfer(address to, uint256 amount) public {
// Anyone can call this
}Only callable from outside the contract:
function deposit() external payable {
// Only external calls
// More gas efficient for large data
}Only callable from this contract or derived contracts:
function _calculateFee(uint256 amount) internal pure returns (uint256) {
return amount * 3 / 100;
}Only callable from this contract:
function _updateState() private {
// Only this contract can call
}Can read and modify state:
function increment() public {
count += 1; // Modifies state
}Can read but not modify state:
function getBalance(address account) public view returns (uint256) {
return balances[account]; // Read-only
}Cannot read or modify state:
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b; // No state access
}Can receive SOL:
function deposit() public payable {
balances[msg.sender] += msg.value;
}function setValue(uint256 value) public {
storedValue = value;
}For complex types:
function processArray(uint256[] memory data) public pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < data.length; i++) {
sum += data[i];
}
return sum;
}Read-only, gas efficient:
function processData(bytes calldata data) external pure returns (uint256) {
return data.length;
}function getValue() public view returns (uint256) {
return storedValue;
}function getDetails() public view returns (uint256, bool, address) {
return (count, active, owner);
}
// Named returns
function getInfo() public view returns (
uint256 balance,
bool isActive,
address ownerAddress
) {
balance = balances[msg.sender];
isActive = active;
ownerAddress = owner;
}(uint256 balance, bool active, address owner) = contract.getDetails();
// Ignore some values
(uint256 balance, , ) = contract.getDetails();Reusable function conditions:
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_; // Continue with function
}
modifier validAddress(address addr) {
require(addr != address(0), "Invalid address");
_;
}
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
// Using modifiers
function withdraw(address to, uint256 amount)
public
onlyOwner
validAddress(to)
nonReentrant
{
// Function body
}modifier minAmount(uint256 min) {
require(msg.value >= min, "Amount too low");
_;
}
function deposit() public payable minAmount(1 ether) {
// ...
}Multiple functions with same name, different parameters:
function transfer(address to, uint256 amount) public {
_transfer(msg.sender, to, amount);
}
function transfer(address from, address to, uint256 amount) public {
require(allowance[from][msg.sender] >= amount, "Not allowed");
_transfer(from, to, amount);
}For inheritance:
contract Base {
function getValue() public view virtual returns (uint256) {
return 10;
}
}
contract Derived is Base {
function getValue() public view override returns (uint256) {
return 20;
}
}Special function called once at deployment:
contract Token {
string public name;
address public owner;
constructor(string memory _name) {
name = _name;
owner = msg.sender;
}
}
// Deploy with: new Token("MyToken")Handle unknown calls and plain transfers:
contract Receiver {
event Received(address sender, uint256 amount);
// Receive plain SOL transfers
receive() external payable {
emit Received(msg.sender, msg.value);
}
// Fallback for unknown function calls
fallback() external payable {
// Handle unknown calls
}
}contract Calculator {
function add(uint256 a, uint256 b) public pure returns (uint256) {
return _add(a, b);
}
function _add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
}interface IToken {
function transfer(address to, uint256 amount) external returns (bool);
}
contract Wallet {
function sendTokens(IToken token, address to, uint256 amount) public {
bool success = token.transfer(to, amount);
require(success, "Transfer failed");
}
}function withdraw(uint256 amount) public {
require(amount > 0, "Amount must be positive");
require(balances[msg.sender] >= amount, "Insufficient balance");
// ...
}error InsufficientBalance(uint256 available, uint256 required);
function withdraw(uint256 amount) public {
if (balances[msg.sender] < amount) {
revert InsufficientBalance(balances[msg.sender], amount);
}
// ...
}// Good
function transferTokens(address recipient, uint256 amount) public { }
// Avoid
function tt(address r, uint256 a) public { }// Consistent: from, to, amount
function transfer(address from, address to, uint256 amount) public { }
function approve(address owner, address spender, uint256 amount) public { }function setOwner(address newOwner) public onlyOwner {
require(newOwner != address(0), "Invalid address");
require(newOwner != owner, "Already owner");
owner = newOwner;
}function transfer(address to, uint256 amount) public {
// ... transfer logic ...
emit Transfer(msg.sender, to, amount);
}Saves gas and clarifies intent:
// Good - clearly read-only
function getBalance() public view returns (uint256) {
return balance;
}
// Good - clearly no state access
function calculate(uint256 a, uint256 b) public pure returns (uint256) {
return a * b;
}- State Variables - Managing contract state
- Events & Errors - Logging and error handling
- Modifiers - Creating reusable modifiers