Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for solidity and LLL contracts and added a function to... #2

Merged
merged 1 commit into from
May 6, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 130 additions & 17 deletions ethjsonrpc.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,48 @@
from ethereum.abi import ContractTranslator
import serpent
import requests
import json

from ethereum import utils
from ethereum.abi import ContractTranslator, encode_abi, decode_abi

class EthJsonRpc:

class EthJsonRpc(object):

DEFAULT_GAS_FOR_TRANSACTIONS = 500000
DEFAULT_GAS_PRICE = 10000

def __init__(self, host, port, contract_code=None, contract_address=None):

# If we don't raise the exceptions below, it's kind of hard to identify
# the problem when any of these variables are null.
if host is None:
raise RuntimeError('RPC hostname cannot be null')
if port is None:
raise RuntimeError('RPC port cannot be null')

self.host = host
self.port = port
self.contract_code = None
self.signature = None
self.translation = None
self.contract_address = contract_address
self.update_code(contract_code)
self.compilers = {}
try:
import serpent
self.compilers['serpent'] = serpent.compile
self.compilers['lll'] = serpent.compile_lll
except ImportError:
print "[WARNING] Could not import module 'serpent'. Compiler will not be available."
try:
import solidity
self.compilers['solidity'] = solidity.compile
except ImportError:
try:
from ethereum._solidity import solc_wrapper
self.compilers['solidity'] = solc_wrapper.compile
except ImportError:
print "[WARNING] Could not import module 'solidity or solc_wrapper'. Compiler will not be available."

def update_code(self, contract_code):
if contract_code:
Expand All @@ -22,8 +51,8 @@ def update_code(self, contract_code):
self.translation = ContractTranslator(self.signature)

def _call(self, method, params=None, _id=0):
if params is None:
params = []

params = params or []
data = json.dumps({
'jsonrpc': '2.0',
'method': method,
Expand All @@ -36,6 +65,93 @@ def _call(self, method, params=None, _id=0):
else:
raise RuntimeError('Error from RPC call. Returned payload: {0}'.format(response))

def _encode_function(self, signature, param_values):

prefix = utils.big_endian_to_int(utils.sha3(signature)[:4])

if signature.find('(') == -1:
raise RuntimeError('Invalid function signature. Missing "(" and/or ")"...')

if signature.find(')') - signature.find('(') == 1:
return utils.encode_int(prefix)

types = signature[signature.find('(') + 1: signature.find(')')].split(',')
encoded_params = encode_abi(types, param_values)
return utils.zpad(utils.encode_int(prefix), 4) + encoded_params

def _install_contract(self, language, contract_code, value=0, from_address=None, gas=None, gas_price=None):
byte_code = self.compilers[language](contract_code)
return self.eth_sendTransaction(data=byte_code, value=value, from_address=from_address, gas=gas, gas_price=gas_price)

def install_solidity_contract(self, contract_code, value=0, from_address=None, gas=None, gas_price=None):
"""
Installs a solidity contract into ethereum node
"""
return self._install_contract('solidity', contract_code, value, from_address, gas, gas_price)

def install_serpent_contract(self, contract_code, value=0, from_address=None, gas=None, gas_price=None):
"""
Installs a serpent contract into ethereum node
"""
return self._install_contract('serpent', contract_code, value, from_address, gas, gas_price)

def install_lll_contract(self, contract_code, value=0, from_address=None, gas=None, gas_price=None):
"""
Installs a lll contract into ethereum node
"""
return self._install_contract('lll', contract_code, value, from_address, gas, gas_price)

def contract_instant_call(self, to_address, function_signature, function_parameters=None, result_types=None, default_block="latest"):
"""
This method makes a instant call on a contract function without the need to have the contract source code.
Examples of function_signature in solidity:
mult(uint x, uint y) => sig: mult(uint256,uint256) (all uint should be transformed to uint256)
setAddress(address entity_address) => sig:setAddress(address)
doSomething() => sig: doSomething() (functions with no parameters must end with the '()')
In serpent, all functions parameter signatures are int256. Example:
setXYZ(x, y, z) => sig: setXYZ(int256,int256,int256)
"""
data = self._encode_function(function_signature, function_parameters)
params = [
{
'to': to_address,
'data': '0x{0}'.format(data.encode('hex'))
},
default_block
]
response = self._call('eth_call', params)
return decode_abi(result_types, response[2:].decode('hex'))

def contract_transaction_call(self, to_address, function_signature, function_parameters=None, from_address=None, gas=None, gas_price=None, default_block="latest"):
"""
This method makes a call on a contract function through a transaction. Returns the transaction_id.
Examples of function_signature in solidity:
mult(uint x, uint y) => sig: mult(uint256,uint256) (all uint should be transformed to uint256)
setAddress(address entity_address) => sig:setAddress(address)
doSomething() => sig: doSomething() (functions with no parameters must end with the '()')
In serpent, all functions parameter signatures are int256. Example:
setXYZ(x, y, z) => sig: setXYZ(int256,int256,int256)
"""
# Default values for gas and gas_price
gas = gas or self.DEFAULT_GAS_FOR_TRANSACTIONS
gas_price = gas_price or self.DEFAULT_GAS_PRICE

# Default value for from_address
from_address = from_address or self.eth_accounts()[0]

data = self._encode_function(function_signature, function_parameters)

params = {
'from': from_address,
'to': to_address,
'gas': '0x{0:x}'.format(gas),
'gasPrice': '0x{0:x}'.format(gas_price),
'value': None,
'data': '0x{0}'.format(data.encode('hex')) if data else None
}
response = self._call('eth_sendTransaction', [params])
return response

def create_contract(self, contract_code, value=0, from_address=None, gas=None, gas_price=None):
self.update_code(contract_code)
byte_code = serpent.compile(contract_code)
Expand All @@ -48,24 +164,22 @@ def eth_sendTransaction(self, to_address=None, function_name=None, data=None, va
Creates new message call transaction or a contract creation, if the data field contains code.
"""
# Default values for gas and gas_price
if gas is None:
gas = 500000
if gas_price is None:
gas_price = 10000
gas = gas or self.DEFAULT_GAS_FOR_TRANSACTIONS
gas_price = gas_price or self.DEFAULT_GAS_PRICE

# Default value for from_address
if from_address is None:
from_address = self.eth_accounts()[0]
from_address = from_address or self.eth_accounts()[0]

if function_name:
if data is None:
data = []
data = self.translation.encode(function_name, data)

params = {
'from': from_address,
'to': to_address,
'gas': '0x{0:x}'.format(gas) if gas else None,
'gasPrice': '0x{0:x}'.format(gas_price) if gas_price else None,
'gas': '0x{0:x}'.format(gas),
'gasPrice': '0x{0:x}'.format(gas_price),
'value': '0x{0:x}'.format(value) if value else None,
'data': '0x{0}'.format(data.encode('hex')) if data else None
}
Expand All @@ -75,8 +189,7 @@ def eth_call(self, to_address, function_name, data=None, code=None, default_bloc
"""
Executes a new message call immediately without creating a transaction on the block chain.
"""
if data is None:
data = []
data = data or []
data = self.translation.encode(function_name, data)
params = [
{
Expand Down Expand Up @@ -117,7 +230,7 @@ def net_listening(self):

def net_peerCount(self):
"""
Returns number of peers currenly connected to the client.
Returns number of peers currently connected to the client.
"""
return self._call('net_peerCount')

Expand Down Expand Up @@ -297,7 +410,7 @@ def eth_newBlockFilter(self, default_block="latest"):

def eth_uninstallFilter(self, filter_id):
"""
Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additonally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time.
Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additionally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time.
"""
return self._call('eth_uninstallFilter', [filter_id])

Expand Down Expand Up @@ -412,7 +525,7 @@ def shh_newFilter(self, to, topics):
def shh_uninstallFilter(self, filter_id):
"""
Uninstalls a filter with given id. Should always be called when watch is no longer needed.
Additonally Filters timeout when they aren't requested with shh_getFilterChanges for a period of time.
Additionally Filters timeout when they aren't requested with shh_getFilterChanges for a period of time.
"""
return self._call('shh_uninstallFilter', [filter_id])

Expand Down