module Ethereum::Tester::SolidityWrapper

Public Class Methods

combined(code, path: nil) click to toggle source

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
compile(code, contract_name: '', libraries: nil, path: nil) click to toggle source

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
compile_code(code, libraries: nil, combined:'bin,abi', optimize: true) click to toggle source
# 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
compile_code_or_path(code, path, contract_name, libraries, combined) click to toggle source
# 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
compile_contract(path, contract_name, libraries: nil, combined: 'bin,abi', optimize: true) click to toggle source
# 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
compile_file(path, libraries: nil, combined: 'bin,abi', optimize: true) click to toggle source

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
compile_last_contract(path, libraries: nil, combined: 'bin,abi', optimize: true) click to toggle source
# 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
compile_rich(code, path: nil) click to toggle source

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
compiler_version() click to toggle source
# 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
mk_full_signature(code, contract_name: '', libraries: nil, path: nil) click to toggle source

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
solc_arguments(libraries: nil, combined: 'bin,abi', optimize: true) click to toggle source
# 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
solc_parse_output(compiler_output) click to toggle source

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
solc_path() click to toggle source
# File lib/ethereum/tester/solidity_wrapper.rb, line 303
def solc_path
  @solc_path ||= which('solc')
end
solc_path=(v) click to toggle source
# File lib/ethereum/tester/solidity_wrapper.rb, line 307
def solc_path=(v)
  @solc_path = v
end
solidity_library_symbol(library_name) click to toggle source

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
solidity_names(code) click to toggle source

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
solidity_resolve_address(hex_code, library_symbol, library_address) click to toggle source

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
solidity_resolve_symbols(hex_code, libraries) click to toggle source
# 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
solidity_unresolved_symbols(hex_code) click to toggle source

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
which(cmd) click to toggle source
# 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