1

I can use Python to read from contracts using functions like:

tx = mySolidityContract.functions.x().call()

to read a variable x on mySolidityContract

Say I have a struct in my solidity contract, like

struct MyStruct { uint x: uint y;}

Which is mapped via

mapping(address => MyStruct) public myStruct;

I can't figure out how read myStruct.x. Let '0x123...' represent an eth address with a mapping to myStuct. I have tried:

mySolidityContract.functions.myStruct.x().call(0x123...)
mySolidityContract.functions.myStruct.x(0x123...).call()
mySolidityContract.functions.myStruct(0x123...).x()

etc.

2 Answers 2

1

Dev Advocate at Chainstack here.

To read a struct from a Solidity smart contract with Web3.py, you would typically access it as a whole rather than its individual fields directly. Since Solidity automatically generates getter functions for public state variables, including mappings to structs, you can call this getter to retrieve the entire struct.

Here's how you can read the MyStruct mapped to an Ethereum address and then access its fields x and y using Web3.py:

  1. Access the Mapping: Call the automatically generated getter for the mapping by using the mapping's name as a function and passing the address as an argument.
  2. Retrieve Struct Fields: The call will return a tuple with the values of the struct fields. You can access these values by index.

Or You can implement a getter function in your contract.

The code you have provided is the correct implementation for this task.

Smart contract example

Here is the smart contract I deployed on Sepolia as a reference, and you can use it if you want.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyContract {
    // Define a struct
    struct MyStruct {
        uint x;
        uint y;
    }

    // Create a mapping of address to MyStruct
    mapping(address => MyStruct) public myStructs;

    // Function to set values in the struct
    function setMyStruct(uint _x, uint _y) public {
        myStructs[msg.sender] = MyStruct(_x, _y);
    }

    // Function to get values from the struct
    // In Solidity, this is not needed because the compiler automatically creates a getter for public state variables.
    // However, if you want to make the contract more explicit or add additional logic, you could include it.
    function getMyStruct(address _address) public view returns (MyStruct memory) {
        return myStructs[_address];
    }
}

Then you can use web3.py like this:

This example shows how to call the automatically generated mappings getter and the explicit getter function from the smart contract.

from web3 import Web3

# Initialize web3
web3 = Web3(
    Web3.HTTPProvider(
        "YOUR_SEPOLIA_RPC"
    )
)

# Contract ABI and address
contract_abi = [
    {
        "inputs": [
            {"internalType": "uint256", "name": "_x", "type": "uint256"},
            {"internalType": "uint256", "name": "_y", "type": "uint256"},
        ],
        "name": "setMyStruct",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function",
    },
    {
        "inputs": [{"internalType": "address", "name": "_address", "type": "address"}],
        "name": "getMyStruct",
        "outputs": [
            {
                "components": [
                    {"internalType": "uint256", "name": "x", "type": "uint256"},
                    {"internalType": "uint256", "name": "y", "type": "uint256"},
                ],
                "internalType": "struct MyContract.MyStruct",
                "name": "",
                "type": "tuple",
            }
        ],
        "stateMutability": "view",
        "type": "function",
    },
    {
        "inputs": [{"internalType": "address", "name": "", "type": "address"}],
        "name": "myStructs",
        "outputs": [
            {"internalType": "uint256", "name": "x", "type": "uint256"},
            {"internalType": "uint256", "name": "y", "type": "uint256"},
        ],
        "stateMutability": "view",
        "type": "function",
    },
]

CONTRACT_ADDRESS = "0xf38c957c6b99baF38e8C02d3AA05CFdF4017D3aA"
MY_ADDRESS = "0xf92c7d42399EeE88fbCF6E7c005aEBa4D6D58B7d"

# Instantiate the contract
contract = web3.eth.contract(address=CONTRACT_ADDRESS, abi=contract_abi)


# Function to read and print struct values from the contract
def print_struct_values(contract, address):
    # Call the mapping to get the struct
    my_struct = contract.functions.myStructs(address).call()
    print("From the struct directly:")
    print(f"x: {my_struct[0]}, y: {my_struct[1]}")

    # Call the explicit getter function to get the struct
    my_struct_via_getter = contract.functions.getMyStruct(address).call()
    print("From the struct getter function:")
    print(f"x: {my_struct_via_getter[0]}, y: {my_struct_via_getter[1]}")


# Read and print the struct values for the given address
print_struct_values(contract, MY_ADDRESS)

As you can see in the function, we access the full struct and then extract the values.

# Function to read and print struct values from the contract
def print_struct_values(contract, address):
    # Call the mapping to get the struct
    my_struct = contract.functions.myStructs(address).call()
    print("From the struct directly:")
    print(f"x: {my_struct[0]}, y: {my_struct[1]}")

    # Call the explicit getter function to get the struct
    my_struct_via_getter = contract.functions.getMyStruct(address).call()
    print("From the struct getter function:")
    print(f"x: {my_struct_via_getter[0]}, y: {my_struct_via_getter[1]}")

The attempts you've made, such as mySolidityContract.functions.myStruct.x().call(0x123...), are incorrect because myStruct.x is not a function. You access the data by calling the mapping itself as a function with the address as the argument, not by trying to call a function on the fields of the struct.

Feel free to reach out if you need help! :)

1
  • Excellent! Thanks so much. My big problem was not putting quotes around the address, as I tried what you suggested without the quotes, but I had no clues. Nov 3, 2023 at 22:16
1

I might be wrong, you cant access the variables directly like that (unless they are standard ERC20 variables like name , decimals etc).

I think you would need a method that returns the value of the struct given the index of the struct which would be passed via the method arguments.

1
  • If I recall correctly you are right. Solidity automatically generates accessor functions for primitive types, but for structs you need do write accessor functions like getMyStruct(address) yourself. Nov 3, 2023 at 17:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.