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

Graph Protocol子图开发报错:PlayerToLottery非空字段缺失值

子图开发多对多关系时Matchstick测试报错排查

我参照Graph Protocol官方多对多关系的高性能实现方式开发子图,使用matchstick-as ^0.5.0编译测试时出现以下错误:

Igniting tests

nft-lottery
--------------------------------------------------
  Enter the NFT Lottery:
thread 'main' panicked at '🆘 Unexpected error upon calling hook: Missing value for non-nullable field 'player' for an entity of type 'PlayerToLottery'.
wasm backtrace:
    0: 0x331a - <unknown>!src/nft-lottery/handleNftLotteryEnter;
    1: 0x3807 - <unknown>!start:tests/nft-lottery.test~anonymous|0~anonymous|0

相关代码

./schema.graphql

type NftLottery @entity {
  id: ID!
  open: BigInt!
  close: BigInt!
  prize: BigInt!
  players: [PlayerToLottery!]! @derivedFrom(field: "lottery")
  requestId: BigInt
  updatedAtTimestamp: BigInt
}

type Player @entity {
  id: ID! # address
  lotteries: [PlayerToLottery!]! @derivedFrom(field: "player")
}

type PlayerToLottery @entity {
  id: ID! # Set playerAddress.toHexString() + lotteryId.toHexString()
  player: Player!
  lottery: NftLottery!
}

# events
type NftLotteryEnter @entity {
  id: ID! # Set lotteryCounter + playerAddress
  lotteryCounter: BigInt!
  player: Bytes!
  numberOfEntrances: [BigInt!]!
  updatedAtTimestamp: BigInt
}

./src/mapping.ts

function getIdFromEventAddressInt(par2: Address, par1: BigInt): string {
  return par1.toHexString() + par2.toHexString()
}

export function handleNftLotteryEnter(event: NftLotteryEnterEvent): void {
  /* if a PlayerToLottery does not exists, create it */
  const playerLotteryId = getIdFromEventAddressInt(event.params.player, event.params.lotteryCounter)
  let playerToLottery = PlayerToLottery.load(playerLotteryId)
  if (!playerToLottery) {
      playerToLottery = new PlayerToLottery(playerLotteryId)
      playerToLottery.save()
    }

  /* if a player does not exist, create them */
  const playerId = event.params.player.toHexString()
  let player = Player.load(playerId)
  if (!player) {
    player = new Player(playerId)
    player.save()
  }

  /* if a lottery does not exist, create it */ 
  const itemId = event.params.lotteryCounter.toHexString()
  let nftLottery = NftLottery.load(itemId)
  if (!nftLottery) {
    nftLottery = new NftLottery(itemId)
    nftLottery.open = event.block.timestamp
    nftLottery.close = BigInt.fromString("000000000000")
    nftLottery.prize = BigInt.fromString(event.params.entranceFee.toString())
    nftLottery.players = new Array<string>()
  }
  // update lottery data
  nftLottery.prize.plus(event.params.entranceFee)
  // update players
  let arrPlayers = nftLottery.players
  arrPlayers.push(event.params.player.toHexString())
  nftLottery.players = arrPlayers

  nftLottery.updatedAtTimestamp = event.block.timestamp
  nftLottery.save()
}

错误原因及修复方案

核心问题

报错明确指出PlayerToLottery实体缺少非空字段player。原因是创建PlayerToLottery实例后直接调用save(),但未给它的player和lottery字段赋值——这两个字段在schema中被标记为!(非空),必须赋值才能保存。

另外还有两个额外问题需要修正:

  1. nftLottery.prize.plus(...)不会修改原BigInt值(BigInt是不可变类型),必须重新赋值给nftLottery.prize
  2. nftLottery.players是通过@derivedFrom从PlayerToLottery的lottery字段派生的关联,不需要手动维护数组,手动操作反而会和自动派生逻辑冲突

修复后的mapping.ts代码

function getIdFromEventAddressInt(par2: Address, par1: BigInt): string {
  return par1.toHexString() + par2.toHexString()
}

export function handleNftLotteryEnter(event: NftLotteryEnterEvent): void {
  /* 先处理Player和NftLottery实体,确保它们存在 */
  const playerId = event.params.player.toHexString()
  let player = Player.load(playerId)
  if (!player) {
    player = new Player(playerId)
    player.save()
  }

  const itemId = event.params.lotteryCounter.toHexString()
  let nftLottery = NftLottery.load(itemId)
  if (!nftLottery) {
    nftLottery = new NftLottery(itemId)
    nftLottery.open = event.block.timestamp
    nftLottery.close = BigInt.fromString("000000000000")
    nftLottery.prize = BigInt.fromString(event.params.entranceFee.toString())
  } else {
    // 修正BigInt赋值问题:重新赋值prize
    nftLottery.prize = nftLottery.prize.plus(event.params.entranceFee)
  }

  /* 处理PlayerToLottery关联实体 */
  const playerLotteryId = getIdFromEventAddressInt(event.params.player, event.params.lotteryCounter)
  let playerToLottery = PlayerToLottery.load(playerLotteryId)
  if (!playerToLottery) {
    playerToLottery = new PlayerToLottery(playerLotteryId)
    // 必须赋值非空字段player和lottery
    playerToLottery.player = player
    playerToLottery.lottery = nftLottery
    playerToLottery.save()
  }

  nftLottery.updatedAtTimestamp = event.block.timestamp
  nftLottery.save()
}

修复说明

  1. 调整顺序:先创建/加载Player和NftLottery,确保后续创建PlayerToLottery时可以直接关联
  2. 给PlayerToLottery实例赋值player和lottery字段后再调用save()
  3. 修正prize字段的更新逻辑:重新赋值nftLottery.prize
  4. 删除手动维护nftLottery.players数组的代码,让@derivedFrom自动处理关联关系

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 17:27:30