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是实际账户地址,因此可以正常返回数据。
修复方案
两种方案选其一即可:
- 初始化合约实例时传入
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) } } }
- 保留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
相关产品推荐
相关产品推荐

