`

以太坊智能合约入门(编写、编译、创建、部署、交互、测试、交易)

阅读更多

什么是以太坊智能合约?

以太坊智能合约是存放在以太坊区块链具有特定地址的代码(它的功能)和数据(它的状态)集合。智能合约账户之间可以相互传递消息以实现图灵完备运算。 智能合约以以太坊特定的二进制字节码通过以太坊虚拟机(EVM)运行于区块链上。

以太坊智能合约通常是以名为 Solidity 的高级语言编写,并被编译为字节码上传到区块链上。

Solidity

Solidity是一种类似JavaScript的语言,允许你开发智能合约并可以被编译成EVM字节码,现在已经是以太坊的旗舰语言并且是最流行的。

编写合约

没有实现Hello World程序的语言是不完整的,在以太坊的环境中,Solidity没有一个明确的方式可以”输出”一个字符串。 最接近的方式就是实用日志事件将一个字符串放入区块链中:

contract HelloWorld {
        event Print(string out);
        function() { Print("Hello, World!"); }
}

 

这条合约每次执行后,会通过Print并带有”Hello World”参数,将一条日志放入区块链中。

编译合约

可以通过多种形式的机制对solidity开发的以太坊 智能合约的编译。

  • 通过命令行使用 solc 编译器。
  • 通过 geth 或 eth``(仍需安装 ``solc 编译器) 提供的javascript控制台使用 web3.eth.compile.solidity 。
  • 通过 实时在线编译器.
  • 通过 Ethereum Wallet.

在geth中设置solidity编译器

如果你启动了 geth 节点,你可以通过如下命令来检查哪些编译器可以使用。

> web3.eth.getCompilers();
["lll", "solidity", "serpent"]

 

这个命令返回当前可用的编译器的字符串数组。

Note

solc 编译器同 cpp-ethereum 一起被安装,作为替代方案,你可以自己编译 。

如果你的 solc 执行档不在指定的标准路径下,你可以通过 --solc 参数指定 solc 的执行路径。

$ geth --solc /usr/local/bin/solc

 

同样的,你可以通过命令行在运行时执行这个操作:

> admin.setSolc("/usr/local/bin/solc")
solc, the solidity compiler commandline interface
Version: 0.2.2-02bb315d/.-Darwin/appleclang/JIT linked to libethereum-1.2.0-8007cef0/.-Darwin/appleclang/JIT
path: /usr/local/bin/solc

 

编译一个简单的合约

我们来编译一个简单的合约代码:

> source = "contract test {
 function multiply(uint a)
 returns(uint d)
 {
 return a * 7;
 } 
}"

 

这个合约提供了一个名为 multiply 的函数,输入一个正整数 a 返回结果 a * 7 。

你已经准备好了编译solidity代码的环境,使用 geth 的JS命令台 eth.compile.solidity():

> contract = eth.compile.solidity(source).test
{
  code: '605280600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b60376004356041565b8060005260206000f35b6000600782029050604d565b91905056',
  info: {
    language: 'Solidity',
    languageVersion: '0',
    compilerVersion: '0.9.13',
    abiDefinition: [{
      constant: false,
      inputs: [{
        name: 'a',
        type: 'uint256'
      } ],
      name: 'multiply',
      outputs: [{
        name: 'd',
        type: 'uint256'
      } ],
      type: 'function'
    } ],
    userDoc: {
      methods: {
      }
    },
    developerDoc: {
      methods: {
      }
    },
    source: 'contract test { function multiply(uint a) returns(uint d) { return a * 7; } }'
  }
}

 

Note

编译器支持RPC <[https://github.com/ethereum/wiki/wiki/JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC)>__ ,因此你可以使用web3.js并通过RPC/IPC连接到 geth 。

下面的例子显示了如何通过使用JSON-RPC的 geth 来使用编译器。

$ geth --datadir ~/eth/ --loglevel 6 --logtostderr=true --rpc --rpcport 8100 --rpccorsdomain '*' --mine console  2>> ~/eth/eth.log
$ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"],"id":1}' http://127.0.0.1:8100

 

编译器为源代码中的每个单独的合约生成一个合约对象,命令 eth.compile.solidity 会返回合约名和合约对象的映射。这个例子中我们的合约名为 test ,所以命令 eth.compile.solidity(source).test 会返回名为test的合约对象,并包含如下相关域:
code:编译生成的以太坊虚拟机字节码
info:编译器输出的额外元数据
source:源代码
language:合约使用的编程语言(Solidity, Serpent, LLL)
languageVersion:合约语言的版本号
compilerVersion:编译合约代码所使用编译器的版本号
abiDefinition:应用程序二进制接口定义
userDoc:提供给用户的 [NatSpec Doc]
developerDoc:提供给开发者的 [NatSpec Doc]

编译器最直观的输出结构(code 和 info)反应出两个完全不同的 部署路径 ,编译出的EVMcode会给发给区块链上特定交易,剩下的(info)会存放在去中心化的区块链云端作为完善代码的元数据。

如果你的源代码包含多个合约,那么输出会包含每一个合约的入口信息,合约的展开信息可以通过名字来获取,你可以通过查看当前的GlobalRegistrar合约来尝试一下效果:

contracts = eth.compile.solidity(globalRegistrarSrc)

 

创建并部署一个合约

在开始这个章节前,请确保你有一个解锁的账户并且里面有一些资金。

你现在可以通过前面章节的EVM代码来向一个空地址 发起一笔交易 。

Note

这个可以通过更容易的方式完成,也就是通过 实时在线Solidity编译器 或者 Mix IDE 。

var primaryAddress = eth.accounts[0]
var abi = [{ constant: false, inputs: { name: 'a', type: 'uint256' } }]
var MyContract = eth.contract(abi)
var contract = MyContract.new(arg1, arg2, ..., {from: primaryAddress, data: evmByteCodeFromPreviousSection})

 

所有的二进制数据都会被序列化为十六进制格式,十六进制的字符串总是以 0x 作为前缀。

Note

请注意 arg1, arg2, ... 是合约的构造参数,可以接受任何输入,如果合约不需要任何构造参数那么这些参数可以被忽略。

值得指出的是执行这些步骤你需要支付一些费用,一旦的交易被打包进区块,你账户的余额会根据以太坊虚拟机的瓦斯费用规则进行扣除,经过一些时间,你的交易会出现在一个状态被确认是一致的区块中,你的合约现在已经存在于区块链中。

异步执行这些步骤的方法如下:

MyContract.new([arg1, arg2, ...,]{from: primaryAccount, data: evmCode}, function(err, contract) {
  if (!err && contract.address)
    console.log(contract.address);
});

 

合约的交互

通常使用抽象层 eth.contract() 来完成与合约的交互,该函数返回一个JavaScript对象,该对象包含了所有可以被JavaScript调用的合约函数。

描述合约可用函数的标准方法是 ABI定义,这个对象是一个数组,该数组包含了每一个可用合约函数的调用签名和返回值。

var Multiply7 = eth.contract(contract.info.abiDefinition);
var myMultiply7 = Multiply7.at(address);

 

现在所有ABI中定义的函数都可以在合约实例中使用了,你可以通过如下两种方法之一来进行调用:

> myMultiply7.multiply.sendTransaction(3, {from: address})
"0x12345"
> myMultiply7.multiply.call(3)
21

 

当使用 sendTransaction 时,通过发送一个交易来调用函数。这种方式会消耗以太币,同时调用会永久被纪录在区块链中,这种方式的返回值就是交易的哈希值。

当使用 call 时,函数会在本地的虚拟机(EVM)上执行,调用的返回值就是函数的返回值。这种方式的调用不会被纪录在区块链中,因此也不会改变合约的内部状态,这种方式被称为常量函数调用。这种调用方式不会消耗以太币。

只关心返回值的情况下你应该使用 call ,如果你关心合约的状态变化那么就使用 sendTransaction 。

在上面的例子中,不涉及改变合约状态,因此 sendTransaction 调用只会白白燃烧燃料(gas)增加宇宙的熵。

合约元数据

在上个章节我们解释了如何在区块链上创建合约,接下来我们处理编译器输出的内容,合约元数据或者合约信息。

当与一个你还没有创建的合约进行交互时,你可能想要说明文档或者查看其源代码。合约作者被鼓励通过区块链或者第三方机构的服务来注册此类信息,例如: EtherChain 。API admin 为注册了这类信息的合约提供了便利的方法来查看。

// get the contract info for contract address to do manual verification
var info = admin.getContractInfo(address) // lookup, fetch, decode
var source = info.source;
var abiDef = info.abiDefinition

 

这项工作生效的基本机制是:

  • 合约信息被上传到一个公共可访问的位置地址 URI 上
  • 知道合约地址任何人都可以找到相关的 URI

这些合约信息通过两步区块链注册被打包: * 第一步:称为 HashReg 的合约通过内容哈希来注册合约代码。 * 第二步:称为 UrlHint 的合约通过内容哈希来注册url。 这些 注册合约 被作为前沿(Frontier)版本的一部分,同时被带入到家园(Homestead)版本中。

使用这个结构,只需要知道合约的地址,然后获取到url,进而获取合约相关的所有元数据。

如果你是一个称职的合约创建者,你需要遵循如下步骤:

  1. 将合约本身部署到区块链上
  2. 获取合约信息的json文件
  3. 部署合约信息的json文件到你选择的url上
  4. 注册代码哈希 -> 内容哈希 -> url

JS API提供帮助让这些步骤变的非常简单,调用 admin.register 来得到合约摘要,将摘要序列化存储到指定的json文件中,计算文件的内容哈希,并最终将这些内容哈希注册为代码哈希。一单你将这些文件部署到任何url,你可以通过使用 admin.registerUrl 在区块链上注册你的内容哈希url(如果使用固定内容寻址模型作为文档存储那么rul-hint就不是必需的了)。

source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"
// 使用solc来编译
contract = eth.compile.solidity(source).test
// 创建合约对象
var MyContract = eth.contract(contract.info.abiDefinition)
// 合约的摘要信息,序列化到指定的json文件中
contenthash = admin.saveInfo(contract.info, "~/dapps/shared/contracts/test/info.json")
// 合约发送到区块链上
MyContract.new({from: primaryAccount, data: contract.code}, function(error, contract){
  if(!error && contract.address) {
    // 计算内容哈希并且将其通过 `HashReg` 注册为代码哈希
    // 使用地址来发送交易
    // 返回我们用来注册url的内容哈希
    admin.register(primaryAccount, contract.address, contenthash)
    // 将 ~/dapps/shared/contracts/test/info.json 部署到一个url上
    admin.registerUrl(primaryAccount, hash, url)
  }
});

 

测试合约和交易

通常要对合约和交易进行测试和调试,这个章节我们来介绍几种调试工具和实践方法。为了测试合约和交易,并且避免产生真实的影响,你最好在一条私有区块链上进行操作,这可以通过配置网络ID(选择一个独一无二的整数)来实现,并且不需要其他对等节点。推荐的做法是为测试设置其他的数据目录和端口,以避免可能的来自其他节点的影响(假设使用默认的参数运行)。通过调试模式来运行 geth ,并设置最高级别的日志:

geth --datadir ~/dapps/testing/00/ --port 30310 --rpcport 8110 --networkid 4567890 --nodiscover --maxpeers 0 --vmdebug --verbosity 6 --pprof --pprofport 6110 console 2>> ~/dapp/testint/00/00.log

 

提交任何交易前,你需要设置好你的测试链,详细内容请查看: <cite style="box-sizing: border-box;">test-networks</cite>

// create account. will prompt for password
personal.newAccount();
// name your primary account, will often use it
primary = eth.accounts[0];
// check your balance (denominated in ether)
balance = web3.fromWei(eth.getBalance(primary), "ether");

// assume an existing unlocked primary account
primary = eth.accounts[0];

// mine 10 blocks to generate ether

// starting miner
miner.start(4);
// sleep for 10 blocks (this can take quite some time).
admin.sleepBlocks(10);
// then stop mining (just not to burn heat in vain)
miner.stop();
balance = web3.fromWei(eth.getBalance(primary), "ether");

 

创建交易后你可以强制执行它们,如下:

miner.start(1);
admin.sleepBlocks(1);
miner.stop();

 

可以通过如下来检查未验证的交易:

// shows transaction pool
txpool.status
// number of pending txs
eth.getBlockTransactionCount("pending");
// print all pending txs
eth.getBlock("pending", true).transactions

 

如果提交了交易创建合约,你可以检查所需代码是否已经插入到当前的区块中:

txhash = eth.sendTansaction({from:primary, data: code})
//... mining
contractaddress = eth.getTransactionReceipt(txhash);
eth.getCode(contractaddress)

 

本文章内容来源于以太坊社区,螃蟹翻译。顺便分享一个适合新手的以太坊教程,这个教程把上面的内容讲的更清楚更透彻。

分享到:
评论

相关推荐

    使用truffle和infura部署以太坊合约中文pdf电子书教程

    在教程的后半部分,汇智网还介绍了以太坊电商DApp实战开发课程的内容,包括以太坊合约开发、ipfs去中心化文件系统、链下mongodb存储等知识点。这是一个实战课程,采用敏捷开发模式,通过8个冲刺周期的详细讲解与在线...

    solidity-以太坊区块链Truffle-webpack开发入门 (四) 编写和编译合约

    在以太坊区块链开发中,Solidity是一种专为智能合约设计的编程语言,而Truffle是一个集成开发环境(IDE),用于简化智能合约的编写、编译、部署和测试。Webpack则是一个模块打包工具,通常用于JavaScript应用,但在...

    Truffle的官方文档中文版

    5. **部署合约**:Truffle支持本地的Ganache测试网以及以太坊主网和其他测试网。通过`truffle migrate`命令,可以将编译后的合约部署到选定的网络上。 6. **编写和执行测试**:在`test`目录下,开发者可以编写...

    智能合约从入门到精通:完整范例.pdf

    Solidity是专为以太坊区块链设计的一种高级编程语言,用于编写智能合约。在本案例中,我们看到Solidity的版本为^0.4.2,这意味着合约遵循该版本的语法和特性。开发者需要掌握Solidity的基础语法,如变量声明、函数...

    Ethereum Pet Shop -- Your First Dapp _ Tutorials _ Truffle Suite.pdf

    以太坊宠物店(Ethereum Pet Shop)是Truffle Suite提供的一个入门级教程,旨在指导开发者完成第一个去中心化应用程序(Dapp)的创建。这个教程非常适合对以太坊和智能合约拥有基础了解的开发者,特别是那些有一定...

    Introducing.Ethereum.and.Solidity

    此外,你还将了解到如何使用 Remix 进行实时编译、测试和部署合约到本地的 Ganache 测试网络或以太坊主网。 安全性和最佳实践也是学习Solidity不可或缺的部分。文档会强调一些常见的安全问题,如重入攻击、未初始化...

    solidity-以太坊区块链Truffle-webpack开发入门 (七) 合约的测试环境

    在以太坊区块链开发中,Solidity是一种常用的智能合约编程语言,而Truffle是一个全面的开发框架,它简化了合约的编译、部署和测试流程。Truffle与Webpack结合使用,可以实现更高效的项目构建。本文将深入探讨如何在...

    mastering ethereum

    代码示例覆盖了以太坊智能合约的编写、编译、部署以及调用等过程,同时还包括了如何与智能合约进行交互的示例代码。为了更好地利用这些代码示例,建议读者具备一定的编程基础,并安装好相关的开发工具和环境,例如...

    ethereum-workshop

    在工作坊中,学员将逐步学习如何创建一个新的以太坊账户,设置开发环境,编写简单的智能合约,如一个简单的代币合约,然后学会如何部署和测试这些合约。通过实际操作,理解以太坊网络的运作机制以及智能合约的生命...

    EthPlayground:测试开发人员

    它允许开发者在实际部署到以太坊网络之前,在一个沙箱环境中编写、编译、部署和测试Solidity合约。这个平台可能包括以下核心功能: 1. **源代码编辑器**:提供一个内置的代码编辑器,支持实时语法高亮和错误检查,...

    embark-tutorial:来自ethereum.org的embark教程

    Embark 是一个强大的框架,...通过这个"embark-tutorial",开发者将学习如何利用 Embark 构建、测试和部署以太坊上的 DApps,以及如何与智能合约进行交互。这是一个全面的入门指南,为深入的区块链开发打下坚实的基础。

    dapp-starter-kit

    4. **Truffle Suite**:Truffle是用于以太坊开发的流行开发环境、框架和命令行工具,它可能作为"dapp-starter-kit"的一部分,提供智能合约的编译、部署和测试工具。 5. **MetaMask**:这是一个浏览器插件,允许用户...

Global site tag (gtag.js) - Google Analytics