def test_add_signature_to_transaction_with_netowrk_id(self):

        for network_id in [1, 2, 66, 100]:

            sender_private_key = "0x0164f7c7399f4bb1eafeaae699ebbb12050bc6a50b2836b9ca766068a9d000c0"
            sender_address = "0xde3d2d9dd52ea80f7799ef4791063a5458d13913"
            to_address = "0x056db290f8ba3250ca64a45d16284d04bc6f5fbf"
            value = 10000000000
            nonce = 1048576
            data = b''
            gasprice = DEFAULT_GASPRICE
            startgas = DEFAULT_STARTGAS
            network_id = 1

            tx1 = Transaction(nonce, gasprice, startgas, to_address, value,
                              data, network_id, 0, 0)
            tx = encode_transaction(tx1)
            tx1.sign(data_decoder(sender_private_key), network_id=network_id)
            expected_signed_tx = encode_transaction(tx1)
            sig = data_encoder(signature_from_transaction(tx1))

            signed_tx = add_signature_to_transaction(tx, sig)

            self.assertEqual(signed_tx, expected_signed_tx)

            tx_obj = decode_transaction(tx)

            add_signature_to_transaction(tx_obj, sig)

            self.assertEqual(tx_obj.network_id, network_id)
            self.assertEqual(data_encoder(tx_obj.sender), sender_address)

            self.assertEqual(encode_transaction(tx_obj), expected_signed_tx)
Example #2
0
    async def faucet(self,
                     to,
                     value,
                     *,
                     from_private_key=FAUCET_PRIVATE_KEY,
                     startgas=None,
                     gasprice=DEFAULT_GASPRICE,
                     nonce=None,
                     data=b"",
                     wait_on_confirmation=True):

        if isinstance(from_private_key, str):
            from_private_key = data_decoder(from_private_key)
        from_address = private_key_to_address(from_private_key)

        ethclient = JsonRPCClient(config['ethereum']['url'])

        to = data_decoder(to)
        if len(to) not in (20, 0):
            raise Exception(
                'Addresses must be 20 or 0 bytes long (len was {})'.format(
                    len(to)))

        if nonce is None:
            nonce = await ethclient.eth_getTransactionCount(from_address)
        balance = await ethclient.eth_getBalance(from_address)

        if startgas is None:
            startgas = await ethclient.eth_estimateGas(from_address,
                                                       to,
                                                       data=data,
                                                       nonce=nonce,
                                                       value=value,
                                                       gasprice=gasprice)

        tx = Transaction(nonce, gasprice, startgas, to, value, data, 0, 0, 0)

        if balance < (tx.value + (tx.startgas * tx.gasprice)):
            raise Exception("Faucet doesn't have enough funds")

        tx.sign(from_private_key)

        tx_encoded = data_encoder(rlp.encode(tx, Transaction))

        tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)

        while wait_on_confirmation:
            resp = await ethclient.eth_getTransactionByHash(tx_hash)
            if resp is None or resp['blockNumber'] is None:
                await asyncio.sleep(0.1)
            else:
                break

        if to == b'':
            print("contract address: {}".format(data_encoder(tx.creates)))

        return tx_hash
Example #3
0
    async def deploy_contract(self,
                              bytecode,
                              *,
                              from_private_key=FAUCET_PRIVATE_KEY,
                              startgas=None,
                              gasprice=DEFAULT_GASPRICE,
                              wait_on_confirmation=True):

        if isinstance(from_private_key, str):
            from_private_key = data_decoder(from_private_key)
        from_address = private_key_to_address(from_private_key)

        ethclient = JsonRPCClient(config['ethereum']['url'])

        nonce = await ethclient.eth_getTransactionCount(from_address)
        balance = await ethclient.eth_getBalance(from_address)

        gasestimate = await ethclient.eth_estimateGas(from_address,
                                                      '',
                                                      data=bytecode,
                                                      nonce=nonce,
                                                      value=0,
                                                      gasprice=gasprice)

        if startgas is None:
            startgas = gasestimate
        elif gasestimate > startgas:
            raise Exception(
                "Estimated gas usage is larger than the provided gas")

        tx = Transaction(nonce, gasprice, startgas, '', 0, bytecode, 0, 0, 0)

        if balance < (tx.value + (tx.startgas * tx.gasprice)):
            raise Exception("Faucet doesn't have enough funds")

        tx.sign(from_private_key)

        tx_encoded = data_encoder(rlp.encode(tx, Transaction))

        tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)

        contract_address = data_encoder(tx.creates)

        while wait_on_confirmation:
            resp = await ethclient.eth_getTransactionByHash(tx_hash)
            if resp is None or resp['blockNumber'] is None:
                await asyncio.sleep(0.1)
            else:
                code = await ethclient.eth_getCode(contract_address)
                if code == '0x':
                    raise Exception("Failed to deploy contract")
                break

        return tx_hash, contract_address
    def test_sign_transaction_with_network_id(self):
        rlp = "0xec831000008504a817c80082520894056db290f8ba3250ca64a45d16284d04bc6f5fbf8502540be40080428080"
        tx = decode_transaction(rlp)
        tx = sign_transaction(tx, FAUCET_PRIVATE_KEY)

        self.assertEqual(data_encoder(tx.sender), FAUCET_ADDRESS)
        # after decoding a transaction, the rlp lib caches the rlp encoding from the given values
        # since the sign_transaction modifies that values, this makes sure the cache isn't used next
        # time the tx is encoded
        self.assertEqual(
            data_encoder(tx.hash),
            "0x102234ef30bb90955517ddf92dc7ce39fff7bb4ef760409b984983ad73585bf5",
            "Incorrect transaction hash after signing")
    def test_tx_network_id(self):

        network_id = 1
        tx = create_transaction(
            nonce=9,
            gasprice=20 * 10**9,
            startgas=21000,
            to="0x3535353535353535353535353535353535353535",
            value=10**18,
            data=b'',
            network_id=network_id)
        key = data_decoder(
            "0x4646464646464646464646464646464646464646464646464646464646464646"
        )

        self.assertEqual(
            "0xec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080",
            encode_transaction(tx))
        tx.sign(key, network_id=network_id)

        self.assertEqual(
            "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83",
            data_encoder(rlp.encode(tx)))

        self.assertEqual((
            37,
            0x28ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276,
            0x67cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83
        ), (tx.v, tx.r, tx.s))
    def do_POST(self):

        # TODO: figure out why read is blocking here
        data = self.rfile.read(len(self.rfile.peek()))
        data = data.decode('utf-8')
        data = json.loads(data)

        if self.path == "/v1/tx/skel":

            gas_price = parse_int(data['gas_price']) if 'gas_price' in data else DEFAULT_GASPRICE
            gas = parse_int(data['gas']) if 'gas' in data else DEFAULT_STARTGAS
            nonce = parse_int(data['nonce']) if 'nonce' in data else 0

            if 'value' not in data or 'from' not in data or 'to' not in data:
                self.write_data(400, {'errors': [{'id': 'bad_arguments', 'message': 'Bad Arguments'}]})
                return
            value = parse_int(data['value'])
            to_address = data['to']
            from_address = data['from']

            if not validate_address(to_address):
                self.write_data(400, {'errors': [{'id': 'invalid_to_address', 'message': 'Invalid To Address'}]})
                return
            if not validate_address(from_address):
                self.write_data(400, {'errors': [{'id': 'invalid_from_address', 'message': 'Invalid From Address'}]})
                return

            tx = create_transaction(nonce=nonce, gasprice=gas_price, startgas=gas,
                                    to=to_address, value=value)

            transaction = encode_transaction(tx)

            self.write_data(200, {
                "tx_data": {
                    "nonce": hex(nonce),
                    "from": from_address,
                    "to": to_address,
                    "value": hex(value),
                    "startGas": hex(gas),
                    "gasPrice": hex(gas_price)
                },
                "tx": transaction
            })

        elif self.path == "/v1/tx":

            tx = decode_transaction(data['tx'])

            if 'signature' in data:

                sig = data_decoder(data['signature'])

                add_signature_to_transaction(tx, sig)

            self.write_data(200, {"tx_hash": data_encoder(tx.hash)})

        else:

            self.write_data(404)
    def test_encode_decode_transaction(self):

        sender_private_key = "0x0164f7c7399f4bb1eafeaae699ebbb12050bc6a50b2836b9ca766068a9d000c0"
        sender_address = "0xde3d2d9dd52ea80f7799ef4791063a5458d13913"
        to_address = "0x056db290f8ba3250ca64a45d16284d04bc6f5fbf"
        value = 10000000000
        nonce = 1048576
        data = b''
        gasprice = 20000000000
        startgas = DEFAULT_STARTGAS
        expected_tx_hash = "0x2f321aa116146a9bc62b61c76508295f708f42d56340c9e613ebfc27e33f240c"

        tx1 = Transaction(nonce, gasprice, startgas, to_address, value, data)
        tx1.sign(data_decoder(sender_private_key))

        self.assertEqual(data_encoder(tx1.hash), expected_tx_hash)

        # rlputx1 = rlp.encode(tx1, UnsignedTransaction)
        # rlpstx1 = rlp.encode(tx1, Transaction)

        tx1 = Transaction(nonce, gasprice, startgas, to_address, value, data)
        enc1 = rlp.encode(tx1, UnsignedTransaction)

        tx2 = rlp.decode(enc1, UnsignedTransaction)
        tx2.sign(data_decoder(sender_private_key))
        tx3 = Transaction(tx2.nonce, tx2.gasprice, tx2.startgas, tx2.to,
                          tx2.value, tx2.data)
        tx3.sign(data_decoder(sender_private_key))

        self.assertEqual(data_encoder(tx3.sender), sender_address)
        self.assertEqual(data_encoder(tx3.hash), expected_tx_hash)
        self.assertEqual(data_encoder(tx2.sender), sender_address)
        # NOTE: this is false because tx2 still thinks it's an unsigned tx
        # so it doesn't include the signature variables in the tx
        # if this suddenly starts failing, it means the behaviour
        # has been modified in the library
        self.assertNotEqual(data_encoder(tx2.hash), expected_tx_hash)
        def test_vector(name, vector):
            if 'transaction' not in vector:
                return  # TODO: process rlp tests
            transaction = vector['transaction']
            tx = create_transaction(
                nonce=parse_int(transaction['nonce']),
                gasprice=parse_int(transaction['gasPrice']),
                startgas=parse_int(transaction['gasLimit']),
                to=transaction['to'],
                value=parse_int(transaction['value']),
                data=data_decoder(transaction['data']),
                v=parse_int(transaction['v']),
                r=parse_int(transaction['r']),
                s=parse_int(transaction['s']))
            self.assertEqual(data_encoder(tx.sender),
                             "0x{}".format(vector['sender']), name)
            self.assertEqual(calculate_transaction_hash(tx),
                             "0x{}".format(vector['hash']), name)
            self.assertEqual(encode_transaction(tx), vector['rlp'], name)

            # test decode transaction -> encode transaction round trip
            tx = decode_transaction(vector['rlp'])
            self.assertEqual(encode_transaction(tx), vector['rlp'], name)
Example #9
0
    async def __call__(self,
                       *args,
                       startgas=None,
                       gasprice=20000000000,
                       value=0,
                       wait_for_confirmation=True):

        # TODO: figure out if we can validate args
        validated_args = []
        for (type, name), arg in zip(
                self.contract.translator.function_data[self.name]['signature'],
                args):
            if type == 'address' and isinstance(arg, str):
                validated_args.append(data_decoder(arg))
            elif (type.startswith("uint")
                  or type.startswith("int")) and isinstance(arg, str):
                validated_args.append(int(arg, 16))
            else:
                validated_args.append(arg)

        ethurl = get_url()

        ethclient = JsonRPCClient(ethurl)

        data = self.contract.translator.encode_function_call(
            self.name, validated_args)

        # TODO: figure out if there's a better way to tell if the function needs to be called via sendTransaction
        if self.is_constant:
            result = await ethclient.eth_call(from_address=self.from_address
                                              or '',
                                              to_address=self.contract.address,
                                              data=data)
            result = data_decoder(result)
            if result:
                decoded = self.contract.translator.decode_function_result(
                    self.name, result)
                # make sure addresses are encoded as expected
                decoded = fix_address_decoding(
                    decoded, self.contract.translator.function_data[self.name]
                    ['decode_types'])
                # return the single value if there is only a single return value
                if len(decoded) == 1:
                    return decoded[0]
                return decoded
            return None

        else:
            if self.from_address is None:
                raise Exception(
                    "Cannot call non-constant function without a sender")

            nonce = await ethclient.eth_getTransactionCount(self.from_address)
            balance = await ethclient.eth_getBalance(self.from_address)

            if startgas is None:
                startgas = await ethclient.eth_estimateGas(
                    self.from_address,
                    self.contract.address,
                    data=data,
                    nonce=nonce,
                    value=value,
                    gasprice=gasprice)
            if startgas == 50000000 or startgas is None:
                raise Exception(
                    "Unable to estimate gas cost, possibly something wrong with the transaction arguments"
                )

            if balance < (startgas * gasprice):
                raise Exception("Given account doesn't have enough funds")

            tx = Transaction(nonce, gasprice, startgas, self.contract.address,
                             value, data, 0, 0, 0)
            tx.sign(self.from_key)

            tx_encoded = data_encoder(rlp.encode(tx, Transaction))

            if self.return_raw_tx:
                return tx_encoded

            try:
                tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)
            except:
                print(balance, startgas * gasprice, startgas)
                raise

            # wait for the contract to be deployed
            if wait_for_confirmation:
                print("waiting on transaction: {}".format(tx_hash))
                starttime = time.time()
                warnlevel = 0
            while wait_for_confirmation:
                resp = await ethclient.eth_getTransactionByHash(tx_hash)
                if resp is None or resp['blockNumber'] is None:
                    await asyncio.sleep(0.1)
                    if resp is None and warnlevel == 0 and time.time(
                    ) - starttime < 10:
                        print(
                            "WARNING: 10 seconds have passed and transaction is not showing as a pending transaction"
                        )
                        warnlevel = 1
                    elif resp is None and warnlevel == 1 and time.time(
                    ) - starttime < 60:
                        print(
                            "WARNING: 60 seconds have passed and transaction is not showing as a pending transaction"
                        )
                        raise Exception(
                            "Unexpected error waiting for transaction to complete"
                        )
                else:
                    receipt = await ethclient.eth_getTransactionReceipt(tx_hash
                                                                        )
                    if 'status' in receipt and receipt['status'] != "0x1":
                        raise Exception(
                            "Transaction status returned {}".format(
                                receipt['status']))
                    break

            # TODO: is it possible for non-const functions to have return types?
            return tx_hash
Example #10
0
    async def from_source_code(cls,
                               sourcecode,
                               contract_name,
                               constructor_data=None,
                               *,
                               address=None,
                               deployer_private_key=None,
                               import_mappings=None,
                               libraries=None,
                               optimize=False,
                               deploy=True,
                               cwd=None,
                               wait_for_confirmation=True):

        if deploy:
            ethurl = get_url()

            if address is None and deployer_private_key is None:
                raise TypeError(
                    "requires either address or deployer_private_key")
            if address is None and not isinstance(constructor_data,
                                                  (list, type(None))):
                raise TypeError(
                    "must supply constructor_data as a list (hint: use [] if args should be empty)"
                )

        args = ['solc', '--combined-json', 'bin,abi']
        if libraries:
            args.extend([
                '--libraries',
                ','.join(['{}:{}'.format(*library) for library in libraries])
            ])
        if optimize:
            args.append('--optimize')
        if import_mappings:
            args.extend([
                "{}={}".format(path, mapping)
                for path, mapping in import_mappings
            ])
        # check if sourcecode is actually a filename
        if cwd:
            filename = os.path.join(cwd, sourcecode)
        else:
            filename = sourcecode
        if os.path.exists(filename):
            args.append(filename)
            sourcecode = None
        else:
            filename = '<stdin>'
        process = subprocess.Popen(args,
                                   stdin=subprocess.PIPE,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   cwd=cwd)
        output, stderrdata = process.communicate(input=sourcecode)
        try:
            output = json_decode(output)
        except json.JSONDecodeError:
            if output and stderrdata:
                output += b'\n' + stderrdata
            elif stderrdata:
                output = stderrdata
            raise Exception("Failed to compile source: {}\n{}\n{}".format(
                filename, ' '.join(args), output.decode('utf-8')))

        try:
            contract = output['contracts']['{}:{}'.format(
                filename, contract_name)]
        except KeyError:
            print(output)
            raise
        abi = json_decode(contract['abi'])

        # deploy contract
        translator = ContractTranslator(abi)
        # fix things that don't have a constructor

        if not deploy:
            return Contract(abi=abi, address=address, translator=translator)

        ethclient = JsonRPCClient(ethurl)

        if address is not None:
            # verify there is code at the given address
            for i in range(10):
                code = await ethclient.eth_getCode(address)
                if code == "0x":
                    await asyncio.sleep(1)
                    continue
                break
            else:
                raise Exception("No code found at given address")
            return Contract(abi=abi, address=address, translator=translator)

        try:
            bytecode = data_decoder(contract['bin'])
        except binascii.Error:
            print(contract['bin'])
            raise

        if constructor_data is not None:
            constructor_call = translator.encode_constructor_arguments(
                constructor_data)
            bytecode += constructor_call

        if isinstance(deployer_private_key, str):
            deployer_private_key = data_decoder(deployer_private_key)
        deployer_address = private_key_to_address(deployer_private_key)
        nonce = await ethclient.eth_getTransactionCount(deployer_address)
        balance = await ethclient.eth_getBalance(deployer_address)

        gasprice = 20000000000
        value = 0

        startgas = await ethclient.eth_estimateGas(deployer_address,
                                                   '',
                                                   data=bytecode,
                                                   nonce=nonce,
                                                   value=0,
                                                   gasprice=gasprice)

        if balance < (startgas * gasprice):
            raise Exception("Given account doesn't have enough funds")

        tx = Transaction(nonce, gasprice, startgas, '', value, bytecode, 0, 0,
                         0)
        tx.sign(deployer_private_key)

        tx_encoded = data_encoder(rlp.encode(tx, Transaction))

        contract_address = data_encoder(tx.creates)

        tx_hash = await ethclient.eth_sendRawTransaction(tx_encoded)

        # wait for the contract to be deployed
        while wait_for_confirmation:
            resp = await ethclient.eth_getTransactionByHash(tx_hash)
            if resp is None or resp['blockNumber'] is None:
                await asyncio.sleep(0.1)
            else:
                code = await ethclient.eth_getCode(contract_address)
                if code == '0x':
                    raise Exception(
                        "Failed to deploy contract: resulting address '{}' has no code"
                        .format(contract_address))
                break

        return Contract(abi=abi,
                        address=contract_address,
                        translator=translator,
                        creation_tx_hash=tx_hash)