如何通过solc编译solidity编写的以太坊智能合约
曲水流觞113
发表于 2022-11-6 23:42:53
177
0
0
7 D: ^7 Y! K+ }. w1 @- l
solidity编写的以太坊智能合约可通过命令行编译工具solc来进行编译,成为以太坊虚拟机中的代码。solc编译后最终部署到链上形成我们所见到的各种智能合约。
作为一个solidity命令行编译工具,我们来看看官网都怎么说solc。 _! m, h F Z
solc的安装很简单:8 E/ v c! G- ?1 R9 }; s
3 o/ p2 A i) }# H# O
npminstall-gsolc
1 H2 ~7 T8 L1 n
//或者
npminstall-gsolc-cli$ @) H2 m+ ?4 a0 i! \
* r% `. L" i" W! Y! I
//或者
sudoapt-getinstallsolc: J' h& v% o; J& y" f- M* }0 Q9 u
安装完成后我们来看,solc--help,solc--help命令显示所有的solc命令选项。编译器可以生成各种输出,比如最终的二进制合约文件、语法树的汇编或者需要预计的要花费的gas等。solc--binsourceFile.sol,可以编译后输出一个名为sourceFile.sol的智能合约文件。如果你想从solc获得更丰富的一些输出变量,你可以使用solc-ooutputDirectory--bin--ast--asmsourceFile.sol。
- h6 Y0 T! f* w
你在部署以太坊智能合约之前可以用solc--optimize--binsourceFile.sol优化一下。默认情况下solc编译器会帮你优化200次。你也可以设置--runs=1,这样就按照最小化的方式进行编译,如果你希望多次交易不太在乎成本,那你可以设置成你想要的次数:)。
3 ^! J7 M& F( D" q
命令行编译器会自动读取需要导入的文件,也可以通过使用prefix=path来指定路径,例如:
solcgithub.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/=/usr/local/lib/fallbackfile.sol
4 m( }6 O9 o* v9 b& Y
这样编译器就会从指定目录github.com/ethereum/dapp-bin/下的/usr/local/lib/dapp-bin/目录开始搜索,如果没有找到文件,它将查看/usr/local/lib/fallback。solc将只读取你指定的这两个路径的,因此像import"/etc/passwd";必须要通过/=重新映射才起作用。如果有多个匹配,则选择具有最长公共前缀的进行匹配。
7 j6 d. |9 T& r
出于安全上的考虑,编译器限制了它可以访问的一些目录。在命令行中指定的源文件的路径(及其子目录)和命令行指定的路径外其他所有内容都会被拒绝。--allow-paths/sample/path,/another/sample/path来切换。
; L9 |! S6 @5 x+ h5 C% f3 Q
如果智能合约使用了libraries,你会注意到字节码包含了__LibraryName______的子字符串。您可以使用solc作为链接器,这意味着它将在这些点为您插入库地址。$ v& g3 P7 \( X3 G
可以通过添加库--libraries"Math:0x12345678901234567890Heap:0xabcdef0123456"到您的命令,以提供每个库的地址,或者使用文件中的说明字符串(每行一个库),并使用--librariesfileName运行solc。. m- v, n i1 m" K) r6 E6 g. b
如果用选项--link调用Solc,则所有输入文件都被解释为未链接的二进制文件(HEX编码),在上面给出的__LibraryName____格式中,将其链接到适当地址(如果从stdin读取输入,则将其写入stdout)。在这种情况下,除了库外,所有选项都被忽略(包括-o)。( d3 D5 o" q# l# g3 Q0 o" S
1 e t& w) H. U) u. B1 u$ Q3 q5 s3 I
如果用--standard-json调用SOLC,它就将标准的JSON输入(如下所述),并返回JSON输出。
#solc编译器输入输出JSON描述' K; c& S. W/ A
- H3 w" ] _) U+ I1 s7 o
这些JSON格式通过编译器API使用,可以通过SOLC获得。内容都是可以修改的,一些对象是可选的(如前所述),其目的是向后兼容。
编译器的API需要一个JSON格式的输入,然后以JSON格式输出编译结果。2 m; N! l4 z) f$ \' @
注意不允许注释。下面示例中的注释,是官网为了学习者更好的理解标注的。
输入格式说明:: M% D' v# [8 g! `. `: R
" C: T, D! q7 Q0 R0 s' a; F) V
{
9 L: w8 O; u( X: S1 ]
//Required:Sourcecodelanguage,suchas"Solidity","serpent","lll","assembly",etc.- C5 M4 N9 }1 [+ M
language:"Solidity",! @: Q: x8 z* E; P9 z
3 o7 {/ y6 B9 m+ L! t) C% A3 V) z( z
//Required
sources:
$ ]' N# F/ u7 d) N( ? x+ A. ?
{
1 I i& Q6 C/ X; _/ C
//Thekeysherearethe"global"namesofthesourcefiles,
( [9 `% P- u z
//importscanuseotherfilesviaremappings(seebelow)." k4 c1 }- M- _4 j- u3 J
$ B- j& K ~# H; F4 O5 C
"myFile.sol":, m- v- Q$ m/ O6 d# p1 d$ v/ O2 ~& P
{; f! S4 i% L7 S
& L8 g# I) l- ]' `7 v
//Optional:keccak256hashofthesourcefile
//ItisusedtoverifytheretrievedcontentifimportedviaURLs.
|( O8 A0 j9 r$ ~
"keccak256":"0x123...",% S( q7 |' G/ u5 n0 C1 r& |
//Required(unless"content"isused,seebelow):URL(s)tothesourcefile.$ w. r q2 I! h# Y7 t2 z k
" r: V3 k8 x: U. p" b
//URL(s)shouldbeimportedinthisorderandtheresultcheckedagainstthe' E! a# [1 J& d$ ^; f9 z5 A, ^$ M& v
//keccak256hash(ifavailable).Ifthehashdoesn'tmatchornoneofthe7 `! b" y# P; Q0 F1 y. d0 B! g+ A
//URL(s)resultinsuccess,anerrorshouldberaised.
"urls":
3 {8 p" N# N1 W p0 ~1 g/ H4 [
[
"bzzr://56ab...",
) F' {( E7 \/ A2 M0 Y4 |
"ipfs://Qma...",' L) P h# O6 Q3 C2 \* J
p, y8 k* L: l2 X
"file:///tmp/path/to/file.sol"4 f) ~% x$ V! q( i4 `7 J8 [9 C5 f
]; l0 W5 d) P& M( V5 _
},+ H! V8 {- U0 [7 W: e
"mortal":# n( Y, g0 l) n! m
{$ ^- Z- h7 A' E: u
//Optional:keccak256hashofthesourcefile ]! Y* I9 _$ s3 ~4 C) Y- V( e; W
"keccak256":"0x234...",/ K T& O7 i) O {
//Required(unless"urls"isused):literalcontentsofthesourcefile
"content":"contractmortalisowned{functionkill(){if(msg.sender==owner)selfdestruct(owner);}}"* { W. j! I3 d& W& @
$ [6 g4 A% z6 b
}
},
" J! E% w6 R8 y8 ^ I
//Optional# Z+ V* [! d1 }; |
0 r' }, N( l- c, ~* h
settings:3 K( X8 h* \# w( z) w- t/ x6 N
{$ d# i, j* Z4 @4 E( \$ ]! i; P
3 B. b' I. ^8 ^+ r8 p
//Optional:Sortedlistofremappings
remappings:[":g/dir"],, e P# e$ X+ m* n
; E$ y1 }# V& \5 E
//Optional:Optimizersettings
1 t! [3 {; y* _3 T8 }* m1 N
optimizer:{8 T6 f# B; G& t
5 q, I% s' z5 y. t
//disabledbydefault
8 h8 x. H, G- |
enabled:true,2 p* O8 f( b3 w6 ^+ S9 Y
//Optimizeforhowmanytimesyouintendtorunthecode.! n" o& W/ J+ p4 z! s ]/ M
: s# D& w1 ?- X9 Y
//Lowervalueswilloptimizemoreforinitialdeploymentcost,highervalueswilloptimizemoreforhigh-frequencyusage.
runs:200
: y6 R4 S$ E& L, D& ~
},3 C; u$ l2 A9 `5 p
% h9 L' t7 x" k6 s0 m( D* R
evmVersion:"byzantium",//VersionoftheEVMtocompilefor.Affectstypecheckingandcodegeneration.Canbehomestead,tangerineWhistle,spuriousDragon,byzantiumorconstantinople4 z' y. J' F# i
//Metadatasettings(optional)- M% Q5 Z z. X& k0 F% t8 y
% l5 m2 ~8 T' ?0 X) [ b
metadata:{
//UseonlyliteralcontentandnotURLs(falsebydefault)& y6 l1 d5 b# A' @% C: j' g4 B
% i& A5 Y/ ]* N' e" X+ g
useLiteralContent:true0 Y8 R/ b) Q" Q& ]
},, d9 s6 N9 E! E2 R
% b7 _8 u: ?( J6 \5 A. \1 |: s n
//Addressesofthelibraries.Ifnotalllibrariesaregivenhere,itcanresultinunlinkedobjectswhoseoutputdataisdifferent.
- K0 D* }" \1 @: N
libraries:{) [! R+ H6 a" v5 ~7 l8 L
//Thetoplevelkeyisthethenameofthesourcefilewherethelibraryisused.# A [- y- ]3 h' S' B
//Ifremappingsareused,thissourcefileshouldmatchtheglobalpathafterremappingswereapplied.
! U/ U# K5 y- M0 W) ^& G, R
//Ifthiskeyisanemptystring,thatreferstoagloballevel.! b+ m* [* u, u
' ~$ M1 W) C0 h( \' k/ r) ]$ t
"myFile.sol":{
"MyLib":"0x123123..."
% }( {' @6 @+ E' w* ]- K6 L" p
}1 b) c1 N( W6 I$ |
}6 l- ]. J' O0 ^, |
) ~1 }$ Y6 N; {( M
//Thefollowingcanbeusedtoselectdesiredoutputs.
+ t6 Y* d6 |( y. J# I
//Ifthisfieldisomitted,thenthecompilerloadsanddoestypechecking,butwillnotgenerateanyoutputsapartfromerrors.
. s9 _! F+ w k3 c# N8 V# \. N
//Thefirstlevelkeyisthefilenameandthesecondisthecontractname,whereemptycontractnamereferstothefileitself,
//whilethestarreferstoallofthecontracts.. |6 x `. m4 }: e* F4 y, P! L
//
//Theavailableoutputtypesareasfollows:
4 q6 b. h+ J, J+ p+ S& e
//abi-ABI/ G7 a5 `5 T# J* Z" {
//ast-ASTofallsourcefiles! l- B' E& k+ L2 R- X% i, [9 Y
9 R. P0 [5 ~ @
//legacyAST-legacyASTofallsourcefiles: m I2 f7 y: R; y' [( V7 H
//devdoc-Developerdocumentation(natspec)5 F) o1 V! k* q9 C% c
: ]8 l/ N) Q8 D. z8 \
//userdoc-Userdocumentation(natspec)0 X# d# y9 \2 i. x% J
//metadata-Metadata
//ir-Newassemblyformatbeforedesugaring
$ V. L# I1 f$ K+ D3 F1 w
//evm.assembly-Newassemblyformatafterdesugaring
6 w( v# p2 Y9 Y6 Q8 ]' W$ T
//evm.legacyAssembly-Old-styleassemblyformatinJSON- Y4 n' m3 ~- a$ i8 @
* D3 ]+ B+ S6 _& l% x8 K5 `
//evm.bytecode.object-Bytecodeobject
7 L8 P2 [! o; d" M/ Z( N1 v6 @# t4 J3 L& [% w
//evm.bytecode.opcodes-Opcodeslist
//evm.bytecode.sourceMap-Sourcemapping(usefulfordebugging)5 ]7 J8 N2 e$ ~: m' U
' R2 d! Q0 R* ?) V$ }
//evm.bytecode.linkReferences-Linkreferences(ifunlinkedobject)
, A8 U) _" ]! \- M/ y v
//evm.deployedBytecode*-Deployedbytecode(hasthesameoptionsasevm.bytecode)# \1 T, k( ?5 p, ^6 s+ ?
//evm.methodIdentifiers-Thelistoffunctionhashes
. y& }/ [* f% ^
//evm.gasEstimates-Functiongasestimates
//ewasm.wast-eWASMS-expressionsformat(notsupportedatm)+ t4 `- K! \( G5 S# x
//ewasm.wasm-eWASMbinaryformat(notsupportedatm)4 |% B# T0 Y" h. [0 G0 s" `
//' J) r' T, W% D' l
D4 W9 b8 o4 I8 ? b5 U
//Notethatusingausing`evm`,`evm.bytecode`,`ewasm`,etc.willselectevery
: r4 v3 o% l: }3 p1 ^
//targetpartofthatoutput.Additionally,`*`canbeusedasawildcardtorequesteverything.
//
outputSelection:{ v# G) O6 G+ H& [. w, M
( j1 t! P/ j- I0 b1 A
//Enablethemetadataandbytecodeoutputsofeverysinglecontract.
"*":{
& R: `- I/ T! J
"*":["metadata","evm.bytecode"]6 g; J- a6 h. c2 l3 B* U4 u
6 m, d" b( \; \ A- c7 U
},
//EnabletheabiandopcodesoutputofMyContractdefinedinfiledef.
, Q/ c2 i) ?7 C
"def":{3 i- Z$ ]# g6 t/ T+ R6 x4 H
"MyContract":["abi","evm.bytecode.opcodes"]* k+ Q0 H/ @: s# i5 K& D
},
2 x! T2 O. ^" a3 t) L% m# P' l
//Enablethesourcemapoutputofeverysinglecontract.1 ` E. q1 b W; M" F( l, G
"*":{
M4 a! \) [, x& D4 x/ V
"*":["evm.bytecode.sourceMap"] J5 p/ Y& O* [& A
},4 C6 X+ |8 ~4 L, I' }" [
: L7 e. N7 D, w# @9 S
//EnablethelegacyASToutputofeverysinglefile.! _) W. m% O( l# f. }
% O; [* Y8 }3 h" u0 Q5 {6 _
"*":{8 Z( U* T! R! b( F
"":["legacyAST"]0 u1 R" e7 p* w
* B' B" K& d& u0 Z! K- \$ M3 ?+ J
}' K2 H) @9 A& s/ W- s
}( o% k2 U' S5 c" M$ l! A) Y
4 [) y2 i1 i9 r' X( T7 [! B ^- x
}
}% @- L" `( F9 I5 z9 A
输出格式说明/ Y M( `* Q4 O& i
0 Z% z( B# S. c$ V+ e
{, B6 n9 j. b5 l7 W3 u' ?
; J9 I9 ?7 b4 D" V s' K
//Optional:notpresentifnoerrors/warningswereencountered8 R5 z' P$ ]( Z9 E0 m
errors:[2 j; f) E; R3 J+ b6 X, m9 ^
{
//Optional:Locationwithinthesourcefile.
sourceLocation:{, y8 w& h5 ^2 ^5 ]9 e) t
file:"sourceFile.sol",# J# r2 c7 i6 L/ h, n: D
start:0,
end:1007 [& s( v4 Z" s# n+ `& U0 h x& W
],: P. N, i: M0 }3 i! l) h; t
//Mandatory:Errortype,suchas"TypeError","InternalCompilerError","Exception",etc.
* S# k1 w( p$ f
//Seebelowforcompletelistoftypes.% s9 r- j. L' }! Y
4 U% O. O; t: _9 U7 r9 f& K. c# y
type:"TypeError",* J$ Z! b7 s! r0 G0 w' T
! h) j! Y/ {. e) f
//Mandatory:Componentwheretheerrororiginated,suchas"general","ewasm",etc.0 z7 ^! _7 D9 k
component:"general",1 I2 r/ k1 f A# v6 `
//Mandatory("error"or"warning")8 j5 H# s/ s) O% J: h
; u" `9 ], d% g k% ?
severity:"error",$ k1 y x2 |+ k( m# m6 U
8 {# {$ I2 o6 F3 T) K' _+ ?* R( R4 `
//Mandatory
4 y$ \! M( c! g2 w0 I4 q! f
message:"Invalidkeyword"0 P# N* e/ ?. L( C1 ~" v
//Optional:themessageformattedwithsourcelocation
( G+ @- s3 ?' T- `& U7 M# e8 [
formattedMessage:"sourceFile.sol:100:Invalidkeyword", y# o$ l0 D2 |
}
2 j. K: G4 h/ I \& W O1 S
],& f- l& T/ m/ T; V, u& E) W p
: d$ w; ~" i7 t3 d- W; w5 Z% v
//Thiscontainsthefile-leveloutputs.Incanbelimited/filteredbytheoutputSelectionsettings.
sources:{
"sourceFile.sol":{" o* R% ^9 g7 v2 }4 l
- g: F' _" S% }6 S
//Identifier(usedinsourcemaps)$ {0 t- @$ M5 o8 b
id:1,/ ^4 l, t) }' e9 J/ e
//TheASTobject# \* e4 F4 t! f( E
ast:{},/ W. Z% q; m8 E" B5 D, ?
& P' a1 l& f' I
//ThelegacyASTobject
legacyAST:{}
}
' ~- V" o Q' r5 L3 t2 C. A, M
},! }" n4 t* h+ Q1 v6 i
+ B% j8 @ ^0 t7 ~" q3 c Y) u5 c
//Thiscontainsthecontract-leveloutputs.Itcanbelimited/filteredbytheoutputSelectionsettings.
contracts:{
|7 _$ V% U9 _4 N" U5 J
"sourceFile.sol":{. T/ W0 ?: ]5 s8 z: g$ S+ E. t* {
//Ifthelanguageusedhasnocontractnames,thisfieldshouldequaltoanemptystring.
& P" A3 v8 k+ x6 x/ }1 `
"ContractName":{. C# ?0 u p' X# w4 T3 D
//TheEthereumContractABI.Ifempty,itisrepresentedasanemptyarray.
1 M- }' [9 {/ K; r/ x- r/ G( J
//Seehttps://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
abi:[],8 f. {5 J3 D0 }# b, `
2 D6 b' e) `; B
//SeetheMetadataOutputdocumentation(serialisedJSONstring)
metadata:"{...}",
! U% Z' |( C9 h5 O& K
//Userdocumentation(natspec)
userdoc:{},' y' ]! x. m0 f5 {" i! l! _$ I. L
//Developerdocumentation(natspec)
devdoc:{},6 ~0 ^% e+ b( c# U$ B# }
//Intermediaterepresentation(string)* q: X' ]: R7 X8 k. @
6 a/ ^9 }# S7 m4 ]3 ^* t: U+ r! d
ir:"",: p% L" g! J1 K( H+ t
l3 o% T: M- Q! A
//EVM-relatedoutputs
evm:{/ `% i0 ~( h Z7 Q: c3 D
# y/ o: i* I" B/ T1 m, c. B
//Assembly(string)
assembly:"",. z4 K: R/ G# Z& C: a
. ]- X" H. l* v9 H* ^
//Old-styleassembly(object)3 ~ H) W" s% ]6 d( U" H% G
legacyAssembly:{},4 S; c) P# j" f$ [7 k5 F' e1 M
: h: O i7 G! z8 k, @: r b, x
//Bytecodeandrelateddetails.
bytecode:{
: B$ p: b# Q& z; u
//Thebytecodeasahexstring.
object:"00fe",$ X1 Q4 d* f# Z6 M7 X
//Opcodeslist(string)- E a4 @$ Y' Q9 x- d
opcodes:"",& v8 W* k5 B2 }2 ~3 k, ?
//Thesourcemappingasastring.Seethesourcemappingdefinition.; C9 N8 M0 D$ k+ B( J$ {; u
sourceMap:"",
/ G% z: l& r/ {" C/ h& n# p% ?
//Ifgiven,thisisanunlinkedobject.' P, n3 N2 |' n4 x2 k
linkReferences:{
+ A! M* ]/ e# p: F$ E" Q
"libraryFile.sol":{
2 X4 R* g& j( {/ _' ?; M
//Byteoffsetsintothebytecode.Linkingreplacesthe20byteslocatedthere.
"Library1":[+ f, w# d6 N% u
, T$ `0 p+ H* Q8 S+ Z w
{start:0,length:20},
{start:200,length:20}, I1 U; I- J t6 t: K/ m
* w# {# f2 _& J4 [# I
]
' P6 v2 G; x. w' D" P/ S i+ U
}
( `- d! n, k1 w3 N
}4 ?9 e* M5 `7 X) C* i6 S* u
},
/ [' q4 B z# H( c' X. m! s- L" _
//Thesamelayoutasabove.
: A& Z* ]+ @" H5 y) D0 c% c$ @
deployedBytecode:{},
8 K' \$ r; x6 P. Q+ b
//Thelistoffunctionhashes
* _/ m# {+ m# o: I2 ]3 @
methodIdentifiers:{. a( G" G/ Y6 W/ H* ~9 [
"delegate(address)":"5c19a95c"
" M/ d T( ]- Y% t' A7 g
},1 q% _# r |5 Q* ]' O9 d1 w
% g/ C8 Z( u) L" b2 a5 T
//Functiongasestimates) m% G# O, t0 A& `( y- J" G
+ ~. j0 [. {% H7 a2 b
gasEstimates:{
7 Q% N9 G! z% n8 Q4 l3 h- ?& ~1 S
creation:{* c( A! Z# I1 O3 _; S
codeDepositCost:"420000",
# E3 a0 y1 s0 {6 U5 w
executionCost:"infinite",
0 g0 ^, K0 Q1 v% ]# R& s& U
totalCost:"infinite"
},
external:{
. z% H q+ M7 b* k. _
"delegate(address)":"25000"
' Q9 o! }' f) D. a
},
internal:{( [/ A, Q1 D, z3 l& c$ T5 N! @
"heavyLifting()":"infinite"
* o6 E9 W) o& E6 ?5 H% J$ g
}
2 Z. }9 T9 Y0 Y6 F
}
4 V+ m' q3 [: ~* ^" ^- y/ ]" q
},
. I! V$ p! V3 l' h; f" L
//eWASMrelatedoutputs
: a& E+ m- {) n
ewasm:{
//S-expressionsformat* L" M7 y2 }4 `$ Y: e7 w- k3 x4 @$ `! C
wast:"",# S& s' F3 u7 e; u1 G7 d) i6 ~# L2 _
; J" Y6 ]! Z! D- P* K+ D* b
//Binaryformat(hexstring)
0 f9 u7 c' g0 c. J
wasm:""
4 c$ ~ w4 o! C
}
}- o0 g$ q6 O( w- b% ]! g. h
0 `3 l% S% ~$ ^" m* {' a5 I
}' h' G- N1 n5 y3 z
}
}
错误类型说明:
0 b& Q( I4 x# n. o: t
& P6 _; z. w, q9 _, f
JSONError:JSON错误,JSON输入不符合要求的格式,例如输入不是JSON对象,不支持语言,等等。 U6 V e( U, I, u
IOError:IO错误,IO和导入处理错误,如提供的源中的不可解析URL或hash不匹配。% V5 i* z" u: X1 K B4 y
2 ?6 G: u% c' S
ParserError:语法f分析错误,源代码不符合语言规则。
h2 O# O% u3 n1 f; [
DocstringParsingError:文档解析错误,无法解析注释块中的NATSPEC标记。
SytRealError:语法错误,如continue在for循环之外使用。& p9 b+ H2 e9 a, i, t* ]/ i
% b) G/ i$ B: n
DeclarationError:声明错误,无效、不可解析或冲突的标识符名称。例如未找到标识符% m, L- {. r l0 [
4 ]6 C% V& R# v7 s0 i' ?
TypeError:类型错误,如无效类型转换、无效赋值等。
UnimplementedFeatureError:编译器不支持该特性,但希望在将来的版本中得到支持。+ L# X' ~* ~7 V6 V+ Z6 C" l+ ]) e
1 A9 J6 {' V' b. U! F7 g4 Y
InternalCompilerError:编译器中触发内部错误,这应该作为一个问题来反馈。
Exception:例外,编译过程中未知的故障,这应该作为一个问题反馈。" g1 c3 t* K) E) Z) a
CompilerError:编译错误,编译器堆栈的使用无效,这应该作为一个问题来反馈。2 s6 Z& C, c4 J5 r; p Q7 Z% A
0 ~4 t; q+ W+ X' @
FatalError:致命错误,这应该作为一个问题来反馈。/ ^. m" }* R) r
, z, R9 b! V2 O( q. i- ~2 }& l2 H
Warning:警告并没有停止编译,但如果可能的话,应该加以处理。' O7 B& D6 Z* ?: O1 C
3 ^2 h/ P. N+ l3 Z9 Y4 P
原文请访问:solc0 J! S! ]0 d o* L+ G
如果你希望马上开始学习以太坊DApp开发,推荐访问一个在线教程:
, j9 R& ?: w0 }# g# n+ a" N
以太坊智能合约,主要介绍智能合约与dapp应用开发,适合入门。. j& ?; z: c8 X* Z/ |) u. I
以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
成为第一个吐槽的人