如何通过solc编译solidity编写的以太坊智能合约
曲水流觞113
发表于 2022-11-6 23:42:53
123
0
0
solidity编写的以太坊智能合约可通过命令行编译工具solc来进行编译,成为以太坊虚拟机中的代码。solc编译后最终部署到链上形成我们所见到的各种智能合约。
3 h' |/ E3 W3 h
作为一个solidity命令行编译工具,我们来看看官网都怎么说solc。
! w+ N8 T1 b. ]2 }. X6 ~! C& A8 u: K
solc的安装很简单:
npminstall-gsolc- ]/ k% N) |$ D
//或者& l3 A( C) W. L) b: V
npminstall-gsolc-cli
1 f% X& }7 y9 _# p2 h8 E; C0 c
//或者
sudoapt-getinstallsolc
/ X$ e* U$ O: `' V
安装完成后我们来看,solc--help,solc--help命令显示所有的solc命令选项。编译器可以生成各种输出,比如最终的二进制合约文件、语法树的汇编或者需要预计的要花费的gas等。solc--binsourceFile.sol,可以编译后输出一个名为sourceFile.sol的智能合约文件。如果你想从solc获得更丰富的一些输出变量,你可以使用solc-ooutputDirectory--bin--ast--asmsourceFile.sol。- j- u& V7 Z, M
: }8 u3 H2 w1 ] [( P1 {+ v
你在部署以太坊智能合约之前可以用solc--optimize--binsourceFile.sol优化一下。默认情况下solc编译器会帮你优化200次。你也可以设置--runs=1,这样就按照最小化的方式进行编译,如果你希望多次交易不太在乎成本,那你可以设置成你想要的次数:)。' P8 o7 c2 m% x: ^2 P" y
命令行编译器会自动读取需要导入的文件,也可以通过使用prefix=path来指定路径,例如:3 \- U! m8 o8 L5 ^" k) U
solcgithub.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/=/usr/local/lib/fallbackfile.sol( z. M; b7 \" p
j+ t- k% W' Z) r) D) f
这样编译器就会从指定目录github.com/ethereum/dapp-bin/下的/usr/local/lib/dapp-bin/目录开始搜索,如果没有找到文件,它将查看/usr/local/lib/fallback。solc将只读取你指定的这两个路径的,因此像import"/etc/passwd";必须要通过/=重新映射才起作用。如果有多个匹配,则选择具有最长公共前缀的进行匹配。6 b. K8 P0 a$ E; i
/ i1 ]: t+ G' B# \, C
出于安全上的考虑,编译器限制了它可以访问的一些目录。在命令行中指定的源文件的路径(及其子目录)和命令行指定的路径外其他所有内容都会被拒绝。--allow-paths/sample/path,/another/sample/path来切换。
如果智能合约使用了libraries,你会注意到字节码包含了__LibraryName______的子字符串。您可以使用solc作为链接器,这意味着它将在这些点为您插入库地址。
2 o# e9 \9 i9 C6 ?2 l4 y
可以通过添加库--libraries"Math:0x12345678901234567890Heap:0xabcdef0123456"到您的命令,以提供每个库的地址,或者使用文件中的说明字符串(每行一个库),并使用--librariesfileName运行solc。
& m; [2 @$ j- D' U6 R* x( d; {* B
如果用选项--link调用Solc,则所有输入文件都被解释为未链接的二进制文件(HEX编码),在上面给出的__LibraryName____格式中,将其链接到适当地址(如果从stdin读取输入,则将其写入stdout)。在这种情况下,除了库外,所有选项都被忽略(包括-o)。
) U4 F8 R$ {, }9 Y
如果用--standard-json调用SOLC,它就将标准的JSON输入(如下所述),并返回JSON输出。
#solc编译器输入输出JSON描述
( _" s$ p Q% ~: H: @
这些JSON格式通过编译器API使用,可以通过SOLC获得。内容都是可以修改的,一些对象是可选的(如前所述),其目的是向后兼容。$ q8 m# f+ D) _
编译器的API需要一个JSON格式的输入,然后以JSON格式输出编译结果。- Z* F' I* @$ u5 j* L
注意不允许注释。下面示例中的注释,是官网为了学习者更好的理解标注的。* ]1 q B4 Z* ]1 I
输入格式说明:
{
$ }8 q/ Z# y: j x( R' p
//Required:Sourcecodelanguage,suchas"Solidity","serpent","lll","assembly",etc.2 c5 [1 E" Z( r( n) C8 u7 u
2 b, u/ a2 P. G9 T7 h. D
language:"Solidity",* |. C6 ?$ f2 ^* d; n$ m
//Required: r+ j* h/ t2 w& J/ L* Y' S
d' q1 ]* L/ v3 i! [1 w+ o) _& f
sources:
{
2 M! `' W0 R% g& d& P
//Thekeysherearethe"global"namesofthesourcefiles,
- K7 b& n" _# X/ e7 a0 d) M2 i
//importscanuseotherfilesviaremappings(seebelow).6 ?& F7 ?4 b) X% G3 f
"myFile.sol":" h1 v: p( C; ]& P. E
{
//Optional:keccak256hashofthesourcefile- L& c3 A$ y# r+ t7 \2 h1 {, I: ~
//ItisusedtoverifytheretrievedcontentifimportedviaURLs.6 y; j Z& R5 t
% G3 J4 n6 h; t
"keccak256":"0x123...",+ A& {2 u4 \4 ]& V
//Required(unless"content"isused,seebelow):URL(s)tothesourcefile.
//URL(s)shouldbeimportedinthisorderandtheresultcheckedagainstthe
//keccak256hash(ifavailable).Ifthehashdoesn'tmatchornoneofthe
//URL(s)resultinsuccess,anerrorshouldberaised.
"urls":
, t2 b% C; A9 E7 L! C
[1 U- f9 i4 ?' n7 x
1 { T( T0 X5 m# L2 ~
"bzzr://56ab...",
"ipfs://Qma...",
"file:///tmp/path/to/file.sol"- V) q L8 z; h4 ~% ~
) J# B+ O/ b9 O( H9 d
]6 ~' a1 l" A4 ~' @* e" }3 ?
},
"mortal":
{- G' j3 T4 I" n. S7 R- ?
; t4 b% A, s7 t. H
//Optional:keccak256hashofthesourcefile
"keccak256":"0x234...",
! R! a J0 W8 f5 N: G v
//Required(unless"urls"isused):literalcontentsofthesourcefile/ v& m u- s+ f
"content":"contractmortalisowned{functionkill(){if(msg.sender==owner)selfdestruct(owner);}}". e7 g2 }8 o) ^ \! u1 N
}
},: Q5 i1 \$ {, H# l5 ?
* R& y9 m; N7 m
//Optional
settings:
6 ~: }+ f2 I3 p* G/ L m6 E
{
& t( v- E( C- c# R6 L8 b
//Optional:Sortedlistofremappings
$ T7 L* J- @- \( m0 u
remappings:[":g/dir"],
$ f' v5 }0 s- m3 d W9 |8 D, |
//Optional:Optimizersettings4 a3 q7 N- F4 Q- L
optimizer:{1 F2 U9 a1 o% L9 y4 n. z
0 Y7 _& I% c n% e3 L
//disabledbydefault
& [( A V4 E {6 U+ L
enabled:true,- k. ~- y& U; q' h
//Optimizeforhowmanytimesyouintendtorunthecode.' v& F5 Z" R, j0 ?, x8 _. h1 ]" o0 |
* K% p3 @1 A! H
//Lowervalueswilloptimizemoreforinitialdeploymentcost,highervalueswilloptimizemoreforhigh-frequencyusage.
( B# R; r1 I( V& ^# }2 U
runs:200' ?, a g, m2 m- z, w, M6 {
F- s% o* c+ A8 H1 f
},
0 y$ D, I( N6 b- O3 F
evmVersion:"byzantium",//VersionoftheEVMtocompilefor.Affectstypecheckingandcodegeneration.Canbehomestead,tangerineWhistle,spuriousDragon,byzantiumorconstantinople# r5 @1 T* v8 M0 }3 a, }
2 r( K3 q- |7 d/ \. h# N/ I- n
//Metadatasettings(optional)9 X0 a& U Z ^
3 b3 m9 U! I% ]. z( u+ n* J
metadata:{- E2 A) Q d+ z, N
( {( p% s3 w0 H; B& E# D# Y% _2 c- p
//UseonlyliteralcontentandnotURLs(falsebydefault)" b; ?$ n+ L" s1 z: y
useLiteralContent:true0 M* z5 N& O: W3 E- k9 o+ z8 e! e4 Q
},
' W# R/ F( e5 d$ ^7 R9 J
//Addressesofthelibraries.Ifnotalllibrariesaregivenhere,itcanresultinunlinkedobjectswhoseoutputdataisdifferent.4 f! I) U9 T5 O. T. v, M4 J; U
6 l( |1 j; a8 L1 u1 P
libraries:{; o3 B5 Y9 c& I: {. r
! b, V* q: I3 X* U7 k1 A" h
//Thetoplevelkeyisthethenameofthesourcefilewherethelibraryisused.& J% D8 V( @6 Q: Y, ~" O! A7 z$ C
8 A6 ^6 |2 L# N9 s
//Ifremappingsareused,thissourcefileshouldmatchtheglobalpathafterremappingswereapplied.* |& B& P9 }, P) n; s! T
; _- D0 o9 S1 K/ H
//Ifthiskeyisanemptystring,thatreferstoagloballevel.0 m. B, ]6 ~" i, T- y
"myFile.sol":{
"MyLib":"0x123123..."( O$ S, }& W; \" X3 p! o/ F5 h; t
3 x: ^1 p+ O7 n! B
}4 Q/ k7 A" T$ O# e1 g# X+ i
3 i3 B2 a- F5 ^. S
}1 z' r7 T6 X6 j( g" ]4 V. W
* Y; _' l) t, u+ z
//Thefollowingcanbeusedtoselectdesiredoutputs.7 S; c: M( d5 Z4 G# K" u8 Y
//Ifthisfieldisomitted,thenthecompilerloadsanddoestypechecking,butwillnotgenerateanyoutputsapartfromerrors.
7 p# `! b* I, {0 a- {3 l+ }
//Thefirstlevelkeyisthefilenameandthesecondisthecontractname,whereemptycontractnamereferstothefileitself,8 i/ g5 X: @0 m3 N3 n; y, S9 K
% l5 L4 F2 \, ]! m
//whilethestarreferstoallofthecontracts.
//
//Theavailableoutputtypesareasfollows:% X8 Y, y2 S$ }9 X5 z X
3 T, Y5 l+ c* }
//abi-ABI
//ast-ASTofallsourcefiles
//legacyAST-legacyASTofallsourcefiles
f K: b0 b2 y! s# H
//devdoc-Developerdocumentation(natspec)% i+ b+ o" b" t! \4 \8 ?
//userdoc-Userdocumentation(natspec)
//metadata-Metadata/ M' k2 ~. b, P9 V
, ~* f. E4 i( H! n
//ir-Newassemblyformatbeforedesugaring7 t2 V% G+ w1 O+ h1 i- z2 ~) m
//evm.assembly-Newassemblyformatafterdesugaring0 S, @. L; k$ F. ^8 c
4 [% r7 K2 k% m! ?8 }' e: @/ X
//evm.legacyAssembly-Old-styleassemblyformatinJSON
3 V/ I% _; j- G6 \5 U9 G- z
//evm.bytecode.object-Bytecodeobject4 S' L/ B. d' A- @" h
//evm.bytecode.opcodes-Opcodeslist
2 o8 W0 u& X1 N! v
//evm.bytecode.sourceMap-Sourcemapping(usefulfordebugging)
//evm.bytecode.linkReferences-Linkreferences(ifunlinkedobject)
//evm.deployedBytecode*-Deployedbytecode(hasthesameoptionsasevm.bytecode)$ R& L7 C7 @! C) R1 u
//evm.methodIdentifiers-Thelistoffunctionhashes
//evm.gasEstimates-Functiongasestimates
//ewasm.wast-eWASMS-expressionsformat(notsupportedatm)$ p2 i( j: p5 W' H( _' L
//ewasm.wasm-eWASMbinaryformat(notsupportedatm)
//9 a' V e T; J& p
; y; M% C+ k. E* p! V8 Y7 }
//Notethatusingausing`evm`,`evm.bytecode`,`ewasm`,etc.willselectevery/ m' g! |) [ F/ d) s
t. Y" m2 D& [6 O% F9 x$ Y& ]8 c
//targetpartofthatoutput.Additionally,`*`canbeusedasawildcardtorequesteverything.
8 m. C: o' c5 }1 {) i
//' v8 m& e6 i/ ]( L3 C
outputSelection:{1 s, A' Z& ]5 P9 s9 {- Q0 N
+ W$ ?# I& Y- t; s' T' a
//Enablethemetadataandbytecodeoutputsofeverysinglecontract.
% l0 I+ ?2 {% }& C W! P( w
"*":{
"*":["metadata","evm.bytecode"]' ^, O! |! R$ e9 D- j# H3 ^( l5 N
- S' g1 {. B7 F/ \- P; E4 g
},% \( n8 ?' s# X2 Y
4 \. o7 N, U- B3 T7 q8 ]
//EnabletheabiandopcodesoutputofMyContractdefinedinfiledef.
"def":{
"MyContract":["abi","evm.bytecode.opcodes"]
! Z6 J6 o% e8 v9 m$ Q" B
},
7 d B- N- X& R: h9 Z. W5 }; \
//Enablethesourcemapoutputofeverysinglecontract.
7 u+ B3 @: Y4 N' `6 d: N/ h
"*":{1 T# q4 ?. Z2 s- E! k
2 C% x" u2 U& c) q" G1 [3 J
"*":["evm.bytecode.sourceMap"]7 s; H( ^/ }( c2 X1 t
},
//EnablethelegacyASToutputofeverysinglefile.
"*":{
"":["legacyAST"]
8 @; t- f: s4 h _
} C/ h R3 Z9 _# [; d& C
% }. Z) o5 W, @- w9 g
}0 D$ N& n4 X! ?7 ~7 ?1 M; N+ _5 t
}$ _9 q" ?6 E J& c; b
& I4 x( t7 B* F/ j0 m C
}5 \2 b: u6 V2 L3 ~" m
输出格式说明
) @5 T1 \# n- _. ?& T
{5 V8 a! d4 r- p- R# v- ^! O
% c% p- B' G6 S% d! h
//Optional:notpresentifnoerrors/warningswereencountered
: o" K8 C+ h7 {/ I8 j- |# I
errors:[( A: R" `, g/ {4 T
) r# \1 N8 y' `! s% e
{% \* L0 q! h9 n$ h4 p, |; f; Y
//Optional:Locationwithinthesourcefile.; b* l" ~0 w+ i! Q; F# h9 t% I
8 a& y$ T6 g% _1 C5 ~3 t
sourceLocation:{" N3 D( Z* V/ {* ~6 c. z* f: |+ ~1 U
I3 R; q) b6 M
file:"sourceFile.sol",
# q& f v8 X- [: T" h6 x' l
start:0,; p; t, w- R. N% h" G
end:100
" ?% _# @# A3 ?) M2 _* ~: b
],# Y0 f x+ q5 _; [! n
//Mandatory:Errortype,suchas"TypeError","InternalCompilerError","Exception",etc.0 x: U8 v6 ~- s: N- F3 S2 U
$ ?' v3 u- x- F$ J0 u
//Seebelowforcompletelistoftypes.
type:"TypeError",9 R$ o/ M- n4 N
* j/ V8 G- l8 c- P0 L8 m
//Mandatory:Componentwheretheerrororiginated,suchas"general","ewasm",etc.
9 Q$ q- s* A8 t* R- f! B4 N% {& x
component:"general",
4 n) z. m' g9 [
//Mandatory("error"or"warning")$ d0 X' k4 a9 n" ]) t2 U
severity:"error",+ Q* E: S" D# \8 _% w3 i
//Mandatory$ [% D$ a4 {9 b. |: J$ ]
message:"Invalidkeyword"
//Optional:themessageformattedwithsourcelocation7 l2 o( U. f" w1 N6 W
* o3 i7 \2 g& Y& J- w
formattedMessage:"sourceFile.sol:100:Invalidkeyword"
}
! o9 P- \( b. S% r
],' L& Z, W5 X. U$ N0 n3 |
//Thiscontainsthefile-leveloutputs.Incanbelimited/filteredbytheoutputSelectionsettings.
6 }! y8 M. Q# ^. D- m* s& o+ U
sources:{
"sourceFile.sol":{
//Identifier(usedinsourcemaps)+ q) F7 k/ m$ n
id:1,
//TheASTobject
ast:{},2 z$ s* b/ c9 ^
1 L5 u% G" X7 T7 A" t: z0 X
//ThelegacyASTobject& |3 o2 t) H! C9 L7 l1 B2 t2 A3 ~
3 @" T( ]5 E" R3 e3 k
legacyAST:{}
: w" l" h" c$ F9 n) T
}% |5 R A) [1 r+ A9 k l
) `. p6 `6 ~/ {1 M; g' J) e& C
},
3 S, b4 u! [& M0 e# t$ |% t+ S
//Thiscontainsthecontract-leveloutputs.Itcanbelimited/filteredbytheoutputSelectionsettings.
contracts:{/ ] D, w* y0 `! ~2 @; h* O/ K" o
' x6 O9 p, K7 z L# ]- b, \$ [
"sourceFile.sol":{
//Ifthelanguageusedhasnocontractnames,thisfieldshouldequaltoanemptystring.
"ContractName":{& t6 w9 I7 k: ~5 n# o
//TheEthereumContractABI.Ifempty,itisrepresentedasanemptyarray.
& {; Z: g3 U9 D+ ]
//Seehttps://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI( X4 y9 I5 ^3 ^; y/ N7 V4 y8 N$ x
& P0 Z, z- p( M; G) l& d
abi:[],- B. ^0 l9 E% ?8 J! j
( n# _, \7 V C9 s e' ~2 F o
//SeetheMetadataOutputdocumentation(serialisedJSONstring)
metadata:"{...}",' q7 e. S2 F8 ~% E, e! D. o
3 m* i! P# F0 Q( d. Q
//Userdocumentation(natspec)
userdoc:{},
//Developerdocumentation(natspec)' G" @2 ~! m5 K
devdoc:{},
//Intermediaterepresentation(string)
ir:"",$ g7 T( y( q" e+ |. i: L
//EVM-relatedoutputs
evm:{
1 Y6 o3 h1 { I% z1 p# I3 ?. i
//Assembly(string), @+ @$ g( V+ y$ C* x: f4 W
/ s) S& I& R$ t9 c
assembly:"",
: U& G f/ G" K, X) ]/ t
//Old-styleassembly(object)
legacyAssembly:{},. ~/ B- o6 R6 S: E' Y8 Z) e' T
//Bytecodeandrelateddetails.+ E+ P/ A/ ?8 v% l4 N
8 c* ~; ?( j0 n; S; e) j
bytecode:{
//Thebytecodeasahexstring.
5 [3 ^2 R; J9 y& {
object:"00fe",3 D* B6 F7 ~3 i2 H
//Opcodeslist(string)
opcodes:"",. W3 N$ e- ~3 I: K# |& v/ e6 W i8 h- O
//Thesourcemappingasastring.Seethesourcemappingdefinition.
* t4 [* Q8 `" M; S1 d
sourceMap:"",) \( @$ n1 z7 o' T( W, R0 ]4 a
1 k) e, B8 C: J1 w2 U
//Ifgiven,thisisanunlinkedobject.# X. ~3 S, L' O: T) g$ u9 y6 A
linkReferences:{" {$ P) n; Z7 g! g2 b/ e
: R% o; Y: Z9 Z9 F. s4 l) N
"libraryFile.sol":{1 B8 O) t% |' B* h
//Byteoffsetsintothebytecode.Linkingreplacesthe20byteslocatedthere.+ w* L K4 s: G; O& c; a- Y) s* ?
"Library1":[4 Z$ ?2 n5 \( g9 Z
V, x5 \" {2 C5 o
{start:0,length:20},
8 N) s9 r+ \! T+ ?. w& K
{start:200,length:20}
! B- V# Q* O* W# D* H
]9 _) Z9 [3 I O x; q# v. U1 L
% Q( x/ C8 y7 f _
}' g! a( |" \5 d$ v4 v
6 _; ]2 K) G3 [) t: u* B1 B
}
},: t$ Q. j3 x" @2 V
//Thesamelayoutasabove.
7 x1 v- u' Y4 t6 c; O, x6 ?& g
deployedBytecode:{},2 n6 ^6 S8 w, N
//Thelistoffunctionhashes( Z) a- ]( ~9 \4 r
& k3 b4 z+ s1 w& u$ B$ n9 o
methodIdentifiers:{
6 _- J' \6 K0 p+ ]
"delegate(address)":"5c19a95c"7 U4 s) {( c% f2 ^4 v5 D2 K5 V
},/ @1 F4 H$ t$ Y
: K b! I2 T, S1 C; t1 [2 a! D
//Functiongasestimates
7 C: F5 e# w) l$ G6 F
gasEstimates:{
creation:{* Y3 z3 e( C, D, Q$ H" x: S9 g3 m
codeDepositCost:"420000",- B, {3 F$ S& K
executionCost:"infinite",$ e" ^ f+ F0 w7 H# L. v' y. t
8 Y' e5 J3 Y/ c2 i% m& j& K0 m1 m
totalCost:"infinite"/ R. y7 I' A: g9 O; [/ N2 t5 T
- U+ \$ i6 O# c6 S5 d8 Z& b
},2 U& ~3 k q3 s7 I" `3 z
8 }! H9 H$ v; E, V. d* O5 x' _
external:{/ W% W2 s2 L4 W2 a8 ^# g! u, o
"delegate(address)":"25000"
! c) X0 d* V& S& J" S) j( Q/ r
},5 ]- v. G% J. s" `5 l
" T- ~0 o* G4 w. Q: w* Q
internal:{
"heavyLifting()":"infinite"- `8 F' U- r/ g1 y3 J
}
}
},
//eWASMrelatedoutputs6 o2 Q! i0 Q1 {' L/ G: g5 o! {
) R( m" p3 A. P8 l' ~+ S4 j+ B
ewasm:{
//S-expressionsformat n& A) N$ s5 b4 M! V1 E$ ~
wast:"",
* r9 N0 y. v- B) ^! O* t! P
//Binaryformat(hexstring)
wasm:""
0 Q! x* |( o& W7 F4 \0 T4 q
} W6 R4 w0 u3 N* p
}
( W4 P/ i* i4 J) g% E
}) q5 G. c, v3 Y& ]- U9 F, b1 w
9 k7 z5 A, p; T" K# x( n1 k
}
9 v0 i, K* N8 Y, O3 N. ^: f
}4 s& w% c# X9 c L e5 v: Y9 w
A8 c( d# U: q% S' R9 E
错误类型说明:2 ~; ~# m( W! O
m4 B. a5 A" H
JSONError:JSON错误,JSON输入不符合要求的格式,例如输入不是JSON对象,不支持语言,等等。
IOError:IO错误,IO和导入处理错误,如提供的源中的不可解析URL或hash不匹配。
ParserError:语法f分析错误,源代码不符合语言规则。
& V) |) L8 c7 k3 }
DocstringParsingError:文档解析错误,无法解析注释块中的NATSPEC标记。1 A$ S9 ?4 w" C0 C* `
+ Z7 q& x; ~% W' v" g6 ~
SytRealError:语法错误,如continue在for循环之外使用。% H \* `% P$ H) X, H
DeclarationError:声明错误,无效、不可解析或冲突的标识符名称。例如未找到标识符# t1 I! J0 ~ q6 \" a6 G
TypeError:类型错误,如无效类型转换、无效赋值等。. h7 [" x6 u6 M# s6 k
# }$ a3 R6 I0 P0 \) t
UnimplementedFeatureError:编译器不支持该特性,但希望在将来的版本中得到支持。
6 A2 A( I( O, ?6 R$ S- Z
InternalCompilerError:编译器中触发内部错误,这应该作为一个问题来反馈。
Exception:例外,编译过程中未知的故障,这应该作为一个问题反馈。
4 T/ u5 K% D1 Y) G
CompilerError:编译错误,编译器堆栈的使用无效,这应该作为一个问题来反馈。
FatalError:致命错误,这应该作为一个问题来反馈。
! e c$ b; W$ H) i0 E
Warning:警告并没有停止编译,但如果可能的话,应该加以处理。( j3 E6 U8 g3 c: c1 y [
( ^# t a1 P+ P. O4 a* Q
原文请访问:solc
如果你希望马上开始学习以太坊DApp开发,推荐访问一个在线教程:2 [% ~6 `6 Y" n {) v& F: T% w
以太坊智能合约,主要介绍智能合约与dapp应用开发,适合入门。* p; I) T' g4 S+ w# r! g
以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
成为第一个吐槽的人