如何通过solc编译solidity编写的以太坊智能合约
曲水流觞113
发表于 2022-11-6 23:42:53
178
0
0
* k* K( x; Q7 M. T; u7 p8 X, |
solidity编写的以太坊智能合约可通过命令行编译工具solc来进行编译,成为以太坊虚拟机中的代码。solc编译后最终部署到链上形成我们所见到的各种智能合约。9 S# N3 y, t) V( I) B% w
作为一个solidity命令行编译工具,我们来看看官网都怎么说solc。
" u- I! M5 [) d
solc的安装很简单:: e6 R- g+ f5 i6 `6 q
P5 c3 k! G) s1 G7 `1 _
npminstall-gsolc
- F5 X$ x; W3 S- U5 w
//或者
1 |& S$ O: o) k# ~3 z5 v, ^
npminstall-gsolc-cli
# ?+ V! q, X5 _
//或者
sudoapt-getinstallsolc
6 h3 C7 L3 U2 |6 ?7 Q- v% q) S7 U
安装完成后我们来看,solc--help,solc--help命令显示所有的solc命令选项。编译器可以生成各种输出,比如最终的二进制合约文件、语法树的汇编或者需要预计的要花费的gas等。solc--binsourceFile.sol,可以编译后输出一个名为sourceFile.sol的智能合约文件。如果你想从solc获得更丰富的一些输出变量,你可以使用solc-ooutputDirectory--bin--ast--asmsourceFile.sol。
你在部署以太坊智能合约之前可以用solc--optimize--binsourceFile.sol优化一下。默认情况下solc编译器会帮你优化200次。你也可以设置--runs=1,这样就按照最小化的方式进行编译,如果你希望多次交易不太在乎成本,那你可以设置成你想要的次数:)。
命令行编译器会自动读取需要导入的文件,也可以通过使用prefix=path来指定路径,例如:$ J, Q* n; G7 k% S" K
! O) V# O/ [2 f7 V o
solcgithub.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/=/usr/local/lib/fallbackfile.sol
, Y3 Q1 P# W1 G# v
这样编译器就会从指定目录github.com/ethereum/dapp-bin/下的/usr/local/lib/dapp-bin/目录开始搜索,如果没有找到文件,它将查看/usr/local/lib/fallback。solc将只读取你指定的这两个路径的,因此像import"/etc/passwd";必须要通过/=重新映射才起作用。如果有多个匹配,则选择具有最长公共前缀的进行匹配。# ~5 w/ m6 R( c5 z3 w8 c) s7 h' U0 e
4 l/ l. ]$ d( V* m! C
出于安全上的考虑,编译器限制了它可以访问的一些目录。在命令行中指定的源文件的路径(及其子目录)和命令行指定的路径外其他所有内容都会被拒绝。--allow-paths/sample/path,/another/sample/path来切换。
1 X6 _# b% K: v. ~
如果智能合约使用了libraries,你会注意到字节码包含了__LibraryName______的子字符串。您可以使用solc作为链接器,这意味着它将在这些点为您插入库地址。2 _8 \8 V5 P+ ]: A6 q5 M" Q
6 K3 j; Z0 o0 c7 u, g3 u5 C; |; j
可以通过添加库--libraries"Math:0x12345678901234567890Heap:0xabcdef0123456"到您的命令,以提供每个库的地址,或者使用文件中的说明字符串(每行一个库),并使用--librariesfileName运行solc。
$ d$ b4 ?% e) `" g; F1 M: C. R
如果用选项--link调用Solc,则所有输入文件都被解释为未链接的二进制文件(HEX编码),在上面给出的__LibraryName____格式中,将其链接到适当地址(如果从stdin读取输入,则将其写入stdout)。在这种情况下,除了库外,所有选项都被忽略(包括-o)。- d. N" V% P% ?& o3 S9 l
如果用--standard-json调用SOLC,它就将标准的JSON输入(如下所述),并返回JSON输出。4 U9 b' {& x9 E! O+ u# z
#solc编译器输入输出JSON描述" W! _; s% S1 _# \ m
这些JSON格式通过编译器API使用,可以通过SOLC获得。内容都是可以修改的,一些对象是可选的(如前所述),其目的是向后兼容。7 Z( M, j! H T0 O7 i4 l
编译器的API需要一个JSON格式的输入,然后以JSON格式输出编译结果。
$ N; ~; p9 E6 Y R" t8 f' E
注意不允许注释。下面示例中的注释,是官网为了学习者更好的理解标注的。: I8 ~8 i G! b$ R( z; V, F
输入格式说明:
# M s9 x5 |* J
{7 V- j: k5 j6 v3 ^$ k) m; s7 |
//Required:Sourcecodelanguage,suchas"Solidity","serpent","lll","assembly",etc.
1 q0 b4 h& x- ?5 F; K+ m
language:"Solidity",5 b; S0 z7 u/ ?4 m
. ~% V$ N+ ^/ y
//Required
sources:
{4 u& J7 W# j& Z8 G: M7 V
; |0 z. ~1 \, z4 f* B( Y
//Thekeysherearethe"global"namesofthesourcefiles,
//importscanuseotherfilesviaremappings(seebelow)." l! v. v$ e, c* u& X4 q
+ t% Q4 a% T4 i
"myFile.sol":: j$ `/ F( ~3 A: Z; {+ r! @
! D+ {1 Y& M2 J: p
{
//Optional:keccak256hashofthesourcefile
//ItisusedtoverifytheretrievedcontentifimportedviaURLs.
! ?+ X) y7 G# Y" d& c( ?
"keccak256":"0x123...",
r) T( k0 Y0 A/ N6 I9 N
//Required(unless"content"isused,seebelow):URL(s)tothesourcefile.
//URL(s)shouldbeimportedinthisorderandtheresultcheckedagainstthe0 C+ r6 ^8 Y4 A( y3 R
$ X$ |- b0 ^3 _
//keccak256hash(ifavailable).Ifthehashdoesn'tmatchornoneofthe; ]1 B" ~! j+ j
//URL(s)resultinsuccess,anerrorshouldberaised. X O" V3 O- d
"urls":1 ~, g$ S9 D. A: n! X. V
[
"bzzr://56ab...",8 @, x" y: i3 K; x% [
"ipfs://Qma...",6 t) f0 N5 C( c2 A
! Z5 O/ `, t# P6 ]0 ]
"file:///tmp/path/to/file.sol"
, L) {' z( h( i$ C3 C) c8 V
]
},
# y" l* R2 X3 ]7 S5 C
"mortal":9 H$ R' C* N) Q5 }5 D9 r
{" x* ?0 }" K% I/ r
//Optional:keccak256hashofthesourcefile+ H6 Q; |2 y3 B+ P% o _! ~
& b* {( o: [2 ?1 r9 w# e# |
"keccak256":"0x234...",
4 c- z& u7 y1 b
//Required(unless"urls"isused):literalcontentsofthesourcefile
"content":"contractmortalisowned{functionkill(){if(msg.sender==owner)selfdestruct(owner);}}" g+ y1 j, @( Y1 V" @7 h: b
}
( u' V$ p* e4 O9 p5 x; t' L' ]8 V1 w
},
7 l1 ^+ X3 ]0 c+ ?, g
//Optional
* i g \/ m5 `' y- b
settings:
{
; X1 a( x4 s5 B% B2 x5 W
//Optional:Sortedlistofremappings
( r2 S9 b( @8 j( K
remappings:[":g/dir"],. H2 n- W4 ^& E9 D: c3 u
//Optional:Optimizersettings( A) A% }2 L: t, Y8 W
optimizer:{" |' f: E/ |( B! p- b. n4 }9 O. H2 S! P
+ }! f) R( }* J/ A. q: H1 x
//disabledbydefault
enabled:true,. b9 k- o# k d) `4 E$ M# e
//Optimizeforhowmanytimesyouintendtorunthecode.- v2 D" F- S; _; n* |
1 o* b+ x8 W, }( h! k
//Lowervalueswilloptimizemoreforinitialdeploymentcost,highervalueswilloptimizemoreforhigh-frequencyusage.
runs:200
},
evmVersion:"byzantium",//VersionoftheEVMtocompilefor.Affectstypecheckingandcodegeneration.Canbehomestead,tangerineWhistle,spuriousDragon,byzantiumorconstantinople
$ c; Y( u8 l2 r! a2 M
//Metadatasettings(optional)
metadata:{" B2 K& g6 d* M) P
//UseonlyliteralcontentandnotURLs(falsebydefault)0 f k1 c/ M v, d& j2 N: R( s) ?
1 ~$ V. H+ H$ Y8 N1 s7 Q9 x+ B
useLiteralContent:true1 p( M% r! A/ N5 H7 O1 I+ X
' B* V/ G9 L4 W Q& |5 m
},
& q+ |3 _, J! i( z2 ~+ g" s, T7 I9 t4 H+ m4 _
//Addressesofthelibraries.Ifnotalllibrariesaregivenhere,itcanresultinunlinkedobjectswhoseoutputdataisdifferent.; u, p. a0 n. K5 e
% l! z: s) c, J4 H' L
libraries:{
//Thetoplevelkeyisthethenameofthesourcefilewherethelibraryisused.
4 A9 |& S' [ f# a
//Ifremappingsareused,thissourcefileshouldmatchtheglobalpathafterremappingswereapplied.
( C1 N( C$ g% z- I* U
//Ifthiskeyisanemptystring,thatreferstoagloballevel. t* V9 _% {( k9 [& ?
"myFile.sol":{! ^0 f# F' C/ f* |
/ F& Z) u* {4 I- T( W) ?
"MyLib":"0x123123..."' U/ P* F8 e! C X T1 }3 G
}& s# j$ D$ E! Q7 X5 t) ^) `$ T
/ }( X# u( z0 X# O) q2 C8 ?6 m
}3 T4 [1 _# Y- n' X1 z' N" Y
//Thefollowingcanbeusedtoselectdesiredoutputs.( d( p6 z/ @- L1 u3 c Y
! X" \. ?# \9 c3 ^8 j9 D* v! I
//Ifthisfieldisomitted,thenthecompilerloadsanddoestypechecking,butwillnotgenerateanyoutputsapartfromerrors.
//Thefirstlevelkeyisthefilenameandthesecondisthecontractname,whereemptycontractnamereferstothefileitself,' l+ x5 H# Z- I9 Q3 E, X4 T" N
% q; l( m8 v$ B3 B6 Z9 s
//whilethestarreferstoallofthecontracts.
! d4 z, J2 H5 W6 K: H
//
//Theavailableoutputtypesareasfollows:3 U6 M+ N# Y1 P% U0 l
' N8 v5 \# Z- F: N
//abi-ABI, t9 Q9 N; @' I, z$ b* W5 E
//ast-ASTofallsourcefiles
( s+ y$ @! p. `* Y. z
//legacyAST-legacyASTofallsourcefiles" A% W: J5 G* [; Z
6 T9 W$ ~% z) n( \& M
//devdoc-Developerdocumentation(natspec)
, [. @! O7 v7 Q
//userdoc-Userdocumentation(natspec)8 u5 l6 N4 ?4 t+ p, b
//metadata-Metadata+ D4 C0 W9 `. p$ ]4 c
//ir-Newassemblyformatbeforedesugaring
//evm.assembly-Newassemblyformatafterdesugaring( Z/ @ K; O5 Z' e& j1 y
6 Y$ H- }7 x6 s2 p
//evm.legacyAssembly-Old-styleassemblyformatinJSON
//evm.bytecode.object-Bytecodeobject- N5 W( f3 ^# l% _) ~ ` G; b) \
3 S9 M; L( h5 r# I; u5 S% ~# r9 B
//evm.bytecode.opcodes-Opcodeslist$ K% O- G& I: p
//evm.bytecode.sourceMap-Sourcemapping(usefulfordebugging)
) K: n( \# u- p
//evm.bytecode.linkReferences-Linkreferences(ifunlinkedobject)
' i: W7 Q2 P1 W0 l2 i3 k
//evm.deployedBytecode*-Deployedbytecode(hasthesameoptionsasevm.bytecode)
+ \9 T* \; [3 b
//evm.methodIdentifiers-Thelistoffunctionhashes# a3 q, `/ L- v3 h6 `
8 S" Q w5 y. \# _. G8 z, G
//evm.gasEstimates-Functiongasestimates$ E7 X4 \, I) I, k- | X
//ewasm.wast-eWASMS-expressionsformat(notsupportedatm). p# w k) l, O2 z
4 F$ y i u F3 @5 x8 u: i
//ewasm.wasm-eWASMbinaryformat(notsupportedatm)/ }- g' L. ]% E8 e* i) E
//
//Notethatusingausing`evm`,`evm.bytecode`,`ewasm`,etc.willselectevery6 J2 q$ x, n+ S8 a( k
//targetpartofthatoutput.Additionally,`*`canbeusedasawildcardtorequesteverything.; a' q; G( v* a& E$ a
7 a- F1 b7 e2 a$ S, x3 b% l! m
//( g* h0 l5 l2 x' W
outputSelection:{
+ r" o& E. Z" ]) S6 O3 Z
//Enablethemetadataandbytecodeoutputsofeverysinglecontract.
# |/ S8 f0 y% r( V3 Z
"*":{4 E; \- i, G( E3 r1 Z3 V
( G2 \; ^# K+ Y( ] v5 M& i
"*":["metadata","evm.bytecode"]
},. V$ O. @( [$ J0 a0 [
//EnabletheabiandopcodesoutputofMyContractdefinedinfiledef.9 e. |% r% e e8 v
"def":{& D5 b9 a. ~3 e
"MyContract":["abi","evm.bytecode.opcodes"]- Y y" ?8 l% ^1 W
},9 @! R/ t) P3 j- Y5 S: X+ |2 u8 u) [6 u
//Enablethesourcemapoutputofeverysinglecontract./ l/ H! ?1 i2 G! E5 \* Q
/ t$ P* K' }, R5 I
"*":{
$ \$ m3 s8 m4 |3 q
"*":["evm.bytecode.sourceMap"]- J/ N: [0 }! Z) W
+ l& d \$ D2 T A: U5 ?) U2 q
},
//EnablethelegacyASToutputofeverysinglefile.
- K+ F g- V8 u! O
"*":{8 X6 S, A- {6 T6 G
"":["legacyAST"]
5 z" a' v. R. Y1 d
}+ Q, w0 E" ^ [$ O3 w
}
}3 F/ H+ z' `2 ~# `6 o
}8 F; q) h+ _$ ~+ i9 I2 i$ ~
9 ? {3 z2 ?$ K9 N
输出格式说明
/ T# [7 u+ q5 @9 V$ C% S
{2 O0 p( e& B; V2 S. `
//Optional:notpresentifnoerrors/warningswereencountered
errors:[
{
: K% j- D. ?% }% N( h
//Optional:Locationwithinthesourcefile.3 g4 i. `. q8 V; L) D* E/ K& C
sourceLocation:{
) t/ J- Y3 W9 q" d* e! }+ S* E
file:"sourceFile.sol",( i5 N3 G. X1 q/ l( h- F
2 k8 E, p5 h2 }* C; d
start:0,
end:100
0 g6 Y. i4 F: l e6 @
],
//Mandatory:Errortype,suchas"TypeError","InternalCompilerError","Exception",etc.* w8 G6 z- y0 i* G
//Seebelowforcompletelistoftypes.- H8 m3 q9 @' A+ x
+ x( B( p: ?' F8 @
type:"TypeError",: S2 G' C+ q: N1 u
//Mandatory:Componentwheretheerrororiginated,suchas"general","ewasm",etc.
* d t" o4 c. h+ q( W
component:"general",; B0 b5 L+ K* K# j+ ?: Z
//Mandatory("error"or"warning")
5 a' t4 e; G" T" E3 i- f. ?/ Y5 E
severity:"error",. T: \4 x' X4 q9 b$ z
//Mandatory0 W+ ^* C- |) Z7 U" e0 }; m
8 @2 x9 z- _# o7 z
message:"Invalidkeyword"
//Optional:themessageformattedwithsourcelocation
0 M- q. M: R l) [) R1 o
formattedMessage:"sourceFile.sol:100:Invalidkeyword"
) K! _. Z# |9 ?; S
}/ w, q& e# m% i% l( `6 @# a
],/ b3 y" F) h# A) {) [* w
//Thiscontainsthefile-leveloutputs.Incanbelimited/filteredbytheoutputSelectionsettings.1 c/ D# q6 A/ @+ _# r- N
6 {* o* e. ^7 r
sources:{& r/ m& @8 ?0 j6 h; h8 \4 p( ^; z
"sourceFile.sol":{- I. s' z6 W) C0 Q2 C
8 Q6 U1 v$ b! I/ d+ e% y
//Identifier(usedinsourcemaps), }% `. Q1 R5 J I' |; B
, ?$ X! ~2 K; Z% g
id:1,; p; h% M+ A& e! U
//TheASTobject. r+ Z* P9 J- g# I8 o
* A4 _3 d5 i, a8 N
ast:{},9 o+ I! _( g8 b' K3 V
% e7 {+ f( v/ f, }4 P# P) R; F
//ThelegacyASTobject
3 j5 y# Q( q& z
legacyAST:{}0 }" C- A% K D+ |' G6 e3 @
}6 `/ d J" R- O& l; _3 T8 z
" X' O% b2 ]( ?4 Z
},
//Thiscontainsthecontract-leveloutputs.Itcanbelimited/filteredbytheoutputSelectionsettings.
8 @7 R( Y, Q4 Y2 k% B3 r$ J9 G j
contracts:{; j) h5 ~3 J8 j$ F' ]: {2 C
"sourceFile.sol":{
//Ifthelanguageusedhasnocontractnames,thisfieldshouldequaltoanemptystring.7 \5 _8 a* \- _. J+ i
"ContractName":{6 I) r$ M9 |" k8 s1 h
//TheEthereumContractABI.Ifempty,itisrepresentedasanemptyarray.
//Seehttps://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI& i+ ?% U; a3 \: |. r. y, f
abi:[],1 n; B% } ]- B/ M K1 v. z
; n v. n: c8 w8 |1 e; O3 ?4 F
//SeetheMetadataOutputdocumentation(serialisedJSONstring)- W( O/ F. v$ `; O4 w0 e
5 @5 C' _, ?6 K# b
metadata:"{...}",# [& A1 p/ L' X( W5 y. q9 O: Z
. X7 [+ O. |. @8 P: [
//Userdocumentation(natspec)
- g% E3 I3 A# @4 }5 h9 r9 i
userdoc:{},
//Developerdocumentation(natspec)
devdoc:{},6 t; x! o& M! Y
' x: Q, j& ]' W2 r9 B
//Intermediaterepresentation(string): k$ a' i- D7 U
/ M" q" M$ ~" F& {; M
ir:"",% [$ N. Q5 Y3 s- n
//EVM-relatedoutputs
evm:{: k, ?$ n4 d) t; n! O
//Assembly(string)
assembly:"",+ n. ^3 `8 z z* R6 s
: T# O" M8 ~- [4 L5 Q
//Old-styleassembly(object)
legacyAssembly:{},) n( a- M5 {* z# U2 l
//Bytecodeandrelateddetails.
bytecode:{5 u8 f# _; @. M& f! Q& P
& j) Z8 Y R$ [3 l5 t; n; P
//Thebytecodeasahexstring.5 K* R; a6 a5 {: N
3 S8 }* t3 L7 m4 S$ y8 F
object:"00fe",4 \! w. Q7 y" e$ X: ^+ |
+ B( b _- n6 ]: Z x; I& t
//Opcodeslist(string)
* V2 P9 G0 { W
opcodes:"",( w' `3 X7 Q- T4 R3 H
//Thesourcemappingasastring.Seethesourcemappingdefinition.
2 z, k2 E; \/ E
sourceMap:"",. I1 \" O# C0 F8 n
//Ifgiven,thisisanunlinkedobject./ O" \7 [* R6 s- i1 B) H7 `
linkReferences:{- E% I$ K0 a; }7 p7 ?. Z; Z+ B
# R* G. n% c, B
"libraryFile.sol":{
2 b7 a6 F$ H, o. H4 Y3 \
//Byteoffsetsintothebytecode.Linkingreplacesthe20byteslocatedthere.9 ~" s1 @; J; z: d- v
"Library1":[# o7 n7 B. Z) C) X; ]
{start:0,length:20},
9 A! Y: ~8 l; V; a- D8 p
{start:200,length:20}, L4 {" d7 s% Z8 H
]. x. f, z8 Q' T: ~0 ~' w$ l
}
}
},7 ~$ z7 o9 F% ^' A1 h
7 x+ G! \" t6 ~- y n
//Thesamelayoutasabove.
9 D% e, A) i* F8 R9 A4 Z
deployedBytecode:{},- T, ~1 B/ @2 o v
//Thelistoffunctionhashes. G7 _( l" N' Z
# G1 }) x0 m7 d- J# P
methodIdentifiers:{
"delegate(address)":"5c19a95c"& ?- ~: n# h9 @- i
},% K5 C: N- J. ~- h
$ X$ @. L+ E# i
//Functiongasestimates
gasEstimates:{5 R, H' Q6 P7 y0 P+ K) \5 [
creation:{
codeDepositCost:"420000",
executionCost:"infinite"," O- R+ z2 I* g: {
totalCost:"infinite"
9 a! c6 e) J" A% r9 d# ?; r
},7 K- Z& N% }+ n# a9 a% c
# U" ^9 e% k* w7 t4 G# e9 t
external:{
/ B: x0 ^; ]% s
"delegate(address)":"25000"1 }/ E; F1 B: d" m$ i9 q; v
3 ^5 a# e9 e) h3 n" L3 c3 O( S
},
6 M( H& j% r6 _4 m. d
internal:{
. w% y) A+ ]5 y `0 A5 { @
"heavyLifting()":"infinite"
}
}
},7 Q) d ]! N, e* }, r. Z
% ^; \# c* R# y1 I* a' t
//eWASMrelatedoutputs
/ [) _) {% q. B5 k
ewasm:{
" W$ S! f2 X' o7 v, |, _
//S-expressionsformat
) h# D+ s, e. u. K" H v
wast:"",
" s0 @1 W( x+ n1 h
//Binaryformat(hexstring)
wasm:""- U' n: ]& N; L3 Q; J9 c
}/ P+ N: ~9 W6 Q/ m
}
}
}5 b' @7 [$ a, J% n
}0 ?5 ~+ h9 N; l3 j) ]( E
" |" }. P" Y0 O, F. j! \
错误类型说明:1 s6 z( G2 w; G( x. g$ i
* i: f( ]2 T2 b; k
1 s" R0 E$ ~) g/ t, O/ r9 b3 M
JSONError:JSON错误,JSON输入不符合要求的格式,例如输入不是JSON对象,不支持语言,等等。
. T; Q- [% p/ G
IOError:IO错误,IO和导入处理错误,如提供的源中的不可解析URL或hash不匹配。0 K" T) C' r' w/ g
ParserError:语法f分析错误,源代码不符合语言规则。
6 f) ]6 X' _# B' H4 z$ [6 B) S
DocstringParsingError:文档解析错误,无法解析注释块中的NATSPEC标记。1 V: ?2 o; H% O J! S
0 l/ y; u5 @, c
SytRealError:语法错误,如continue在for循环之外使用。. n- U6 e$ k7 A/ J
\, q' A; s; h2 z \2 P: f
DeclarationError:声明错误,无效、不可解析或冲突的标识符名称。例如未找到标识符
* U* f9 B3 ?" d
TypeError:类型错误,如无效类型转换、无效赋值等。9 }# i8 k9 [5 D5 c& P+ Q% j/ N o
& @* e" R6 i U0 ^; R9 B
UnimplementedFeatureError:编译器不支持该特性,但希望在将来的版本中得到支持。
# T8 v/ g# g, I1 I$ x/ J' }/ T/ F
InternalCompilerError:编译器中触发内部错误,这应该作为一个问题来反馈。
5 S8 I5 j. g. N/ @; g$ s
Exception:例外,编译过程中未知的故障,这应该作为一个问题反馈。. Z q) ?( D4 _
CompilerError:编译错误,编译器堆栈的使用无效,这应该作为一个问题来反馈。) y8 m# l3 n5 C# ~4 j/ J+ p
2 d4 e. U2 Y' F+ R
FatalError:致命错误,这应该作为一个问题来反馈。0 ^4 E( p3 r6 j) n
8 a9 s3 O5 D, q& b
Warning:警告并没有停止编译,但如果可能的话,应该加以处理。* Q: |$ }! [3 w$ ~, t7 S* o+ ^
. `2 f5 s1 L' i. q; u: y
5 e' d: ?% w8 O q6 |0 A; L
原文请访问:solc
2 l4 O% {; D0 t
如果你希望马上开始学习以太坊DApp开发,推荐访问一个在线教程:5 T4 Z7 b" `# @: Y0 k
以太坊智能合约,主要介绍智能合约与dapp应用开发,适合入门。$ D$ n3 p9 s) d- L: r* v
以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
成为第一个吐槽的人