module Ethereum::Tester::SolidityWrapper
Public Class Methods
Compile combined-json with abi,bin,devdoc,userdoc.
# File lib/ethereum/tester/solidity_wrapper.rb, line 43 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
Returns binary of last contract in code.
# File lib/ethereum/tester/solidity_wrapper.rb, line 27 def compile(code, contract_name: '', libraries: nil, path: nil) result = compile_code_or_path code, path, contract_name, libraries, 'bin' result['bin'] end
# File lib/ethereum/tester/solidity_wrapper.rb, line 83 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
# File lib/ethereum/tester/solidity_wrapper.rb, line 11 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
# File lib/ethereum/tester/solidity_wrapper.rb, line 107 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
Return the compiled contract code.
@param path [String] Path to the contract source code. @param libraries [Hash] A hash mapping library name to its address. @param combined [Array] The argument for solc's –combined-json. @param optimize [Bool] Enable/disables compiler optimization.
@return [Hash] A mapping from the contract name to it's bytecode.
# File lib/ethereum/tester/solidity_wrapper.rb, line 122 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
# File lib/ethereum/tester/solidity_wrapper.rb, line 101 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
Full format as returned by jsonrpc.
# File lib/ethereum/tester/solidity_wrapper.rb, line 63 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
# File lib/ethereum/tester/solidity_wrapper.rb, line 251 def compiler_version output = `#{solc_path} --version`.strip output =~ /^Version: ([0-9a-zA-Z.\-+]+)/m ? $1 : nil end
Returns signature of last contract in code.
# File lib/ethereum/tester/solidity_wrapper.rb, line 35 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
# File lib/ethereum/tester/solidity_wrapper.rb, line 286 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
Parse compiler output.
# File lib/ethereum/tester/solidity_wrapper.rb, line 259 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
# File lib/ethereum/tester/solidity_wrapper.rb, line 303 def solc_path @solc_path ||= which('solc') end
# File lib/ethereum/tester/solidity_wrapper.rb, line 307 def solc_path=(v) @solc_path = v end
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.
# File lib/ethereum/tester/solidity_wrapper.rb, line 196 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
Return the library and contract names in order of appearence.
# File lib/ethereum/tester/solidity_wrapper.rb, line 143 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
Change the bytecode to use the given library address.
@param hex_code [String] The bytecode encoded in hex. @param library_name [String] The library that will be resolved. @param library_address [String] The address of the library.
@return [String] The bytecode encoded in hex with the library
references.
# File lib/ethereum/tester/solidity_wrapper.rb, line 213 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
# File lib/ethereum/tester/solidity_wrapper.rb, line 226 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
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.
@param hex_code [String] The bytecode encoded as hex.
# File lib/ethereum/tester/solidity_wrapper.rb, line 247 def solidity_unresolved_symbols(hex_code) hex_code.scan(/_.{39}/).uniq end
# File lib/ethereum/tester/solidity_wrapper.rb, line 311 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