@@ -4192,4 +4192,228 @@ TEST(ContractQraffle, AssetRaffle_MultipleEpochs_PerCreatorCounterReset)
41924192 makeBundle (t201, 100 ));
41934193 EXPECT_EQ (r.returnCode , QRAFFLE_SUCCESS ) << " epoch 201 raffle " << i;
41944194 }
4195- }
4195+ }
4196+
4197+ // ── TEST: MIGRATE_PreservesAllOldFieldsAndZeroesNew ───────────────────────────
4198+ // Direct unit test for QRAFFLE::__impl_migrate. Builds a populated OldStateData
4199+ // (every preserved field gets a unique sentinel value), zeroes the new state
4200+ // buffer to mimic what core does before invoking MIGRATE, calls the migration,
4201+ // then asserts:
4202+ // 1. every preserved scalar / Array / HashMap / HashSet field round-trips,
4203+ // 2. shareholdersList content is intentionally dropped,
4204+ // 3. every new asset-raffle field is zero (i.e., MIGRATE didn't touch them).
4205+ //
4206+ // MIGRATE state buffers are large (multi-MB); both old and new state are heap-
4207+ // allocated to avoid blowing the test stack.
4208+ TEST (ContractQraffle, MIGRATE_PreservesAllOldFieldsAndZeroesNew)
4209+ {
4210+ ContractTestingQraffle qraffle; // sets up contractStates[QRAFFLE_CONTRACT_INDEX] and runs INITIALIZE
4211+
4212+ // ── Build a populated OldStateData on the heap. ───────────────────────────
4213+ auto * oldStatePtr = (QRAFFLE ::OldStateData*)malloc (sizeof (QRAFFLE ::OldStateData));
4214+ ASSERT_NE (oldStatePtr, nullptr );
4215+ setMem (oldStatePtr, sizeof (QRAFFLE ::OldStateData), 0 );
4216+ QRAFFLE ::OldStateData& oldState = *oldStatePtr;
4217+
4218+ // Sentinel ids — distinct so any mis-routing in MIGRATE produces a visible mismatch.
4219+ const id senReg1 = id (0x1001 , 0x1002 , 0x1003 , 0x1004 );
4220+ const id senReg2 = id (0x2001 , 0x2002 , 0x2003 , 0x2004 );
4221+ const id senReg3 = id (0x3001 , 0x3002 , 0x3003 , 0x3004 );
4222+ const id senReg4 = id (0x4001 , 0x4002 , 0x4003 , 0x4004 );
4223+ const id senReg5 = id (0x5001 , 0x5002 , 0x5003 , 0x5004 );
4224+ const id senCharity = id (0xC001 , 0xC002 , 0xC003 , 0xC004 );
4225+ const id senFee = id (0xF001 , 0xF002 , 0xF003 , 0xF004 );
4226+ const id senIssuer = id (0xA001 , 0xA002 , 0xA003 , 0xA004 );
4227+ const id senVoter = id (0xB001 , 0xB002 , 0xB003 , 0xB004 );
4228+ const id senQuMember = id (0xD001 , 0xD002 , 0xD003 , 0xD004 );
4229+ const id senTokenMem = id (0xE001 , 0xE002 , 0xE003 , 0xE004 );
4230+ const id senProposer = id (0x9001 , 0x9002 , 0x9003 , 0x9004 );
4231+ const id senQuEntryUser = id (0x8001 , 0x8002 , 0x8003 , 0x8004 );
4232+ const id senDeadField = id (0xDEAD , 0xDEAD , 0xDEAD , 0xDEAD ); // for shareholdersList — must NOT migrate
4233+
4234+ // Scalars: assign uniquely-recognizable values.
4235+ oldState.initialRegister1 = senReg1;
4236+ oldState.initialRegister2 = senReg2;
4237+ oldState.initialRegister3 = senReg3;
4238+ oldState.initialRegister4 = senReg4;
4239+ oldState.initialRegister5 = senReg5;
4240+ oldState.charityAddress = senCharity;
4241+ oldState.feeAddress = senFee;
4242+ oldState.QXMRIssuer = senIssuer;
4243+ oldState.epochRevenue = 1'000'001ull ;
4244+ oldState.epochQXMRRevenue = 1'000'002ull ;
4245+ oldState.qREAmount = 1'000'003ull ;
4246+ oldState.totalBurnAmount = 1'000'004ull ;
4247+ oldState.totalCharityAmount = 1'000'005ull ;
4248+ oldState.totalShareholderAmount = 1'000'006ull ;
4249+ oldState.totalRegisterAmount = 1'000'007ull ;
4250+ oldState.totalFeeAmount = 1'000'008ull ;
4251+ oldState.totalWinnerAmount = 1'000'009ull ;
4252+ oldState.largestWinnerAmount = 1'000'010ull ;
4253+ oldState.numberOfRegisters = 7u ;
4254+ oldState.numberOfQuRaffleMembers = 8u ;
4255+ oldState.numberOfEntryAmountSubmitted = 9u ;
4256+ oldState.numberOfProposals = 10u ;
4257+ oldState.numberOfActiveTokenRaffle = 11u ;
4258+ oldState.numberOfEndedTokenRaffle = 12u ;
4259+
4260+ // HashMaps / HashSets: one sentinel entry each.
4261+ oldState.registers .set (senReg1, /* tokenType=*/ 1u );
4262+ oldState.quRaffleMemberSet .add (senQuMember);
4263+ oldState.quRaffleEntryAmount .set (senQuEntryUser, 42'424'242ull );
4264+ oldState.proposalsPerProposer .set (senProposer, 3u );
4265+
4266+ QPI ::BitArray<QRAFFLE_MAX_PROPOSAL_EPOCH > participation; participation.setAll (0 ); participation.set (2 , 1 );
4267+ QPI ::BitArray<QRAFFLE_MAX_PROPOSAL_EPOCH > values; values.setAll (0 ); values.set (2 , 1 );
4268+ oldState.voteParticipation .set (senVoter, participation);
4269+ oldState.voteValues .set (senVoter, values);
4270+
4271+ QPI ::BitArray<QRAFFLE_MAX_PROPOSAL_EPOCH > tokenPart; tokenPart.setAll (0 ); tokenPart.set (4 , 1 );
4272+ oldState.tokenRaffleParticipation .set (senTokenMem, tokenPart);
4273+
4274+ // shareholdersList: dropped in MIGRATE — populate to prove we don't accidentally migrate it.
4275+ oldState.shareholdersList .add (senDeadField);
4276+
4277+ // Arrays: one sentinel entry per array.
4278+ QRAFFLE ::ProposalInfo p{};
4279+ p.token .assetName = 0xAA55AAull ; p.token .issuer = senIssuer;
4280+ p.proposer = senProposer; p.entryAmount = 7'654'321ull ; p.nYes = 13u ; p.nNo = 5u ;
4281+ oldState.proposals .set (3 , p);
4282+ oldState.numberOfVotedInProposal .set (3 , 18u );
4283+
4284+ oldState.quRaffleMembers .set (0 , senQuMember);
4285+
4286+ QRAFFLE ::ActiveTokenRaffleInfo ar{};
4287+ ar.token .assetName = 0xBB66BBull ; ar.token .issuer = senIssuer; ar.entryAmount = 9'999ull ;
4288+ oldState.activeTokenRaffle .set (2 , ar);
4289+
4290+ oldState.tokenRaffleMemberSlots .set (0 , senTokenMem);
4291+ oldState.numberOfTokenRaffleMembers .set (2 , 1u );
4292+
4293+ QRAFFLE ::QuRaffleInfo qri{};
4294+ qri.epochWinner = senQuMember; qri.receivedAmount = 11'111'111ull ;
4295+ qri.entryAmount = 100u ; qri.numberOfMembers = 1u ; qri.winnerIndex = 0u ;
4296+ oldState.QuRaffles .set (100 , qri);
4297+
4298+ QRAFFLE ::TokenRaffleInfo tri{};
4299+ tri.epochWinner = senTokenMem; tri.token .assetName = 0xCC77CCull ; tri.token .issuer = senIssuer;
4300+ tri.entryAmount = 50u ; tri.numberOfMembers = 1u ; tri.winnerIndex = 0u ; tri.epoch = 199u ;
4301+ oldState.tokenRaffle .set (7 , tri);
4302+
4303+ oldState.daoMemberCount .set (100 , 7u );
4304+
4305+ // ── Pre-zero the new state buffer (mimics core's setMem before MIGRATE). ──
4306+ setMem (contractStates[QRAFFLE_CONTRACT_INDEX ], sizeof (QRAFFLE ::StateData), 0 );
4307+
4308+ // ── Invoke __impl_migrate directly. ───────────────────────────────────────
4309+ QpiContextUserFunctionCall qpiCtx (QRAFFLE_CONTRACT_INDEX );
4310+ auto & state = *reinterpret_cast <QPI ::ContractState<QRAFFLE ::StateData, QRAFFLE_CONTRACT_INDEX >*>(
4311+ contractStates[QRAFFLE_CONTRACT_INDEX ]);
4312+ QRAFFLE ::MIGRATE_locals locals;
4313+ QRAFFLE::__impl_migrate (qpiCtx, state, oldState, locals);
4314+
4315+ // ── Assert preserved fields round-tripped. ────────────────────────────────
4316+ QRaffleChecker* s = qraffle.getState ();
4317+
4318+ // Scalars
4319+ EXPECT_EQ (s->initialRegister1 , senReg1);
4320+ EXPECT_EQ (s->initialRegister2 , senReg2);
4321+ EXPECT_EQ (s->initialRegister3 , senReg3);
4322+ EXPECT_EQ (s->initialRegister4 , senReg4);
4323+ EXPECT_EQ (s->initialRegister5 , senReg5);
4324+ EXPECT_EQ (s->charityAddress , senCharity);
4325+ EXPECT_EQ (s->feeAddress , senFee);
4326+ EXPECT_EQ (s->QXMRIssuer , senIssuer);
4327+ EXPECT_EQ (s->epochRevenue , 1'000'001ull );
4328+ EXPECT_EQ (s->epochQXMRRevenue , 1'000'002ull );
4329+ EXPECT_EQ (s->qREAmount , 1'000'003ull );
4330+ EXPECT_EQ (s->totalBurnAmount , 1'000'004ull );
4331+ EXPECT_EQ (s->totalCharityAmount , 1'000'005ull );
4332+ EXPECT_EQ (s->totalShareholderAmount , 1'000'006ull );
4333+ EXPECT_EQ (s->totalRegisterAmount , 1'000'007ull );
4334+ EXPECT_EQ (s->totalFeeAmount , 1'000'008ull );
4335+ EXPECT_EQ (s->totalWinnerAmount , 1'000'009ull );
4336+ EXPECT_EQ (s->largestWinnerAmount , 1'000'010ull );
4337+ EXPECT_EQ (s->numberOfRegisters , 7u );
4338+ EXPECT_EQ (s->numberOfQuRaffleMembers , 8u );
4339+ EXPECT_EQ (s->numberOfEntryAmountSubmitted , 9u );
4340+ EXPECT_EQ (s->numberOfProposals , 10u );
4341+ EXPECT_EQ (s->numberOfActiveTokenRaffle , 11u );
4342+ EXPECT_EQ (s->numberOfEndedTokenRaffle , 12u );
4343+
4344+ // HashMaps / HashSets
4345+ EXPECT_TRUE (s->registers .contains (senReg1));
4346+ uint8 regType = 0 ; s->registers .get (senReg1, regType);
4347+ EXPECT_EQ (regType, 1u );
4348+
4349+ EXPECT_TRUE (s->quRaffleMemberSet .contains (senQuMember));
4350+
4351+ EXPECT_TRUE (s->quRaffleEntryAmount .contains (senQuEntryUser));
4352+ uint64 entryAmt = 0 ; s->quRaffleEntryAmount .get (senQuEntryUser, entryAmt);
4353+ EXPECT_EQ (entryAmt, 42'424'242ull );
4354+
4355+ EXPECT_TRUE (s->proposalsPerProposer .contains (senProposer));
4356+ uint8 ppCount = 0 ; s->proposalsPerProposer .get (senProposer, ppCount);
4357+ EXPECT_EQ (ppCount, 3u );
4358+
4359+ QPI ::BitArray<QRAFFLE_MAX_PROPOSAL_EPOCH > partOut; partOut.setAll (0 );
4360+ EXPECT_TRUE (s->voteParticipation .get (senVoter, partOut));
4361+ EXPECT_EQ (partOut.get (2 ), 1 );
4362+
4363+ QPI ::BitArray<QRAFFLE_MAX_PROPOSAL_EPOCH > valOut; valOut.setAll (0 );
4364+ EXPECT_TRUE (s->voteValues .get (senVoter, valOut));
4365+ EXPECT_EQ (valOut.get (2 ), 1 );
4366+
4367+ QPI ::BitArray<QRAFFLE_MAX_PROPOSAL_EPOCH > tokPartOut; tokPartOut.setAll (0 );
4368+ EXPECT_TRUE (s->tokenRaffleParticipation .get (senTokenMem, tokPartOut));
4369+ EXPECT_EQ (tokPartOut.get (4 ), 1 );
4370+
4371+ // Arrays
4372+ EXPECT_EQ (s->proposals .get (3 ).token .assetName , 0xAA55AAull );
4373+ EXPECT_EQ (s->proposals .get (3 ).token .issuer , senIssuer);
4374+ EXPECT_EQ (s->proposals .get (3 ).proposer , senProposer);
4375+ EXPECT_EQ (s->proposals .get (3 ).entryAmount , 7'654'321ull );
4376+ EXPECT_EQ (s->proposals .get (3 ).nYes , 13u );
4377+ EXPECT_EQ (s->proposals .get (3 ).nNo , 5u );
4378+ EXPECT_EQ (s->numberOfVotedInProposal .get (3 ), 18u );
4379+
4380+ EXPECT_EQ (s->quRaffleMembers .get (0 ), senQuMember);
4381+ EXPECT_EQ (s->activeTokenRaffle .get (2 ).token .assetName , 0xBB66BBull );
4382+ EXPECT_EQ (s->activeTokenRaffle .get (2 ).entryAmount , 9'999ull );
4383+ EXPECT_EQ (s->tokenRaffleMemberSlots .get (0 ), senTokenMem);
4384+ EXPECT_EQ (s->numberOfTokenRaffleMembers .get (2 ), 1u );
4385+
4386+ EXPECT_EQ (s->QuRaffles .get (100 ).epochWinner , senQuMember);
4387+ EXPECT_EQ (s->QuRaffles .get (100 ).receivedAmount , 11'111'111ull );
4388+
4389+ EXPECT_EQ (s->tokenRaffle .get (7 ).epochWinner , senTokenMem);
4390+ EXPECT_EQ (s->tokenRaffle .get (7 ).token .assetName , 0xCC77CCull );
4391+ EXPECT_EQ (s->tokenRaffle .get (7 ).epoch , 199u );
4392+
4393+ EXPECT_EQ (s->daoMemberCount .get (100 ), 7u );
4394+
4395+ // ── Assert ALL new asset-raffle fields are zero. ──────────────────────────
4396+ EXPECT_EQ (s->numberOfActiveAssetRaffles , 0u );
4397+ EXPECT_EQ (s->numberOfEndedAssetRaffles , 0u );
4398+ EXPECT_EQ (s->epochAssetRaffleDaoBucket , 0ull );
4399+ EXPECT_EQ (s->totalAssetRaffleProposalFees , 0ull );
4400+ EXPECT_EQ (s->totalAssetRaffleCreatorPaid , 0ull );
4401+ EXPECT_EQ (s->totalAssetRaffleRefunded , 0ull );
4402+ EXPECT_EQ (s->totalAssetRafflesCreated , 0u );
4403+ EXPECT_EQ (s->totalAssetRafflesSucceeded , 0u );
4404+ EXPECT_EQ (s->totalAssetRafflesFailed , 0u );
4405+ // assetRafflesPerCreator and assetRaffleParticipation are HashMaps; "zero" means empty.
4406+ EXPECT_EQ (s->assetRafflesPerCreator .population (), 0u );
4407+ EXPECT_EQ (s->assetRaffleParticipation .population (), 0u );
4408+ EXPECT_EQ (s->assetRaffleBuyerSlotIndex .population (), 0u );
4409+ // Sample one slot from each new Array to confirm zero (full-array equality would be wasteful).
4410+ EXPECT_EQ (s->activeAssetRaffles .get (0 ).creator , id (0 ,0 ,0 ,0 ));
4411+ EXPECT_EQ (s->activeAssetRaffles .get (0 ).reservePriceQu , 0ull );
4412+ EXPECT_EQ (s->activeAssetRaffleItems .get (0 ).numberOfShares , 0 );
4413+ EXPECT_EQ (s->activeAssetRaffleBuyers .get (0 ), id (0 ,0 ,0 ,0 ));
4414+ EXPECT_EQ (s->activeAssetRaffleBuyerTickets .get (0 ), 0u );
4415+ EXPECT_EQ (s->endedAssetRaffles .get (0 ).creator , id (0 ,0 ,0 ,0 ));
4416+ EXPECT_EQ (s->endedAssetRaffles .get (0 ).grossPoolQu , 0ull );
4417+
4418+ free (oldStatePtr);
4419+ }
0 commit comments