Skip to content

Commit 4044d46

Browse files
author
abstrct
committed
Fixed the wrong PlayerCache object lookup in Proxy Join. Expanded test coverage to deal with failures better.
1 parent b85fcb3 commit 4044d46

17 files changed

Lines changed: 693 additions & 269 deletions

tests/test_chain.sh

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,25 +1970,28 @@ section "PHASE 4e: Permission System"
19701970
# SourceAllocation=256, GuildMembership=512, SubstationConnection=1024, AllocationConnection=2048
19711971

19721972
# ─── permission-grant-on-object: grant Player 5 Grid permission on substation ───
1973+
VAL_BEFORE_GRANT=$(get_permission_value_for_player "${SUBSTATION_ID}" "${PLAYER_5_ID}")
19731974
run_tx "Granting Player 5 Grid permission (32) on substation ${SUBSTATION_ID}" \
19741975
tx structs permission-grant-on-object "${SUBSTATION_ID}" "${PLAYER_5_ID}" 32 --from alice
19751976

1976-
PERM_JSON=$(query query structs permission-by-object "${SUBSTATION_ID}" 2>/dev/null || echo '{}')
1977-
info "Permissions on substation after grant:"
1978-
echo "${PERM_JSON}" | jq -r '.permissionRecord[]? | " player=\(.objectId) val=\(.value)"' 2>/dev/null || echo " (no records)"
1977+
VAL_AFTER_GRANT=$(get_permission_value_for_player "${SUBSTATION_ID}" "${PLAYER_5_ID}")
1978+
EXPECTED_AFTER_GRANT=$(( VAL_BEFORE_GRANT | 32 ))
1979+
assert_eq "Permission on substation after grant includes Grid" "${EXPECTED_AFTER_GRANT}" "${VAL_AFTER_GRANT}"
19791980

19801981
# ─── permission-revoke-on-object ───
19811982
run_tx "Revoking Player 5 Grid permission on substation" \
19821983
tx structs permission-revoke-on-object "${SUBSTATION_ID}" "${PLAYER_5_ID}" 32 --from alice
19831984

1985+
VAL_AFTER_REVOKE=$(get_permission_value_for_player "${SUBSTATION_ID}" "${PLAYER_5_ID}")
1986+
EXPECTED_AFTER_REVOKE=$(( EXPECTED_AFTER_GRANT & ~32 ))
1987+
assert_eq "Permission on substation after revoke removes Grid" "${EXPECTED_AFTER_REVOKE}" "${VAL_AFTER_REVOKE}"
1988+
19841989
# ─── permission-set-on-object: set a specific permission set ───
19851990
run_tx "Setting Player 5 permissions on substation to Assets+Grid (40)" \
19861991
tx structs permission-set-on-object "${SUBSTATION_ID}" "${PLAYER_5_ID}" 40 --from alice
19871992

1988-
# Verify with query
1989-
PERM_JSON=$(query query structs permission-by-object "${SUBSTATION_ID}" 2>/dev/null || echo '{}')
1990-
info "Permissions on substation after set:"
1991-
echo "${PERM_JSON}" | jq -r '.permissionRecord[]? | " player=\(.objectId) val=\(.value)"' 2>/dev/null || echo " (no records)"
1993+
VAL_AFTER_SET=$(get_permission_value_for_player "${SUBSTATION_ID}" "${PLAYER_5_ID}")
1994+
assert_eq "Permission on substation after set (40)" "40" "${VAL_AFTER_SET}"
19921995

19931996
# Clean up: revoke all from Player 5
19941997
run_tx "Clearing Player 5 permissions on substation" \
@@ -2635,6 +2638,9 @@ run_tx "Granting Player 5 PermSubstationConnection on substation for connection
26352638
run_tx "Connecting Player 5 allocation to substation" \
26362639
tx structs substation-allocation-connect "${P5_ALLOC_ID}" "${SUBSTATION_ID}" --from player_5
26372640

2641+
ALLOC_JSON=$(query query structs allocation "${P5_ALLOC_ID}")
2642+
assert_eq "Allocation connected to substation" "${SUBSTATION_ID}" "$(jqr "${ALLOC_JSON}" '.Allocation.destinationId' '')"
2643+
26382644
# ─── substation-allocation-disconnect ───
26392645
run_tx "Disconnecting Player 5 allocation from substation" \
26402646
tx structs substation-allocation-disconnect "${P5_ALLOC_ID}" --from player_5
@@ -2647,6 +2653,9 @@ assert_eq "Allocation disconnected" "" "${ALLOC_DST}"
26472653
run_tx "Reconnecting Player 5 allocation" \
26482654
tx structs substation-allocation-connect "${P5_ALLOC_ID}" "${SUBSTATION_ID}" --from player_5
26492655

2656+
ALLOC_JSON=$(query query structs allocation "${P5_ALLOC_ID}")
2657+
assert_eq "Allocation reconnected to substation" "${SUBSTATION_ID}" "$(jqr "${ALLOC_JSON}" '.Allocation.destinationId' '')"
2658+
26502659
# ─── Dual-path disconnect: substation owner disconnects player's allocation ───
26512660
# CanBeDisconnectedBy checks PermAllocationConnection on allocation first,
26522661
# then falls back to PermAllocationConnection on destination (substation).
@@ -2847,6 +2856,11 @@ run_tx "Proxy joining guild for external address" \
28472856
0c1623a753074f49bc20c6e8bb9e6572903b90e386598c4baa34e056e468e53076938ec4ab411f5889adb771f63b2be9b15912d5e1e70a97d1b091926181c8ae01 \
28482857
--from alice
28492858

2859+
# Verify proxy join created a player for the target address
2860+
PROXY_JOIN_JSON=$(query query structs address structs1wfs4s5er9lpkxlcrh8ezdqayewjnudkrlwpxqc)
2861+
PROXY_PLAYER=$(jqr "${PROXY_JOIN_JSON}" '.playerId')
2862+
assert_not_empty "Proxy joined player ID" "${PROXY_PLAYER}"
2863+
28502864
fi # phase 5
28512865

28522866
if run_phase 550; then
@@ -3566,17 +3580,17 @@ if [ -n "${STEALTH_BOMBER_ID}" ]; then
35663580
tx structs struct-stealth-activate "${STEALTH_BOMBER_ID}" --from player_3
35673581

35683582
SB_JSON=$(query query structs struct "${STEALTH_BOMBER_ID}" 2>/dev/null || echo '{}')
3569-
SB_HIDDEN=$(jqr "${SB_JSON}" '.Struct.status' '')
3570-
info "Stealth Bomber status after activate: '${SB_HIDDEN}'"
3583+
SB_HIDDEN=$(jqr "${SB_JSON}" '.structAttributes.isHidden' 'false')
3584+
assert_eq "Stealth Bomber hidden after activate" "true" "${SB_HIDDEN}"
35713585

35723586
# ─── struct-stealth-deactivate ───
35733587
wait_for_charge "${PLAYER_3_ID}" "${CHARGE_ACTIVATE}"
35743588
run_tx "Deactivating stealth on Stealth Bomber" \
35753589
tx structs struct-stealth-deactivate "${STEALTH_BOMBER_ID}" --from player_3
35763590

35773591
SB_JSON=$(query query structs struct "${STEALTH_BOMBER_ID}" 2>/dev/null || echo '{}')
3578-
SB_HIDDEN_AFTER=$(jqr "${SB_JSON}" '.Struct.status' '')
3579-
info "Stealth Bomber status after deactivate: '${SB_HIDDEN_AFTER}'"
3592+
SB_HIDDEN_AFTER=$(jqr "${SB_JSON}" '.structAttributes.isHidden' 'false')
3593+
assert_eq "Stealth Bomber visible after deactivate" "false" "${SB_HIDDEN_AFTER}"
35803594

35813595
# struct-attribute query coverage
35823596
info "Struct attribute query coverage:"

testutil/keeper/structs.go

Lines changed: 186 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,40 @@ func (m *MockBankKeeper) BurnCoins(ctx context.Context, moduleName string, amt s
183183
return nil
184184
}
185185

186-
// MockStakingKeeper is a mock implementation of the StakingKeeper interface
187-
type MockStakingKeeper struct{}
186+
// delegationKey uniquely identifies a delegation by delegator+validator.
187+
type delegationKey struct {
188+
Delegator string
189+
Validator string
190+
}
191+
192+
// MockStakingKeeper is a stateful mock of the StakingKeeper interface.
193+
// It tracks validators, delegations, and unbonding delegations so that
194+
// reactor infuse/defuse/migrate/cancel tests can exercise the full handler path.
195+
type MockStakingKeeper struct {
196+
validators map[string]stakingtypes.Validator
197+
delegations map[delegationKey]stakingtypes.Delegation
198+
unbondings map[delegationKey]stakingtypes.UnbondingDelegation
199+
nextUnbondID uint64
200+
}
188201

189202
func NewMockStakingKeeper() *MockStakingKeeper {
190-
return &MockStakingKeeper{}
203+
return &MockStakingKeeper{
204+
validators: make(map[string]stakingtypes.Validator),
205+
delegations: make(map[delegationKey]stakingtypes.Delegation),
206+
unbondings: make(map[delegationKey]stakingtypes.UnbondingDelegation),
207+
nextUnbondID: 1,
208+
}
209+
}
210+
211+
// AddValidator registers a bonded validator with 1:1 token-to-share ratio.
212+
func (m *MockStakingKeeper) AddValidator(operatorAddr sdk.ValAddress, tokens math.Int) {
213+
val := stakingtypes.Validator{
214+
OperatorAddress: operatorAddr.String(),
215+
Status: stakingtypes.Bonded,
216+
Tokens: tokens,
217+
DelegatorShares: math.LegacyNewDecFromInt(tokens),
218+
}
219+
m.validators[operatorAddr.String()] = val
191220
}
192221

193222
func (m *MockStakingKeeper) ConsensusAddressCodec() address.Codec {
@@ -199,70 +228,212 @@ func (m *MockStakingKeeper) ValidatorByConsAddr(ctx context.Context, consAddr sd
199228
}
200229

201230
func (m *MockStakingKeeper) GetValidator(ctx context.Context, addr sdk.ValAddress) (stakingtypes.Validator, error) {
202-
return stakingtypes.Validator{}, nil
231+
val, ok := m.validators[addr.String()]
232+
if !ok {
233+
return stakingtypes.Validator{}, stakingtypes.ErrNoValidatorFound
234+
}
235+
return val, nil
203236
}
204237

205238
func (m *MockStakingKeeper) GetAllValidators(ctx context.Context) ([]stakingtypes.Validator, error) {
206-
return nil, nil
239+
vals := make([]stakingtypes.Validator, 0, len(m.validators))
240+
for _, v := range m.validators {
241+
vals = append(vals, v)
242+
}
243+
return vals, nil
207244
}
208245

209246
func (m *MockStakingKeeper) GetValidators(ctx context.Context, maxRetrieve uint32) ([]stakingtypes.Validator, error) {
210-
return nil, nil
247+
return m.GetAllValidators(ctx)
211248
}
212249

213250
func (m *MockStakingKeeper) GetValidatorDelegations(ctx context.Context, valAddr sdk.ValAddress) ([]stakingtypes.Delegation, error) {
214-
return nil, nil
251+
var result []stakingtypes.Delegation
252+
for k, d := range m.delegations {
253+
if k.Validator == valAddr.String() {
254+
result = append(result, d)
255+
}
256+
}
257+
return result, nil
215258
}
216259

217260
func (m *MockStakingKeeper) GetDelegation(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (stakingtypes.Delegation, error) {
218-
return stakingtypes.Delegation{}, nil
261+
dk := delegationKey{Delegator: delAddr.String(), Validator: valAddr.String()}
262+
del, ok := m.delegations[dk]
263+
if !ok {
264+
return stakingtypes.Delegation{}, stakingtypes.ErrNoDelegation
265+
}
266+
return del, nil
219267
}
220268

221269
func (m *MockStakingKeeper) GetUnbondingDelegation(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (stakingtypes.UnbondingDelegation, error) {
222-
return stakingtypes.UnbondingDelegation{}, nil
270+
dk := delegationKey{Delegator: delAddr.String(), Validator: valAddr.String()}
271+
ubd, ok := m.unbondings[dk]
272+
if !ok {
273+
return stakingtypes.UnbondingDelegation{}, stakingtypes.ErrNoUnbondingDelegation
274+
}
275+
return ubd, nil
223276
}
224277

225278
func (m *MockStakingKeeper) GetUnbondingDelegationByUnbondingID(ctx context.Context, id uint64) (stakingtypes.UnbondingDelegation, error) {
226-
return stakingtypes.UnbondingDelegation{}, nil
279+
for _, ubd := range m.unbondings {
280+
for _, entry := range ubd.Entries {
281+
if entry.UnbondingId == id {
282+
return ubd, nil
283+
}
284+
}
285+
}
286+
return stakingtypes.UnbondingDelegation{}, stakingtypes.ErrNoUnbondingDelegation
227287
}
228288

229289
func (m *MockStakingKeeper) GetDelegatorDelegations(ctx context.Context, delegator sdk.AccAddress, maxRetrieve uint16) ([]stakingtypes.Delegation, error) {
230-
return nil, nil
290+
var result []stakingtypes.Delegation
291+
for k, d := range m.delegations {
292+
if k.Delegator == delegator.String() {
293+
result = append(result, d)
294+
if uint16(len(result)) >= maxRetrieve {
295+
break
296+
}
297+
}
298+
}
299+
return result, nil
231300
}
232301

233302
func (m *MockStakingKeeper) SetDelegation(ctx context.Context, delegation stakingtypes.Delegation) error {
303+
dk := delegationKey{Delegator: delegation.DelegatorAddress, Validator: delegation.ValidatorAddress}
304+
m.delegations[dk] = delegation
234305
return nil
235306
}
236307

237308
func (m *MockStakingKeeper) RemoveDelegation(ctx context.Context, delegation stakingtypes.Delegation) error {
309+
dk := delegationKey{Delegator: delegation.DelegatorAddress, Validator: delegation.ValidatorAddress}
310+
delete(m.delegations, dk)
238311
return nil
239312
}
240313

314+
// ValidateUnbondAmount returns the shares corresponding to amt using a 1:1 ratio.
315+
// Returns an error if no delegation exists or the amount exceeds delegated shares.
241316
func (m *MockStakingKeeper) ValidateUnbondAmount(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt math.Int) (math.LegacyDec, error) {
242-
return math.LegacyZeroDec(), nil
317+
dk := delegationKey{Delegator: delAddr.String(), Validator: valAddr.String()}
318+
del, ok := m.delegations[dk]
319+
if !ok {
320+
return math.LegacyZeroDec(), stakingtypes.ErrNoDelegation
321+
}
322+
shares := math.LegacyNewDecFromInt(amt)
323+
if shares.GT(del.Shares) {
324+
return math.LegacyZeroDec(), stakingtypes.ErrNotEnoughDelegationShares
325+
}
326+
return shares, nil
243327
}
244328

245329
func (m *MockStakingKeeper) BeginRedelegation(ctx context.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount math.LegacyDec) (time.Time, error) {
246-
return time.Now(), nil
330+
srcKey := delegationKey{Delegator: delAddr.String(), Validator: valSrcAddr.String()}
331+
srcDel, ok := m.delegations[srcKey]
332+
if !ok {
333+
return time.Time{}, stakingtypes.ErrNoDelegation
334+
}
335+
if sharesAmount.GT(srcDel.Shares) {
336+
return time.Time{}, stakingtypes.ErrNotEnoughDelegationShares
337+
}
338+
srcDel.Shares = srcDel.Shares.Sub(sharesAmount)
339+
if srcDel.Shares.IsZero() {
340+
delete(m.delegations, srcKey)
341+
} else {
342+
m.delegations[srcKey] = srcDel
343+
}
344+
dstKey := delegationKey{Delegator: delAddr.String(), Validator: valDstAddr.String()}
345+
dstDel, exists := m.delegations[dstKey]
346+
if !exists {
347+
dstDel = stakingtypes.Delegation{
348+
DelegatorAddress: delAddr.String(),
349+
ValidatorAddress: valDstAddr.String(),
350+
Shares: math.LegacyZeroDec(),
351+
}
352+
}
353+
dstDel.Shares = dstDel.Shares.Add(sharesAmount)
354+
m.delegations[dstKey] = dstDel
355+
return time.Now().Add(21 * 24 * time.Hour), nil
247356
}
248357

249358
func (m *MockStakingKeeper) BondDenom(ctx context.Context) (string, error) {
250359
return "stake", nil
251360
}
252361

362+
// Delegate creates or adds to a delegation using a 1:1 token-to-share ratio.
253363
func (m *MockStakingKeeper) Delegate(ctx context.Context, delAddr sdk.AccAddress, bondAmt math.Int, tokenSrc stakingtypes.BondStatus, validator stakingtypes.Validator, subtractAccount bool) (math.LegacyDec, error) {
254-
return math.LegacyZeroDec(), nil
364+
dk := delegationKey{Delegator: delAddr.String(), Validator: validator.OperatorAddress}
365+
del, exists := m.delegations[dk]
366+
if !exists {
367+
del = stakingtypes.Delegation{
368+
DelegatorAddress: delAddr.String(),
369+
ValidatorAddress: validator.OperatorAddress,
370+
Shares: math.LegacyZeroDec(),
371+
}
372+
}
373+
newShares := math.LegacyNewDecFromInt(bondAmt)
374+
del.Shares = del.Shares.Add(newShares)
375+
m.delegations[dk] = del
376+
377+
if val, ok := m.validators[validator.OperatorAddress]; ok {
378+
val.Tokens = val.Tokens.Add(bondAmt)
379+
val.DelegatorShares = val.DelegatorShares.Add(newShares)
380+
m.validators[validator.OperatorAddress] = val
381+
}
382+
return newShares, nil
255383
}
256384

385+
// Undelegate removes shares from a delegation and creates an unbonding entry.
257386
func (m *MockStakingKeeper) Undelegate(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount math.LegacyDec) (time.Time, math.Int, error) {
258-
return time.Now(), math.ZeroInt(), nil
387+
dk := delegationKey{Delegator: delAddr.String(), Validator: valAddr.String()}
388+
del, ok := m.delegations[dk]
389+
if !ok {
390+
return time.Time{}, math.ZeroInt(), stakingtypes.ErrNoDelegation
391+
}
392+
if sharesAmount.GT(del.Shares) {
393+
return time.Time{}, math.ZeroInt(), stakingtypes.ErrNotEnoughDelegationShares
394+
}
395+
del.Shares = del.Shares.Sub(sharesAmount)
396+
if del.Shares.IsZero() {
397+
delete(m.delegations, dk)
398+
} else {
399+
m.delegations[dk] = del
400+
}
401+
402+
returnAmount := sharesAmount.TruncateInt()
403+
sdkCtx := sdk.UnwrapSDKContext(ctx)
404+
completionTime := sdkCtx.BlockTime().Add(21 * 24 * time.Hour)
405+
406+
ubd, exists := m.unbondings[dk]
407+
if !exists {
408+
ubd = stakingtypes.UnbondingDelegation{
409+
DelegatorAddress: delAddr.String(),
410+
ValidatorAddress: valAddr.String(),
411+
}
412+
}
413+
id := m.nextUnbondID
414+
m.nextUnbondID++
415+
ubd.Entries = append(ubd.Entries, stakingtypes.UnbondingDelegationEntry{
416+
CreationHeight: sdkCtx.BlockHeight(),
417+
CompletionTime: completionTime,
418+
InitialBalance: returnAmount,
419+
Balance: returnAmount,
420+
UnbondingId: id,
421+
UnbondingOnHoldRefCount: 0,
422+
})
423+
m.unbondings[dk] = ubd
424+
425+
return completionTime, returnAmount, nil
259426
}
260427

261428
func (m *MockStakingKeeper) RemoveUnbondingDelegation(ctx context.Context, ubd stakingtypes.UnbondingDelegation) error {
429+
dk := delegationKey{Delegator: ubd.DelegatorAddress, Validator: ubd.ValidatorAddress}
430+
delete(m.unbondings, dk)
262431
return nil
263432
}
264433

265434
func (m *MockStakingKeeper) SetUnbondingDelegation(ctx context.Context, ubd stakingtypes.UnbondingDelegation) error {
435+
dk := delegationKey{Delegator: ubd.DelegatorAddress, Validator: ubd.ValidatorAddress}
436+
m.unbondings[dk] = ubd
266437
return nil
267438
}
268439

x/structs/keeper/keeper.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ func (k Keeper) BankKeeper() types.BankKeeper {
114114
return k.bankKeeper
115115
}
116116

117+
// StakingKeeper returns the staking keeper
118+
func (k Keeper) StakingKeeper() types.StakingKeeper {
119+
return k.stakingKeeper
120+
}
121+
117122
// AccountKeeper returns the account keeper
118123
func (k Keeper) AccountKeeper() types.AccountKeeper {
119124
return k.accountKeeper

x/structs/keeper/msg_server_guild_membership_join_proxy.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ func (k msgServer) GuildMembershipJoinProxy(goCtx context.Context, msg *types.Ms
2323
k.AddressEmitActivity(ctx, msg.Creator)
2424

2525
// Look up requesting account
26-
proxyPlayer := cc.UpsertPlayer(msg.Creator)
26+
proxyPlayer, err := cc.GetPlayerByAddress(msg.Creator)
27+
if err != nil {
28+
return emptyResponse, err
29+
}
2730

2831
// look up destination guild
2932
guild := cc.GetGuild(proxyPlayer.GetGuildId())

0 commit comments

Comments
 (0)