Skip to content

Commit

Permalink
Order Processing: e2e tests (#213)
Browse files Browse the repository at this point in the history
* Order Processing e2e tests

* progress

* common functions

* purchase region

* untested

* support any os

* fix typo

* fix

* readme

* improve logging

* progress

* works :)

* fixes

* refund surplus

* temporary solution

* fix psvm

* cleanup tests

* resolve todo & format

* fixes

* last touches
  • Loading branch information
Szegoo authored Aug 3, 2024
1 parent a53cf2d commit fe023ca
Show file tree
Hide file tree
Showing 21 changed files with 616 additions and 349 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- uses: ./.github/actions/setup

- name: Install Rust
run: cargo install --git https://github.com/paritytech/psvm --rev a3ecef700e4c1429c2d01e265a145654ceb3cc49 psvm
run: cargo install --git https://github.com/paritytech/psvm psvm
- name: Check Dependency Versions
run: |
chmod +x ./scripts/check-dependency-versions.sh
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ cumulus-pallet-xcmp-queue = { version = "0.7.0", default-features = false }
cumulus-primitives-core = { version = "0.7.0", default-features = false }
cumulus-primitives-timestamp = { version = "0.7.0", default-features = false }
cumulus-primitives-utility = { version = "0.7.3", default-features = false }
pallet-collator-selection = { version = "9.0.0", default-features = false }
pallet-collator-selection = { version = "9.0.2", default-features = false }
parachain-info = { version = "0.7.0", package = "staging-parachain-info", default-features = false }
parachains-common = { version = "7.0.0", default-features = false }
sp-timestamp = { version = "26.0.0", default-features = false }
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@
cargo build --release --features fast-runtime
```

3. Get the polkadot binary:

```sh
zombienet-linux setup polkadot polkadot-parachain
Please add the dir to your $PATH by running the command:
export PATH=/home/<username>/RegionX-Node/:$PATH
3. Install dependencies:
```
npm i
```

4. Run the tests:
Expand Down Expand Up @@ -66,4 +62,12 @@

```
npm run test -- ./zombienet_tests/xc-transfer/region-transfer.zndsl
```
```

- order tests

- processing

```
npm run test -- ./zombienet_tests/order/processing.zndsl
```
33 changes: 29 additions & 4 deletions e2e_tests/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,14 @@ async function setupRelayAsset(api: ApiPromise, signer: KeyringPair, initialBala
// Transfer the relay chain asset to the parachain specified by paraId.
// Receiver address is same as the sender's.
async function transferRelayAssetToPara(
amount: bigint,
paraId: number,
relayApi: ApiPromise,
signer: KeyringPair
signer: KeyringPair,
paraId: number,
receiver: string,
amount: bigint
) {
const receiverKeypair = new Keyring();
receiverKeypair.addFromAddress(signer.address);
receiverKeypair.addFromAddress(receiver);

// If system parachain we use teleportation, otherwise we do a reserve transfer.
const transferKind = paraId < 2000 ? 'limitedTeleportAssets' : 'limitedReserveTransferAssets';
Expand Down Expand Up @@ -97,6 +98,23 @@ async function transferRelayAssetToPara(
await submitExtrinsic(signer, reserveTransfer, {});
}

async function openHrmpChannel(
signer: KeyringPair,
relayApi: ApiPromise,
senderParaId: number,
recipientParaId: number
) {
const openHrmp = relayApi.tx.parasSudoWrapper.sudoEstablishHrmpChannel(
senderParaId, // sender
recipientParaId, // recipient
8, // Max capacity
102400 // Max message size
);
const sudoCall = relayApi.tx.sudo.sudo(openHrmp);

return submitExtrinsic(signer, sudoCall, {});
}

async function sleep(milliseconds: number) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
Expand All @@ -117,10 +135,17 @@ const getFreeBalance = async (api: ApiPromise, address: string): Promise<bigint>
return BigInt(free);
};

function log(message: string) {
// Green log.
console.log('\x1b[32m%s\x1b[0m', message);
}

export {
RELAY_ASSET_ID,
log,
setupRelayAsset,
sleep,
openHrmpChannel,
submitExtrinsic,
transferRelayAssetToPara,
getAddressFromModuleId,
Expand Down
4 changes: 2 additions & 2 deletions e2e_tests/consts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const UNIT = 10 ** 12; // ROC has 12 decimals
const UNIT = 10n ** 12n; // ROC has 12 decimals

const INITIAL_PRICE = 50 * UNIT;
const INITIAL_PRICE = 1n * UNIT;
const CORE_COUNT = 10;
const TIMESLICE_PERIOD = 80;
const IDEAL_CORES_SOLD = 5;
Expand Down
51 changes: 51 additions & 0 deletions e2e_tests/coretime.common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ApiPromise } from '@polkadot/api';
import { KeyringPair } from '@polkadot/keyring/types';
import { RegionId } from 'coretime-utils';
import { submitExtrinsic } from './common';
import { CONFIG, CORE_COUNT, INITIAL_PRICE } from './consts';

async function configureBroker(coretimeApi: ApiPromise, signer: KeyringPair): Promise<void> {
const configCall = coretimeApi.tx.broker.configure(CONFIG);
const sudo = coretimeApi.tx.sudo.sudo(configCall);
return submitExtrinsic(signer, sudo, {});
}

async function startSales(coretimeApi: ApiPromise, signer: KeyringPair): Promise<void> {
const startSaleCall = coretimeApi.tx.broker.startSales(INITIAL_PRICE, CORE_COUNT);
const sudo = coretimeApi.tx.sudo.sudo(startSaleCall);
return submitExtrinsic(signer, sudo, {});
}

async function purchaseRegion(
coretimeApi: ApiPromise,
buyer: KeyringPair
): Promise<RegionId | null> {
const callTx = async (resolve: (regionId: RegionId | null) => void) => {
const purchase = coretimeApi.tx.broker.purchase(INITIAL_PRICE * 10n);
const unsub = await purchase.signAndSend(buyer, async (result: any) => {
if (result.status.isInBlock) {
const regionId = await getRegionId(coretimeApi);
unsub();
resolve(regionId);
}
});
};

return new Promise(callTx);
}

async function getRegionId(coretimeApi: ApiPromise): Promise<RegionId | null> {
const events: any = await coretimeApi.query.system.events();

for (const record of events) {
const { event } = record;
if (event.section === 'broker' && event.method === 'Purchased') {
const data = event.data[1].toHuman();
return data;
}
}

return null;
}

export { configureBroker, startSales, purchaseRegion };
89 changes: 89 additions & 0 deletions e2e_tests/ismp.common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { ApiPromise } from '@polkadot/api';
import { KeyringPair } from '@polkadot/keyring/types';
import { ISubmittableResult } from '@polkadot/types/types';
import { submitExtrinsic } from './common';
import { Get, IsmpRequest } from './types';

async function ismpAddParachain(signer: KeyringPair, regionXApi: ApiPromise) {
const addParaCall = regionXApi.tx.ismpParachain.addParachain([1005]);
const sudoCall = regionXApi.tx.sudo.sudo(addParaCall);
return submitExtrinsic(signer, sudoCall, {});
}

async function queryRequest(regionxApi: ApiPromise, commitment: string): Promise<IsmpRequest> {
const leafIndex = regionxApi.createType('LeafIndexQuery', { commitment });
const requests = await (regionxApi as any).rpc.ismp.queryRequests([leafIndex]);
// We only requested a single request so we only get one in the response.
return requests.toJSON()[0] as IsmpRequest;
}

async function makeIsmpResponse(
regionXApi: ApiPromise,
coretimeApi: ApiPromise,
request: IsmpRequest,
responderAddress: string
): Promise<void> {
if (isGetRequest(request)) {
const hashAt = (
await coretimeApi.query.system.blockHash(Number(request.get.height))
).toString();
const proofData = await coretimeApi.rpc.state.getReadProof([request.get.keys[0]], hashAt);

const stateMachineProof = regionXApi.createType('StateMachineProof', {
hasher: 'Blake2',
storage_proof: proofData.proof,
});

const substrateStateProof = regionXApi.createType('SubstrateStateProof', {
StateProof: stateMachineProof,
});
const response = regionXApi.tx.ismp.handleUnsigned([
{
Response: {
datagram: {
Request: [request],
},
proof: {
height: {
id: {
stateId: {
Kusama: 1005,
},
consensusStateId: 'PARA',
},
height: request.get.height.toString(),
},
proof: substrateStateProof.toHex(),
},
signer: responderAddress,
},
},
]);

return new Promise((resolve, reject) => {
const unsub = response.send((result: ISubmittableResult) => {
const { status, isError } = result;
console.log(`Current status is ${status}`);
if (status.isInBlock) {
console.log(`Transaction included at blockHash ${status.asInBlock}`);
} else if (status.isFinalized) {
console.log(`Transaction finalized at blockHash ${status.asFinalized}`);
unsub.then();
return resolve();
} else if (isError) {
console.log('Transaction error');
unsub.then();
return reject();
}
});
});
} else {
new Error('Expected a Get request');
}
}

const isGetRequest = (request: IsmpRequest): request is { get: Get } => {
return (request as { get: Get }).get !== undefined;
};

export { makeIsmpResponse, queryRequest, ismpAddParachain };
107 changes: 107 additions & 0 deletions e2e_tests/order/processing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { ApiPromise, Keyring, WsProvider } from '@polkadot/api';
import assert from 'node:assert';
import {
log,
openHrmpChannel,
RELAY_ASSET_ID,
setupRelayAsset,
sleep,
submitExtrinsic,
transferRelayAssetToPara,
} from '../common';
import { UNIT } from '../consts';
import { configureBroker, purchaseRegion, startSales } from '../coretime.common';
import { ismpAddParachain } from '../ismp.common';
import { REGIONX_API_TYPES, REGIONX_CUSTOM_RPC } from '../types';
import { transferRegionToRegionX } from '../xc-regions.common';

async function run(_nodeName: string, networkInfo: any, _jsArgs: any) {
const { wsUri: regionXUri } = networkInfo.nodesByName['regionx-collator01'];
const { wsUri: coretimeUri } = networkInfo.nodesByName['coretime-collator01'];
const { wsUri: rococoUri } = networkInfo.nodesByName['rococo-validator01'];

const regionXApi = await ApiPromise.create({
provider: new WsProvider(regionXUri),
types: { ...REGIONX_API_TYPES },
rpc: REGIONX_CUSTOM_RPC,
});
const rococoApi = await ApiPromise.create({ provider: new WsProvider(rococoUri) });
const coretimeApi = await ApiPromise.create({ provider: new WsProvider(coretimeUri) });

// account to submit tx
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
const bob = keyring.addFromUri('//Bob');

const txSetRelayXcmVersion = rococoApi.tx.xcmPallet.forceDefaultXcmVersion([3]);
const txSetCoretimeXcmVersion = coretimeApi.tx.polkadotXcm.forceDefaultXcmVersion([3]);
log('Setting XCM version: ');
await submitExtrinsic(alice, rococoApi.tx.sudo.sudo(txSetRelayXcmVersion), {});
await submitExtrinsic(alice, coretimeApi.tx.sudo.sudo(txSetCoretimeXcmVersion), {});

log('Setting up relay asset: ');
await setupRelayAsset(regionXApi, alice, 500n * UNIT);

log('Opening HRMP: ');
await openHrmpChannel(alice, rococoApi, 1005, 2000);
await openHrmpChannel(alice, rococoApi, 2000, 1005);
log('Adding ISMP: ');
await ismpAddParachain(alice, regionXApi);

log('Transfering rc token to RegionX:');
await transferRelayAssetToPara(rococoApi, alice, 1005, alice.address, 100n * UNIT);
await transferRelayAssetToPara(rococoApi, alice, 2000, alice.address, 100n * UNIT);

log('Configuring coretime chain:');
await configureBroker(coretimeApi, alice);
log('Starting sales:');
await startSales(coretimeApi, alice);

const regionId = await purchaseRegion(coretimeApi, alice);
if (!regionId) throw new Error('RegionId not found');

log('Transferring region to RegionX');
await transferRegionToRegionX(coretimeApi, regionXApi, alice, regionId);

const paraId = 2000;
const orderRequirements = {
begin: 40,
end: 45,
coreOccupancy: 57600, // full core
};

log('Creating order');
const createOrderCall = regionXApi.tx.orders.createOrder(paraId, orderRequirements);
await submitExtrinsic(alice, createOrderCall, {});

const order = (await regionXApi.query.orders.orders(0)).toJSON();
assert.deepStrictEqual(order, {
creator: alice.address,
paraId: 2000,
requirements: orderRequirements,
});

log('Giving Bob tokens');
const transferToBobCall = regionXApi.tx.tokens.transfer(bob.address, RELAY_ASSET_ID, 30n * UNIT);
await submitExtrinsic(alice, regionXApi.tx.sudo.sudo(transferToBobCall), {});

log('Bob making a contribution');
const contributeCall = regionXApi.tx.orders.contribute(0, 10n * UNIT);
await submitExtrinsic(bob, contributeCall, {});

log('Alice fulfilling the order');
const fulfillCall = regionXApi.tx.processor.fulfillOrder(0, regionId);
await submitExtrinsic(alice, fulfillCall, {});
// Region should be removed after making the assignment call:
const regions = await regionXApi.query.regions.regions.entries();
assert.equal(regions.length, 0);

// `fulfillOrder` will make a cross-chain call to the Coretime chain to make the assignment.
// We will wait a bit since it will take some time.
await sleep(5000);

const workplan = await coretimeApi.query.broker.workplan.entries();
assert.equal(workplan.length, 1);
}

export { run };
File renamed without changes.
Loading

0 comments on commit fe023ca

Please sign in to comment.