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.