1
+ # -*- coding: utf8 -*-
1
2
import re
2
3
import subprocess
3
4
import os
4
- import yaml # use yaml instead of json to get non unicode
5
+
6
+ import yaml
7
+
8
+ BINARY = 'solc'
5
9
6
10
7
11
class CompileError (Exception ):
8
12
pass
9
13
10
14
11
- class solc_wrapper (object ):
15
+ def get_compiler_path ():
16
+ """ Return the path to the solc compiler.
12
17
13
- "wraps solc binary"
18
+ This funtion will search for the solc binary in the $PATH and return the
19
+ path of the first executable occurence.
20
+ """
21
+ for path in os .getenv ('PATH' , '' ).split (os .pathsep ):
22
+ path = path .strip ('"' )
23
+ executable_path = os .path .join (path , BINARY )
14
24
15
- @classmethod
16
- def compiler_available (cls ):
17
- program = 'solc'
25
+ if os .path .isfile (executable_path ) and os .access (executable_path , os .X_OK ):
26
+ return executable_path
18
27
19
- def is_exe (fpath ):
20
- return os .path .isfile (fpath ) and os .access (fpath , os .X_OK )
28
+ return None
21
29
22
- fpath , fname = os .path .split (program )
23
- if fpath :
24
- if is_exe (program ):
25
- return program
26
- else :
27
- for path in os .environ ["PATH" ].split (os .pathsep ):
28
- path = path .strip ('"' )
29
- exe_file = os .path .join (path , program )
30
- if is_exe (exe_file ):
31
- return exe_file
32
30
33
- return None
31
+ def get_solidity ():
32
+ """ Return the singleton used to interact with the solc compiler. """
33
+ if get_compiler_path () is None :
34
+ return None # the compiler wasn't found in $PATH
34
35
35
- @classmethod
36
- def contract_names (cls , code ):
37
- return re .findall (r'^\s*(contract|library) (\S*) ' , code , re .MULTILINE )
36
+ return solc_wrapper
37
+
38
+
39
+ def solc_arguments (libraries = None , combined = 'bin,abi' , optimize = True ):
40
+ """ Build the arguments to call the solc binary. """
41
+ args = [
42
+ '--combined-json' , combined ,
43
+ '--add-std' ,
44
+ ]
45
+
46
+ if optimize :
47
+ args .append ('--optmize' )
48
+
49
+ if libraries is not None :
50
+ addresses = [
51
+ '{name}:{address}' .format (name = name , address = address )
52
+ for name , address in libraries .items ()
53
+ ]
54
+ args .extend ([
55
+ '--libraries' ,
56
+ ',' .join (addresses ),
57
+ ])
58
+
59
+ return args
60
+
61
+
62
+ def solc_parse_output (compiler_output ):
63
+ """ Parses the compiler output. """
64
+ result = yaml .safe_load (compiler_output )['contracts' ]
65
+
66
+ if 'bin' in result .values ()[0 ]:
67
+ for value in result .values ():
68
+ value ['bin_hex' ] = value ['bin' ]
69
+ value ['bin' ] = value ['bin_hex' ].decode ('hex' )
70
+
71
+ for json_data in ('abi' , 'devdoc' , 'userdoc' ):
72
+ # the values in the output can be configured through the
73
+ # --combined-json flag, check that it's present in the first value and
74
+ # assume all values are consistent
75
+ if json_data not in result .values ()[0 ]:
76
+ continue
77
+
78
+ for value in result .values ():
79
+ value [json_data ] = yaml .safe_load (value [json_data ])
80
+
81
+ return result
82
+
83
+
84
+ def compiler_version ():
85
+ """ Return the version of the installed solc. """
86
+ version_info = subprocess .check_output (['solc' , '--version' ])
87
+ match = re .search ('^Version: ([0-9a-z.-]+)/' , version_info , re .MULTILINE )
88
+
89
+ if match :
90
+ return match .group (1 )
91
+
92
+
93
+ def solidity_names (code ):
94
+ """ Return the library and contract names in order of appearence. """
95
+ # the special sequence \s is equivalent to the set [ \t\n\r\f\v]
96
+ return re .findall (r'(contract|library)\s+([a-zA-Z][a-zA-Z0-9]*)' , code , re .MULTILINE )
97
+
98
+
99
+ def compile_file (filepath , libraries = None , combined = 'bin,abi' , optimize = True ):
100
+ """ Return the compile contract code.
101
+
102
+ Args:
103
+ filepath (str): The path to the contract source code.
104
+ libraries (dict): A dictionary mapping library name to address.
105
+ combined (str: The flags passed to the solidity compiler to defined
106
+ what output should be used.
107
+ optimize (bool): Flag to set up compiler optimization.
108
+
109
+ Returns:
110
+ dict: A mapping from the contract name to it's binary.
111
+ """
112
+
113
+ workdir , filename = os .path .split (filepath )
114
+
115
+ args = solc_arguments (libraries = libraries , combined = combined , optimize = optimize )
116
+ args .insert (0 , get_compiler_path ())
117
+ args .append (filename )
118
+
119
+ output = subprocess .check_output (args , cwd = workdir )
120
+
121
+ return solc_parse_output (output )
122
+
123
+
124
+ def compile_contract (filepath , contract_name , libraries = None , combined = 'bin,abi' , optimize = True ):
125
+ all_contracts = compile_file (
126
+ filepath ,
127
+ libraries = libraries ,
128
+ combined = combined ,
129
+ optimize = optimize ,
130
+ )
131
+
132
+ return all_contracts [contract_name ]
133
+
134
+
135
+ def compile_last_contract (filepath , libraries = None , combined = 'bin,abi' , optimize = True ):
136
+ with open (filepath ) as handler :
137
+ all_names = solidity_names (handler .read ())
138
+
139
+ all_contract_names = [
140
+ name
141
+ for kind , name in all_names
142
+ # if kind == 'contract'
143
+ ]
144
+
145
+ last_contract = all_contract_names [- 1 ]
146
+
147
+ return compile_contract (
148
+ filepath ,
149
+ last_contract ,
150
+ libraries = libraries ,
151
+ combined = combined ,
152
+ optimize = optimize ,
153
+ )
154
+
155
+
156
+ def compile_code (sourcecode , libraries = None , combined = 'bin,abi' , optimize = True ):
157
+ args = solc_arguments (libraries = libraries , combined = combined , optimize = optimize )
158
+ args .insert (0 , get_compiler_path ())
159
+
160
+ process = subprocess .Popen (args , stdin = subprocess .PIPE , stdout = subprocess .PIPE )
161
+ stdoutdata , _ = process .communicate (input = sourcecode )
162
+
163
+ return solc_parse_output (stdoutdata )
164
+
165
+
166
+ class Solc (object ):
167
+ """ Wraps the solc binary. """
168
+
169
+ compiler_available = staticmethod (get_compiler_path )
170
+ contract_names = staticmethod (solidity_names )
171
+ compiler_version = staticmethod (compiler_version )
172
+
173
+ @staticmethod
174
+ def _code_or_path (sourcecode , path , contract_name , libraries , combined ):
175
+ if sourcecode and path :
176
+ raise ValueError ('sourcecode and path are mutually exclusive.' )
177
+
178
+ if path and contract_name :
179
+ return compile_contract (path , contract_name , libraries = libraries , combined = combined )
180
+
181
+ if path :
182
+ return compile_last_contract (path , libraries = libraries , combined = combined )
183
+
184
+ all_names = solidity_names (sourcecode )
185
+ all_contract_names = [
186
+ name
187
+ for kind , name in all_names
188
+ if kind == 'contract'
189
+ ]
190
+ last_contract = all_contract_names [- 1 ]
191
+
192
+ result = compile_code (sourcecode , libraries = libraries , combined = combined )
193
+ return result [last_contract ]
38
194
39
195
@classmethod
40
196
def compile (cls , code , path = None , libraries = None , contract_name = '' ):
41
- "returns binary of last contract in code"
42
- sorted_contracts = cls .combined (code , path = path )
43
- if contract_name :
44
- idx = [x [0 ] for x in sorted_contracts ].index (contract_name )
45
- else :
46
- idx = - 1
47
- if libraries :
48
- if cls .compiler_version () < "0.1.2" :
49
- raise CompileError ('Compiler does not support libraries. Please update compiler.' )
50
- for lib_name , lib_address in libraries .iteritems ():
51
- sorted_contracts [idx ][1 ]['bin' ] = sorted_contracts [idx ][1 ]['bin' ].replace (
52
- "__{}{}" .format (lib_name , "_" * (38 - len (lib_name ))), lib_address )
53
- return sorted_contracts [idx ][1 ]['bin' ].decode ('hex' )
197
+ """ Return the binary of last contract in code. """
198
+ result = cls ._code_or_path (code , path , contract_name , libraries , 'bin' )
199
+ return result ['bin' ]
54
200
55
201
@classmethod
56
202
def mk_full_signature (cls , code , path = None , libraries = None , contract_name = '' ):
57
203
"returns signature of last contract in code"
58
- sorted_contracts = cls .combined (code , path = path )
59
- if contract_name :
60
- idx = [x [0 ] for x in sorted_contracts ].index (contract_name )
61
- else :
62
- idx = - 1
63
- return sorted_contracts [idx ][1 ]['abi' ]
204
+
205
+ result = cls ._code_or_path (code , path , contract_name , libraries , 'abi' )
206
+ return result ['abi' ]
64
207
65
208
@classmethod
66
209
def combined (cls , code , path = None ):
67
- """compile combined-json with abi,bin,devdoc,userdoc
210
+ """ Compile combined-json with abi,bin,devdoc,userdoc.
211
+
68
212
@param code: literal solidity code as a string.
69
213
@param path: absolute path to solidity-file. Note: code & path are exclusive!
70
214
"""
71
- p = None
72
- if path is None :
73
- p = subprocess .Popen (['solc' , '--add-std' , '--optimize' , '--combined-json' , 'abi,bin,devdoc,userdoc' ],
74
- stdin = subprocess .PIPE , stdout = subprocess .PIPE )
75
- stdoutdata , stderrdata = p .communicate (input = code )
76
- else :
77
- assert code is None or len (code ) == 0 , "`code` and `path` are exclusive!"
78
- workdir , fn = os .path .split (path )
79
- p = subprocess .Popen (['solc' , '--add-std' , '--optimize' , '--combined-json' , 'abi,bin,devdoc,userdoc' , fn ],
80
- stdout = subprocess .PIPE , cwd = workdir )
81
- stdoutdata = p .stdout .read ().strip ()
82
- p .terminate ()
83
- if p .returncode :
84
- raise CompileError ('compilation failed' )
85
- # contracts = json.loads(stdoutdata)['contracts']
86
- contracts = yaml .safe_load (stdoutdata )['contracts' ]
87
- for contract_name , data in contracts .items ():
88
- data ['abi' ] = yaml .safe_load (data ['abi' ])
89
- data ['devdoc' ] = yaml .safe_load (data ['devdoc' ])
90
- data ['userdoc' ] = yaml .safe_load (data ['userdoc' ])
91
-
92
- names = cls .contract_names (code or open (path ).read ())
93
- assert len (names ) <= len (contracts ) # imported contracts are not returned
215
+
216
+ contracts = cls ._code_or_path (
217
+ sourcecode = code ,
218
+ path = path ,
219
+ contract_name = None ,
220
+ libraries = None ,
221
+ combined = 'abi,bin,devdoc,userdoc' ,
222
+ )
223
+
224
+ if path :
225
+ with open (path ) as handler :
226
+ code = handler .read ()
227
+
94
228
sorted_contracts = []
95
- for name in names :
229
+ for name in solidity_names ( code ) :
96
230
sorted_contracts .append ((name [1 ], contracts [name [1 ]]))
97
231
return sorted_contracts
98
232
99
- @classmethod
100
- def compiler_version (cls ):
101
- version_info = subprocess .check_output (['solc' , '--version' ])
102
- match = re .search ("^Version: ([0-9a-z.-]+)/" , version_info , re .MULTILINE )
103
- if match :
104
- return match .group (1 )
105
-
106
233
@classmethod
107
234
def compile_rich (cls , code , path = None ):
108
235
"""full format as returned by jsonrpc"""
109
236
110
237
return {
111
238
contract_name : {
112
- 'code' : "0x" + contract .get ('bin' ),
239
+ 'code' : '0x' + contract .get ('bin' ),
113
240
'info' : {
114
241
'abiDefinition' : contract .get ('abi' ),
115
242
'compilerVersion' : cls .compiler_version (),
@@ -125,51 +252,4 @@ def compile_rich(cls, code, path=None):
125
252
}
126
253
127
254
128
- def get_solidity (try_import = False ):
129
- if try_import :
130
- try :
131
- import solidity , tester
132
- tester .languages ['solidity' ] = solidity
133
- except ImportError :
134
- pass
135
- if not solc_wrapper .compiler_available ():
136
- return None
137
- return solc_wrapper
138
-
139
-
140
- if __name__ == '__main__' :
141
- import tester
142
- assert 'solidity' in tester .languages
143
-
144
- one_contract = """
145
- contract foo {
146
- function seven() returns (int256 y) {
147
- y = 7;
148
- }
149
- function mul2(int256 x) returns (int256 y) {
150
- y = x * 2;
151
- }
152
- }
153
- """
154
-
155
- two_contracts = one_contract + """
156
- contract baz {
157
- function echo(address a) returns (address b) {
158
- b = a;
159
- return b;
160
- }
161
- function eight() returns (int256 y) {
162
- y = 8;
163
- }
164
- }
165
- """
166
-
167
- # test
168
- assert 'solidity' in tester .languages
169
-
170
- s = tester .state ()
171
-
172
- c1 = s .abi_contract (one_contract , language = 'solidity' )
173
- assert c1 .seven () == 7
174
- assert c1 .mul2 (2 ) == 4
175
- assert c1 .mul2 (- 2 ) == - 4
255
+ solc_wrapper = Solc # pylint: disable=invalid-name
0 commit comments