Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weird type mismatch is not of kind error. #1105

Closed
Benjythebee opened this issue Sep 26, 2021 · 7 comments
Closed

Weird type mismatch is not of kind error. #1105

Benjythebee opened this issue Sep 26, 2021 · 7 comments

Comments

@Benjythebee
Copy link

Benjythebee commented Sep 26, 2021

I've been making a subgraph based on Opensea's exchange contract and I'm struggling quite a bit due to some pretty weird error causing the subgraph to crash. I'm posting this here as I'm now clueless and I suspect there might be a misinterpretation in types from ts -> wasm -> eth or ABI -> ts -> wasm (Just a bit suspicious).

If we can't figure out the issue I'm hoping this issue may at least lead to some better feedback when an error like that is ever encountered again.

The error

Failed to call function "hashOrder_" of contract "WyvernExchange": type mismatch, token 
Array([Address(0x7be8076f4ea4a4ad08075c2508e481d6c946d12b), Address(0xb13c38273e001f0427c26f15b79b57db90606a91), Address(0x0000000000000000000000000000000000000000), Address(0x0000000000000000000000000000000000000000), Address(0x79986af15539de2db9a5086382daeda917a9cf0c), Address(0x0000000000000000000000000000000000000000), Address(0x0000000000000000000000000000000000000000)])

 is not of kind 

Array([Address(0x7be8076f4ea4a4ad08075c2508e481d6c946d12b), Address(0xb13c38273e001f0427c26f15b79b57db90606a91), Address(0x0000000000000000000000000000000000000000), Address(0x0000000000000000000000000000000000000000), Address(0x79986af15539de2db9a5086382daeda917a9cf0c), Address(0x0000000000000000000000000000000000000000), Address(0x0000000000000000000000000000000000000000)])

As you can see, the error message is not really informative. At least it doesn't tell us what the expected type is.

The hashOrder_ function in solidity:

    function hashOrder_(
        address[7] addrs,
        uint[9] uints,
        FeeMethod feeMethod,
        SaleKindInterface.Side side,
        SaleKindInterface.SaleKind saleKind,
        AuthenticatedProxy.HowToCall howToCall,
        bytes calldata,
        bytes replacementPattern,
        bytes staticExtradata)
        public
        pure
        returns (bytes32)
    { 
        return hashToSign(
          Order(addrs[0], addrs[1], addrs[2], uints[0], uints[1], uints[2], uints[3], addrs[3], feeMethod, side, saleKind, addrs[4], howToCall, calldata, replacementPattern, addrs[5], staticExtradata, ERC20(addrs[6]), uints[4], uints[5], uints[6], uints[7], uints[8])
        );
    }

graph init interprets hashOrder_ call as

  hashOrder_(
    addrs: Array<Address>,
    uints: Array<BigInt>,
    feeMethod: i32,
    side: i32,
    saleKind: i32,
    howToCall: i32,
    calldata: Bytes,
    replacementPattern: Bytes,
    staticExtradata: Bytes
  ): Bytes {
    let result = super.call(
      "hashOrder_",
      "hashOrder_(address[7],uint256[9],uint8,uint8,uint8,uint8,bytes,bytes,bytes):(bytes32)",
      [
        ethereum.Value.fromAddressArray(addrs),
        ethereum.Value.fromUnsignedBigIntArray(uints),
        ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(feeMethod)),
        ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(side)),
        ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(saleKind)),
        ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(howToCall)),
        ethereum.Value.fromBytes(calldata),
        ethereum.Value.fromBytes(replacementPattern),
        ethereum.Value.fromBytes(staticExtradata)
      ]
    );

Here is my mapping.ts for the ones curious:

export function handleAtomicMatch(event: AtomicMatch_Call): void {
  let addrs = event.inputValues[0].value.toAddressArray();
  let uints = event.inputValues[1].value.toBigIntArray();
  let feeMethodsSidesKindsHowToCalls = event.inputValues[2].value.toI32Array();

  let exchangeAddress = addrs[0];

  let makerRelayerFeeBuy = uints[0];
  let makerRelayerFeeSell = uints[9];

  let takerRelayerFeeBuy = uints[1];
  let takerRelayerFeeSell = uints[10];

  let makerProtocolFeeBuy = uints[2];
  let makerProtocolFeeSell = uints[11];

  let takerProtocolFeeBuy = uints[3];
  let takerProtocolFeeSell = uints[12];

  let basePriceBuy = uints[4];
  let basePriceSell = uints[13];

  let extraBuy = uints[5];
  let extraSell = uints[14];

  let listingTimeBuy = uints[6];
  let listingTimeSell = uints[15];

  let expirationTimeBuy = uints[7];
  let expirationTimeSell = uints[16];

  let saltBuy = uints[8];
  let saltSell = uints[17];

  let addressesBuy: Array<Address> = [];
  addressesBuy.push(addrs[0]);
  addressesBuy.push(addrs[1]);
  addressesBuy.push(addrs[2]);
  addressesBuy.push(addrs[3]);
  addressesBuy.push(addrs[4]);
  addressesBuy.push(addrs[5]);
  addressesBuy.push(addrs[6]);

  let addressesSell: Array<Address> = [];
  addressesSell.push(addrs[7]);
  addressesSell.push(addrs[8]);
  addressesSell.push(addrs[9]);
  addressesSell.push(addrs[10]);
  addressesSell.push(addrs[11]);
  addressesSell.push(addrs[12]);
  addressesSell.push(addrs[13]);

  let uintsBuy: Array<BigInt> = [];
  uintsBuy.push(uints[0]);
  uintsBuy.push(uints[1]);
  uintsBuy.push(uints[2]);
  uintsBuy.push(uints[3]);
  uintsBuy.push(uints[4]);
  uintsBuy.push(uints[5]);
  uintsBuy.push(uints[6]);
  uintsBuy.push(uints[7]);
  uintsBuy.push(uints[8]);

  let uintsSell: Array<BigInt> = [];
  uintsSell.push(uints[9]);
  uintsSell.push(uints[10]);
  uintsSell.push(uints[11]);
  uintsSell.push(uints[12]);
  uintsSell.push(uints[13]);
  uintsSell.push(uints[14]);
  uintsSell.push(uints[15]);
  uintsSell.push(uints[16]);
  uintsSell.push(uints[17]);

  let feeMethodBuy = feeMethodsSidesKindsHowToCalls[0];
  let feeMethodSell = feeMethodsSidesKindsHowToCalls[4];

  let sideBuy = feeMethodsSidesKindsHowToCalls[1];
  let sideSell = feeMethodsSidesKindsHowToCalls[5];

  let saleKindBuy = feeMethodsSidesKindsHowToCalls[2];
  let saleKindSell = feeMethodsSidesKindsHowToCalls[6];

  let howToCallBuy = feeMethodsSidesKindsHowToCalls[3];
  let howToCallSell = feeMethodsSidesKindsHowToCalls[7];

  let callDataBuy = event.inputValues[3].value.toBytes();
  let callDataSell = event.inputValues[4].value.toBytes();

  let replacementPatternBuy = event.inputValues[5].value.toBytes();
  let replacementPatternSell = event.inputValues[6].value.toBytes();

  let staticExtradataBuy = event.inputValues[7].value.toBytes();
  let staticExtradataSell = event.inputValues[8].value.toBytes();

  let buyHash = exchange.hashOrder_(
    addressesBuy,
    uintsBuy,
    feeMethodBuy,
    sideBuy,
    saleKindBuy,
    howToCallBuy ,
    callDataBuy,
    replacementPatternBuy,
    staticExtradataBuy
  );

  let buyOrder = Order.load(buyHash.toHexString());
  if (buyOrder === null) {
    buyOrder = new Order(buyHash.toHexString());
  }
...
  buyOrder.save();
}

The opensea contract code:
https://etherscan.io/address/0x7be8076f4ea4a4ad08075c2508e481d6c946d12b#code

The Wyvern Exchange contract documentation:
https://docs.projectwyvern.com/docs/Exchange/

Typical Opensea transaction:
https://etherscan.io/tx/0xc4364dcfb016e277290036cd10f6c84a86f3758e2dea6bb6ce978d065dfb46cd

Am I missing something?

Dependencies:
"@graphprotocol/graph-cli": "^0.22.1",
"@graphprotocol/graph-ts": "^0.22.1"

Graph node: v0.24.1+17

@xenoliss
Copy link

Hello there, facing the exact same issue, have you been able to solve it ? Thanks

@Benjythebee
Copy link
Author

Nope, I ended up not using it :/

@xenoliss
Copy link

Ok same here, recoded the entire smart contrat function in the mapping. Would be cool to have any update on it

@evaporei
Copy link
Contributor

Hello, sorry we didn't look into this earlier, I'll see if I can take a look over the weekend. I agree that it seems a conversion error, but I think it is on graph-cli actually.

@evaporei
Copy link
Contributor

evaporei commented Dec 6, 2021

Hello! Sorry for the long time to respond 😅

Well, I've investigated through the code and here is the "stack trace" of it:

It happens because of a token.type_check(kind) call. This is implemented in the ethabi crate, here's a couple of useful links of their docs:

We actually use a fork of this crate, there are two possible scenarios:

  1. Our fork has the comparison bug and the latest version doesn't
  2. The bug is present in both versions (fork or not)

Either way we shall patch it. I've yet to reproduce the bug locally on my machine, I don't think it will be hard though, I'll document it here. More updates coming in the following days 🙂

Edit: I've used both ethabi in the latest version 15.0.0 and our fork, both seem to do the comparison correctly, maybe we're calling it with the wrong values here, still investigating 🔍 🕵️‍♀️

Sample code:

use ethabi::{Token, Address, ParamType};
use std::str::FromStr;

// Array([
//     Address(0x7be8076f4ea4a4ad08075c2508e481d6c946d12b),
//     Address(0xb13c38273e001f0427c26f15b79b57db90606a91),
//     Address(0x0000000000000000000000000000000000000000),
//     Address(0x0000000000000000000000000000000000000000),
//     Address(0x79986af15539de2db9a5086382daeda917a9cf0c),
//     Address(0x0000000000000000000000000000000000000000),
//     Address(0x0000000000000000000000000000000000000000)
// ])

fn main() {
    let a0 = Token::Address(Address::from_str("7be8076f4ea4a4ad08075c2508e481d6c946d12b").unwrap());
    let a1 = Token::Address(Address::from_str("b13c38273e001f0427c26f15b79b57db90606a91").unwrap());
    let a2 = Token::Address(Address::from_str("0000000000000000000000000000000000000000").unwrap());
    let a3 = Token::Address(Address::from_str("0000000000000000000000000000000000000000").unwrap());
    let a4 = Token::Address(Address::from_str("79986af15539de2db9a5086382daeda917a9cf0c").unwrap());
    let a5 = Token::Address(Address::from_str("0000000000000000000000000000000000000000").unwrap());
    let a6 = Token::Address(Address::from_str("0000000000000000000000000000000000000000").unwrap());

    let token = Token::Array(vec![a0, a1, a2, a3, a4, a5, a6]);

    assert!(token.type_check(&ParamType::Array(Box::new(ParamType::Address))));
}

And Cargo.toml file:

[package]
name = "ethabi-bug"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
# works for both versions below:
ethabi = "15.0.0"
# ethabi = { git = "https://github.com/graphprotocol/ethabi" }

@evaporei
Copy link
Contributor

evaporei commented Dec 6, 2021

@Benjythebee @xenoliss I think I've found the issue, we have a printing bug in graph-node, we're printing twice the same data, I've made a PR for the fix. Once that's merged and rolled out in the Hosted Service I'll ping you guys, the message should be helpful after that 😊

graphprotocol/graph-node#3034

@dotansimha dotansimha transferred this issue from graphprotocol/graph-ts Feb 23, 2023
@azf20 azf20 added this to Tooling Mar 8, 2023
@azf20
Copy link
Contributor

azf20 commented Mar 9, 2023

Closing per graphprotocol/graph-node#3034, please re-open if still an issue

@azf20 azf20 closed this as completed Mar 9, 2023
@github-project-automation github-project-automation bot moved this to ✅ Done in Tooling Mar 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

No branches or pull requests

4 participants