Stack Exchange
Web3.py fails to encode struct function call
I wrote a contract that uses a struct for a function call's parameters:
struct SwapParameters {
      address router;
      address recipient;
      address tokenIn;
      address tokenOut;
      uint24 fee;
      uint256 amountIn;
      uint16 slippage;
      uint32 oracleSeconds;
    }

function swap(SwapParameters memory params) external auth(false) returns (uint256 amountOut) {...}

The problem is that web3.py throws this error whenever I try to encode the data for this call:
Unlock wallet >>
[~] Successfully unlocked wallet .. 
[+] Swap parameters: 
[SELL]: 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9
[BUY]: 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1
[QTY] 5000000
[+] Params: Router:  0x4C60051384bd2d3C01bfc845Cf5F4b44bcbE9de5 Recipient:  0x82CDcA25Fafa32eE128C54ADd0DF235eBfb8Ba2d Tokens1:  0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9 Token2 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 Amount:  5000000 Fee:  3000 Slippage:  1 Secs: 60
[+] Performing checks ... 
Traceback (most recent call last):
  File "/home/anon/PycharmProjects/tradeproxy/main.py", line 133, in <module>
    main()
  File "/home/anon/PycharmProjects/tradeproxy/main.py", line 128, in main
    cli.swap(to_checksum_address(router_address), wallet.sw3.account.address, args.sell_token, args.buy_token,
  File "/home/anon/PycharmProjects/tradeproxy/main.py", line 94, in swap
    "data": self._contract.encodeABI('swap',
  File "/home/anon/PycharmProjects/tradeproxy/venv/lib/python3.10/site-packages/eth_utils/decorators.py", line 18, in _wrapper
    return self.method(obj, *args, **kwargs)
  File "/home/anon/PycharmProjects/tradeproxy/venv/lib/python3.10/site-packages/web3/contract/base_contract.py", line 186, in encodeABI
    fn_abi, fn_selector, fn_arguments = get_function_info(
  File "/home/anon/PycharmProjects/tradeproxy/venv/lib/python3.10/site-packages/web3/_utils/contracts.py", line 374, in get_function_info
    fn_abi = find_matching_fn_abi(contract_abi, abi_codec, fn_name, args, kwargs)
  File "/home/anon/PycharmProjects/tradeproxy/venv/lib/python3.10/site-packages/web3/_utils/contracts.py", line 208, in find_matching_fn_abi
    raise Web3ValidationError(message)
web3.exceptions.Web3ValidationError: 
Could not identify the intended function with name `swap`, positional arguments with type(s) `address,address,address,address,int,int,int,int` and keyword arguments with type(s) `{}`.
Found 1 function(s) with the name `swap`: ['swap((address,address,address,address,uint24,uint256,uint16,uint32))']
Function invocation failed due to improper number of arguments.

But I don't know why this happens. Here is the python code:
def swap(self, router: ChecksumAddress, recipient: ChecksumAddress, token1: ChecksumAddress,
         token2: ChecksumAddress,
         amount: int, fee: int = 3000, slippage: int = 1, secs: int = 60):
    print(router, recipient, token1, token2, amount, fee, slippage, secs)
    print('[+] Performing checks ... ')
    current_balance = self.token_balance(token1, True)
    assert current_balance >= amount
    current_allowance = self.token_allowance(token1)
    if amount > current_allowance:
        print('[+] Must approve contract to spend tokens ... ')
        txid = self.approve_trade_proxy(token1)
        self.wallet.poll_receipt(txid)
    # data = self.encode_swap(router, recipient, token1, token2, fee, amount, slippage, secs)
    tx = {
        "from": self.account.address,
        "to": self.contract_address,
        "data": self._contract.encodeABI('swap',
                                         args=(router, recipient, token1, token2, fee, amount, slippage, secs)),
        "value": 0,
        "chainId": self.w3.eth.chain_id
    }
    return self.wallet.broadcast_raw_tx(tx)

I really don't understand what I am doing wrong. I am passing the correct datatypes, but encodeABI fails. Can anyone tell me what I am doing wrong?

You are not passing the parameters as a struct when encoding the function call, swap expects a single parameter of type SwapParameters, you need to pass the arguments as a struct.
def swap(self, router: ChecksumAddress, recipient: ChecksumAddress, token1: ChecksumAddress,
         token2: ChecksumAddress,
         amount: int, fee: int = 3000, slippage: int = 1, secs: int = 60):
    print(router, recipient, token1, token2, amount, fee, slippage, secs)
    print('[+] Performing checks ... ')
    current_balance = self.token_balance(token1, True)
    assert current_balance >= amount
    current_allowance = self.token_allowance(token1)
    if amount > current_allowance:
        print('[+] Must approve contract to spend tokens ... ')
        txid = self.approve_trade_proxy(token1)
        self.wallet.poll_receipt(txid)
    # data = self.encode_swap(router, recipient, token1, token2, fee, amount, slippage, secs)
    swap_params = (router, recipient, token1, token2, fee, amount, slippage, secs)
    tx = {
        "from": self.account.address,
        "to": self.contract_address,
        "data": self._contract.encodeABI('swap', args=(swap_params,)),
        "value": 0,
        "chainId": self.w3.eth.chain_id
    }
    return self.wallet.broadcast_raw_tx(tx)


Yes! Thank you!
