Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 43 additions & 32 deletions packages/admin/__tests__/ProtocolGovernance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { type Address } from 'viem';

// Define mocks using vi.hoisted
const mocks = vi.hoisted(() => {
const mockRegistry = {
setSuperPaymaster: vi.fn(),
setStaking: vi.fn(),
adminConfigureRole: vi.fn(),
const mockRegistry = {
setSuperPaymaster: vi.fn(),
setStaking: vi.fn(),
getRoleConfig: vi.fn(),
configureRole: vi.fn(),
transferOwnership: vi.fn(),
SUPER_PAYMASTER: vi.fn(),
GTOKEN_STAKING: vi.fn(),
Expand Down Expand Up @@ -58,7 +59,21 @@ describe('ProtocolGovernance', () => {
// Default Resolves
mocks.mockRegistry.setSuperPaymaster.mockResolvedValue('0xSetSPHash');
mocks.mockRegistry.setStaking.mockResolvedValue('0xSetStakingHash');
mocks.mockRegistry.adminConfigureRole.mockResolvedValue('0xConfigRoleHash');
mocks.mockRegistry.getRoleConfig.mockResolvedValue({
minStake: 0n,
ticketPrice: 0n,
slashThreshold: 0n,
slashBase: 0n,
slashInc: 0n,
slashMax: 0n,
exitFeePercent: 0n,
isActive: true,
minExitFee: 0n,
description: '',
owner: '0x0000000000000000000000000000000000000000',
roleLockDuration: 0n,
});
mocks.mockRegistry.configureRole.mockResolvedValue('0xConfigRoleHash');
mocks.mockRegistry.transferOwnership.mockResolvedValue('0xTransferHash');

// Getters
Expand Down Expand Up @@ -92,39 +107,35 @@ describe('ProtocolGovernance', () => {
});

describe('Role Governance', () => {
it('configureRole should call adminConfigureRole', async () => {
const params = {
roleId: '0xRole' as const, // Cast as specific Hex if checked, strict typing
minStake: 100n,
entryBurn: 50n
};

// Note: In test file we might need exact types matching what viem expects
// but mocks usually accept any args. config.params.roleId needs to match.

const ROLE_ID = '0x1234567890123456789012345678901234567890123456789012345678901234' as const;

it('configureRole should read current config then write merged config', async () => {
const result = await governance.configureRole({
roleId: '0x1234567890123456789012345678901234567890123456789012345678901234',
minStake: 100n
roleId: ROLE_ID,
minStake: 100n,
ticketPrice: 50n,
});

expect(result).toBe('0xConfigRoleHash');
expect(mocks.mockRegistry.adminConfigureRole).toHaveBeenCalledWith(expect.objectContaining({
roleId: '0x1234567890123456789012345678901234567890123456789012345678901234',
minStake: 100n,
entryBurn: 0n // Defaulted in method
expect(mocks.mockRegistry.getRoleConfig).toHaveBeenCalledWith({ roleId: ROLE_ID });
expect(mocks.mockRegistry.configureRole).toHaveBeenCalledWith(expect.objectContaining({
roleId: ROLE_ID,
config: expect.objectContaining({
minStake: 100n,
ticketPrice: 50n,
}),
}));
});

it('configureRole should throw on invalid params', async () => {
// The implementation checks if some params are defined.
// If we pass an empty object (besides required roleId), it might fail if we don't handle logic.
// Let's check implementation behavior:
// "if (params.minStake !== undefined || params.entryBurn !== undefined) { ... }"
// Else throws.

await expect(governance.configureRole({
roleId: '0x1234567890123456789012345678901234567890123456789012345678901234'
})).rejects.toThrow('Invalid parameters');
it('configureRole with no overrides should still call configureRole with unchanged values', async () => {
const result = await governance.configureRole({ roleId: ROLE_ID });

expect(result).toBe('0xConfigRoleHash');
expect(mocks.mockRegistry.getRoleConfig).toHaveBeenCalledWith({ roleId: ROLE_ID });
expect(mocks.mockRegistry.configureRole).toHaveBeenCalledWith(expect.objectContaining({
roleId: ROLE_ID,
config: expect.objectContaining({ minStake: 0n }),
}));
});
});

Expand Down
35 changes: 17 additions & 18 deletions packages/admin/src/ProtocolGovernance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,31 +88,30 @@ export class ProtocolGovernance extends BaseClient {
}

/**
* Configure a Role's parameters (Admin only)
* e.g. Setting minStake for ROLE_PAYMASTER_SUPER
* Configure a Role's parameters (Admin only).
* Reads the current on-chain config first, then merges the provided overrides
* and writes back the full struct via configureRole.
*/
async configureRole(params: {
roleId: Hex;
minStake?: bigint;
entryBurn?: bigint;
ticketPrice?: bigint;
exitFeePercent?: bigint;
minExitFee?: bigint;
}, options?: TransactionOptions): Promise<Hash> {
const registry = registryActions(this.registryAddress);

// Use the admin shortcut if only updating financial params
if (params.minStake !== undefined || params.entryBurn !== undefined) {
return await registry(this.client).adminConfigureRole({
roleId: params.roleId,
minStake: params.minStake || 0n, // Careful: this might overwrite if API expects full set
entryBurn: params.entryBurn || 0n,
exitFeePercent: params.exitFeePercent || 0n,
minExitFee: params.minExitFee || 0n,
account: options?.account
});
}

throw new Error("Invalid parameters for configureRole");
const registry = registryActions(this.registryAddress)(this.client);
const current = await registry.getRoleConfig({ roleId: params.roleId });
return await (registry as any).configureRole({
roleId: params.roleId,
config: {
...current,
...(params.minStake !== undefined && { minStake: params.minStake }),
...(params.ticketPrice !== undefined && { ticketPrice: params.ticketPrice }),
...(params.exitFeePercent !== undefined && { exitFeePercent: params.exitFeePercent }),
...(params.minExitFee !== undefined && { minExitFee: params.minExitFee }),
},
account: options?.account,
});
}

// ===========================================
Expand Down
6 changes: 2 additions & 4 deletions packages/core/__tests__/actions/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ describe('RegistryActions Bulk Coverage', () => {
beforeEach(() => { resetMocks(); p = createMockPublicClient(); w = createMockWalletClient(); });

describe('Role Management (Writes)', () => {
it('adminConfigureRole', async () => { w.writeContract.mockResolvedValue('0x'); await registryActions(ADDR)(w).adminConfigureRole({ roleId: '0x01', minStake: 1n, entryBurn: 1n, exitFeePercent: 1n, minExitFee: 1n, account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('createNewRole', async () => { w.writeContract.mockResolvedValue('0x'); await registryActions(ADDR)(w).createNewRole({ roleId: '0x01', config: {} as any, roleOwner: USER, account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('setRoleLockDuration', async () => { w.writeContract.mockResolvedValue('0x'); await registryActions(ADDR)(w).setRoleLockDuration({ roleId: '0x01', duration: 100n, account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('setRoleOwner', async () => { w.writeContract.mockResolvedValue('0x'); await registryActions(ADDR)(w).setRoleOwner({ roleId: '0x01', newOwner: USER, account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('exitRole', async () => { w.writeContract.mockResolvedValue('0x'); await registryActions(ADDR)(w).exitRole({ roleId: '0x01', account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('syncExitFees - sends tx for non-empty array', async () => { w.writeContract.mockResolvedValue('0x'); await registryActions(ADDR)(w).syncExitFees({ roles: ['0x01' as `0x${string}`], account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('syncExitFees - rejects empty array', async () => { await expect(registryActions(ADDR)(w).syncExitFees({ roles: [], account: USER })).rejects.toThrow('must not be empty'); });
});

describe('Community & Credit (Views/Writes)', () => {
Expand Down Expand Up @@ -57,10 +58,7 @@ describe('RegistryActions Bulk Coverage', () => {
it('creditTierConfig', async () => { p.readContract.mockResolvedValue(100n); expect(await registryActions(ADDR)(p).creditTierConfig({ tierIndex: 0n })).toBe(100n); });
it('levelThresholds', async () => { p.readContract.mockResolvedValue(100n); expect(await registryActions(ADDR)(p).levelThresholds({ levelIndex: 0n })).toBe(100n); });
it('calculateExitFee', async () => { p.readContract.mockResolvedValue(5n); expect(await registryActions(ADDR)(p).calculateExitFee({ roleId: '0x01', amount: 100n })).toBe(5n); });
it('accountToUser', async () => { p.readContract.mockResolvedValue(USER); expect(await registryActions(ADDR)(p).accountToUser({ account: USER })).toBe(USER); });
it('roleOwners', async () => { p.readContract.mockResolvedValue(USER); expect(await registryActions(ADDR)(p).roleOwners({ roleId: '0x01' })).toBe(USER); });
it('roleStakes', async () => { p.readContract.mockResolvedValue(100n); expect(await registryActions(ADDR)(p).roleStakes({ roleId: '0x01', user: USER })).toBe(100n); });
it('roleLockDurations', async () => { p.readContract.mockResolvedValue(100n); expect(await registryActions(ADDR)(p).roleLockDurations({ roleId: '0x01' })).toBe(100n); });
it('roleMetadata', async () => { p.readContract.mockResolvedValue('0x'); expect(await registryActions(ADDR)(p).roleMetadata({ roleId: '0x01', user: USER })).toBe('0x'); });
});

Expand Down
2 changes: 1 addition & 1 deletion packages/core/__tests__/actions/staking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('StakingActions Bulk Coverage', () => {
beforeEach(() => { resetMocks(); p = createMockPublicClient(); w = createMockWalletClient(); });

describe('Staking Operations', () => {
it('lockStake', async () => { w.writeContract.mockResolvedValue('0x'); await stakingActions(ADDR)(w).lockStake({ user: USER, roleId: '0x01', stakeAmount: 100n, entryBurn: 10n, payer: USER, account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('lockStake', async () => { w.writeContract.mockResolvedValue('0x'); await stakingActions(ADDR)(w).lockStake({ user: USER, roleId: '0x01', stakeAmount: 100n, ticketPrice: 10n, payer: USER, account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('topUpStake', async () => { w.writeContract.mockResolvedValue('0x'); await stakingActions(ADDR)(w).topUpStake({ user: USER, roleId: '0x01', stakeAmount: 100n, payer: USER, account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('unlockStake', async () => { w.writeContract.mockResolvedValue('0x'); await stakingActions(ADDR)(w).unlockStake({ user: USER, roleId: '0x01', account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
it('slash', async () => { w.writeContract.mockResolvedValue('0x'); await stakingActions(ADDR)(w).slash({ user: USER, amount: 100n, reason: 'test', account: USER }); expect(w.writeContract).toHaveBeenCalled(); });
Expand Down
Loading