認識以太坊智能合約----我的認知和手工布署

引言

在目前以太坊之外的各個區(qū)塊鏈項目中,雖然也是聲稱利用區(qū)塊鏈技術(shù)解決了各種各樣的問題,但在技術(shù)層面,多數(shù)項目本質(zhì)上與比特幣一樣:Transaction 保證了代幣可以在不同的人手中收發(fā);打包 Block 以保存 Transaction;使用不同的共識將 Block 形成鏈,以保證不可篡改。這一套做法的重點在「交易」上。但以太坊不同,以太坊在區(qū)塊鏈的特性(無中心、不可篡改)之上提供了智能合約的功能,區(qū)塊鏈技術(shù)(如打包 Block 并使用共識形成鏈)只是為智能合約提供底層支持。以太坊的重點在智能合約,而不是「交易」。因此,學(xué)習(xí)以太坊不能不學(xué)習(xí)智能合約,這篇文章就是我個人對以太坊智能合約的一些認知和布署等問題的介紹。

需要注意的是,這篇文章并不會深入的講解如何去寫一個合約。如果你想了解這方面的知識,可以去看官方文檔(或中文翻譯版,但翻譯版有延遲,可能比英文版版本老一些)。

另外這篇文章也不會詳細講解各種智能合約的開發(fā)框架(如 Truffle 和 Embark)。這篇文章介紹的,僅僅是我對智能合約的思考,和一些很基本的了解。原因我會在最后提及。

智能合約:事物發(fā)展的約定

在沒有接觸以太坊和智能合約以前,我總是想不明白智能合約加上區(qū)塊鏈會有怎樣的應(yīng)用:「合約」一詞代表某一事物經(jīng)由雙方或多方確認并無法修改,區(qū)塊鏈就可以做到,那么把一紙靜態(tài)的「合約」放到區(qū)塊鏈里,有什么特殊的呢?

簡單學(xué)習(xí)了一下合約,才恍然大悟:現(xiàn)實中合約是一張紙,是靜態(tài)的,但以太坊中合約是動態(tài)的、可編程的。這里的合約不僅僅是約定好了一件或幾件事情(比如張三借了李四100塊錢,一年以后還),更是約定好了事情可以怎樣發(fā)展,甚至可以約定不按照即定情況發(fā)展會有怎樣的措施。由于這些約定是在區(qū)塊鏈的基礎(chǔ)之上,因此可以不用擔心會被某一方篡改。

不得不感慨大神們的思維真是活躍。

下面我們來看看一般情況下,合約是如何被使用的。以官方文檔中的一個投票的例子為例:

pragma solidity >=0.4.22 <0.7.0;

/// @title Voting with delegation.
contract Ballot {
    // 投票者信息。weight 代表此人的投票權(quán)重,一般為 1;如果為 2,可以理解成投一票頂一般人的兩票
    struct Voter {
        uint weight;
        bool voted;
        address delegate; // person delegated to
        uint vote;   // index of the voted proposal
    }

    // 被投票人信息,包含名字和票數(shù)
    struct Proposal {
        bytes32 name;
        uint voteCount;
    }

    // 發(fā)起此次投票的人,即 「主席」
    address public chairperson;

    // 一個 map,記錄所有投票人信息
    mapping(address => Voter) public voters;

    // 一個數(shù)組,記錄所有被投票人信息
    Proposal[] public proposals;

    // constructor 在合約創(chuàng)建時被調(diào)用,相當于 C++ 的構(gòu)造函數(shù)
    // 這里主要保存了創(chuàng)建此合約的人(即「主席」)的地址,并初始化所有被投票人信息
    constructor(bytes32[] memory proposalNames) public {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        for (uint i = 0; i < proposalNames.length; i++) {
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }

    // 「主席」調(diào)用此方法,可以開放給某人投票權(quán)限。
    // 注意只有「主席」可以調(diào)用這個方法
    function giveRightToVote(address voter) public {
        require(
            msg.sender == chairperson,
            "Only chairperson can give right to vote."
        );
        require(
            !voters[voter].voted,
            "The voter already voted."
        );
        require(voters[voter].weight == 0);
        voters[voter].weight = 1;
    }

    // 將投票權(quán)交給別人代理。如果有 10 個人交給 A 代理投票,
    // 那么 A 的權(quán)重為 10。(相當于 1 票頂 10票)
    // 這個方法復(fù)雜在處理多層代理上,即 A 交給 B 代理,B 又交給 C 代理的情況??梢圆挥眉毧础?    function delegate(address to) public {
        // assigns reference
        Voter storage sender = voters[msg.sender];
        require(!sender.voted, "You already voted.");

        require(to != msg.sender, "Self-delegation is disallowed.");

        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;

            // We found a loop in the delegation, not allowed.
            require(to != msg.sender, "Found loop in delegation.");
        }

        // Since `sender` is a reference, this
        // modifies `voters[msg.sender].voted`
        sender.voted = true;
        sender.delegate = to;
        Voter storage delegate_ = voters[to];
        if (delegate_.voted) {
            // If the delegate already voted,
            // directly add to the number of votes
            proposals[delegate_.vote].voteCount += sender.weight;
        } else {
            // If the delegate did not vote yet,
            // add to her weight.
            delegate_.weight += sender.weight;
        }
    }

    // 給某人投票。投票人必須有投票權(quán)限且沒有行使過投票權(quán)。
    function vote(uint proposal) public {
        Voter storage sender = voters[msg.sender];
        require(sender.weight != 0, "Has no right to vote");
        require(!sender.voted, "Already voted.");
        sender.voted = true;
        sender.vote = proposal;

        // If `proposal` is out of the range of the array,
        // this will throw automatically and revert all
        // changes.
        proposals[proposal].voteCount += sender.weight;
    }

    // 返回得票最高的人的信息
    function winningProposal() public view
            returns (uint winningProposal_)
    {
        uint winningVoteCount = 0;
        for (uint p = 0; p < proposals.length; p++) {
            if (proposals[p].voteCount > winningVoteCount) {
                winningVoteCount = proposals[p].voteCount;
                winningProposal_ = p;
            }
        }
    }

    // 返回得票最高的人的名字
    function winnerName() public view
            returns (bytes32 winnerName_)
    {
        winnerName_ = proposals[winningProposal()].name;
    }
}

這是一個用來投票的智能合約,合約比較簡單,不用學(xué) solidity 語言,估計也能看懂大體的意思。這里我們不在意合約的編寫細節(jié),而是要看這個合約是如何被用來起的。

這個合約創(chuàng)建了這樣一個投票情景:投票這件事情需要某個人發(fā)起,這個人也是創(chuàng)建這個合約的人,被稱為「主席」。合約創(chuàng)建時,通過「構(gòu)造函數(shù)」記錄了所有被投票人的信息?!钢飨箘?chuàng)建合約后,需要將投票權(quán)限開放給可以投票的人,這之后這個人才能投票。最后,任何一個人都可以調(diào)用相應(yīng)的方法,獲取投票的結(jié)果。

可以看到,與普通腳本不同,solidity 語言增加了一些權(quán)限管理手段,可以約束方法的調(diào)用者和做的事情:只有特定的人才能調(diào)用某個方法,只有特定的人才能執(zhí)行某個動作。這樣就可以保證在這個合約上,不是作何一個人想做什么事就可以做什么的,從而約束投票這件事按「規(guī)定」發(fā)展。

從這個例子中,我們可以總結(jié)出如下合約的應(yīng)用場景:

  • 任何一個合約,需要有人主動創(chuàng)建;創(chuàng)建時可以傳入一些初始信息。
  • 根據(jù)合約的需求,合約可以規(guī)定哪此人可以調(diào)用哪些方法,從而對方法的調(diào)用權(quán)進行控制(也就是對信息的修改權(quán)進行控制)。
  • 有些人開始的時候可能沒有權(quán)限作任何事情,只有等待被授權(quán)以后才行。

建議讀者看一懂 solidity 官方的合約示例:Solidity by Example,會對「動態(tài)的合約」有更好的理解。

手動布署合約

雖然這篇文章里不會講合約的具體語法,但其實我還是看了一下官文文檔、稍微學(xué)習(xí)了一下的。我覺得語法比較好學(xué),并且官文文檔已經(jīng)很詳細了(雖然感覺有點亂),但官方文檔里并沒有說如何手動布署合約(也可能是我沒找到)。所以這里我要記錄一下如何手動布署合約。

首先講為什么要手動布署。其實官方已經(jīng)給出了一些簡便的布署和測試合約的方法(比如下一小節(jié)里要講的 remix),但我的目的就是為了學(xué)習(xí)和調(diào)試以太坊的源代碼,所以自己手動布署一個合約測試環(huán)境還是很有必要的。

這里假設(shè)你已經(jīng)搭建好了一個本地測試環(huán)境(關(guān)于本地測試環(huán)境的搭建,可以參看這篇文章)。因此剩下的事情,就是要編寫合約、編譯合約、提交合約、調(diào)用合約。我們分別來看。

編寫合約

這個其實沒什么好講的,純粹是為了文章的完整性。只要隨便打開一個編輯器,都可以用來編寫合約(也可能有比較友好的編輯器可以高亮、代碼提示等,我不知道。如果有可以留言告訴我)。

后面的介紹中,我們以這個簡單的合約為例進行介紹:

pragma solidity >=0.4.4;

contract test { 
    uint256 x;

    function multiply(uint256 a) public view returns(uint256){
        return x * a;
    }

    function multiplyState(uint256 a) public returns(uint256){
        x = a * 7;
        return x;
    }
}

編譯合約

寫好合約源碼以后,需要使用合約編譯器進行編譯。合約編譯器也是以太坊的項目之一,地址在這里。你可以直接從它的 releases 項里下載合約編譯器的可執(zhí)行程序,也可以 clone 源代碼自己編譯。這是一個 C++ 寫的項目,需要用到 CMake 及 C++ 相關(guān)編譯工具,以及 boost 庫。合約編譯器項目的 readme 文件中 Build and Install 節(jié)詳細記錄了編譯的步驟,這里就不再贅述了。按照步驟編譯成功后,編譯目錄下的 solc/solc 程序就是合約編譯器。

有了編譯器以后,就可以編譯我們寫的合約了。假設(shè)合約源代碼存放在 mycontract.sol 中,那么你可以使用如下命令編譯:

solc --bin -o ./ ./mycontract.sol

其中 --bin 參數(shù)指定輸出數(shù)據(jù)的格式; -o 參數(shù)指定輸出文件所在目錄,這里指定的是當前目錄。因此運行此命令以后,會在當前目錄生成一個 test.bin 文件,里面存放的是合約編譯后的二進制的字符串表示形式。

solc 程序還可以輸出其它格式的數(shù)據(jù),比如 --asm 可以輸出 solidity 語言的匯編指令,讀者可以自已嘗試一下。

提交合約

合約編譯成功以后,就可以提交給以太坊節(jié)點了。這里我們使用以太坊的 rpc 命令提交,因此首先需要將之前搭建好的測試環(huán)境運行起來,保證有一個節(jié)點啟動了 rpc 功能,且有節(jié)點可以正常挖礦。

現(xiàn)在我們假設(shè) rpc 節(jié)點已經(jīng)在本地正常運行,且使用默認端口,因此 rpc 地址為 localhost:8545。

我們知道,合約在以太坊的 EVM 中運行是需要 gas 的。gas 之于合約的運行,相當于汽油之于汽車的運行:汽車每跑一步,都需要消耗汽油;合約每執(zhí)行一條匯編指令,都需要消耗 gas。(其實還有一點非常相似,gas 并不是以太幣的計價單位,只是合約匯編指令的計價單位,因此 gas 的價格是可變的;同樣汽油的價格也是浮動的、可變的。只是這一條與我們目前討論的沒有關(guān)系,因此不重點介紹)。

由于每執(zhí)行一條合約的匯編指令,都需要消耗 gas,而一條合約在執(zhí)行過程中如果 gas 耗盡,那么 EVM 將立即中止合約的執(zhí)行,已經(jīng)消耗的 gas 也不會退回。如果發(fā)生這種情況,相當于錢已經(jīng)花了、但因為花得少而沒辦成事,很冤枉。所以我們要盡量避免這種情況的發(fā)生。那么我們?nèi)绾沃牢覀儗懙暮霞s需要多少 gas 呢?rpc 有一個 eth_estimateGas 方法,可以幫我們評估我們的合約需要多少 gas。示例如下:

curl -X POST -H 'Content-Type: application/json'  --data '{"jsonrpc":"2.0","method":"eth_estimateGas", "params":[{"from":"0x7656bde2ff583c0b0817386e7b88eb8991f4d90c", "data":"0x608060405234801561001057600080fd5b50610134806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80631122db9a146037578063c6888fa1146076575b600080fd5b606060048036036020811015604b57600080fd5b810190808035906020019092919050505060b5565b6040518082815260200191505060405180910390f35b609f60048036036020811015608a57600080fd5b810190808035906020019092919050505060cb565b6040518082815260200191505060405180910390f35b6000600782026000819055506000549050919050565b60008160005402905091905056fea265627a7a72315820fb8e1458bd3bdfe4c1385508d7a7e2ee7262d6d834c779a219a63886cf2e57cf64736f6c637828302e352e31312d646576656c6f702e323031392e372e32362b636f6d6d69742e34666137383030340058"}], "id":1}' localhost:8545

# output:
{"jsonrpc":"2.0","id":1,"result":"0x21667"}

data 參數(shù)就是剛才 solc 使用 --bin 命令輸出的數(shù)據(jù),但在加了 0x 前綴。后面所有方法用到編譯后合約的 bin 數(shù)據(jù)時,都需要加 0x 前綴。可以看見在我的例子中,這個合約需要的 gas 值為 0x21667

這里需要注意, eth_estimateGas 估算的值并不保證準確,所以保險起見,最好在這個值的基礎(chǔ)之上再寫大一些(多余的 gas 用不完會退回來)。

大體知道了需要用到的 gas 量,我們就可以提交合約了。合約的提交,也就是創(chuàng)建,是通過發(fā)送一個交易實現(xiàn)的,即使用 rpc 的 eth_sendTransaction 方法。以太坊規(guī)定在發(fā)送一筆交易時,如果接收者為空,則使用 data 參數(shù)中的數(shù)據(jù)創(chuàng)建一個新的合約。創(chuàng)建合約的示例如下:

curl -X POST -H 'Content-Type: application/json'  --data '{"jsonrpc":"2.0","method":"eth_sendTransaction", "params":[{"from":"0x7656bde2ff583c0b0817386e7b88eb8991f4d90c", "gas":"0x121667", "data":"0x608060405234801561001057600080fd5b50610134806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80631122db9a146037578063c6888fa1146076575b600080fd5b606060048036036020811015604b57600080fd5b810190808035906020019092919050505060b5565b6040518082815260200191505060405180910390f35b609f60048036036020811015608a57600080fd5b810190808035906020019092919050505060cb565b6040518082815260200191505060405180910390f35b6000600782026000819055506000549050919050565b60008160005402905091905056fea265627a7a72315820fb8e1458bd3bdfe4c1385508d7a7e2ee7262d6d834c779a219a63886cf2e57cf64736f6c637828302e352e31312d646576656c6f702e323031392e372e32362b636f6d6d69742e34666137383030340058"}], "id":1}' localhost:8545

# output:
{"jsonrpc":"2.0","id":1,"result":"0x050414e6fd576fdbdbb67962de784e9089adfc71f69a813b605f5d18c96cc9c7"}

eth_sendTransaction 的方法中, gas 參數(shù)的值比剛才通過 eth_estimateGas 評估得到的值大了一些;data 參數(shù)與剛才一樣,是合約編譯后的輸出值(加 0x 前綴)。這個方法返回值為新創(chuàng)建的交易的哈希。

至此,合約已經(jīng)提交了,但我們并不知道合約是否創(chuàng)建成功了,以及成功后合約的地址是多少。這些信息可以通過 eth_getTransactionReceipt 方法來獲?。ǖ枰谔峤缓霞s后稍等片刻,因為以太坊節(jié)點需要時間將新提交的交易打包到區(qū)塊中):

curl -X POST -H 'Content-Type: application/json'  --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt", "params":["0x050414e6fd576fdbdbb67962de784e9089adfc71f69a813b605f5d18c96cc9c7"], "id":1}' localhost:8545

# output:
{"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x19572a7448b4b0678c7e247d66d7bb7ec54ab69eb334adbc2c09ea8df2f9090d","blockNumber":"0x1f0","contractAddress":"0x55a4ccf5907c7f3891f77137ffb346a68a129516","cumulativeGasUsed":"0x1b2cd","from":"0x7656bde2ff583c0b0817386e7b88eb8991f4d90c","gasUsed":"0x1b2cd","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","to":null,"transactionHash":"0x2fc90503131ac2dbb1e7423700d8988a66ad02e4dbf5106162d6318e487ea9f5","transactionIndex":"0x0"}}

eth_getTransactionReceipt 方法的唯一一個參數(shù),就是剛才 eth_sendTransaction 返回的交易哈希。這個方法會返回交易成功后的所有相關(guān)信息,其中有一項 contractAddress,就是我們新創(chuàng)建的合約的地址啦。另外一個需要重點注意的是 status 字段的狀態(tài),只有其值為「真」時,合約才真正創(chuàng)建成功。因為雖然 eth_getTransactionReceipt 顯示提交的 Transaction 被成功打包,但并不代表合約創(chuàng)建的成功:創(chuàng)建合約的代碼可能在 EVM 中執(zhí)行失敗,此時合約創(chuàng)建失敗,但 Transaction 仍然可以是打包成功的。

調(diào)用合約

合約創(chuàng)建成功后,就可以調(diào)用合約的 public 方法了。有兩個 rpc 的方法可以調(diào)用合約:eth_calleth_sendTransaction。 這兩個方法的參數(shù)是一樣的:to 參數(shù)為將要調(diào)用的合約的地址;data 參數(shù)存放被調(diào)用的方法和參數(shù)信息。

這兩個方法的區(qū)別在于,eth_call 僅可以調(diào)用不會改變合約狀態(tài)信息的方法(其實即使改變了也不會被保存),且可以立即得到合約的方法的返回值;而 eth_sendTransaction 可以調(diào)用所有方法,但無法得到返回值。所以在我們的例子中,eth_call 用來調(diào)用 test.multiply 比較合適;而 eth_sendTransaction 用來調(diào)用 test.multiplyState 比較合適。

無論用哪個方法調(diào)用,我們首先要解決的是怎么填充 data 參數(shù)。調(diào)用合約,實際上就是調(diào)用合約的某個 public 方法(類似于C++對象的某個 public 方法),那么 data 里必須有數(shù)據(jù)能標識調(diào)用的是哪個方法;然后就是這個方法的參數(shù)數(shù)據(jù),也需要在 data 參數(shù)中。

對于調(diào)用哪個方法的標識,官方文檔中將其稱為「函數(shù)選擇子」(function selector)。實際上這個值是被調(diào)用的方法的方法名和參數(shù)聲明的字符串(不帶空格)的 Keccak 哈希的前 4 個字節(jié)。這句話很繞,看個例子就很簡單了。例如對于文章前面布署的合約:

pragma solidity >=0.4.4;
  
contract test { 
    uint256 x;

    function multiply(uint256 a) public view returns(uint256){
        return x * a;
    }

    function multiplyState(uint256 a) public returns(uint256){
        x = a * 7;
        return x;
    }
}

如果我想調(diào)用 multiplymultiplyState 方法,那么可以這樣得到函數(shù)選擇子:

# KeccakHash 只是一個代號。具體計算哈希的方法下面會講
KeccakHash("multiply(uint256)")[0:4] => c6888fa1
KeccakHash("multiplyState(uint256)")[0:4] => 1122db9a

所以這個合約的 multiply 方法的函數(shù)選擇子為 c6888fa1,而 multiplyState 的函數(shù)選擇子為 1122db9a

但是這個例子中 KeccakHash 這個計算哈希的函數(shù)并不存在,那么該怎么計算哈希呢?簡單一點,你可以使用 console 中的 web3.sha3 函數(shù)。例如你以以下命令啟動某個節(jié)點的 console:

geth --datadir my/datadir console 2>xxx.log

接著就得到了以太坊控制臺的輸入界面。然后就可以計算哈希了:

> web3.sha3("multiply(uint256)")
"0xc6888fa159d67f77c2f3d7a402e199802766bd7e8d4d1ecd2274fc920265d56a"
> web3.sha3("multiplyState(uint256)")
"0x1122db9a73f12708d7674a9a5d0d5dd2bdb260193a8fcc233d1817ca8708fc93"

使用 2>xxx.log 是為了不讓 geth 程序的 log 輸出影響到 console 的使用。

或者你還可以使用 rpc 的 web3_sha3 方法。使用這個方法時注意參數(shù)是字符串的 16 進制形式,并在前面加 0x 前綴。比如對于 multiply(uint256) 這個字符串來說來說它的 16 進制形式為 0x6d756c7469706c792875696e7432353629。所以你可以使用如下命令得到 multiply(uint256) 這個字符串的哈希:

curl -X POST -H 'Content-Type: application/json'  --data '{"jsonrpc":"2.0","method":"web3_sha3", "params":["0x6d756c7469706c792875696e7432353629"], "id":1}' localhost:8545

# output:
{"jsonrpc":"2.0","id":1,"result":"0xc6888fa159d67f77c2f3d7a402e199802766bd7e8d4d1ecd2274fc920265d56a"}

有了函數(shù)選擇子,乘下的就是參數(shù)的編碼了。合約的方法的參數(shù)有多個,且類型也很多,這里就不一一介紹每種類型的編碼方式了,具體細節(jié)可以參看這篇官方文檔。在我們的例子中,只有一個 uint256 類型的參數(shù),它將被編碼為一個 32 字節(jié)的 16 進制數(shù)字,所以如果我們想通過傳入一個 10 進制值為 26 的整數(shù),那么它的編碼為:

0x000000000000000000000000000000000000000000000000000000000000001a

現(xiàn)在函數(shù)選擇子和參數(shù)都有了,只需要將它們拼在一起就行了,所以我們得到了以 26 為參數(shù)調(diào)用 multiplyState 方法的數(shù)據(jù):

0x1122db9a000000000000000000000000000000000000000000000000000000000000001a

和以 2 為參數(shù)調(diào)用 multiply 方法的數(shù)據(jù):

0xc6888fa10000000000000000000000000000000000000000000000000000000000000002

現(xiàn)在我們可以開始調(diào)用合約的方法了。我們首先以參數(shù) 26 調(diào)用 multiplyState,根據(jù)代碼,這會將合約的狀態(tài)變量 x 設(shè)置為 182。根據(jù)剛才的說明,這里需要使用 eth_sendTransaction 進行調(diào)用(我們忽徊了使用 eth_estimateGas 評估 gas 值的步驟):

curl -X POST -H 'Content-Type: application/json'  --data '{"jsonrpc":"2.0","method":"eth_sendTransaction", "params":[{"from":"0x7656bde2ff583c0b0817386e7b88eb8991f4d90c", "to":"0x55a4ccf5907c7f3891f77137ffb346a68a129516", "gas":"0x153d8", "data":"0x1122db9a000000000000000000000000000000000000000000000000000000000000001a"}], "id":1}' localhost:8545

# output:
{"jsonrpc":"2.0","id":1,"result":"0x3e7a15879105924589a45c5a83b8a8650d702a4c909ff982d94ec9f8814bbe42"}

雖然我們知道 multiplyState 方法有一個返回值,但我們是查不到這個返回值的。(你可以使用 eth_getTransactionReceipt 和上面返回的交易哈希,查詢一下交易的數(shù)據(jù)和是否成功,但里面是沒有返回值的)

雖然沒有返回值,但如果方法執(zhí)行成功,我們確信合約的狀態(tài)變量 x 此時已經(jīng)是 182 了。此時我們再使用 eth_call ,以參數(shù) 2 調(diào)用 multiply,應(yīng)該返回 364(0x16c):

curl -X POST -H 'Content-Type: application/json'  --data '{"jsonrpc":"2.0","method":"eth_call", "params":[{"from":"0x7656bde2ff583c0b0817386e7b88eb8991f4d90c", "to":"0x55a4ccf5907c7f3891f77137ffb346a68a129516", "data":"0xc6888fa10000000000000000000000000000000000000000000000000000000000000002"}, "latest"], "id":1}' localhost:8545

# output:
{"jsonrpc":"2.0","id":1,"result":"0x000000000000000000000000000000000000000000000000000000000000016c"}

可以看到 eth_call 直接返回了結(jié)果,result 字段里正是我們期望的值 0x16c。

使用 remix

雖然以調(diào)用以太坊 rpc 的方式與合約交互,可以達到我們調(diào)試代碼、熟悉 EVM 的目的,但如果關(guān)心的是合約本身,那么這種方法就太麻煩了。以太坊官方實現(xiàn)了一個非常好用的在線合約 IDE:remix。它提供了代碼高亮和自動提示等編輯器功能,還可以一鍵布署合約,一鍵調(diào)用,或者對合約進行單步調(diào)試,非常好用,強烈推薦大家試試。

總結(jié)

這篇文章主要介紹了以太坊合約的兩個方面,一是我自己對智能合約的認識:這是一種動態(tài)合約,是事物發(fā)展的約定,而不僅僅是我們?nèi)粘K姷哪欠N紙質(zhì)的、靜態(tài)的合同;二是從熟悉原理和源代碼的目的入手,介紹了一下如何使用 rpc 方法這種「笨辦法」與智能合約交互,包括創(chuàng)建和調(diào)用等方法。

最后說一點我自己對智能合約的看法吧。不可否認,以太坊智能合約是一個很棒的想法,但我覺得它的應(yīng)用非常有限。首要的一個原因是它無法使用打補丁的方式升級,一旦布署,如果要修改只能重新布署一個新的合約。其次因為無法升級,加上以太坊去中心化的特性,這些原因?qū)е聦霞s的編寫要求非常高:寫出來的合約必須毫無破綻,否則合約被惡意利用,有可能會造成很大損失。而作為程序員,我覺得很少有人能寫出完全沒有 bug 的代碼。當然這個想法不一定對,如果有不同想法,歡迎留言討論。

限于水平,文章可能有錯誤的地方。如果發(fā)現(xiàn),感謝留言指正。


本文最先發(fā)表于我的博客,歡迎評論和轉(zhuǎn)載。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容