ethers-io / ethers.js

Complete Ethereum library and wallet implementation in JavaScript.
https://ethers.org/
MIT License
7.91k stars 1.84k forks source link

Bug: functionFragment.format is not a function getting function sig #4340

Open steddyman opened 1 year ago

steddyman commented 1 year ago

Ethers Version

5.7.2

Search Terms

abi

Describe the Problem

functionFragment.format throws 'is not a function' execption when running inside a Docker container. Works fine on laptop, but reflection of functionFragement shows it is missing.

The following function works fine on Vercel and fine running locally with NPM RUN DEV. However, when built into a Dockerfile, this specific function fails. I've added the following debug line:

console.log(typeof td.functionFragment, Object.keys(td.functionFragment))

On my laptop it outputs the following:

object [
  'type',
  'name',
  'constant',
  'inputs',
  'outputs',
  'payable',
  'stateMutability',
  'gas',
  '_isFragment',
  'constructor',
  'format'
]

Running inside a Docker container with the exact same version of @ethersproject, build from the same package.json, shows the following (missing constructor and format):

object [  
  'type',  
  'name',  
  'constant',  
  'inputs',  
  'outputs',  
  'payable',  
  'stateMutability',  
  'gas',  
  '_isFragment'
]

This is decoding the exact same transaction with all the same inputs.

Code Snippet

export const getContractFunctionSigAndData = async (value, data, abi) => {
    // We have the ABI so use ethers to find the function name
    const iface = new ethers.utils.Interface(abi)
    // consoleLog("-- Interface --")
    // console.dir(iface)
    // Get TranactionDescription object
    try {
        console.log(`Reading transaction values via parseTransaction`)
        // console.dir(txn)
        console.log(`DATA: ${data}`)
        console.log(`VALUE: ${value}`)
        const td = iface.parseTransaction({ data: data, value: value })
        if (td) {
            console.log(`Obtained TransactionDetails for function with ABI`)
            // console.dir(td.functionFragment.inputs)
            let sig = td.signature
            console.log(`td.signature = ${sig}`)
            console.log(typeof td.functionFragment, Object.keys(td.functionFragment))
            let fullFuncName = td.functionFragment.format(FormatTypes.json)
            let funcWithParams = td.functionFragment.format(FormatTypes.full)
            console.log(`FormatTypes.full = ${fullFuncName}`)
            let funcName = td.name
            // Decode data
            const funcData = iface.decodeFunctionData(sig, data)
            console.log(`Function name returned: ${funcName}`)
            console.log(`Full Func name: ${fullFuncName}`)
            console.log(`Function name with params: ${funcWithParams}`)
            console.log(`Function data: ${funcData}`)
            return { sig: fullFuncName, data: funcData, funcWithParams }
        } else {
            console.log(`TransactionDetails not returned`)
            return null
        }
    } catch (e) {
        console.log(`Error ${e} getting function sig`)
    }
}

Contract ABI

N/A

Errors

Error TypeError: td.functionFragment.format is not a function getting function sig

Environment

node.js (v12 or newer)

Environment (Other)

No response

steddyman commented 1 year ago

I've looked at the build logs of the container and spotted the following messages, could these be related?

2023-08-27T19:38:58Z #12 63.36 > Using @sveltejs/adapter-node
2023-08-27T19:39:16Z #12 81.17 node_modules/@ethersproject/contracts/lib.esm/index.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:16Z #12 81.17 node_modules/@ethersproject/contracts/lib.esm/index.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:16Z #12 81.22 node_modules/@ethersproject/abstract-signer/lib.esm/index.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:16Z #12 81.22 node_modules/@ethersproject/abstract-signer/lib.esm/index.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:16Z #12 81.29 node_modules/@ethersproject/wallet/lib.esm/index.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:16Z #12 81.30 node_modules/@ethersproject/wallet/lib.esm/index.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:18Z #12 83.39 node_modules/@ethersproject/properties/lib.esm/index.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:18Z #12 83.39 node_modules/@ethersproject/properties/lib.esm/index.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:18Z #12 83.51 node_modules/@ethersproject/web/lib.esm/index.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:18Z #12 83.51 node_modules/@ethersproject/web/lib.esm/index.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 83.96 node_modules/@ethersproject/providers/lib.esm/base-provider.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 83.97 node_modules/@ethersproject/providers/lib.esm/base-provider.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.04 node_modules/@ethersproject/providers/lib.esm/cloudflare-provider.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.04 node_modules/@ethersproject/providers/lib.esm/cloudflare-provider.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.06 node_modules/@ethersproject/providers/lib.esm/etherscan-provider.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.06 node_modules/@ethersproject/providers/lib.esm/etherscan-provider.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.10 node_modules/@ethersproject/providers/lib.esm/fallback-provider.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.10 node_modules/@ethersproject/providers/lib.esm/fallback-provider.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.18 node_modules/@ethersproject/providers/lib.esm/json-rpc-provider.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.18 node_modules/@ethersproject/providers/lib.esm/json-rpc-provider.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.37 node_modules/@ethersproject/providers/lib.esm/url-json-rpc-provider.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.37 node_modules/@ethersproject/providers/lib.esm/url-json-rpc-provider.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.41 node_modules/@ethersproject/providers/lib.esm/websocket-provider.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.41 node_modules/@ethersproject/providers/lib.esm/websocket-provider.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.62 node_modules/@ethersproject/abstract-provider/lib.esm/index.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:19Z #12 84.62 node_modules/@ethersproject/abstract-provider/lib.esm/index.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:20Z #12 85.01 node_modules/@ethersproject/hash/lib.esm/typed-data.js (1:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:20Z #12 85.01 node_modules/@ethersproject/hash/lib.esm/typed-data.js (1:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:20Z #12 85.06 node_modules/@ethersproject/json-wallets/lib.esm/keystore.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:20Z #12 85.06 node_modules/@ethersproject/json-wallets/lib.esm/keystore.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:20Z #12 85.38 node_modules/@ethersproject/web/lib.esm/geturl.js (2:17) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
2023-08-27T19:39:20Z #12 85.38 node_modules/@ethersproject/web/lib.esm/geturl.js (2:25) The 'this' keyword is equivalent to 'undefined' at the top level of an ES module, and has been rewritten
steddyman commented 1 year ago

More context. I've added the following debug line:

console.log(iface.constructor.toString());

On my mac laptop, it contains only the definition of a function called Interface:

function Interface(fragments) {
        var _newTarget = this.constructor;
         ...

In the Docker container, it contains a full Class definiition:

class Interface {
    constructor(fragments) {
        let abi = [];
        if (typeof (fragments) === "string") {
            abi = JSON.parse(fragments);
        }        else { 
           abi = fragments;
        }

Also tried an NPM RUN BUILD followed by NPM RUN PREVIEW on my laptop. Still works and behaves identically to NPM RUN DEV.

Here is my Dockerfile. The package.json and package-lock.json are identical between the Mac and when I exec into the container:

# Use a node base image
FROM node:18.16.1-alpine as build

WORKDIR /app
COPY package*.json ./
RUN npm install

COPY . .
# Provide dummy values for environment variables
ENV DISCORD_ID=dummy DISCORD_SECRET=dummy AUTH_SECRET=dummy DISCORD_SERVER=dummy DISCORD_ROLE=dummy TOASTER_KEY=dummy

RUN npm run build

FROM node:18.16.1-alpine

WORKDIR /app

COPY --from=build /app/build .

COPY package*.json ./
RUN npm install --prod

CMD ["node", "./index.js"]

Note: also tried with the non-alphine OS, same issue.

ricmoo commented 1 year ago

This definitely looks weird, but I suspect it is a change in the version (or configuration) of Babel being used to transpile the code. Can you validate Svelte is configured identically in both cases? Or perhaps it pulls some configuration from the environment (browser version, OS, etc.?).

steddyman commented 1 year ago

After more research and asking around on the Sveltekit discord, I believe it relates to this known issue with the 5.x version of Ethers: https://github.com/ethers-io/ethers.js/issues/1784

I can confirm this by running node build/index.js locally, and I do see the same issue then. It works running NPM RUN PREVIEW.

Is the solution to this problem to move to Ethers v6?