用Go來做以太坊開發(fā)④智能合約

智能合約

在這個(gè)章節(jié)中我們會(huì)介紹如何用Go來編譯慎框,部署路狮,寫入和讀取智能合約莱衩。

智能合約的編譯與ABI

與智能合約交互悴晰,我們要先生成相應(yīng)智能合約的應(yīng)用二進(jìn)制接口ABI(application binary interface),并把ABI編譯成我們可以在Go應(yīng)用中調(diào)用的格式节榜。

第一步是安裝 Solidity編譯器 (solc).

Solc 在Ubuntu上有snapcraft包羡玛。

sudo snap install solc --edge

Solc在macOS上有Homebrew的包别智。

brew update
brew tap ethereum/ethereum
brew install solidity

其他的平臺(tái)或者從源碼編譯的教程請(qǐng)查閱官方solidity文檔install guide.

我們還得安裝一個(gè)叫abigen的工具宗苍,來從solidity智能合約生成ABI。

假設(shè)您已經(jīng)在計(jì)算機(jī)上設(shè)置了Go薄榛,只需運(yùn)行以下命令即可安裝abigen工具讳窟。

go get -u github.com/ethereum/go-ethereum
cd $GOPATH/src/github.com/ethereum/go-ethereum/
make
make devtools

我們將創(chuàng)建一個(gè)簡(jiǎn)單的智能合約來測(cè)試。 學(xué)習(xí)更復(fù)雜的智能合約敞恋,或者智能合約的開發(fā)的內(nèi)容則超出了本書的范圍丽啡。 我強(qiáng)烈建議您查看truffle framework 來學(xué)習(xí)開發(fā)和測(cè)試智能合約。

這里只是一個(gè)簡(jiǎn)單的合約硬猫,就是一個(gè)鍵/值存儲(chǔ)补箍,只有一個(gè)外部方法來設(shè)置任何人的鍵/值對(duì)改执。 我們還在設(shè)置值后添加了要發(fā)出的事件。

pragma solidity ^0.4.24;

contract Store {
  event ItemSet(bytes32 key, bytes32 value);

  string public version;
  mapping (bytes32 => bytes32) public items;

  constructor(string _version) public {
    version = _version;
  }

  function setItem(bytes32 key, bytes32 value) external {
    items[key] = value;
    emit ItemSet(key, value);
  }
}

雖然這個(gè)智能合約很簡(jiǎn)單坑雅,但它將適用于這個(gè)例子辈挂。

現(xiàn)在我們可以從一個(gè)solidity文件生成ABI。

solc --abi Store.sol

它會(huì)將其寫入名為“Store_sol_Store.abi”的文件中

現(xiàn)在讓我們用abigen將ABI轉(zhuǎn)換為我們可以導(dǎo)入的Go文件裹粤。 這個(gè)新文件將包含我們可以用來與Go應(yīng)用程序中的智能合約進(jìn)行交互的所有可用方法终蒂。

abigen --abi=Store_sol_Store.abi --pkg=store --out=Store.go

為了從Go部署智能合約,我們還需要將solidity智能合約編譯為EVM字節(jié)碼遥诉。 EVM字節(jié)碼將在事務(wù)的數(shù)據(jù)字段中發(fā)送拇泣。 在Go文件上生成部署方法需要bin文件。

solc --bin Store.sol

現(xiàn)在我們編譯Go合約文件矮锈,其中包括deploy方法霉翔,因?yàn)槲覀儼薭in文件。

abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

在接下來的課程中苞笨,我們將學(xué)習(xí)如何部署智能合約早龟,然后與之交互。

完整代碼

Commands

go get -u github.com/ethereum/go-ethereum
cd $GOPATH/src/github.com/ethereum/go-ethereum/
make
make devtools

solc --abi Store.sol
solc --bin Store.sol
abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

pragma solidity ^0.4.24;

contract Store {
  event ItemSet(bytes32 key, bytes32 value);

  string public version;
  mapping (bytes32 => bytes32) public items;

  constructor(string _version) public {
    version = _version;
  }

  function setItem(bytes32 key, bytes32 value) external {
    items[key] = value;
    emit ItemSet(key, value);
  }
}

solc version used for these examples

$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

部署智能合約

如果你還沒看之前的章節(jié)猫缭,請(qǐng)先學(xué)習(xí)編譯智能合約的章節(jié)因?yàn)檫@節(jié)內(nèi)容葱弟,需要先了解如何將智能合約編譯為Go文件。

假設(shè)你已經(jīng)導(dǎo)入從abigen生成的新創(chuàng)建的Go包文件猜丹,并設(shè)置ethclient芝加,加載您的私鑰,下一步是創(chuàng)建一個(gè)有配置密匙的交易發(fā)送器(tansactor)射窒。 首先從go-ethereum導(dǎo)入accounts/abi/bind包藏杖,然后調(diào)用傳入私鑰的NewKeyedTransactor。 然后設(shè)置通常的屬性脉顿,如nonce蝌麸,燃?xì)鈨r(jià)格,燃?xì)馍暇€限制和ETH值艾疟。

auth := bind.NewKeyedTransactor(privateKey)
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0)     // in wei
auth.GasLimit = uint64(300000) // in units
auth.GasPrice = gasPrice

如果你還記得上個(gè)章節(jié)的內(nèi)容, 我們創(chuàng)建了一個(gè)非常簡(jiǎn)單的“Store”合約来吩,用于設(shè)置和存儲(chǔ)鍵/值對(duì)。 生成的Go合約文件提供了部署方法蔽莱。 部署方法名稱始終以單詞Deploy開頭弟疆,后跟合約名稱,在本例中為Store盗冷。

deploy函數(shù)接受有密匙的事務(wù)處理器怠苔,ethclient,以及智能合約構(gòu)造函數(shù)可能接受的任何輸入?yún)?shù)仪糖。我們測(cè)試的智能合約接受一個(gè)版本號(hào)的字符串參數(shù)柑司。 此函數(shù)將返回新部署的合約地址迫肖,事務(wù)對(duì)象,我們可以交互的合約實(shí)例攒驰,還有錯(cuò)誤(如果有)咒程。

input := "1.0"
address, tx, instance, err := store.DeployStore(auth, client, input)
if err != nil {
  log.Fatal(err)
}

fmt.Println(address.Hex())   // 0x147B8eb97fD247D06C4006D269c90C1908Fb5D54
fmt.Println(tx.Hash().Hex()) // 0xdae8ba5444eefdc99f4d45cd0c4f24056cba6a02cefbf78066ef9f4188ff7dc0

_ = instance // will be using the instance in the 下個(gè)章節(jié)

就這么簡(jiǎn)單:)你可以用事務(wù)哈希來在Etherscan上查詢合約的部署狀態(tài): https://rinkeby.etherscan.io/tx/0xdae8ba5444eefdc99f4d45cd0c4f24056cba6a02cefbf78066ef9f4188ff7dc0

完整代碼

Commands

solc --abi Store.sol
solc --bin Store.sol
abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

pragma solidity ^0.4.24;

contract Store {
  event ItemSet(bytes32 key, bytes32 value);

  string public version;
  mapping (bytes32 => bytes32) public items;

  constructor(string _version) public {
    version = _version;
  }

  function setItem(bytes32 key, bytes32 value) external {
    items[key] = value;
    emit ItemSet(key, value);
  }
}

contract_deploy.go

package main

import (
    "context"
    "crypto/ecdsa"
    "fmt"
    "log"
    "math/big"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"

    store "./contracts" // for demo
)

func main() {
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
    if err != nil {
        log.Fatal(err)
    }

    publicKey := privateKey.Public()
    publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        log.Fatal("error casting public key to ECDSA")
    }

    fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        log.Fatal(err)
    }

    gasPrice, err := client.SuggestGasPrice(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    auth := bind.NewKeyedTransactor(privateKey)
    auth.Nonce = big.NewInt(int64(nonce))
    auth.Value = big.NewInt(0)     // in wei
    auth.GasLimit = uint64(300000) // in units
    auth.GasPrice = gasPrice

    input := "1.0"
    address, tx, instance, err := store.DeployStore(auth, client, input)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(address.Hex())   // 0x147B8eb97fD247D06C4006D269c90C1908Fb5D54
    fmt.Println(tx.Hash().Hex()) // 0xdae8ba5444eefdc99f4d45cd0c4f24056cba6a02cefbf78066ef9f4188ff7dc0

    _ = instance
}

solc version used for these examples

$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

加載智能合約

這寫章節(jié)需要了解如何將智能合約的ABI編譯成Go的合約文件。如果你還沒看讼育, 前先讀上一個(gè)章節(jié) 帐姻。

一旦使用abigen工具將智能合約的ABI編譯為Go包,下一步就是調(diào)用“New”方法奶段,其格式為“New<contractname style="box-sizing: border-box; font-size: 16px; -ms-text-size-adjust: auto; -webkit-tap-highlight-color: transparent;">”饥瓷,所以在我們的例子中如果你 回想一下它將是NewStore。 此初始化方法接收智能合約的地址痹籍,并返回可以開始與之交互的合約實(shí)例呢铆。</contractname>

address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
instance, err := store.NewStore(address, client)
if err != nil {
  log.Fatal(err)
}

_ = instance // we'll be using this in the 下個(gè)章節(jié)

完整代碼

Commands

solc --abi Store.sol
solc --bin Store.sol
abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

pragma solidity ^0.4.24;

contract Store {
  event ItemSet(bytes32 key, bytes32 value);

  string public version;
  mapping (bytes32 => bytes32) public items;

  constructor(string _version) public {
    version = _version;
  }

  function setItem(bytes32 key, bytes32 value) external {
    items[key] = value;
    emit ItemSet(key, value);
  }
}

contract_load.go

package main

import (
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"

    store "./contracts" // for demo
)

func main() {
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
    instance, err := store.NewStore(address, client)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("contract is loaded")
    _ = instance
}

solc version used for these examples

$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

查詢智能合約

Querying a Smart Contract

這寫章節(jié)需要了解如何將智能合約的ABI編譯成Go的合約文件。如果你還沒看蹲缠, 前先讀上一個(gè)章節(jié) 棺克。

在上個(gè)章節(jié)我們學(xué)習(xí)了如何在Go應(yīng)用程序中初始化合約實(shí)例。 現(xiàn)在我們將使用新合約實(shí)例提供的方法來閱讀智能合約线定。 如果你還記得我們?cè)诓渴疬^程中設(shè)置的合約中有一個(gè)名為version的全局變量娜谊。 因?yàn)樗枪_的,這意味著它們將成為我們自動(dòng)創(chuàng)建的getter函數(shù)斤讥。 常量和view函數(shù)也接受bind.CallOpts作為第一個(gè)參數(shù)纱皆。了解可用的具體選項(xiàng)要看相應(yīng)類的文檔 一般情況下我們可以用 nil

version, err := instance.Version(nil)
if err != nil {
  log.Fatal(err)
}

fmt.Println(version) // "1.0"

完整代碼

Commands

solc --abi Store.sol
solc --bin Store.sol
abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

pragma solidity ^0.4.24;

contract Store {
  event ItemSet(bytes32 key, bytes32 value);

  string public version;
  mapping (bytes32 => bytes32) public items;

  constructor(string _version) public {
    version = _version;
  }

  function setItem(bytes32 key, bytes32 value) external {
    items[key] = value;
    emit ItemSet(key, value);
  }
}

contract_read.go

package main

import (
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"

    store "./contracts" // for demo
)

func main() {
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
    instance, err := store.NewStore(address, client)
    if err != nil {
        log.Fatal(err)
    }

    version, err := instance.Version(nil)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(version) // "1.0"
}

solc version used for these examples

$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

寫入智能合約

這寫章節(jié)需要了解如何將智能合約的ABI編譯成Go的合約文件芭商。如果你還沒看派草, 前先讀上一個(gè)章節(jié)

寫入智能合約需要我們用私鑰來對(duì)交易事務(wù)進(jìn)行簽名铛楣。

privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
if err != nil {
  log.Fatal(err)
}

publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
  log.Fatal("error casting public key to ECDSA")
}

fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)

我們還需要先查到nonce和燃?xì)鈨r(jià)格近迁。

nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
  log.Fatal(err)
}

gasPrice, err := client.SuggestGasPrice(context.Background())
if err != nil {
  log.Fatal(err)
}

接下來,我們創(chuàng)建一個(gè)新的keyed transactor簸州,它接收私鑰鉴竭。

auth := bind.NewKeyedTransactor(privateKey)

然后我們需要設(shè)置keyed transactor的標(biāo)準(zhǔn)交易選項(xiàng)。

auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0)     // in wei
auth.GasLimit = uint64(300000) // in units
auth.GasPrice = gasPrice

現(xiàn)在我們加載一個(gè)智能合約的實(shí)例勿侯。如果你還記得上個(gè)章節(jié) 我們創(chuàng)建一個(gè)名為Store的合約拓瞪,并使用abigen工具生成一個(gè)Go文件缴罗。 要初始化它助琐,我們只需調(diào)用合約包的New方法,并提供智能合約地址和ethclient面氓,它返回我們可以使用的合約實(shí)例兵钮。

address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
instance, err := store.NewStore(address, client)
if err != nil {
  log.Fatal(err)
}

我們創(chuàng)建的智能合約有一個(gè)名為SetItem的外部方法蛆橡,它接受solidity“bytes32”格式的兩個(gè)參數(shù)(key,value)掘譬。 這意味著Go合約包要求我們傳遞一個(gè)長(zhǎng)度為32個(gè)字節(jié)的字節(jié)數(shù)組泰演。 調(diào)用SetItem方法需要我們傳遞我們之前創(chuàng)建的auth對(duì)象(keyed transactor)。 在幕后葱轩,此方法將使用它的參數(shù)對(duì)此函數(shù)調(diào)用進(jìn)行編碼睦焕,將其設(shè)置為事務(wù)的data屬性,并使用私鑰對(duì)其進(jìn)行簽名靴拱。 結(jié)果將是一個(gè)已簽名的事務(wù)對(duì)象垃喊。

key := [32]byte{}
value := [32]byte{}
copy(key[:], []byte("foo"))
copy(value[:], []byte("bar"))

tx, err := instance.SetItem(auth, key, value)
if err != nil {
  log.Fatal(err)
}

fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0x8d490e535678e9a24360e955d75b27ad307bdfb97a1dca51d0f3035dcee3e870

現(xiàn)在我就可以看到交易已經(jīng)成功被發(fā)送到了以太坊網(wǎng)絡(luò)了: https://rinkeby.etherscan.io/tx/0x8d490e535678e9a24360e955d75b27ad307bdfb97a1dca51d0f3035dcee3e870

要驗(yàn)證鍵/值是否已設(shè)置,我們可以讀取智能合約中的值袜炕。

result, err := instance.Items(nil, key)
if err != nil {
  log.Fatal(err)
}

fmt.Println(string(result[:])) // "bar"

搞定本谜!

完整代碼

Commands

solc --abi Store.sol
solc --bin Store.sol
abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

pragma solidity ^0.4.24;

contract Store {
  event ItemSet(bytes32 key, bytes32 value);

  string public version;
  mapping (bytes32 => bytes32) public items;

  constructor(string _version) public {
    version = _version;
  }

  function setItem(bytes32 key, bytes32 value) external {
    items[key] = value;
    emit ItemSet(key, value);
  }
}

contract_write.go

package main

import (
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"

    store "./contracts" // for demo
)

func main() {
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    privateKey, err := crypto.HexToECDSA("fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19")
    if err != nil {
        log.Fatal(err)
    }

    publicKey := privateKey.Public()
    publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
    if !ok {
        log.Fatal("error casting public key to ECDSA")
    }

    fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
    nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
    if err != nil {
        log.Fatal(err)
    }

    gasPrice, err := client.SuggestGasPrice(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    auth := bind.NewKeyedTransactor(privateKey)
    auth.Nonce = big.NewInt(int64(nonce))
    auth.Value = big.NewInt(0)     // in wei
    auth.GasLimit = uint64(300000) // in units
    auth.GasPrice = gasPrice

    address := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
    instance, err := store.NewStore(address, client)
    if err != nil {
        log.Fatal(err)
    }

    key := [32]byte{}
    value := [32]byte{}
    copy(key[:], []byte("foo"))
    copy(value[:], []byte("bar"))

    tx, err := instance.SetItem(auth, key, value)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("tx sent: %s", tx.Hash().Hex()) // tx sent: 0x8d490e535678e9a24360e955d75b27ad307bdfb97a1dca51d0f3035dcee3e870

    result, err := instance.Items(nil, key)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(result[:])) // "bar"
}

solc version used for these examples

$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

讀取智能合約的字節(jié)碼

有時(shí)您需要讀取已部署的智能合約的字節(jié)碼。 由于所有智能合約字節(jié)碼都存在于區(qū)塊鏈中偎窘,因此我們可以輕松獲取它乌助。

首先設(shè)置客戶端和要讀取的字節(jié)碼的智能合約地址。

client, err := ethclient.Dial("https://rinkeby.infura.io")
if err != nil {
  log.Fatal(err)
}

contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")

現(xiàn)在你需要調(diào)用客戶端的codeAt方法陌知。 codeAt方法接受智能合約地址和可選的塊編號(hào)他托,并以字節(jié)格式返回字節(jié)碼。

bytecode, err := client.CodeAt(context.Background(), contractAddress, nil) // nil is latest block
if err != nil {
  log.Fatal(err)
}

fmt.Println(hex.EncodeToString(bytecode)) // 60806...10029

你也可以在etherscan上查詢16進(jìn)制格式的字節(jié)碼 https://rinkeby.etherscan.io/address/0x147b8eb97fd247d06c4006d269c90c1908fb5d54#code

完整代碼

contract_bytecode.go

package main

import (
    "context"
    "encoding/hex"
    "fmt"
    "log"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    client, err := ethclient.Dial("https://rinkeby.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
    bytecode, err := client.CodeAt(context.Background(), contractAddress, nil) // nil is latest block
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(hex.EncodeToString(bytecode)) // 60806...10029
}

查詢ERC20代幣智能合約

首先創(chuàng)建一個(gè)ERC20智能合約interface仆葡。 這只是與您可以調(diào)用的函數(shù)的函數(shù)定義的契約上祈。

pragma solidity ^0.4.24;

contract ERC20 {
    string public constant name = "";
    string public constant symbol = "";
    uint8 public constant decimals = 0;

    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

然后將interface智能合約編譯為JSON ABI,并使用abigen從ABI創(chuàng)建Go包浙芙。

solc --abi erc20.sol
abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go

假設(shè)我們已經(jīng)像往常一樣設(shè)置了以太坊客戶端登刺,我們現(xiàn)在可以將新的token包導(dǎo)入我們的應(yīng)用程序并實(shí)例化它。這個(gè)例子里我們用Golem 代幣的地址.

tokenAddress := common.HexToAddress("0xa74476443119A942dE498590Fe1f2454d7D4aC0d")
instance, err := token.NewToken(tokenAddress, client)
if err != nil {
  log.Fatal(err)
}

我們現(xiàn)在可以調(diào)用任何ERC20的方法嗡呼。 例如纸俭,我們可以查詢用戶的代幣余額。

address := common.HexToAddress("0x0536806df512d6cdde913cf95c9886f65b1d3462")
bal, err := instance.BalanceOf(&bind.CallOpts{}, address)
if err != nil {
  log.Fatal(err)
}

fmt.Printf("wei: %s\n", bal) // "wei: 74605500647408739782407023"

我們還可以讀ERC20智能合約的公共變量南窗。

name, err := instance.Name(&bind.CallOpts{})
if err != nil {
  log.Fatal(err)
}

symbol, err := instance.Symbol(&bind.CallOpts{})
if err != nil {
  log.Fatal(err)
}

decimals, err := instance.Decimals(&bind.CallOpts{})
if err != nil {
  log.Fatal(err)
}

fmt.Printf("name: %s\n", name)         // "name: Golem Network"
fmt.Printf("symbol: %s\n", symbol)     // "symbol: GNT"
fmt.Printf("decimals: %v\n", decimals) // "decimals: 18"

我們可以做一些簡(jiǎn)單的數(shù)學(xué)運(yùn)算將余額轉(zhuǎn)換為可讀的十進(jìn)制格式揍很。

fbal := new(big.Float)
fbal.SetString(bal.String())
value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals))))

fmt.Printf("balance: %f", value) // "balance: 74605500.647409"

同樣的信息也可以在etherscan上查詢: https://etherscan.io/token/0xa74476443119a942de498590fe1f2454d7d4ac0d?a=0x0536806df512d6cdde913cf95c9886f65b1d3462

完整代碼

Commands

solc --abi erc20.sol
abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go

erc20.sol

pragma solidity ^0.4.24;

contract ERC20 {
    string public constant name = "";
    string public constant symbol = "";
    uint8 public constant decimals = 0;

    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

contract_read_erc20.go

package main

import (
    "fmt"
    "log"
    "math"
    "math/big"

    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"

    token "./contracts_erc20" // for demo
)

func main() {
    client, err := ethclient.Dial("https://mainnet.infura.io")
    if err != nil {
        log.Fatal(err)
    }

    // Golem (GNT) Address
    tokenAddress := common.HexToAddress("0xa74476443119A942dE498590Fe1f2454d7D4aC0d")
    instance, err := token.NewToken(tokenAddress, client)
    if err != nil {
        log.Fatal(err)
    }

    address := common.HexToAddress("0x0536806df512d6cdde913cf95c9886f65b1d3462")
    bal, err := instance.BalanceOf(&bind.CallOpts{}, address)
    if err != nil {
        log.Fatal(err)
    }

    name, err := instance.Name(&bind.CallOpts{})
    if err != nil {
        log.Fatal(err)
    }

    symbol, err := instance.Symbol(&bind.CallOpts{})
    if err != nil {
        log.Fatal(err)
    }

    decimals, err := instance.Decimals(&bind.CallOpts{})
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("name: %s\n", name)         // "name: Golem Network"
    fmt.Printf("symbol: %s\n", symbol)     // "symbol: GNT"
    fmt.Printf("decimals: %v\n", decimals) // "decimals: 18"

    fmt.Printf("wei: %s\n", bal) // "wei: 74605500647408739782407023"

    fbal := new(big.Float)
    fbal.SetString(bal.String())
    value := new(big.Float).Quo(fbal, big.NewFloat(math.Pow10(int(decimals))))

    fmt.Printf("balance: %f", value) // "balance: 74605500.647409"
}

solc version used for these examples

$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

文章不定期更新,小編微信:grey0805万伤,歡迎交流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窒悔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子敌买,更是在濱河造成了極大的恐慌简珠,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異聋庵,居然都是意外死亡膘融,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門祭玉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氧映,“玉大人,你說我怎么就攤上這事脱货〉憾迹” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵振峻,是天一觀的道長(zhǎng)疗绣。 經(jīng)常有香客問我,道長(zhǎng)铺韧,這世上最難降的妖魔是什么多矮? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮哈打,結(jié)果婚禮上塔逃,老公的妹妹穿的比我還像新娘。我一直安慰自己料仗,他們只是感情好湾盗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著立轧,像睡著了一般格粪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氛改,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天帐萎,我揣著相機(jī)與錄音,去河邊找鬼胜卤。 笑死疆导,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葛躏。 我是一名探鬼主播澈段,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼舰攒!你這毒婦竟也來了败富?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤摩窃,失蹤者是張志新(化名)和其女友劉穎兽叮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡充择,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年德玫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了匪蟀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椎麦。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖材彪,靈堂內(nèi)的尸體忽然破棺而出观挎,到底是詐尸還是另有隱情,我是刑警寧澤段化,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布嘁捷,位于F島的核電站,受9級(jí)特大地震影響显熏,放射性物質(zhì)發(fā)生泄漏雄嚣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一喘蟆、第九天 我趴在偏房一處隱蔽的房頂上張望缓升。 院中可真熱鬧,春花似錦蕴轨、人聲如沸港谊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)歧寺。三九已至,卻和暖如春棘脐,著一層夾襖步出監(jiān)牢的瞬間斜筐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工蛀缝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奴艾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓内斯,卻偏偏與公主長(zhǎng)得像蕴潦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俘闯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容