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

Existing memory invariance #263

Draft
wants to merge 5 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ luacov-html
dist
luacov.stats.out
coverage
tools/fixtures/memory/*
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"monitor:devnet": "IO_PROCESS_ID=GaQrvEMKBpkjofgnBi_B3IgIDmY_XYelVLB6GcRGrHc node --test tests/monitor/monitor.test.mjs",
"monitor:testnet": "IO_PROCESS_ID=agYcCFJtrMG6cqMuZfskIkFTGvUPddICmtQSBIoPdiA node --test tests/monitor/monitor.test.mjs",
"evolve": "yarn build && node tools/evolve.mjs",
"update-memory-fixtures": "yarn update-testnet-memory && yarn update-devnet-memory",
"update-testnet-memory": "IO_NETWORK=\"testnet\" node tools/update-memory.mjs",
"update-devnet-memory": "IO_NETWORK=\"devnet\" node tools/update-memory.mjs",
"prepare": "husky"
},
"devDependencies": {
Expand Down
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;
});
});
});
3 changes: 2 additions & 1 deletion tests/helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
STUB_TIMESTAMP,
STUB_MESSAGE_ID,
validGatewayTags,
TESTNET_MEMORY,
} from '../tools/constants.mjs';

const initialOperatorStake = 100_000_000_000;
Expand All @@ -17,7 +18,7 @@ export const basePermabuyPrice = 2_500_000_000;
export const baseLeasePrice = 600_000_000;

const { handle: originalHandle, memory } = await createAosLoader();
export const startMemory = memory;
export const startMemory = TESTNET_MEMORY;

export async function handle(options = {}, mem = startMemory) {
return originalHandle(
Expand Down
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);
}
8 changes: 8 additions & 0 deletions tools/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ export const AOS_WASM = fs.readFileSync(
),
);

export const TESTNET_MEMORY = fs.readFileSync(
path.join(__dirname, 'fixtures/memory/testnet'),
);

export const DEVNET_MEMORY = fs.readFileSync(
path.join(__dirname, 'fixtures/memory/devnet'),
);

export const BUNDLED_SOURCE_CODE = fs.readFileSync(
path.join(__dirname, '../dist/aos-bundled.lua'),
'utf-8',
Expand Down
Loading
Loading