Solidityの関数修飾子(Modifier)は、コントラクト関数の振る舞いを変更するための強力な機能です。これにより、関数が実行される前や後に特定のロジックを挿入し、コードの重複を減らし、可読性を向上させることができます。特に、アクセス制御、入力値の検証、状態変更の追跡といった共通のチェックロジックを抽象化するのに役立ちます。
関数修飾子の基本的な定義と利用
修飾子はmodifierキーワードを使用して定義され、関数に適用されます。最も一般的な用途は、特定の条件が満たされた場合にのみ関数を実行するよう制限することです。以下の例では、RestrictedDataVaultコントラクトのデータ更新を特定のコントローラーアドレスのみに制限しています。
pragma solidity ^0.8.0;
contract RestrictedDataVault {
string private storedMessage; // 格納されるメッセージ
address private controllerAddress; // コントローラーのアドレス
// コントラクトデプロイ時にコントローラーを設定
constructor() {
controllerAddress = msg.sender;
}
// メッセージを更新する関数。onlyController修飾子を適用。
function updateMessage(string memory newMessage) public onlyController {
storedMessage = newMessage;
}
// 現在のメッセージを取得するビュー関数
function getMessage() public view returns (string memory) {
return storedMessage;
}
// 修飾子: コントローラーのみが実行可能
modifier onlyController() {
// 現在の呼び出し元がコントローラーでなければエラー
require(msg.sender == controllerAddress, "Access denied: Not the authorized controller.");
// ここに '_' があることで、修飾子が適用された関数の本体が実行されます。
_;
}
}
上記のコードでは、onlyController修飾子が定義されており、updateMessage関数に適用されています。この修飾子の内部にあるrequire文は、呼び出し元がcontrollerAddressと一致しない場合にトランザクションを即座にリバートします。_(アンダースコア)は、修飾子が適用されている関数の本体が実行される場所を示します。つまり、requireのチェックが成功した場合にのみ、updateMessage関数のstoredMessage = newMessage;というロジックが実行されます。
関数パラメーターを伴う修飾子
修飾子は、関数と同様にパラメーターを受け取ることができます。これにより、関数の引数に対して特定のバリデーションを行うことができます。以下のSafeCalculatorコントラクトは、除算を行う際に、除数がゼロでないことを修飾子で検証しています。
pragma solidity ^0.8.0;
contract SafeCalculator {
// 安全な除算を実行する関数
function safeDivision(uint256 dividend, uint256 divisor) public pure validateDivisor(divisor) returns (uint256) {
return dividend / divisor;
}
// 除数がゼロでないことを検証する修飾子
modifier validateDivisor(uint256 _divisor) {
require(_divisor != 0, "Error: Divisor cannot be zero.");
_;
}
}
この例では、safeDivision関数にvalidateDivisor(divisor)修飾子が適用され、divisorパラメーターが修飾子に渡されます。修飾子内部でこの_divisorがゼロでないことが検証され、条件を満たさない場合はトランザクションが失敗します。これにより、関数の本体は安全に除算ロジックを実行できます。
複数の修飾子と実行順序
一つの関数に対して複数の修飾子を適用することも可能です。その場合、修飾子は左から右へ記述された順序で実行されます。
pragma solidity ^0.8.0;
contract MultiModifierExample {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier isActive() {
// 仮の条件
require(true, "Not active");
_;
}
// onlyOwnerが先に実行され、次にisActiveが実行される
function performAction() public onlyOwner isActive {
// アクションのロジック
}
}
さらに、修飾子の中には_の後にコードを記述することもできます。この場合、関数の本体が実行された後、修飾子の残りの部分が実行されます。これは、関数実行後のクリーンアップや状態変更を追跡する際に有用です。
pragma solidity ^0.8.0;
contract StateManipulator {
uint256 public value; // 公開状態変数
constructor() {
value = 0; // 初期値を設定
}
// 値を更新し、修飾子の動作を確認する関数
function performOperation(uint256 inputVal) public applyPreAndPostHooks(inputVal) returns (uint256) {
value = inputVal + 5; // 関数本体での操作
return value; // ここで一時的な値を返す
}
// 前処理と後処理を持つ修飾子
modifier applyPreAndPostHooks(uint256 /*_param*/) { // _paramは例として残すが、ここでは未使用
value = 100; // 前処理: 関数本体の前にvalueを100に設定
_; // 関数本体 (performOperation) の実行
value = 200; // 後処理: 関数本体実行後にvalueを200に設定
}
}
上記のStateManipulatorコントラクトでは、performOperation関数がapplyPreAndPostHooks修飾子を使用しています。performOperation(10)を呼び出した際のvalueの変化を見てみましょう。
applyPreAndPostHooksが実行開始。- 修飾子内で
value = 100;が実行され、valueは100になります。 _に到達し、performOperation関数本体が実行されます。performOperation内でvalue = inputVal + 5;(つまり10 + 5 = 15)が実行され、valueは15になります。performOperationが15を返します。_から修飾子に戻り、修飾子の残りのコードvalue = 200;が実行され、最終的にvalueは200になります。
この結果からわかるように、performOperation関数自体は15を返しますが、トランザクションが完了した後のコントラクトのvalue状態変数は200になります。これは、関数本体のreturnステートメントが修飾子の後続コードの実行を停止させないためです。