以太坊智能合约是存放在以太坊区块链具备特定地址的代码(它的功能)和数据(它的状态)集合。智能合约帐户之间能够相互传递消息以实现图灵完备运算。 智能合约以以太坊特定的二进制字节码经过以太坊虚拟机(EVM)运行于区块链上。javascript
以太坊智能合约一般是以名为 Solidity 的高级语言编写,并被编译为字节码上传到区块链上。java
Solidity是一种相似JavaScript的语言,容许你开发智能合约并能够被编译成EVM字节码,如今已是以太坊的旗舰语言而且是最流行的。git
没有实现Hello World程序的语言是不完整的,在以太坊的环境中,Solidity没有一个明确的方式能够”输出”一个字符串。 最接近的方式就是实用日志事件将一个字符串放入区块链中:github
contract HelloWorld { event Print(string out); function() { Print("Hello, World!"); } }
这条合约每次执行后,会经过Print并带有”Hello World”参数,将一条日志放入区块链中。web
能够经过多种形式的机制对solidity开发的以太坊 智能合约的编译。编程
solc
编译器。geth
或 eth``(仍需安装 ``solc
编译器) 提供的javascript控制台使用 web3.eth.compile.solidity
。若是你启动了 geth
节点,你能够经过以下命令来检查哪些编译器可使用。json
> web3.eth.getCompilers(); ["lll", "solidity", "serpent"]
这个命令返回当前可用的编译器的字符串数组。数组
Note网络
solc
编译器同 cpp-ethereum
一块儿被安装,做为替代方案,你能够本身编译 。app
若是你的 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
这项工做生效的基本机制是:
这些合约信息经过两步区块链注册被打包: * 第一步:称为 HashReg
的合约经过内容哈希来注册合约代码。 * 第二步:称为 UrlHint
的合约经过内容哈希来注册url。 这些 注册合约 被做为前沿(Frontier)版本的一部分,同时被带入到家园(Homestead)版本中。
使用这个结构,只须要知道合约的地址,而后获取到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)
本文章内容来源于以太坊社区,螃蟹翻译。顺便分享一个适合新手的以太坊教程,这个教程把上面的内容讲的更清楚更透彻。