スマートコントラクトを運用していく上で必要になってくるのが権限の管理。
これはイーサリアムの基盤ではなくスマートコントラクト内でロジックを記述して行う。
msg.senderがスマートコントラクトに登録されているアドレスと一致しない場合はエラーとするのが一般的な方法で、代表的なライブラリがopenzeppelinのOwnable。
これをUpgradeableなコントラクトに適用できるようにしたのがOwnableUpgradeable。
いずれにせよストレージスロットにownerのアドレスを記録させることによってowner()関数から呼び出せるようになっている。
裏返せば、ストレージ領域への書き込みと読み込みが発生している。ストレージへのアクセス(特に書き込み)はガス代が高くつくので、運用によってはimmutable変数で定義することもできるのでは?というのが今回の趣旨。
openzeppelinのOwnableには管理者権限の委譲、イベントの記録などの機能もあるが、個人開発において、今すぐ、または近い将来必要なるかと言われれば、そうでない場合もあるだろう。
そういう場合には、immutable変数でownerをコントラクトのバイトコードとして埋め込んでおくことでガス代を抑えられる。書いたコードはこんな感じ。
abstract contract OwnableImmutable is ContextUpgradeable {
address private immutable _OWNER;
error OwnableUnauthorizedAccount(address account);
constructor(address initialOwner) {
_OWNER = initialOwner;
}
function _owner() internal view virtual returns (address) {
return _OWNER;
}
function _checkOwner() internal view virtual {
if (_OWNER != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
modifier onlyOwner() {
_checkOwner();
_;
}
}
使い方としては、このコントラクトを継承して、コンストラクタの引数で所有者のアドレスを渡す。通常は_msgSender()あるいはmsg.senderを渡せばよいが、CREATE2等を使っている場合はtx.origin等にする必要があるかもしれない。あらかじめアドレスが決まっているのであれば固定の値を渡してもよい。
基本的にopenzeppelinのインタフェースと合わせているが、owner関数を定義していないので、外部からowner()を呼びたい場合は継承先のコントラクトで実装する必要がある。
owner関数を実装していないのは、これもまたガス代節約のため。immutableな変数なので、デプロイ後はownerの変更ができない。つまり、外部からownerを呼び出す必要はあまりない。publicで定義してしまうとその分バイトコードが増えるので未実装とした。通常は_owner関数を呼び出すだけで事足りるだろう。
ownerの書き換えはできない、と書いたが、Upgradeableなコントラクトの場合、アップグレード時に別のアドレスを指定してあげれば、一応管理者権限の委譲は可能。ただしそのためだけにコントラクトを再デプロイするのはガス代が多くかかるので本末転倒に思える。管理者権限の委譲が不要、または管理者権限移譲までに何度もコントラクトをアップグレードする可能性がある、という場合はこの手法が有効かと思う。
……とここまで書いたが、まだ構想段階なので今後なにか問題がでるかもしれない。まずはこれを使ってテストネットへのデプロイを行って仮運用を開始したい。
コメント