Solidity 的编译现在不能直接用 web3 这个包来编译了:
var compiled = web3.eth.compile.solidity(contractSource)
以前可以这样用web3来编译,可是现在会报错:Returned error: Error: Method eth_compileSolidity not supported.
原因是 web3 的 Solidity 编译器已经被移除了。
替代方案:使用 solc 来编译 Solidity:
const solc = require('solc')
const solcOutput = solc.compile({sources: {main: contractSource}}, 1)
这样编译后的ABI存放在 solcOutput.contracts['main:Greeting'].interface
中(假设合约名字叫Greeting
), 而编译后的字节码存放在 solcOutput.contracts['main:Greeting'].bytecode
。
获取ETH账户地址
以前可以用 web3.eth.accounts
就可以直接获取到所有的地址的一个数组,然而现在必须要调用web3.eth.getAccounts()
这个函数。而且需要注意的是这个函数是一个异步函数,返回的不是地址,而是一个Promise<地址列表>
. 建议使用 async/await 来处理:
const accountsList = await web3.eth.getAccounts()
/* accountsList 是一个字符串数组(地址列表)
[ '0x055Ce03B2DE2e40e9dA322b6098378f5F6280A33',
'0x30B540058a8AF78c6b73CF2b72eA81B665280D0d',
'0x0B5baD78Ce6d3D1935fabd9C1AA2aA4F65317DCC',
'0x6B706d2058591C2Af396dbC539c30625f0BAD68F',
'0x52f581eCDd4522B7bA49b15C4E20B0a90aa31553',
'0x805F94503Fa8a3510a06636Fb62f6d9a6b6f7294',
'0xdD3598E8b4Bc232e9D966685bfFb2aa0F5A70E25',
'0x7246738dd4EbCd92fa4527A5521359b9560519E8',
'0x1B85A66ED4C0472A395594Dc865C1406d1859cF0',
'0xA6293d77DF43f6C4C106D047b8373194c1b55Ef9' ]
*/
发布合约
以前在 web3.eth.compile.solidity()
还能正常工作的时候,直接调用其返回值的 new()
方法就能发布合约了;然而现在要分成两步:
- 创建合约
- 发布合约
第一步,创建合约使用的是 web3.eth.Contract
这个类(注意大小写)。需要创建这个类的一个实例,比如:
const myContact = new web3.eth.Contract(contractAbi, {data: contractBytecode})
其中的 contractBytecode
是以16进制编码的合约字节码,即之前solc编译返回的solcOutput.contracts['main:Greeting'].bytecode
.
第二步,发布合约要调用 myContact.deploy().send()
。
对于 deploy()
而言,如果你的合约的构造函数是带参数的,则调用 deploy()
函数一定要把参数带上 -- Solidity 不像 JavaScript 一样缺少的参数会容忍的。
而调用 send()
的时候别忘了指定是从哪个地址扣费(from
选项),以及燃料多少(gas
选项)。send()
函数比较有意思的是,它返回的不光是一个 Promise
,还是一个 EventEmiter
,因而可以监听发布过程中的一些事件:
transactionHash
- 生成了交易hash的事件,一般只会触发一次receipt
- 合约已经打包到区块上了,一般只触发一次,这时就已经可以调用这个合约了confirmation
- 合约已经打包到区块链上并收到了确认,每次确认都会触发一次,最多到第24次确认error
- 发布合约失败
最后,本Demo中的 Greeting 合约而言就是这样来发布的:
myContact.deploy({arguments: ["Hello world!"]}) // arguments will be passed to the contract's constructor
.send({from: deployAddr, gas: gasEstimate * 10}) // send() returns a Promise & EventEmit
.on('transactionHash', function(transactionHash){
util.log("deploy transaction hash: ", transactionHash)
})
.on('receipt', function(receipt){
util.log("deploy receipt: ", receipt)
})
.on('confirmation', function(confirmationNum, receipt){
util.log("got confirmations number: ", confirmationNum)
})
.then(async function(myContactInstance){
util.log("deployed successfully.")
util.log("now the addr %o balance is %o", deployAddr, await web3.eth.getBalance(deployAddr))
testContact(myContactInstance)
})
.catch(err => {
util.log("Error: failed to deploy, detail:", err)
})
gasEstimate为什么要乘以10?
一定要记得 web3.eth.estimateGas()
返回的 gas 的数量只是一个估计值,建议实际调用的时候要多加点 gas -- 比如像我一样乘以10,以免gas不够就尴尬了。
调用合约
以前的合约的方法就直接在合约实例上,所以直接调用就行了,如myContactInstance.greet.call({from:xxxAddr})
;但是现在合约的方法在.methods
里面,即要这样调用:myContactInstance.methods.greet().call({from: testAddr})
.
调用合约的时候有两种方式,以前是分.call()
和.sendTransaction()
,现在是.call()
和.send()
. 其中前者(.call()
)适用于 constant
类型的方法,而后者(.send()
)适用于其他方法(有交易或会修改storage的方法)。
.call()
的返回值(Promise解决后)就是合约方法的返回值,然而.send()
的返回值(Promise解决后)是这次交易的信息,包括transactionHash和blockHash等 -- 即使在 Solidity 中有返回值也将被忽略。所以,非 constant
类型的方法建议不要有返回值,出错了就直接抛异常挂掉;如果真的想返回什么,建议通过事件的方式来通知调用者。
转载自:初探ETH智能合约 — Solidity开发部署踩坑记
https://www.clarencep.com/2018/03/28/eth-smart-contract-solidity-develop-and-deploy-demo/
完整的demo等见原文;