Module: Ethereum::Tester::SolidityWrapper
- Defined in:
- lib/ethereum/tester/solidity_wrapper.rb
Defined Under Namespace
Classes: CompileError
Class Method Summary collapse
-
.combined(code, path: nil) ⇒ Object
Compile combined-json with abi,bin,devdoc,userdoc.
-
.compile(code, contract_name: '', libraries: nil, path: nil) ⇒ Object
Returns binary of last contract in code.
- .compile_code(code, libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Object
- .compile_code_or_path(code, path, contract_name, libraries, combined) ⇒ Object
- .compile_contract(path, contract_name, libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Object
-
.compile_file(path, libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Hash
Return the compiled contract code.
- .compile_last_contract(path, libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Object
-
.compile_rich(code, path: nil) ⇒ Object
Full format as returned by jsonrpc.
- .compiler_version ⇒ Object
-
.mk_full_signature(code, contract_name: '', libraries: nil, path: nil) ⇒ Object
Returns signature of last contract in code.
- .solc_arguments(libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Object
-
.solc_parse_output(compiler_output) ⇒ Object
Parse compiler output.
- .solc_path ⇒ Object
- .solc_path=(v) ⇒ Object
-
.solidity_library_symbol(library_name) ⇒ Object
Return the symbol used in the bytecode to represent the ‘library_name`.
-
.solidity_names(code) ⇒ Object
Return the library and contract names in order of appearence.
-
.solidity_resolve_address(hex_code, library_symbol, library_address) ⇒ String
Change the bytecode to use the given library address.
- .solidity_resolve_symbols(hex_code, libraries) ⇒ Object
-
.solidity_unresolved_symbols(hex_code) ⇒ Object
Return the unresolved symbols contained in the ‘hex_code`.
- .which(cmd) ⇒ Object
Class Method Details
.combined(code, path: nil) ⇒ Object
Compile combined-json with abi,bin,devdoc,userdoc.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 44 def combined(code, path: nil) raise ValueError, "code and path are mutually exclusive." if code && path if path contracts = compile_file path code = File.read(path) elsif code contracts = compile_code code else raise ValueError, 'either code or path needs to be supplied.' end solidity_names(code).map do |(kind, name)| [name, contracts[name]] end end |
.compile(code, contract_name: '', libraries: nil, path: nil) ⇒ Object
Returns binary of last contract in code.
28 29 30 31 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 28 def compile(code, contract_name: '', libraries: nil, path: nil) result = compile_code_or_path code, path, contract_name, libraries, 'bin' result['bin'] end |
.compile_code(code, libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 84 def compile_code(code, libraries: nil, combined:'bin,abi', optimize: true) args = solc_arguments libraries: libraries, combined: combined, optimize: optimize args.unshift solc_path out = Tempfile.new 'solc_output_' pipe = IO.popen(args, 'w', [:out, :err] => out) pipe.write code pipe.close_write unless $?.success? out.rewind output = out.read raise CompileError, "compilation failed:\n#{output}" end out.rewind solc_parse_output out.read end |
.compile_code_or_path(code, path, contract_name, libraries, combined) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 12 def compile_code_or_path(code, path, contract_name, libraries, combined) raise ValueError, 'code and path are mutually exclusive' if code && path return compile_contract(path, contract_name, libraries: libraries, combined: combined) if path && contract_name.true? return compile_last_contract(path, libraries: libraries, combined: combined) if path all_names = solidity_names code all_contract_names = all_names.map(&:last) result = compile_code(code, libraries: libraries, combined: combined) result[all_contract_names.last] end |
.compile_contract(path, contract_name, libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Object
108 109 110 111 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 108 def compile_contract(path, contract_name, libraries: nil, combined: 'bin,abi', optimize: true) all_contracts = compile_file path, libraries: libraries, combined: combined, optimize: optimize all_contracts[contract_name] end |
.compile_file(path, libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Hash
Return the compiled contract code.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 123 def compile_file(path, libraries: nil, combined: 'bin,abi', optimize: true) workdir = File.dirname path filename = File.basename path args = solc_arguments libraries: libraries, combined: combined, optimize: optimize args.unshift solc_path args.push filename out = Tempfile.new 'solc_output_' Dir.chdir(workdir) do pipe = IO.popen(args, 'w', [:out, :err] => out) pipe.close_write end out.rewind solc_parse_output out.read end |
.compile_last_contract(path, libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Object
102 103 104 105 106 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 102 def compile_last_contract(path, libraries: nil, combined: 'bin,abi', optimize: true) all_names = solidity_names File.read(path) all_contract_names = all_names.map(&:last) # don't filter libraries compile_contract path, all_contract_names.last, libraries: libraries, combined: combined, optimize: optimize end |
.compile_rich(code, path: nil) ⇒ Object
Full format as returned by jsonrpc.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 64 def compile_rich(code, path: nil) combined(code, path: path).map do |(name, contract)| [ name, { 'code' => "0x#{contract['bin_hex']}", 'info' => { 'abiDefinition' => contract['abi'], 'compilerVersion' => compiler_version, 'developerDoc' => contract['devdoc'], 'language' => 'Solidity', 'languageVersion' => '0', 'source' => code, 'userDoc' => contract['userdoc'] } } ] end.to_h end |
.compiler_version ⇒ Object
252 253 254 255 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 252 def compiler_version output = `#{solc_path} --version`.strip output =~ /^Version: ([0-9a-zA-Z.\-+]+)/m ? $1 : nil end |
.mk_full_signature(code, contract_name: '', libraries: nil, path: nil) ⇒ Object
Returns signature of last contract in code.
36 37 38 39 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 36 def mk_full_signature(code, contract_name: '', libraries: nil, path: nil) result = compile_code_or_path code, path, contract_name, libraries, 'abi' result['abi'] end |
.solc_arguments(libraries: nil, combined: 'bin,abi', optimize: true) ⇒ Object
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 287 def solc_arguments(libraries: nil, combined: 'bin,abi', optimize: true) args = [ '--combined-json', combined, '--add-std' ] args.push '--optimize' if optimize if libraries && !libraries.empty? addresses = libraries.map {|name, addr| "#{name}:#{addr}" } args.push '--libraries' args.push addresses.join(',') end args end |
.solc_parse_output(compiler_output) ⇒ Object
Parse compiler output.
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 260 def solc_parse_output(compiler_output) result = JSON.parse(compiler_output)['contracts'] if result.values.first.has_key?('bin') result.each_value do |v| v['bin_hex'] = v['bin'] # decoding can fail if the compiled contract has unresolved symbols begin v['bin'] = Utils.decode_hex v['bin'] rescue TypeError # do nothing end end end %w(abi devdoc userdoc).each do |json_data| next unless result.values.first.has_key?(json_data) result.each_value do |v| v[json_data] = JSON.parse v[json_data] end end result end |
.solc_path ⇒ Object
304 305 306 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 304 def solc_path @solc_path ||= which('solc') end |
.solc_path=(v) ⇒ Object
308 309 310 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 308 def solc_path=(v) @solc_path = v end |
.solidity_library_symbol(library_name) ⇒ Object
Return the symbol used in the bytecode to represent the ‘library_name`.
The symbol is always 40 characters in length with the minimum of two leading and trailing underscores.
197 198 199 200 201 202 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 197 def solidity_library_symbol(library_name) len = [library_name.size, 36].min lib_piece = library_name[0,len] hold_piece = '_' * (36 - len) "__#{lib_piece}#{hold_piece}__" end |
.solidity_names(code) ⇒ Object
Return the library and contract names in order of appearence.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 144 def solidity_names(code) names = [] in_string = nil backslash = false comment = nil # "parse" the code by hand to handle the corner cases: # # - the contract or library can be inside a comment or string # - multiline comments # - the contract and library keywords could not be at the start of the line code.each_char.with_index do |char, pos| if in_string if !backslash && in_string == char in_string = nil backslash = false end backslash = char == "\\" elsif comment == "//" comment = nil if ["\n", "\r"].include?(char) elsif comment == "/*" comment = nil if char == "*" && code[pos + 1] == "/" else in_string = char if %w(' ").include?(char) if char == "/" char2 = code[pos + 1] comment = char + char2 if %w(/ *).include?(char2) end if char == 'c' && code[pos, 8] == 'contract' result = code[pos..-1] =~ /^contract[^_$a-zA-Z]+([_$a-zA-Z][_$a-zA-Z0-9]*)/ names.push ['contract', $1] if result end if char == 'l' && code[pos, 7] == 'library' result = code[pos..-1] =~ /^library[^_$a-zA-Z]+([_$a-zA-Z][_$a-zA-Z0-9]*)/ names.push ['library', $1] if result end end end names end |
.solidity_resolve_address(hex_code, library_symbol, library_address) ⇒ String
Change the bytecode to use the given library address.
214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 214 def solidity_resolve_address(hex_code, library_symbol, library_address) raise ValueError, "Address should not contain the 0x prefix" if library_address =~ /\A0x/ raise ValueError, "Address with wrong length" if library_symbol.size != 40 || library_address.size != 40 begin Utils.decode_hex library_address rescue TypeError raise ValueError, "library_address contains invalid characters, it must be hex encoded." end hex_code.gsub library_symbol, library_address end |
.solidity_resolve_symbols(hex_code, libraries) ⇒ Object
227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 227 def solidity_resolve_symbols(hex_code, libraries) symbol_address = libraries .map {|name, addr| [solidity_library_symbol(name), addr] } .to_h solidity_unresolved_symbols(hex_code).each do |unresolved| address = symbol_address[unresolved] hex_code = solidity_resolve_address(hex_code, unresolved, address) end hex_code end |
.solidity_unresolved_symbols(hex_code) ⇒ Object
Return the unresolved symbols contained in the ‘hex_code`.
Note: the binary representation should not be provided since this function relies on the fact that the ‘_’ is invalid in hex encoding.
248 249 250 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 248 def solidity_unresolved_symbols(hex_code) hex_code.scan(/_.{39}/).uniq end |
.which(cmd) ⇒ Object
312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/ethereum/tester/solidity_wrapper.rb', line 312 def which(cmd) return ENV['SOLC_BINARY'] if ENV['SOLC_BINARY'] exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| exts.each { |ext| exe = File.join(path, "#{cmd}#{ext}") return exe if File.executable?(exe) && !File.directory?(exe) } end return nil end |