Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PE-7211: Invariance integration testing #242

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
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
44 changes: 35 additions & 9 deletions tests/gar.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
delegateStake,
decreaseOperatorStake,
} from './helpers.mjs';
import { describe, it, before } from 'node:test';
import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert';
import {
STUB_TIMESTAMP,
Expand All @@ -29,6 +29,7 @@ import {
INITIAL_OPERATOR_STAKE,
INITIAL_DELEGATE_STAKE,
} from '../tools/constants.mjs';
import { assertNoInvariants } from './invariants.mjs';

const delegatorAddress = 'delegator-address-'.padEnd(43, 'x');

Expand All @@ -40,15 +41,22 @@ describe('GatewayRegistry', async () => {

let sharedMemory = startMemory; // memory we'll use across unique tests;

before(async () => {
beforeEach(async () => {
const { memory: joinNetworkMemory } = await joinNetwork({
address: STUB_ADDRESS,
memory: sharedMemory,
memory: startMemory,
});
// NOTE: all tests will start with this gateway joined to the network - use `sharedMemory` for the first interaction for each test to avoid having to join the network again
sharedMemory = joinNetworkMemory;
});

afterEach(async () => {
await assertNoInvariants({
timestamp: STUB_TIMESTAMP,
memory: sharedMemory,
});
});

describe('Join-Network', () => {
it('should allow joining of the network record', async () => {
// check the gateway record from contract
Expand Down Expand Up @@ -210,7 +218,7 @@ describe('GatewayRegistry', async () => {

it('should allow joining of the network with an allow list', async () => {
const otherGatewayAddress = ''.padEnd(43, '3');
const updatedMemory = await allowlistJoinTest({
sharedMemory = await allowlistJoinTest({
gatewayAddress: otherGatewayAddress,
tags: [
{ name: 'Allow-Delegated-Staking', value: 'allowlist' },
Expand All @@ -229,7 +237,7 @@ describe('GatewayRegistry', async () => {
});

const delegateItems = await getDelegatesItems({
memory: updatedMemory,
memory: sharedMemory,
gatewayAddress: otherGatewayAddress,
});
assert.deepStrictEqual(
Expand All @@ -244,7 +252,7 @@ describe('GatewayRegistry', async () => {
);

const { result: getAllowedDelegatesResult } = await getAllowedDelegates({
memory: updatedMemory,
memory: sharedMemory,
from: STUB_ADDRESS,
timestamp: STUB_TIMESTAMP,
gatewayAddress: otherGatewayAddress,
Expand Down Expand Up @@ -342,6 +350,8 @@ describe('GatewayRegistry', async () => {
},
],
);

sharedMemory = leaveNetworkMemory;
});
});

Expand Down Expand Up @@ -444,7 +454,7 @@ describe('GatewayRegistry', async () => {
}

it('should allow updating the gateway settings', async () => {
await updateGatewaySettingsTest({
sharedMemory = await updateGatewaySettingsTest({
settingsTags: [
{ name: 'Label', value: 'new-label' },
{ name: 'Note', value: 'new-note' },
Expand Down Expand Up @@ -494,7 +504,7 @@ describe('GatewayRegistry', async () => {
expectedAllowedDelegates: [STUB_ADDRESS_9], // probs empty
});

await updateGatewaySettingsTest({
sharedMemory = await updateGatewaySettingsTest({
settingsTags: [
{ name: 'Allow-Delegated-Staking', value: 'false' },
{ name: 'Allowed-Delegates', value: STUB_ADDRESS_9 },
Expand Down Expand Up @@ -573,7 +583,7 @@ describe('GatewayRegistry', async () => {
JSON.parse(delegationsResult.Messages[0].Data).items,
);

await updateGatewaySettingsTest({
sharedMemory = await updateGatewaySettingsTest({
inputMemory: updatedMemory,
settingsTags: [{ name: 'Allow-Delegated-Staking', value: 'false' }],
expectedUpdatedGatewayProps: {
Expand Down Expand Up @@ -640,6 +650,7 @@ describe('GatewayRegistry', async () => {
sortOrder: 'desc',
},
);
sharedMemory = updatedMemory;
});
});

Expand All @@ -666,6 +677,7 @@ describe('GatewayRegistry', async () => {
...gatewayBefore,
operatorStake: INITIAL_OPERATOR_STAKE + increaseQty, // matches the initial operator stake from the test setup plus the increase
});
sharedMemory = increaseStakeMemory;
});
});

Expand Down Expand Up @@ -722,6 +734,7 @@ describe('GatewayRegistry', async () => {
},
],
);
sharedMemory = decreaseStakeMemory;
});

it('should not allow decreasing the operator stake if below the minimum withdrawal', async () => {
Expand Down Expand Up @@ -752,6 +765,7 @@ describe('GatewayRegistry', async () => {
),
'Error tag should be present',
);
sharedMemory = decreaseOperatorStakeResult.Memory;
});

it('should allow decreasing the operator stake instantly, for a fee', async () => {
Expand Down Expand Up @@ -832,6 +846,7 @@ describe('GatewayRegistry', async () => {
balancesBefore[STUB_ADDRESS] + amountWithdrawn;
assert.equal(balancesAfter[PROCESS_ID], expectedProtocolBalance);
assert.equal(balancesAfter[STUB_ADDRESS], expectedOperatorBalance);
sharedMemory = decreaseInstantMemory;
});
});

Expand Down Expand Up @@ -874,6 +889,7 @@ describe('GatewayRegistry', async () => {
],
delegateItems,
);
sharedMemory = delegatedStakeMemory;
});
});

Expand Down Expand Up @@ -949,6 +965,7 @@ describe('GatewayRegistry', async () => {
type: 'stake',
},
]);
sharedMemory = decreaseStakeMemory;
});

it('should fail to withdraw a delegated stake if below the minimum withdrawal limitation', async () => {
Expand Down Expand Up @@ -995,6 +1012,7 @@ describe('GatewayRegistry', async () => {
memory: decreaseStakeMemory,
});
assert.deepStrictEqual(gatewayAfter, gatewayBefore);
sharedMemory = decreaseStakeMemory;
});
});

Expand Down Expand Up @@ -1039,6 +1057,7 @@ describe('GatewayRegistry', async () => {
});
// no changes to the gateway after a withdrawal is cancelled
assert.deepStrictEqual(gatewayAfter, gatewayBefore);
sharedMemory = cancelWithdrawalMemory;
});
it('should allow cancelling an operator withdrawal', async () => {
const decreaseStakeTimestamp = STUB_TIMESTAMP + 1000 * 60 * 15; // 15 minutes after stubbedTimestamp
Expand Down Expand Up @@ -1084,6 +1103,7 @@ describe('GatewayRegistry', async () => {
...gatewayBefore,
operatorStake: INITIAL_OPERATOR_STAKE + decreaseQty, // the decrease was cancelled and returned to the operator
});
sharedMemory = cancelWithdrawalMemory;
});
});

Expand Down Expand Up @@ -1156,6 +1176,8 @@ describe('GatewayRegistry', async () => {
balancesAfter[PROCESS_ID],
balancesBefore[PROCESS_ID] + penaltyAmount,
); // original stake + penalty

sharedMemory = instantWithdrawalMemory;
});
});

Expand Down Expand Up @@ -1197,6 +1219,7 @@ describe('GatewayRegistry', async () => {
fetchedGateways.map((g) => g.gatewayAddress),
[STUB_ADDRESS, secondGatewayAddress],
);
sharedMemory = addGatewayMemory2;
});
});

Expand Down Expand Up @@ -1278,6 +1301,7 @@ describe('GatewayRegistry', async () => {
if (!cursor) break;
}
assert.deepStrictEqual(fetchedDelegations, expectedDelegations);
sharedMemory = decreaseStakeMemory;
}

it('should paginate active and vaulted stakes by ascending balance correctly', async () => {
Expand Down Expand Up @@ -1508,6 +1532,7 @@ describe('GatewayRegistry', async () => {
redelegationFeeRate: 0,
},
);
sharedMemory = redelegateStakeMemory;
});

it("should allow re-delegating stake with a vault and the vault's balance", async () => {
Expand Down Expand Up @@ -1608,6 +1633,7 @@ describe('GatewayRegistry', async () => {
}),
[],
);
sharedMemory = redelegateStakeMemory;
});
});
});
97 changes: 97 additions & 0 deletions tests/invariants.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import assert from 'node:assert';
import { getBalances, getVaults } from './helpers.mjs';

function assertValidBalance(balance, expectedMin = 1) {
assert(
Number.isInteger(balance) &&
balance >= expectedMin &&
balance <= 1_000_000_000_000_000,
`Invariant violated: balance ${balance} is invalid`,
);
}

function assertValidAddress(address) {
assert(address.length > 0, `Invariant violated: address ${address} is empty`);
}

function assertValidTimestampsAtTimestamp({
startTimestamp,
endTimestamp,
timestamp,
}) {
assert(
startTimestamp <= timestamp,
`Invariant violated: startTimestamp ${startTimestamp} is in the future`,
);
assert(
endTimestamp === null || endTimestamp > startTimestamp,
`Invariant violated: endTimestamp of ${endTimestamp} for vault ${address}`,
);
}

export async function assertNoInvariants({ timestamp, memory }) {
await assertNoBalanceInvariants({ timestamp, memory });
await assertNoBalanceVaultInvariants({ timestamp, memory });
}

async function assertNoBalanceInvariants({ timestamp, memory }) {
// Assert all balances are >= 0 and all belong to valid addresses
const balances = await getBalances({
memory,
timestamp,
});
for (const [address, balance] of Object.entries(balances)) {
assertValidBalance(balance, 0);
assertValidAddress(address);
}
}

async function assertNoBalanceVaultInvariants({ timestamp, memory }) {
const { result } = await getVaults({
memory,
limit: 1_000_000, // egregiously large limit to make sure we get them all
});

for (const vault of JSON.parse(result.Messages?.[0]?.Data).items) {
const { address, balance, startTimestamp, endTimestamp } = vault;
assertValidBalance(balance);
assertValidAddress(address);
assertValidTimestampsAtTimestamp({
startTimestamp,
endTimestamp,
timestamp,
});
}
}

async function assertNoTotalSupplyInvariants({ timestamp, memory }) {
const supplyResult = await handle({
Tags: [
{
name: 'Action',
value: 'Total-Token-Supply',
},
],
});

// assert no errors
assert.deepEqual(supplyResult.Messages?.[0]?.Error, undefined);
// assert correct tag in message by finding the index of the tag in the message
const notice = supplyResult.Messages?.[0]?.Tags?.find(
(tag) => tag.name === 'Action' && tag.value === 'Total-Token-Supply-Notice',
);
assert.ok(notice, 'should have a Total-Token-Supply-Notice tag');

const supplyData = JSON.parse(supplyResult.Messages?.[0]?.Data);

assert.ok(
supplyData.total === 1000000000 * 1000000,
'total supply should be 1 billion IO but was ' + supplyData.total,
);
assertValidBalance(supplyData.circulating);
assertValidBalance(supplyData.locked, 0);
assertValidBalance(supplyData.staked, 0);
assertValidBalance(supplyData.delegated, 0);
assertValidBalance(supplyData.withdrawn, 0);
assertValidBalance(supplyData.protocolBalance, 0);
}
Loading