1

I'm currently having an issue with decoding a function call inside a Subgraph mapping that seems to be related to builtin abi decoder of a Graph node:

Given this input:

00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f36e7bc1dae85ed18cd492f9e856acf2bd13398e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

This doesn't work (mapping.ts of subgraph), it returns null, no error message given.

const decoded = ethereum.decode(
    '(address[],uint256[],uint32,bytes32[],bytes)',
    input
  );

whereas this works (Rust cli, 13.0 & latest):

ethabi decode params -t address[] -t uint256[] -t uint32 -t bytes32[] -t bytes <input>

outputs:

address[] [f36e7bc1dae85ed18cd492f9e856acf2bd13398e]
uint256[] [1]
uint32 1
bytes32[] []
bytes

(and it also works using the abidecoder from ethers.js ->

ethers.utils.defaultAbiCoder.decode(["address[]", "uint256[]", "uint32", "bytes32[]", "bytes"], inp)

Sidenote: this actually works inside a mapping, so I assume the issue might be related to arrays (or their abi notation) :

const decoded = ethereum.decode('(address,uint256,uint32)', input);
  

what am I missing?

1

1 Answer 1

3

They can, actually. I ran into a totally different problem: decoding tuples vs. decoding arrays.

When trying to call ethereum.decode using this type string (address[],uint256[],uint32,bytes32[],bytes) it actually tries to decode a tuple of those types (denoted by the outer braces). There is no way I found to tell the EthABI based decoder to use a type/param-string like address[],uint256[],uint32,bytes32[],bytes, so I dug into how Ethereum's ABI Encoder encodes tuples.

Given the input from above, if you prepend it with

0000000000000000000000000000000000000000000000000000000000000020

it's recognized as a tuple of the parameter list by the decoder and can be decoded.

Here's some code that proves that behaviour:

use ethabi::decode;
use ethabi::param_type::{ParamType, Reader};
use hex_literal::hex;

//this code is here to demonstrate function parameter decoding with a prefixed tuple code
fn main() {
    let types: &str = "(address[],uint[],uint,bytes32[],bytes)";
    let param_types_str = Reader::read(&types).unwrap(); //or_else(|e| Err(anyhow::anyhow!("Failed to read types: {}", e)));
    let param_types_def = ParamType::Tuple(vec![
        Box::new(ParamType::Array(Box::new(ParamType::Address))),
        Box::new(ParamType::Array(Box::new(ParamType::Uint(256)))),
        Box::new(ParamType::Uint(256)),
        Box::new(ParamType::Array(Box::new(ParamType::FixedBytes(32)))),
        Box::new(ParamType::Bytes),
    ]);

    println!("type(str): {:?}", param_types_str);
    println!("type(def): {:?}", param_types_def);

    let encoded = hex!(
        "

        0000000000000000000000000000000000000000000000000000000000000020 
        
        00000000000000000000000000000000000000000000000000000000000000a0
        00000000000000000000000000000000000000000000000000000000000000e0
        0000000000000000000000000000000000000000000000000000000000000001
        0000000000000000000000000000000000000000000000000000000000000120
        0000000000000000000000000000000000000000000000000000000000000140
        0000000000000000000000000000000000000000000000000000000000000001
        000000000000000000000000f36e7bc1dae85ed18cd492f9e856acf2bd13398e
        0000000000000000000000000000000000000000000000000000000000000001
        0000000000000000000000000000000000000000000000000000000000000001
        0000000000000000000000000000000000000000000000000000000000000000
        0000000000000000000000000000000000000000000000000000000000000000
        "
    );

    let res_str = decode(&[param_types_str], &encoded).unwrap();
    println!("decoded(str): {:?}", res_str);

    let res_def = decode(&[param_types_def], &encoded).unwrap();
    println!("decoded(def): {:?}", res_def);
}

type(str): Tuple([Array(Address), Array(Uint(256)), Uint(256), Array(FixedBytes(32)), Bytes])
type(def): Tuple([Array(Address), Array(Uint(256)), Uint(256), Array(FixedBytes(32)), Bytes])
decoded(str): [Tuple([Array([Address(0xf36e7bc1dae85ed18cd492f9e856acf2bd13398e)]), Array([Uint(1)]), Uint(1), Array([]), Bytes([])])]
decoded(def): [Tuple([Array([Address(0xf36e7bc1dae85ed18cd492f9e856acf2bd13398e)]), Array([Uint(1)]), Uint(1), Array([]), Bytes([])])]

In terms of The Graph you can actually "convert" an input argument of a function parameter list that you want to decode, by prefixing it with that encoded tuple bytes. In AssemblyScript it looks like:

import {  
  Bytes,
  ByteArray,
  ethereum
} from '@graphprotocol/graph-ts';

const functionInput = event.transaction.input.subarray(4);

//prepend a "tuple" prefix (function params are arrays, not tuples)
const tuplePrefix = ByteArray.fromHexString(
  '0x0000000000000000000000000000000000000000000000000000000000000020'
);

const functionInputAsTuple = new Uint8Array(
  tuplePrefix.length + functionInput.length
);

//concat prefix & original input
functionInputAsTuple.set(tuplePrefix, 0);
functionInputAsTuple.set(functionInput, tuplePrefix.length);

const tupleInputBytes = Bytes.fromUint8Array(functionInputAsTuple);
const decoded = ethereum.decode(
  '(address[],uint[],uint,bytes32[],bytes)',
  tupleInputBytes
);

const t = decoded.toTuple();
const originAddresses = t[0].toAddressArray();
const firstAddress = originAddresses[0];

1
  • 1
    Wow, incredible work thank you Commented Jan 4, 2023 at 23:04

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.