[Ethereum] スマートコントラクトを呼び出してみよう(call編)

uenohiro4
Nayuta ブログ(日本語)
10 min readJun 16, 2020
callといえば電話ですが、黒電話って懐かしいですね

スマートコントラクトの関数を2種類に分けると、状態を変更する関数(send)と変更しない関数(call)がある。

sendやcallという呼び方は、web3.jsの web3.eth.Contract.methods で実行するときの .send().call() を使っただけで、一般的な呼び方ではないと思う。

状態を変更する場合、ブロックチェーンのデータを更新することになるので、自分だけでなく他のトランザクションも確認して整合を取る。つまり、ブロックにトランザクションが取り込まれるまでは状態が更新されない。

状態を変更しない場合、今の状態を呼び出すだけだったり、固定の値を呼び出すだけだったりする。そういう場合は現在だけわかればよいのですぐに結果を返すことができる。

今回は、状態を変更しない場合の呼び出しをどのように行うのか見ていく。

アプリがEthereumクライアントに何かを要求する場合、web3.jsethers.jsを使うことが多いと思う。あるいは、golangでgo-ethereumライブラリを使ったり、Javaでweb3jを使うかもしれない。

どういうライブラリを使ったとしても、Ethereumクライアントとは最終的にJSON-RPCで通信している。HTTPなのかWebsocketなのかはEthereumクライアントの設定によるが、ともかくそういうものなのだ。

初めてEthereumを扱ったときはみんなweb3.jsを使っているので、「きっとweb3.jsがEthereumプロトコルに変換しているんだ」とか思っていたのだけど、全然そういうことはなかった。

例えばblockNumberの取得は、web3.jsでは

web3.eth.getBlockNumber();

となるし、JSON-RPCであれば

{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}

となる。

つまり、JSON-RPCの作り方さえ分かればcurlなどでもアクセスできるのだ。
今回は状態を変更しないスマートコントラクトの関数を、次回は状態を変更する関数を呼び出す。

状態を変更しない関数は、Solidityだと pureview がついた関数になる。 pure は状態どころか引数と関数内だけで完結する。 viewpure に状態の参照が加わったものだ。

view にしていても pure みたいに状態を参照しないこともできるのだが、solcだと “Warning: Function state mutability can be restricted to pure”と警告はしてくれるので、そんなことはしない方がよいだろう。

もちろん viewpure も付けずに状態を変更しない関数も書けるが、コンパイラも警告してくれることだし、そういうのはやらない方が良かろう。

では、テストする環境を作る。
truffleなどはインストールされているとして、Solidityは動けば何でもよいので以前作ったERC20サンプルを使おう。

これに pureview の関数を追加する。

function funcPure(uint256 param1, uint256 param2) pure public returns(uint256) {
return 100 * (param1 + param2);
}
function funcView() view public returns (string memory) {
return this.name();
}

truffleでデプロイと関数呼び出しを確認しておく。

truffle develop
truffle(develop)> migrate --reset
truffle(develop)> let inst = await OzToken.deployed()
truffle(develop)> let v = await inst.funcView()
truffle(develop)> v
'Ozln'
truffle(develop)> v = await inst.funcPure(2, 3)
truffle(develop)> v
BN {
negative: 0,
words: [ 500, <1 empty item> ],
length: 1,
red: null
}

funcView() の方はERC20の name() を返す。 funcPure() は100を返す。

さて、コントラクト関数の viewpure を呼び出すのには、JSON-RPCでは eth_call を使う。

optionalなものを除けば to だけなのだが、関数呼び出しなので data も埋める必要がある。

まず view から。
funcView関数は引数がないので、 dataは関数のsignatureだけあればよい。
signatureは、自分でkeccak256を求めても良いし、前回のスクリプトを使ってもよいし、Visual Studio CodeにSolidity Visual Auditorをインストールしておくと、関数の上に出てくる”funcSig”をクリックすると計算してくれる。

Solidity Visual Auditorの機能の1つでsignatureも取得できる

signatureは 0xb19d7a46だった。
to にコントラクトアドレスを入れると、curlする内容はこうなる。

$ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[
{"to": "0x62338aE2638d74Fc91cF9d61d93Bcdb4e657E3DE", "data": "0xb19d7a46"}
],"id":1}' http://localhost:9545

これを実行すると、すぐに結果が返ってくる(改行は私が追加した)。

{
"id":1,
"jsonrpc":"2.0",
"result":"0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000044f7a6c6e00000000000000000000000000000000000000000000000000000000"
}

戻り値は、前回の関数パラメータと同じである。

まず32バイトごとに区切る。

0000000000000000000000000000000000000000000000000000000000000020
0000000000000000000000000000000000000000000000000000000000000004
4f7a6c6e00000000000000000000000000000000000000000000000000000000

戻り値の型はstringなので可変サイズ。つまりデータへのオフセットが最初に来て、オフセット先にまず文字サイズ、続けて文字が続く。

4文字なので 0x4f7a6c6e までが戻り値。
UTF-8なのでこれを文字列に直すと Ozln 。先ほどtruffleで実行した値と一致する。

pure の方もやろう。

funcPure関数のsignatureは 0xf890bda4
今度は引数がある。2つとも固定長なので、ビッグエンディアンの32バイトにそろえてしまえばよい。

f890bda4
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003

では、curlの形式にする。

$ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[
{"to": "0x62338aE2638d74Fc91cF9d61d93Bcdb4e657E3DE",
"data": "0xf890bda400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003"}
],"id":1}' http://localhost:9545

これも呼び出すとすぐに結果が戻ってくる。

{"id":1,"jsonrpc":"2.0","result":"0x00000000000000000000000000000000000000000000000000000000000001f4"}

固定長で1つしか戻り値がないので、そのまま数値に変換すれば良い。
0x1f4 なので、500だ。

このように eth_call はトランザクションの送信ではないので署名は不要である。

ではgasはどうか?
これは、パラメータに gasgasPrice があるので必要となる場合があるはずだ。あるはずなのだが、私はcallの場合はgasが消費されないと思い込んでいたせいもあり、考えたことがなかった(というか、この記事を書いていて気付いた)。

ちなみに、truffleでestimateGasをすると値は出てくる。

truffle(develop)> await inst.funcPure.estimateGas(2,3)
21775

署名もしていないし、そもそも from がないのだから請求できるアドレスがない。見えているアドレスはコントラクトアドレスだが、そこにamountがない場合も多いだろうし、この例でも入っていない。もしコントラクトアドレスから差し引くのだったとしたら、邪魔したいコントラクトを eth_call するだけで妨害し放題になってしまう。

うーん、なんだろう。。。

https://ethereum.org/en/

--

--

No responses yet