Solidity
#28
TIOBE#41
GitHub#53
IEEESpectrum#23
プログラミング言語
Solidity
概要
SolidityはEthereum仮想マシン(EVM)上で動作するスマートコントラクトを開発するための静的型付けプログラミング言語です。
詳細
Solidityは2014年にEthereum Foundation(主にGavin Wood氏)によって開発されたブロックチェーン特化のプログラミング言語で、Ethereum仮想マシン(EVM)上で実行されるスマートコントラクトの開発に使用されます。JavaScript、Python、C++から影響を受けた構文を持ち、契約指向の設計思想に基づいています。コントラクト、継承、ライブラリ、複雑なユーザー定義型などをサポートし、金融アプリケーション(DeFi)、NFT、分散型自律組織(DAO)、ゲームなどの分散アプリケーション(DApps)開発に使用されます。ガス効率の最適化、セキュリティ考慮事項、アップグレーダビリティパターンなど、ブロックチェーン固有の課題に対応する機能を提供します。Ethereum以外にもBinance Smart Chain、Polygon、Avalancheなど、EVM互換のブロックチェーンでも使用されています。
書き方の例
Hello World
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 基本的なコントラクト
contract HelloWorld {
string private message;
constructor() {
message = "Hello, World!";
}
function getMessage() public view returns (string memory) {
return message;
}
function setMessage(string memory newMessage) public {
message = newMessage;
}
}
// イベントを使った例
contract HelloWorldWithEvents {
string private message;
event MessageUpdated(string oldMessage, string newMessage, address updatedBy);
constructor() {
message = "こんにちは、Solidity!";
}
function getMessage() public view returns (string memory) {
return message;
}
function setMessage(string memory newMessage) public {
string memory oldMessage = message;
message = newMessage;
emit MessageUpdated(oldMessage, newMessage, msg.sender);
}
}
基本的なデータ型
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract DataTypes {
// 符号付き整数
int8 smallInt = -128;
int256 bigInt = -2**255; // int256 = int
// 符号なし整数
uint8 smallUint = 255;
uint256 bigUint = 2**256 - 1; // uint256 = uint
// 固定小数点数
// fixed128x18 fixedPoint = 3.14; // 将来の実装予定
// アドレス型
address public owner;
address payable public recipient;
// ブール型
bool public isActive = true;
// バイト型
bytes1 singleByte = 0x42;
bytes32 hash = keccak256("Hello");
bytes dynamicBytes = "動的バイト配列";
// 文字列
string public name = "コントラクト名";
// 配列
uint[] public dynamicArray;
uint[5] public fixedArray = [1, 2, 3, 4, 5];
// マッピング(ハッシュテーブル)
mapping(address => uint) public balances;
mapping(string => bool) public permissions;
// 構造体
struct User {
string name;
uint age;
bool isActive;
}
User[] public users;
mapping(address => User) public userProfiles;
// 列挙型
enum Status { Pending, Active, Inactive, Suspended }
Status public currentStatus = Status.Pending;
constructor() {
owner = msg.sender;
recipient = payable(msg.sender);
}
function addUser(string memory _name, uint _age) public {
users.push(User(_name, _age, true));
userProfiles[msg.sender] = User(_name, _age, true);
}
function updateBalance(address user, uint amount) public {
balances[user] = amount;
}
function setPermission(string memory action, bool allowed) public {
permissions[action] = allowed;
}
function changeStatus(Status newStatus) public {
require(msg.sender == owner, "Only owner can change status");
currentStatus = newStatus;
}
}
関数と修飾子
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract FunctionsAndModifiers {
address public owner;
uint public totalSupply;
bool public paused;
mapping(address => uint) public balances;
event Transfer(address indexed from, address indexed to, uint amount);
event Paused(address account);
event Unpaused(address account);
constructor(uint _initialSupply) {
owner = msg.sender;
totalSupply = _initialSupply;
balances[owner] = _initialSupply;
paused = false;
}
// 修飾子(modifier)
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
modifier validAddress(address _addr) {
require(_addr != address(0), "Invalid address");
_;
}
// ビュー関数(状態を変更しない)
function getBalance(address account) public view returns (uint) {
return balances[account];
}
// ピュア関数(状態にアクセスしない)
function calculatePercentage(uint amount, uint percentage)
public
pure
returns (uint)
{
return (amount * percentage) / 100;
}
// 状態変更関数
function transfer(address to, uint amount)
public
whenNotPaused
validAddress(to)
returns (bool)
{
require(balances[msg.sender] >= amount, "Insufficient balance");
require(to != msg.sender, "Cannot transfer to yourself");
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
// オーナー限定関数
function mint(address to, uint amount)
public
onlyOwner
validAddress(to)
{
totalSupply += amount;
balances[to] += amount;
emit Transfer(address(0), to, amount);
}
function pause() public onlyOwner {
paused = true;
emit Paused(msg.sender);
}
function unpause() public onlyOwner {
paused = false;
emit Unpaused(msg.sender);
}
// オーナー移譲
function transferOwnership(address newOwner)
public
onlyOwner
validAddress(newOwner)
{
owner = newOwner;
}
// フォールバック関数とreceive関数
receive() external payable {
// Etherを受け取った時の処理
}
fallback() external payable {
// 存在しない関数が呼ばれた時の処理
}
}
イベントとエラーハンドリング
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract EventsAndErrors {
address public owner;
mapping(address => uint) public deposits;
// イベント定義
event Deposit(address indexed user, uint amount, uint timestamp);
event Withdrawal(address indexed user, uint amount, uint timestamp);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// カスタムエラー(Solidity 0.8.4+)
error InsufficientBalance(uint requested, uint available);
error Unauthorized(address caller);
error InvalidAmount();
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
if (msg.sender != owner) {
revert Unauthorized(msg.sender);
}
_;
}
function deposit() public payable {
if (msg.value == 0) {
revert InvalidAmount();
}
deposits[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value, block.timestamp);
}
function withdraw(uint amount) public {
uint balance = deposits[msg.sender];
if (amount > balance) {
revert InsufficientBalance(amount, balance);
}
deposits[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount, block.timestamp);
}
function emergencyWithdraw() public onlyOwner {
uint contractBalance = address(this).balance;
payable(owner).transfer(contractBalance);
}
// require, assert, revertの使い分け例
function demonstrateErrorHandling(uint value) public pure returns (uint) {
// require: 入力検証や条件チェック
require(value > 0, "Value must be positive");
require(value <= 1000, "Value too large");
uint result = value * 2;
// assert: 内部エラー、不変条件のチェック
assert(result >= value); // オーバーフローチェック
// revert: 条件付きで明示的にエラーを発生
if (result > 1500) {
revert("Result exceeds maximum allowed");
}
return result;
}
// try-catch例(外部コントラクトの呼び出し)
function safeExternalCall(address target, bytes calldata data)
public
returns (bool success, bytes memory result)
{
try this.externalCall(target, data) returns (bytes memory returnData) {
return (true, returnData);
} catch Error(string memory reason) {
// revert with reason string
return (false, bytes(reason));
} catch Panic(uint errorCode) {
// panic error (e.g., division by zero, array access out of bounds)
return (false, abi.encodePacked("Panic: ", errorCode));
} catch (bytes memory lowLevelData) {
// other errors
return (false, lowLevelData);
}
}
function externalCall(address target, bytes calldata data)
external
returns (bytes memory)
{
(bool success, bytes memory result) = target.call(data);
require(success, "External call failed");
return result;
}
}
継承とインターフェース
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// 抽象コントラクト
abstract contract Animal {
string public name;
constructor(string memory _name) {
name = _name;
}
function makeSound() public virtual returns (string memory);
function introduce() public view returns (string memory) {
return string(abi.encodePacked("I am ", name));
}
}
// インターフェース
interface IFlyable {
function fly() external returns (string memory);
function getAltitude() external view returns (uint);
}
interface ISwimmable {
function swim() external returns (string memory);
function getDivingDepth() external view returns (uint);
}
// 継承とインターフェース実装
contract Dog is Animal {
string public breed;
constructor(string memory _name, string memory _breed)
Animal(_name)
{
breed = _breed;
}
function makeSound() public pure override returns (string memory) {
return "Woof!";
}
function fetch() public view returns (string memory) {
return string(abi.encodePacked(name, " fetched the ball!"));
}
}
contract Duck is Animal, IFlyable, ISwimmable {
uint private altitude;
uint private divingDepth;
constructor(string memory _name) Animal(_name) {
altitude = 0;
divingDepth = 0;
}
function makeSound() public pure override returns (string memory) {
return "Quack!";
}
function fly() public override returns (string memory) {
altitude = 100;
return string(abi.encodePacked(name, " is flying at ", toString(altitude), "m"));
}
function swim() public override returns (string memory) {
divingDepth = 5;
return string(abi.encodePacked(name, " is swimming at ", toString(divingDepth), "m depth"));
}
function getAltitude() public view override returns (uint) {
return altitude;
}
function getDivingDepth() public view override returns (uint) {
return divingDepth;
}
// ユーティリティ関数
function toString(uint value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint temp = value;
uint digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}
// 多重継承
contract Penguin is Animal, ISwimmable {
uint private divingDepth;
constructor(string memory _name) Animal(_name) {
divingDepth = 0;
}
function makeSound() public pure override returns (string memory) {
return "Squawk!";
}
function swim() public override returns (string memory) {
divingDepth = 50;
return string(abi.encodePacked(name, " is diving deep!"));
}
function getDivingDepth() public view override returns (uint) {
return divingDepth;
}
}
// ライブラリ
library StringUtils {
function concat(string memory a, string memory b)
internal
pure
returns (string memory)
{
return string(abi.encodePacked(a, b));
}
function length(string memory str) internal pure returns (uint) {
return bytes(str).length;
}
}
// ライブラリを使用するコントラクト
contract Zoo {
using StringUtils for string;
Animal[] public animals;
function addAnimal(Animal animal) public {
animals.push(animal);
}
function getAnimalInfo(uint index) public view returns (string memory) {
require(index < animals.length, "Animal not found");
Animal animal = animals[index];
return animal.name().concat(" says ").concat(animal.makeSound());
}
}
DeFiアプリケーション例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// ERC20トークンインターフェース
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// シンプルなステーキングコントラクト
contract SimpleStaking {
IERC20 public stakingToken;
IERC20 public rewardToken;
address public owner;
uint public rewardRate; // リワードレート(秒あたり)
uint public lastUpdateTime;
uint public rewardPerTokenStored;
mapping(address => uint) public userRewardPerTokenPaid;
mapping(address => uint) public rewards;
mapping(address => uint) public balances;
uint private _totalSupply;
event Staked(address indexed user, uint amount);
event Withdrawn(address indexed user, uint amount);
event RewardPaid(address indexed user, uint reward);
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = block.timestamp;
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
constructor(address _stakingToken, address _rewardToken, uint _rewardRate) {
owner = msg.sender;
stakingToken = IERC20(_stakingToken);
rewardToken = IERC20(_rewardToken);
rewardRate = _rewardRate;
lastUpdateTime = block.timestamp;
}
function rewardPerToken() public view returns (uint) {
if (_totalSupply == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored +
((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / _totalSupply;
}
function earned(address account) public view returns (uint) {
return (balances[account] *
(rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18 +
rewards[account];
}
function stake(uint amount) external updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
_totalSupply += amount;
balances[msg.sender] += amount;
stakingToken.transferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
function withdraw(uint amount) public updateReward(msg.sender) {
require(amount > 0, "Cannot withdraw 0");
require(balances[msg.sender] >= amount, "Insufficient balance");
_totalSupply -= amount;
balances[msg.sender] -= amount;
stakingToken.transfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
function getReward() public updateReward(msg.sender) {
uint reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardToken.transfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
function exit() external {
withdraw(balances[msg.sender]);
getReward();
}
function setRewardRate(uint _rewardRate) external onlyOwner updateReward(address(0)) {
rewardRate = _rewardRate;
}
function totalSupply() external view returns (uint) {
return _totalSupply;
}
function balanceOf(address account) external view returns (uint) {
return balances[account];
}
}
// シンプルなDEX(分散型取引所)
contract SimpleDEX {
mapping(address => mapping(address => uint)) public tokens;
mapping(address => mapping(address => uint)) public orders;
event Deposit(address indexed token, address indexed user, uint amount, uint balance);
event Withdraw(address indexed token, address indexed user, uint amount, uint balance);
event Order(address indexed tokenGive, uint amountGive, address indexed tokenGet, uint amountGet, address indexed user);
event Trade(address indexed tokenGive, uint amountGive, address indexed tokenGet, uint amountGet, address userGive, address userGet);
function depositToken(address token, uint amount) public {
require(token != address(0), "Invalid token");
IERC20(token).transferFrom(msg.sender, address(this), amount);
tokens[token][msg.sender] += amount;
emit Deposit(token, msg.sender, amount, tokens[token][msg.sender]);
}
function withdrawToken(address token, uint amount) public {
require(tokens[token][msg.sender] >= amount, "Insufficient balance");
tokens[token][msg.sender] -= amount;
IERC20(token).transfer(msg.sender, amount);
emit Withdraw(token, msg.sender, amount, tokens[token][msg.sender]);
}
function depositEther() public payable {
tokens[address(0)][msg.sender] += msg.value;
emit Deposit(address(0), msg.sender, msg.value, tokens[address(0)][msg.sender]);
}
function withdrawEther(uint amount) public {
require(tokens[address(0)][msg.sender] >= amount, "Insufficient balance");
tokens[address(0)][msg.sender] -= amount;
payable(msg.sender).transfer(amount);
emit Withdraw(address(0), msg.sender, amount, tokens[address(0)][msg.sender]);
}
function balanceOf(address token, address user) public view returns (uint) {
return tokens[token][user];
}
}
特徴的な機能
ガス最適化テクニック
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GasOptimization {
// パッキング:構造体のフィールド順序でガスを節約
struct OptimizedStruct {
uint128 value1; // 16バイト
uint128 value2; // 16バイト - 同じスロットに収まる
address addr; // 20バイト
uint96 value3; // 12バイト - addrと同じスロットに収まる
}
struct UnoptimizedStruct {
uint256 value1; // 32バイト - 1スロット
address addr; // 20バイト - 1スロット(12バイト無駄)
uint256 value2; // 32バイト - 1スロット
}
// 配列処理の最適化
uint[] public numbers;
function inefficientLoop() public view returns (uint sum) {
for (uint i = 0; i < numbers.length; i++) { // 毎回length読み取り
sum += numbers[i];
}
}
function efficientLoop() public view returns (uint sum) {
uint length = numbers.length; // 一度だけ読み取り
for (uint i = 0; i < length; i++) {
sum += numbers[i];
}
}
// uncheckedブロックでオーバーフローチェックを無効化
function efficientMath(uint a, uint b) public pure returns (uint) {
unchecked {
return a + b; // オーバーフローチェックなし(適切な場合のみ)
}
}
// 定数とimmutableの使用
uint256 public constant RATE = 100; // コンパイル時定数
uint256 public immutable DEPLOYED_TIMESTAMP; // デプロイ時設定
constructor() {
DEPLOYED_TIMESTAMP = block.timestamp;
}
// バイトコード最適化のためのpure/view関数
function calculateFee(uint amount) public pure returns (uint) {
return (amount * RATE) / 10000;
}
}
アップグレーダブルコントラクト
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// プロキシパターン(簡易版)
contract Proxy {
address public implementation;
address public admin;
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
constructor(address _implementation) {
implementation = _implementation;
admin = msg.sender;
}
function upgrade(address newImplementation) external onlyAdmin {
implementation = newImplementation;
}
fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
// 実装コントラクトV1
contract ImplementationV1 {
uint256 public value;
function setValue(uint256 _value) external {
value = _value;
}
function getValue() external view returns (uint256) {
return value;
}
}
// 実装コントラクトV2(新機能追加)
contract ImplementationV2 {
uint256 public value;
uint256 public multiplier; // 新しい状態変数
function setValue(uint256 _value) external {
value = _value;
}
function getValue() external view returns (uint256) {
return value * (multiplier == 0 ? 1 : multiplier);
}
function setMultiplier(uint256 _multiplier) external { // 新しい関数
multiplier = _multiplier;
}
}
セキュリティパターン
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SecurityPatterns {
mapping(address => uint) public balances;
bool private locked;
event Withdrawal(address indexed user, uint amount);
// リエントランシー攻撃対策
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
// Pull Over Push パターン
function withdraw() external nonReentrant {
uint amount = balances[msg.sender];
require(amount > 0, "No balance to withdraw");
// 状態を先に更新(Check-Effects-Interactions パターン)
balances[msg.sender] = 0;
// 外部呼び出しは最後に
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
emit Withdrawal(msg.sender, amount);
}
// タイムロック
mapping(address => uint) public unlockTime;
function lockFunds(uint _unlockTime) external payable {
require(_unlockTime > block.timestamp, "Unlock time must be in future");
balances[msg.sender] += msg.value;
unlockTime[msg.sender] = _unlockTime;
}
function withdrawLocked() external {
require(block.timestamp >= unlockTime[msg.sender], "Funds still locked");
require(balances[msg.sender] > 0, "No balance");
uint amount = balances[msg.sender];
balances[msg.sender] = 0;
unlockTime[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
// Oracle問題への対策例
mapping(address => bool) public trustedOracles;
mapping(bytes32 => uint) public prices;
modifier onlyTrustedOracle() {
require(trustedOracles[msg.sender], "Not trusted oracle");
_;
}
function updatePrice(bytes32 asset, uint price) external onlyTrustedOracle {
prices[asset] = price;
}
}
バージョン
バージョン | ステータス | 主要な特徴 | リリース年 |
---|---|---|---|
Solidity 0.8.x | Latest | オーバーフロー保護、カスタムエラー | 2021 |
Solidity 0.7.x | Maintenance | インラインアセンブリ改善 | 2020 |
Solidity 0.6.x | Legacy | 継承の線形化改善 | 2019 |
Solidity 0.5.x | Legacy | ABI エンコーダ v2 | 2018 |
参考ページ
公式ドキュメント
- Solidity Documentation - 公式ドキュメント
- Ethereum.org - Ethereum開発者向けリソース
- OpenZeppelin Contracts - セキュアなコントラクトライブラリ
学習リソース
- CryptoZombies - ゲーム形式の学習プラットフォーム
- Solidity by Example - 実例中心の学習サイト
- Smart Contract Programmer - 動画チュートリアル
開発ツール
- Remix IDE - ブラウザベースのIDE
- Hardhat - 開発フレームワーク
- Truffle Suite - 開発環境とテストフレームワーク