Solidity双层mapping存储写入失败求助:Hardhat操作gardens[20][22]遇阻
问题:无法通过Hardhat将地址写入Solidity嵌套mapping的指定位置
我正在学习Solidity存储机制,参与Node Guardian任务,部署了地址为0xb1d01315081351dbb747c3db92754acf8686a219的智能合约,合约代码如下:
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.19; contract Temple { uint128 public entrance; address public mainHall; mapping(uint8 => mapping(uint8 => address)) public gardens; bytes20[] public chambers; /// Write data to the contract's ith storage slot function write(uint256 i, bytes32 data) public { assembly { sstore(i, data) } } }
我需要通过Hardhat将我的地址写入gardens[20][22],编写了以下代码但操作失败:
const { ethers } = require("hardhat"); async function main() { const [deployer] = await ethers.getSigners(); console.log("Deploying contracts with the account:", deployer.address); const temple = await ethers.getContractAt("Temple", "0xb1d01315081351DBb747c3DB92754acF8686A219"); // const Temple = await ethers.getContractFactory("Temple"); // const temple = await Temple.deploy(); console.log("Temple address:", temple.address); // Set a higher gas limit for writing data to the contract const gasLimit = 300000; // Adjust this value as needed // Convert your address to bytes32 // const encodedAddress = ethers.utils.defaultAbiCoder.encode(["address"], [deployer.address]); const encodedAddress = ethers.utils.defaultAbiCoder.encode(["address"], [deployer.address]); // Calculate the storage slot for gardens[20][2] const slotForGardens20 = ethers.utils.keccak256(ethers.utils.solidityPack(["uint8", "uint8"], [20, 2])); // Calculate the storage slot for gardens[20][22] const slotForGardens2022 = ethers.utils.keccak256(ethers.utils.solidityPack(["uint8", "bytes32"], [22, slotForGardens20])); // Use the write function to write your address to gardens[20][22] await temple.write(slotForGardens2022, encodedAddress); await temple.write(ethers.utils.keccak256(ethers.utils.solidityPack(["uint8", "uint8"], [20, 22])), encodedAddress, { gasLimit }); // const addressInSlot = await temple.read(20, 22); // console.log("Address read from gardens[20][22]:", addressInSlot); } main() .then(() => process.exit(0)) .catch(error => { console.error(error); process.exit(1); });
问题分析与修正方案
核心错误:存储槽计算逻辑错误
Solidity中嵌套mapping的存储槽计算必须遵循固定规则:
- 确定外层mapping的基础槽位:合约变量按声明顺序分配槽位,
entrance占槽0,mainHall占槽1,因此gardens的基础槽位是2。 - 计算外层mapping的索引哈希:
keccak256(abi.encodePacked(外层key, 外层mapping的槽位)) - 计算内层mapping的最终槽位:
keccak256(abi.encodePacked(内层key, 外层索引哈希))
你的代码错误在于未使用gardens的基础槽位,直接用两个key组合计算,导致槽位完全偏离实际位置。
修正后的Hardhat代码
const { ethers } = require("hardhat"); async function main() { const [deployer] = await ethers.getSigners(); console.log("Using account:", deployer.address); const temple = await ethers.getContractAt("Temple", "0xb1d01315081351DBb747c3DB92754acF8686A219"); console.log("Temple contract address:", temple.address); // 1. gardens的基础存储槽是2(根据合约变量声明顺序) const gardensBaseSlot = 2; // 2. 计算外层mapping(gardens[20])的哈希值 const outerHash = ethers.utils.keccak256( ethers.utils.solidityPack(["uint8", "uint256"], [20, gardensBaseSlot]) ); // 3. 计算gardens[20][22]的最终存储槽 const targetSlot = ethers.utils.keccak256( ethers.utils.solidityPack(["uint8", "bytes32"], [22, outerHash]) ); // 4. 将地址编码为bytes32(地址自动左对齐填充到32字节) const encodedAddress = ethers.utils.defaultAbiCoder.encode(["address"], [deployer.address]); // 5. 写入目标存储槽 const tx = await temple.write(targetSlot, encodedAddress, { gasLimit: 300000 }); await tx.wait(); console.log("Transaction confirmed, address written to gardens[20][22]"); // 可选:验证写入结果(无需合约read方法) // const storedValue = await ethers.provider.getStorageAt(temple.address, targetSlot); // console.log("Stored value (bytes32):", storedValue); // console.log("Decoded address:", ethers.utils.defaultAbiCoder.decode(["address"], storedValue)[0]); } main() .then(() => process.exit(0)) .catch(error => { console.error(error); process.exit(1); });
额外说明
- 可通过
ethers.provider.getStorageAt直接读取目标槽位值并解码,验证写入结果。 - 确保调用账户拥有足够的Goerli测试网ETH支付gas费用。
内容的提问来源于stack exchange,提问作者ThomasJanko
相关产品推荐
相关产品推荐

