原文:https://medium.com/@k3no/ethereum-tokens-smart-contracts-3346b62d2a0
在编译之前,需要安装node以及npm,之后需要建立一个文件夹,使用以下指令
$ mkdir web3test && cd web3test
$ npm init
$npm install web3 --save
In a nutshell, Solidity ( check for the correct docs version !) is the language in which you write contracts, it’s syntax is similar to javascript (importantly it is statically typed and supports libraries and other cool features). So the plan is to write a contract, then compile it and then release/interact with it through web3.js which we briefly covered in the last part of this series. So let’s start with a very very simple contract.
pragma solidity ^0.4.0;
contract HelloWorldContract {
function sayHi() constant returns (string){
return 'Hello World';
}
}
While it might look simple, there is a lot going on here, so let’s comment this example:
// You will also need a solidity linter for ATOM ! like linter-solidity
// FILE : HelloWorldContract.sol
// Notice the .sol (for solidity) extension.
pragma solidity ^0.4.0;
// will not compile with a compiler earlier than version 0.4.0
contract HelloWorldContract {
// notice the word contract
function sayHi() constant returns (string){
// As a typed language, this function specifies what it returns via the constant returns (string) bit.
return 'Hello World';
}
}
This contract should respond with “Hellow World” when called (we’ll look into that later) via the sayHi() function.
As mentioned, in order to make contracts work, we first need to compile them and then release or deploy them into the network, the first thing we will need to compile is the solc ( solidity ) node package:
npm install solc
And now in atom we actually do the compiling:
以下所有的js文件都需要在一个终端运行命令行:eth -j -admin-via-http
,否则会出现各种错误。
// FILE: compile.js
const fs = require ('fs');
const solc = require ('solc');
const input = fs.readFileSync('HelloWorldContract.sol');
const output = solc.compile(input.toString(), 1);
for (var contractName in output.contracts){
console.log(contractName + ': ' + output.contracts[contractName].bytecode)
}
// :helloWorldContract:
6060604052341561000f57600080fd5b5b6101488061001f6000396000f300606060405263ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416630c49c36c811461003d575b600080fd5b341561004857600080fd5b6100506100c8565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561008d5780820151818401525b602001610074565b50505050905090810190601f1680156100ba5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100d061010a565b60408051908101604052600b81527f48656c6c6f20576f726c64000000000000000000000000000000000000000000602082015290505b90565b602060405190810160405260008152905600a165627a7a72305820db2690e18e82d5fc2cb625d3b4d4a0a8d9abbdd5df5e343e091d6bf3ed3a2e5a0029
Success ! the weird looking block at the bottom is our compiled contract ( Ethereum-specific binary format or bytecode ), we now need to deploy it to the blockchain and then interact with it:
In order to deploy our contract we first need to create a contract object in web3, in order to do that we need to format those numbers up there into an Ethereum contract ABI ( Application Binary Interface) Array :
// FILE: compileDeploy.js
const fs = require ('fs');
const solc = require ('solc');
const Web3 = require ('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
const input = fs.readFileSync('HelloWorldContract.sol');
const output = solc.compile(input.toString(), 1);
const bytecode = output.contracts[':helloWorldContract'].bytecode;
const abi = output.contracts[':helloWorldContract'].interface;
const helloWorldContract = web3.eth.contract(JSON.parse(abi));
console.log(helloWorldContract); // contract structure...
An ABI in case you were wondering“ it’s basically how you can encode solidity contracts for the EVM and backwards how to read the data out of transactions as well as contract to contract communication.” Neat
// FILE: compileDeploy.js
console.log('Setting up...');
const fs = require ('fs');
const solc = require ('solc');
const Web3 = require ('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
console.log('Reading Contract...');
const input = fs.readFileSync('HelloWorldContract.sol');
console.log('Compiling Contract...');
const output = solc.compile(input.toString(), 1);
const bytecode = output.contracts[':helloWorldContract'].bytecode;
const abi = output.contracts[':helloWorldContract'].interface;
//Contract Object
const helloWorldContract = web3.eth.contract(JSON.parse(abi));
console.log('unlocking Coinbase account');
const password = "账户密码";
try {
web3.personal.unlockAccount(自己的账户名称, password,100);
} catch(e) {
console.log(e);
return;
}
console.log("Deploying the contract");
const helloWorldContractInstance = helloWorldContract.new({
data: '0x' + bytecode,
from: 账户名称,
gas: 1000000
}, (err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res.transactionHash);
// If we have an address property, the contract was deployed
if (res.address) {
console.log('Contract address: ' + res.address);
}
});
And the Readout:
Setting up...
Reading Contract...
Compiling Contract...
unlocking Coinbase account
Deploying the contract
0x876453431987c163b84153e99ec8f6f7a7bd232bb41dfe0537884907d7769664
0x876453431987c163b84153e99ec8f6f7a7bd232bb41dfe0537884907d7769664
Contract address: 0x2759054bbfbaed66319ecbce83824efce04ee63c
In Ethereum, a contract is created by sending a transaction (from an address you control - hence the unlock part), the payload of this transaction ( which has to be formatted accordingly — hence the contractInstance part) is the contract code which now lives on the blockchain and has an address, let’s query our trusty block explorer (etherscan.io) and see what it tells us:
Beyond the obvious observation that it exists and was identified as a contract , there is a TxHash and a Contract Creator Address ( along with the Contract Address ) that is now part of the blockchains history; and if you click on the Contract Code tab ?
You will get the exact compiled contract we started with, an instance of which now lives on the blockchain and is ready for us to interact with ! ( scroll up to the :helloworld code comment, it’s the same code plus the 0x bit ).
A note about costs: Even though we are on a testnet where the Ether is free, it should be noted that this transaction incurred in some costs and there is a complexity associated with them in the form of gas, gas prices and gas limits which I will sidestep for now, just be mindful they exist and where covered by the gas: 1000000 line while deploying.
So now that our smart contract has been created and deployed, our last task will be to interact with it, if you remember our contract has one function:
function sayHi() constant returns (string){
return 'Hello World';
}
Which if we are successful will somehow return the words “Hello World”, well that’s the plan anyways.
Now that our contract lives in the blockchain, we can query the blockchain for our contracts code like so:
console.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
console.log('Reading contract...');
const deployedHelloWorldContractInstance = web3.eth.getCode("0x2759054bbfbaed66319ecbce83824efce04ee63c");
console.log('Contract Code: ' + deployedHelloWorldContractInstance);
And our readout:
Setting up...
Reading contract...
Contract Code: 0x606060405263ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416630c49c36c811461003d575b600080fd5b341561004857600080fd5b6100506100c8565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561008d5780820151818401525b602001610074565b50505050905090810190601f1680156100ba5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100d061010a565b60408051908101604052600b81527f48656c6c6f20576f726c64000000000000000000000000000000000000000000602082015290505b90565b602060405190810160405260008152905600a165627a7a72305820db2690e18e82d5fc2cb625d3b4d4a0a8d9abbdd5df5e343e091d6bf3ed3a2e5a0029
Which surprise surprise is still the same code we added to our hello world contract.
Gotcha time: Now in order to call our method: sayHi() we need to instantiate a contract, the gotcha is that we need an ABI to do so, and the original maker of the contract (that’s us) needs to make it available, so we need to rewrite our original contract code (compileDeploy.js ) so we can write our ABI and then read it in json format, for clarity sake here’s a script that takes care of the writing part ,you can merge it with the compileDeploy one:
// FILE: exportABI.js
console.log('Setting up...');
const fs = require ('fs');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading Contract...');
const input = fs.readFileSync('HelloWorldContract.sol');
console.log('Compiling Contract...');
const output = solc.compile(input.toString(), 1);
const bytecode = output.contracts[':HelloWorldContract'].bytecode;
console.log('Generating ABI...');
const abi = output.contracts[':HelloWorldContract'].interface;
fs.writeFile("./HelloWorldABI.JSON", abi, function(err) {
if(err) {
return console.log(err);
}
console.log("ABI Saved");
});
Resulting in the following HelloWorldABI.json:
[{"constant":true,"inputs":[],"name":"sayHi","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]
let’s try instantiating a contract by joining these 2 parts ( the abi and the contract code):
FILE: callContract.js
console.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const HelloWorldABI = require("./HelloWorldABI.json");
console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
const helloWorldContract = web3.eth.contract(HelloWorldABI);
var helloWorldContractInstance = helloWorldContract.at("0x2759054bbfbaed66319ecbce83824efce04ee63c");
console.log(helloWorldContractInstance);
And our resulting instance contract code:
Contract {
_eth:
Eth {
_requestManager: RequestManager { provider: [Object], polls: {}, timeout: null },
getBalance: { [Function: send] request: [Function: bound ], call: 'eth_getBalance' },
getStorageAt: { [Function: send] request: [Function: bound ], call: 'eth_getStorageAt' },
getCode: { [Function: send] request: [Function: bound ], call: 'eth_getCode' },
getBlock: { [Function: send] request: [Function: bound ], call: [Function: blockCall] },
getUncle: { [Function: send] request: [Function: bound ], call: [Function: uncleCall] },
getCompilers: { [Function: send] request: [Function: bound ], call: 'eth_getCompilers' },
getBlockTransactionCount:
{ [Function: send]
request: [Function: bound ],
call: [Function: getBlockTransactionCountCall] },
getBlockUncleCount:
{ [Function: send]
request: [Function: bound ],
call: [Function: uncleCountCall] },
getTransaction:
{ [Function: send]
request: [Function: bound ],
call: 'eth_getTransactionByHash' },
getTransactionFromBlock:
{ [Function: send]
request: [Function: bound ],
call: [Function: transactionFromBlockCall] },
getTransactionReceipt:
{ [Function: send]
request: [Function: bound ],
call: 'eth_getTransactionReceipt' },
getTransactionCount: { [Function: send] request: [Function: bound ], call: 'eth_getTransactionCount' },
call: { [Function: send] request: [Function: bound ], call: 'eth_call' },
estimateGas: { [Function: send] request: [Function: bound ], call: 'eth_estimateGas' },
sendRawTransaction: { [Function: send] request: [Function: bound ], call: 'eth_sendRawTransaction' },
signTransaction: { [Function: send] request: [Function: bound ], call: 'eth_signTransaction' },
sendTransaction: { [Function: send] request: [Function: bound ], call: 'eth_sendTransaction' },
sign: { [Function: send] request: [Function: bound ], call: 'eth_sign' },
compile: { solidity: [Object], lll: [Object], serpent: [Object] },
submitWork: { [Function: send] request: [Function: bound ], call: 'eth_submitWork' },
getWork: { [Function: send] request: [Function: bound ], call: 'eth_getWork' },
coinbase: [Getter],
getCoinbase: { [Function: get] request: [Function: bound ] },
mining: [Getter],
getMining: { [Function: get] request: [Function: bound ] },
hashrate: [Getter],
getHashrate: { [Function: get] request: [Function: bound ] },
syncing: [Getter],
getSyncing: { [Function: get] request: [Function: bound ] },
gasPrice: [Getter],
getGasPrice: { [Function: get] request: [Function: bound ] },
accounts: [Getter],
getAccounts: { [Function: get] request: [Function: bound ] },
blockNumber: [Getter],
getBlockNumber: { [Function: get] request: [Function: bound ] },
protocolVersion: [Getter],
getProtocolVersion: { [Function: get] request: [Function: bound ] },
iban:
{ [Function: Iban]
fromAddress: [Function],
fromBban: [Function],
createIndirect: [Function],
isValid: [Function] },
sendIBANTransaction: [Function: bound transfer] },
transactionHash: null,
address: '0x2759054bbfbaed66319ecbce83824efce04ee63c',
abi:
[ { constant: true,
inputs: [],
name: 'sayHi',
outputs: [Array],
payable: false,
stateMutability: 'view',
type: 'function' } ],
sayHi:
{ [Function: bound ]
request: [Function: bound ],
call: [Function: bound ],
sendTransaction: [Function: bound ],
estimateGas: [Function: bound ],
getData: [Function: bound ],
'': [Circular] },
allEvents: [Function: bound ] }
You can ignore most of it for now, but notice that our contract now has our method definition: sayHi provided by the abi we just read, so we are ready to call our function, but before so let’s talk about the ways we can call contract functions in the Ethereum Network :
In order to finally call our sayHi() function, we need to be aware that there are 2 ways of interacting with a contract:
1、Call a ‘dry run’ which happens in your computer and has no effects on the blockchain contract ( it doesn’t change it’s state ) or has costs beyond initial deployment :
console.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const HelloWorldABI = require("./HelloWorldABI.json");
console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
console.log('Creating contract instance');
const helloWorldContract = web3.eth.contract(HelloWorldABI);
var helloWorldContractInstance = helloWorldContract.at("0x2759054bbfbaed66319ecbce83824efce04ee63c");
console.log ('calling the contract locally');
console.log(helloWorldContractInstance.sayHi.call());
And our readout:
Setting up...
Reading abi
Connecting
Creating contract instance
Calling the contract locally
Hello World // Finally !
2.- sendTransaction Contracts can receive transactions along with values/parameters (which I am skipping for now) and these transactions in turn are the only ones that can affect the state of the contract, they also cost gas, here’s an example although since our contract has no state or state changing functions it does nothing, yet costs us ether.
console.log('Setting up...');
const solc = require ('solc');
const Web3 = require ('web3');
console.log('Reading abi');
const HelloWorldABI = require("./HelloWorldABI.json");
console.log('Connecting');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
console.log('Creating contract instance');
const helloWorldContract = web3.eth.contract(HelloWorldABI);
var helloWorldContractInstance = helloWorldContract.at("0x2759054bbfbaed66319ecbce83824efce04ee63c");
console.log('unlocking Coinbase account');
const password = "yourPassword";
try {
web3.personal.unlockAccount(web3.eth.coinbase, password);
} catch(e) {
console.log(e);
return;
}
console.log ('sending Transaction to the contract');
helloWorldContractInstance.sayHi.sendTransaction({from:web3.eth.coinbase}, function(err, txHash) {
if (err != null) {
console.error("Error while sending transaction: " + err);
}
else{
console.log("Transaction Sent here's you txHash: " + txHash);
}
});
And our readout:
Setting up...
Reading abi
Connecting
Creating contract instance
unlocking Coinbase account
sending Transaction to the contract
Transaction Sent here's you txHash: 0x0750fdf4e24055aa4f73bd25e06ba19fa73111f3925d2108422e47bebd01d12a
The main difference with call, is that the transaction does not return a value, and it is not processed immediately, instead we get a txHash you can check on the block explorer :
And in parity UI :
This last type of way of interacting with a contract is perhaps the most challenging and rewarding and we will dive into it in the next part before tackling tokens, but for now let’s take a breath and recap what we have so far covered:
Notes Part 1 : Setting up : Getting a wallet/client , connecting to a test ethereum blockchain and getting some test ether.
Notes Part 2: web3.js/node : Interacting with the blockchain through web3.js and node for more convenience and to ease development.
Notes Part 3: Solidity ( this post ) :Installing solidity (Ethereums contract writing language ) , Creating a very basic contract, compiling it, deploying it to the blockchain ( the test one) and interacting with it.
If this information was valuable to you, please consider donating to the following addresses:
Bitcoin: 1KBGyZW1raxCCLZgfXexeYwJZxtdLnUG6d
Ether: 0xD9E724BFb376F1AbDb30382D1923eA8aD1eD22c4
Cheers !
Keno
About the Author :
Born Eugenio Noyola Leon (Keno) I am a Designer,Web Developer/programmer, Artist and Inventor, currently living in Mexico City, you can find me at www.k3no.com