Solidity合约struct内嵌mapping时数据未持久化问题咨询
问题背景
查阅mapping使用方法、struct嵌套mapping相关技术资料,参考社区讨论编写了如下Solidity测试合约,已知Solidity 0.7.0版本后调整了struct内嵌套mapping的语法规则,代码如下:
contract Test { constructor() { } struct Bid { uint auction_id; address addr; uint amount; } struct Auction { uint id; string dtype; uint start_date; uint end_date; string label; uint price; uint amount; bool closed; mapping(uint => Bid) bids; uint bidCount; } uint public auctionCount = 0; mapping(uint => Auction) public auctions; function createAuction( string memory plabel, string memory ptype, uint nbhours, uint pprice) external { Auction storage nd = auctions[auctionCount]; nd.id = auctionCount; nd.dtype = ptype; nd.start_date = block.timestamp; nd.end_date = block.timestamp+nbhours*60*60; nd.label = plabel; nd.price = pprice; nd.amount = 0; nd.closed = false; nd.bidCount = 0; auctionCount++; } }
代码可正常编译,createAuction交易执行返回成功。在Ganache中查看合约状态时,auctionCount变量可正常递增,但auctions mapping中没有新增对应条目;通过Truffle调试交易,确认函数执行过程中完成了各字段赋值,但状态变更看起来没有被持久化存储。曾尝试移除一个string类型属性(曾看到资料称struct内存在3个string属性可能引发问题,当前代码中最多只有2个string字段),问题仍未解决。
根本原因
该问题不是Solidity语法错误,也不是状态没有持久化,是本地开发工具的展示能力限制:
- 合约逻辑本身完全符合Solidity存储规则:当通过
Auction storage nd = auctions[auctionCount];访问mapping中不存在的键时,Solidity会自动在存储中为该键初始化空的struct实例,后续对nd的所有字段赋值都会直接写入链上存储,auctionCount成功递增已经证明交易被正常打包执行,状态写入实际已经生效。 - Ganache状态面板、旧版本Truffle调试器没有实现对含嵌套mapping的动态大小struct的解析能力:包含mapping类型成员的struct属于复杂动态存储结构,工具无法自动遍历解析这类结构的链上数据,因此不会在状态面板中展示对应条目,本质是工具“看不到”已经存好的数据,不是数据没存上。
- 网传“struct内3个string会引发问题”是Solidity 0.6之前版本的编译器bug,0.7及之后版本已经完全修复,和当前现象无关。
验证与修复方案
可以通过自定义视图函数自行验证存储状态,操作如下:
- 在合约中新增专门的查询函数,手动返回struct中除嵌套mapping外的所有字段(含mapping的struct无法直接在memory中承载,因此不能整体作为返回值):
function getAuction(uint _auctionId) external view returns( uint id, string memory dtype, uint start_date, uint end_date, string memory label, uint price, uint amount, bool closed, uint bidCount ) { Auction storage targetAuction = auctions[_auctionId]; return ( targetAuction.id, targetAuction.dtype, targetAuction.start_date, targetAuction.end_date, targetAuction.label, targetAuction.price, targetAuction.amount, targetAuction.closed, targetAuction.bidCount ); }
- 重新部署合约,调用
createAuction发起交易,待交易确认后,调用getAuction传入对应拍卖ID,即可读取到之前写入的所有字段值,直接证明状态已经完成持久化。 - 后续如果需要读取
Auctionstruct内嵌套的bidsmapping数据,单独编写对应入参为拍卖ID、出价ID的查询函数即可,不要尝试直接返回整个struct。
内容的提问来源于stack exchange,提问作者Sebastien VAX
相关产品推荐
相关产品推荐

