You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Solidity函数Remix正常返回但ethers.js/hardhat调用返回空数组

问题背景

Solidity开发中getOwnCards函数在Remix IDE调用可正常返回预期卡牌ID数组,但使用ethers.js/hardhat调用时返回空数组,两边使用的ABI完全一致。相关代码与运行现象如下:

合约代码

function getOwnCards() external view returns(uint[] memory _cards) {
    uint[] memory result = new uint[](userToCardsCount[msg.sender]);
    uint counter = 0;
    for (uint i = 0; i < cards.length; i++) {
        if (cardIdToUser[i] == msg.sender) {
            result[counter] = cards[i];
            counter++;
        }
    }
    return result;
}

Remix IDE调用返回结果

0:
uint256[]: _cards 62180033277912311002789072835418559502128313467724667052439679862541172075622,31220469133090072532919887703804680190688655804240516732101690706958488894069,35568354368614544905898949308353469583138320508979990175291873927902398948450

ethers.js调用返回结果

[]

前端调用代码

const getCards = async () => {
    if (typeof window.ethereum !== 'undefined'){
        //@ts-ignore 
        const provider = new ethers.providers.Web3Provider(window.ethereum) 
        const contract = new ethers.Contract(cardsAddress, Cards.abi, provider)
        try {
            const data = await contract.getOwnCards()
            console.log(data)
        } catch (error) {
            console.log(error)
        }
    }
}
问题根因
  • 前端初始化合约实例时传入的是provider对象,用该实例发起view函数调用属于无签名的eth_call请求,默认不会携带当前钱包地址作为from参数,合约内获取到的msg.sender是零地址0x0000000000000000000000000000000000000000。
  • 合约逻辑中返回数组的初始长度取的是userToCardsCount[msg.sender],零地址对应的卡牌持有数为0,因此初始化的结果数组长度为0;同时零地址没有对应卡牌,循环逻辑不会命中赋值分支,最终返回空数组。
  • Remix IDE调用时默认会用当前已连接的钱包地址作为调用发起方,msg.sender是实际账户地址,因此可以正常返回数据。
修复方案

两种方案选其一即可:

  1. 初始化合约实例时传入signer签名者对象而非provider,调用时会自动将当前连接的钱包地址作为from参数传入:
const getCards = async () => {
    if (typeof window.ethereum !== 'undefined'){
        //@ts-ignore 
        const provider = new ethers.providers.Web3Provider(window.ethereum)
        // 获取签名者对象
        const signer = provider.getSigner()
        // 用signer初始化合约实例
        const contract = new ethers.Contract(cardsAddress, Cards.abi, signer)
        try {
            const data = await contract.getOwnCards()
            console.log(data)
        } catch (error) {
            console.log(error)
        }
    }
}
  1. 保留provider初始化的合约实例,调用函数时显式传入from参数指定调用地址:
const getCards = async () => {
    if (typeof window.ethereum !== 'undefined'){
        //@ts-ignore 
        const provider = new ethers.providers.Web3Provider(window.ethereum)
        const contract = new ethers.Contract(cardsAddress, Cards.abi, provider)
        try {
            // 请求钱包连接并获取当前账户地址
            const accounts = await provider.send("eth_requestAccounts", [])
            const currentAddress = accounts[0]
            // 调用时显式指定from参数
            const data = await contract.getOwnCards({from: currentAddress})
            console.log(data)
        } catch (error) {
            console.log(error)
        }
    }
}

*额外优化建议:当前合约逻辑存在数组越界风险,如果userToCardsCount[msg.sender]的计数和实际cardIdToUser映射中属于该用户的卡牌数量不一致,循环中给result[counter]赋值时会触发越界报错,建议改为动态截断数组的写法,避免计数不一致导致的异常:

function getOwnCards() external view returns(uint[] memory _cards) {
    uint[] memory result = new uint[](cards.length);
    uint counter = 0;
    for (uint i = 0; i < cards.length; i++) {
        if (cardIdToUser[i] == msg.sender) {
            result[counter] = cards[i];
            counter++;
        }
    }
    // 将数组截断到实际有效长度
    assembly {
        mstore(result, counter)
    }
    return result;
}

内容的提问来源于stack exchange,提问作者Alwin Brauns

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 22:03:33