NameError exception when trying to ran working Python script using pybind11

44 Views Asked by At

I have a Python script that uses multicall to get information about requested assets ETH prices:

import json
import sys
from decimal import Decimal

from web3 import Web3

# infura_url = 'https://mainnet.infura.io/v3/24bc27dfd3f446dea326a13defb98d71'
# multicall_abi_path = '/home/oleksii/Backend/common/resources/multicall_abi.json'
# offchain_oracle_abi_path = '/home/oleksii/Backend/common/resources/offchain_oracle_abi.json'
# tokens_array_json = '''
# [
#     {
#         "address": "0x6b175474e89094c44da98b954eedeac495271d0f",
#         "decimals": 18
#     },
#     {
#         "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
#         "decimals": 6
#     },
#     {
#         "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
#         "decimals": 6
#     },
#     {
#         "address": "0x111111111117dc0aa78b770fa6a738034120c302",
#         "decimals": 18
#     }
# ]
# '''

try:
    # set up connection
    web3 = Web3(Web3.HTTPProvider(infura_url, request_kwargs={'verify': False}))
except Exception as e:
    print(f"An error occurred while trying to connect to Infura: {e}")
    sys.exit(1)

with open(multicall_abi_path) as json_file:
    multicall_abi = json.load(json_file)

with open(offchain_oracle_abi_path) as json_file:
    offchain_oracle_abi = json.load(json_file)

multi_call_contract = web3.eth.contract(address=web3.toChecksumAddress('0xda3c19c6fe954576707fa24695efb830d9cca1ca'),
                                        abi=multicall_abi)
off_chain_oracle_contract = web3.eth.contract(abi=offchain_oracle_abi)

tokens = json.loads(tokens_array_json)

call_data = [{'to': '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8',  # Ethereum Mainnet oracle
              'data': off_chain_oracle_contract.encodeABI(fn_name='getRateToEth',
                                                          args=[web3.toChecksumAddress(token['address']), True])} for
             token in tokens]

result = multi_call_contract.functions.multicall(call_data).call()
results, success = result[0], result[1]

prices = {}
for i in range(len(results)):
    if not success[i]:
        continue
    decoded_rate = int(results[i].hex(), 16)
    numerator = 10 ** tokens[i]['decimals']
    denominator = 10 ** 18  # ETH decimals
    price = Decimal(decoded_rate * numerator) / (denominator ** 2)
    prices[tokens[i]['address']] = str(price)

prices = json.dumps(prices)

print(prices)

Where multicall_abi.json is:

[
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "address",
            "name": "to",
            "type": "address"
          },
          {
            "internalType": "bytes",
            "name": "data",
            "type": "bytes"
          }
        ],
        "internalType": "struct MultiCall.Call[]",
        "name": "calls",
        "type": "tuple[]"
      }
    ],
    "name": "multicall",
    "outputs": [
      {
        "internalType": "bytes[]",
        "name": "results",
        "type": "bytes[]"
      },
      {
        "internalType": "bool[]",
        "name": "success",
        "type": "bool[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

and offchain_oracle_abi.json is:

[
  {
    "inputs": [
      {
        "internalType": "contract IERC20",
        "name": "srcToken",
        "type": "address"
      },
      {
        "internalType": "bool",
        "name": "useSrcWrappers",
        "type": "bool"
      }
    ],
    "name": "getRateToEth",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "weightedRate",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Script works perfect with a commented example: Assets ETH prices getting using Python script example

But I need to execute this script from C++ code using pybind11. So here`s the method I wrote for it:

std::optional< boost::json::object >
SmartContractRunner::getAssetsPrices( std::string_view infura_url,
                                      const boost::json::array& assets_json )
{
    namespace py = pybind11;

    py::gil_scoped_acquire acquire;

    auto local{ py::dict() };
    local[ "infura_url" ]         = infura_url;
    local[ "tokens_array_json" ]  = boost::json::serialize( assets_json );
    local[ "multicall_abi_path" ] = BlinkFinance::Common::fullPathToExe() + kMulticallAbi;
    local[ "offchain_oracle_abi_path" ] =
        BlinkFinance::Common::fullPathToExe() + kOffchainOracleAbi;

    py::object scope{ py::module::import( pyMain ).attr( pyDict ) };
    std::optional< boost::json::object > result;
    try
    {
        py::eval_file( BlinkFinance::Common::fullPathToExe() + pyGetAssetsPricesScript,
                       scope,
                       local );
        result = boost::json::parse( local[ "prices" ].cast< std::string >() ).as_object();
    }
    catch ( const py::error_already_set& error )
    {
        const auto errorMessage{ getErrorMessageForBlockchain( error.what() ) };
        errorMessage_ = errorMessage;
        loge( "Problem with getting assets prices: {}", errorMessage );
    }
    catch ( const std::exception& ex )
    {
        const auto errorMessage{ getErrorMessageForBlockchain( ex.what() ) };
        errorMessage_ = errorMessage;
        loge( "Problem with getting assets prices: {}", errorMessage );
    }
    catch ( ... )
    {
        loge( "Problem with getting assets prices. Can't get error message" );
    }

    py::gil_scoped_release release;

    return result;
}

Trying to call it and getting an error below: C++ method that uses pybind11 to call Python script calling error

Please, help me with this issue :) I`ve been searching for an answer for a while now :)

Tried to do not create objects in Python script and call everything inline, but at the end I`ve got NameError: name 'web3' is not defined exception and gave up.

Also have been checking the information I`m passing into script and comparing with my example. Everything is the same.

0

There are 0 best solutions below