前書き
CryptoZombiesで学んだinterfaceの使用法と今の使用法が異なるようなので自分で調べてみたいと思います。Solidity by Exampleを主に参考にします。
※筆者はプログラミング初心者です。誤った情報があるかもしれませんがご了承ください。
Interfaceとは
Interfaceを宣言すると、自分のコントラクトと外部のコントラクトを相互作用することができます。公開されたコントラクトの関数を自分のコントラクトで使用することができます。
Interfaceの特性とルール
1.機能を持つ関数を実装することはできない
2.他のインターフェースを継承することができる
3.中で宣言する関数はexternalでなければならない
4.コンストラクタを宣言することはできない
5.状態変数を宣言することはできない
Interfaceを書いてみよう
呼び出したい外部のコントラクト
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;
contract NumberIncrement {
uint256 public number;
function increment() public {
number += 1;
}
}
publicの状態変数は自動的にゲッタ関数が生成され、この場合生成されたnumber()関数を呼び出すことでnumberの値を参照できます。remixIDEでは、デプロイするとnumber()というボタンが現れ、実行できます。
自分のコントラクトとInterface
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;
interface NumberIncrement {
function number() external view returns(uint256);
function increment() external;
}
contract MyContract {
NumberIncrement private numberIncrement;
constructor(address interfaceContractAddress) {
numberIncrement = NumberIncrement(interfaceContractAddress);
}
function useInterfaceFunction() public returns(uint) {
numberIncrement.increment();
return numberIncrement.number();
}
}
インターフェースの名前はなんでも良さそうですが、Hardhatのテストの際、コントラクト名とInterface名が同じだとエラーが出たので違う方が良さそうです(RemixIDEでは同じでもデプロイできました)。また元の関数にvirtual,overrideが付いている場合、Interfaceではどちらも不要のようです(検証していないので間違っていたらすみません)。
ハードコードでアドレスを指定するよりはコンストラクタ内で動的に指定してあげた方が、interfaceするコントラクトのアドレスが変わった際、自分のコントラクトを編集する必要がないので楽かもしれません。
ちなみにSolidity by Exampleでは、自分のコントラクト内の関数の引数にinterfaceするコントラクトのアドレスを持たせています。こちらでは、interfaceするコントラクトアドレスが変わっても再デプロイする必要がなく、かつ安全だと思います。
Solidity by Example風に書くとこうなります
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;
interface NumberIncrement {
function number() external view returns(uint256);
function increment() external;
}
contract MyContract {
NumberIncrement private numberIncrement;
function useInterfaceFunction(address numberIncrementAddress) public returns(uint) {
NumberIncrement(numberIncrementAddress).increment();
return NumberIncrement(numberIncrementAddress).number();
}
}
関数を呼び出す時は面倒かもしれませんがこっちの方がスマートで安全でいいと思います。
関数の実行
先にNumberIncrementコントラクトをデプロイします。そしてそのコントラクトアドレスを指定しMyContractコントラクトをデプロイします。
useInterfaceFunction()を実行するとremixIDEのコンソールに出力されるログの
decoded outputの箇所にインクリメントされた数字が出力されます。
考察
BコントラクトでAコントラクトをinterfaceし、Aコントラクトの状態変数を変更する関数を実行すると、その変更はAコントラクトそのものに影響し、保存されるっぽいです。
参考文献