Skip to content

Commit 72c595f

Browse files
committed
Add typescript p-chain tests
1 parent eec7fc0 commit 72c595f

File tree

10 files changed

+481
-0
lines changed

10 files changed

+481
-0
lines changed

test-scripts/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# dependencies
2+
/node_modules
3+
4+
# production
5+
/dist
6+
7+
# misc
8+
.DS_Store

test-scripts/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "go-flare-scripts",
3+
"version": "1.0.0",
4+
"scripts": {
5+
"p-chain-import": "ts-node ./src/test-p-chain-import.ts",
6+
"p-chain-export": "ts-node ./src/test-p-chain-export.ts",
7+
"add-validator": "ts-node ./src/test-add-validator.ts",
8+
"add-delegator": "ts-node ./src/test-add-delegator.ts"
9+
},
10+
"keywords": [],
11+
"author": "Flare Foundation",
12+
"license": "ISC",
13+
"description": "Test scripts for Flare's P-Chain operations",
14+
"devDependencies": {
15+
"typescript": "^5.8.3"
16+
},
17+
"dependencies": {
18+
"@flarenetwork/flarejs": "^4.0.5",
19+
"ethers": "^6.15.0"
20+
}
21+
}

test-scripts/src/runner.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { CChainBalance, formatDecimal, PChainBalance } from "./utils";
2+
3+
async function runWithBalanches(func: () => Promise<void>) {
4+
// Log the current balances before calling the function
5+
let cBalanceFLR = await CChainBalance();
6+
let pBalanceFLR = await PChainBalance();
7+
8+
console.log(`Current C chain balance: ${formatDecimal(cBalanceFLR, 18)} FLR`);
9+
console.log(`Current P chain balance: ${formatDecimal(pBalanceFLR, 9)} FLR\n`);
10+
11+
await func();
12+
13+
// Log the balances after calling the function
14+
cBalanceFLR = await CChainBalance();
15+
pBalanceFLR = await PChainBalance();
16+
17+
console.log(`\nNew C chain balance: ${formatDecimal(cBalanceFLR, 18)} FLR`);
18+
console.log(`New P chain balance: ${formatDecimal(pBalanceFLR, 9)} FLR\n`);
19+
}
20+
21+
export async function runTest(func: () => Promise<void>) {
22+
try {
23+
await runWithBalanches(func);
24+
console.log('Script completed successfully');
25+
} catch (error) {
26+
console.error('Script failed:', error);
27+
}
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { networkIDs, pvm, utils } from "@flarenetwork/flarejs";
2+
import { issuePChainTx, localFlareContext } from "./utils";
3+
import { runTest } from "./runner";
4+
5+
async function addDelegator(nodeID: string, endTime: number, weight: number) {
6+
const ctx = await localFlareContext();
7+
8+
// Create the transaction to add a delegator
9+
console.log(`Creating add delegator transaction for node ${nodeID} with weight ${weight}...`);
10+
11+
const { utxos } = await ctx.pvmapi.getUTXOs({
12+
addresses: [ctx.addressP]
13+
});
14+
const tx = pvm.newAddPermissionlessDelegatorTx(
15+
ctx.context,
16+
utxos,
17+
[utils.bech32ToBytes(ctx.addressP)],
18+
nodeID,
19+
networkIDs.PrimaryNetworkID.toString(),
20+
BigInt(Date.now()) / 1000n,
21+
BigInt(endTime),
22+
BigInt(weight * 1e9),
23+
[utils.bech32ToBytes(ctx.addressP)],
24+
);
25+
26+
await issuePChainTx(ctx.pvmapi, tx, ctx.privateKey);
27+
}
28+
29+
runTest(() => addDelegator(
30+
'NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg',
31+
Math.ceil(Date.now() / 1000) + 60 * 60 + 5, // 1 hour (+ 5 seconds) from now
32+
10_000
33+
))
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { networkIDs, pvm, utils } from "@flarenetwork/flarejs";
2+
import { issuePChainTx, localFlareContext } from "./utils";
3+
import { runTest } from "./runner";
4+
5+
const blsPublicKey = '0x917386c374aab0ea3d2bda96768f2be1f0b11483cd5c41bd9cddd3892b693ab84efecc6cf70300d614dcdc6d298ab659';
6+
const blsSignature = '0x81c7174fc3bc9bbf00a1e26cc7b177d92589b517df5c46844498c14ede655bfedb89929e3d2974210983a1ff06a2a39b05208974a2d3dddac4948bcc81f367717d960be9704b775f21022d639b439135d34b7fb1f2bacbb5ffab3ddafc86220f'
7+
8+
async function addValidator(nodeID: string, endTime: number, weight: number) {
9+
const ctx = await localFlareContext();
10+
11+
// Create the transaction to add a validator
12+
console.log(`Creating add validator transaction for node ${nodeID} with weight ${weight}...`);
13+
14+
const { utxos } = await ctx.pvmapi.getUTXOs({
15+
addresses: [ctx.addressP]
16+
});
17+
const tx = pvm.newAddPermissionlessValidatorTx(
18+
ctx.context,
19+
utxos,
20+
[utils.bech32ToBytes(ctx.addressP)],
21+
nodeID,
22+
networkIDs.PrimaryNetworkID.toString(),
23+
BigInt(Date.now()) / 1000n,
24+
BigInt(endTime),
25+
BigInt(weight * 1e9),
26+
[utils.bech32ToBytes(ctx.addressP)],
27+
[utils.bech32ToBytes(ctx.addressP)],
28+
10_0000,
29+
undefined,
30+
1,
31+
0n,
32+
utils.hexToBuffer(blsPublicKey),
33+
utils.hexToBuffer(blsSignature)
34+
);
35+
36+
await issuePChainTx(ctx.pvmapi, tx, ctx.privateKey);
37+
}
38+
39+
runTest(() => addValidator(
40+
'NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ',
41+
Math.ceil(Date.now() / 1000) + 14 * 24 * 60 * 60 + 5, // 14 days (+ 5 seconds) from now
42+
10_000
43+
))
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { evm, pvm, utils, TransferableOutput } from '@flarenetwork/flarejs';
2+
import { issueCChainTx, issuePChainTx, localFlareContext } from './utils';
3+
import { runTest } from './runner';
4+
5+
async function PtoCExport(amountFLR: number) {
6+
const ctx = await localFlareContext();
7+
const baseFee = await ctx.evmapi.getBaseFee();
8+
9+
// Create and issue a P to C export transaction
10+
console.log(`Creating P to C export transaction for ${amountFLR} FLR...`);
11+
12+
const { utxos: utxosp } = await ctx.pvmapi.getUTXOs({
13+
addresses: [ctx.addressP]
14+
});
15+
const exportTx = pvm.newExportTx(
16+
ctx.context,
17+
ctx.context.cBlockchainID,
18+
[utils.bech32ToBytes(ctx.addressP)],
19+
utxosp,
20+
[
21+
TransferableOutput.fromNative(ctx.context.avaxAssetID, BigInt(amountFLR * 1e9), [
22+
utils.bech32ToBytes(ctx.addressP)
23+
])
24+
]
25+
);
26+
await issuePChainTx(ctx.pvmapi, exportTx, ctx.privateKey);
27+
28+
// Create and issue a P to C chain import transaction
29+
console.log('\nCreating P to C chain import transaction');
30+
31+
const { utxos: utxosc } = await ctx.evmapi.getUTXOs({
32+
sourceChain: 'P',
33+
addresses: ['C-' + ctx.addressP.slice(2)],
34+
});
35+
36+
const importTx = evm.newImportTxFromBaseFee(
37+
ctx.context,
38+
utils.hexToBuffer(ctx.addressC),
39+
[utils.bech32ToBytes(ctx.addressP)],
40+
utxosc,
41+
ctx.context.pBlockchainID,
42+
baseFee / BigInt(1e9),
43+
);
44+
45+
await issueCChainTx(ctx.evmapi, importTx, ctx.privateKey);
46+
}
47+
48+
49+
runTest(() => PtoCExport(100))
50+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { evm, pvm, utils } from '@flarenetwork/flarejs';
2+
import { issueCChainTx, issuePChainTx, localFlareContext } from './utils';
3+
import { runTest } from './runner';
4+
5+
async function CtoPExport(amountFLR: number) {
6+
const ctx = await localFlareContext();
7+
const baseFee = await ctx.evmapi.getBaseFee();
8+
const txCount = await ctx.provider.getTransactionCount(ctx.addressC);
9+
10+
// Create and issue a C to P export transaction
11+
console.log(`Creating C to P export transaction for ${amountFLR} FLR...`);
12+
13+
const exportTx = evm.newExportTxFromBaseFee(
14+
ctx.context,
15+
baseFee / BigInt(1e9),
16+
BigInt(amountFLR * 1e9),
17+
ctx.context.pBlockchainID,
18+
utils.hexToBuffer(ctx.addressC),
19+
[utils.bech32ToBytes(ctx.addressP)],
20+
BigInt(txCount),
21+
);
22+
23+
await issueCChainTx(ctx.evmapi, exportTx, ctx.privateKey);
24+
25+
// Create and issue a C to P chain import transaction
26+
console.log('\nCreating C to P chain import transaction');
27+
28+
const { utxos } = await ctx.pvmapi.getUTXOs({
29+
sourceChain: 'C',
30+
addresses: [ctx.addressP]
31+
});
32+
const importTx = pvm.newImportTx(
33+
ctx.context,
34+
ctx.context.cBlockchainID,
35+
utxos,
36+
[utils.bech32ToBytes(ctx.addressP)],
37+
[utils.bech32ToBytes(ctx.addressP)]
38+
);
39+
40+
await issuePChainTx(ctx.pvmapi, importTx, ctx.privateKey);
41+
}
42+
43+
runTest(() => CtoPExport(100))

test-scripts/src/utils.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { evm, pvm, Context, addTxSignatures, UnsignedTx, utils} from '@flarenetwork/flarejs';
2+
import { JsonRpcProvider } from 'ethers'
3+
4+
export const LocalURL = 'http://localhost:9650';
5+
export const TestCAddress = '0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC';
6+
export const TestPAddress = 'P-localflare18jma8ppw3nhx5r4ap8clazz0dps7rv5uj3gy4v';
7+
export const TestPrivateKey = '0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027';
8+
9+
export interface TestContext {
10+
context: Context.Context;
11+
evmapi: evm.EVMApi;
12+
pvmapi: pvm.PVMApi;
13+
provider: JsonRpcProvider;
14+
addressC: string;
15+
addressP: string;
16+
privateKey: string;
17+
}
18+
19+
export async function CChainBalance(): Promise<BigInt> {
20+
const context = await localFlareContext();
21+
return context.provider.getBalance(TestCAddress);
22+
}
23+
24+
export async function PChainBalance(): Promise<BigInt> {
25+
const context = await localFlareContext();
26+
const { balance } = await context.pvmapi.getBalance({
27+
addresses: [TestPAddress],
28+
});
29+
return balance
30+
}
31+
32+
export async function localFlareContext(): Promise<TestContext> {
33+
const evmapi = new evm.EVMApi(LocalURL);
34+
const pvmapi = new pvm.PVMApi(LocalURL);
35+
const context = await Context.getContextFromURI(LocalURL);
36+
const provider = new JsonRpcProvider(LocalURL + '/ext/bc/C/rpc');
37+
return {
38+
context,
39+
evmapi,
40+
pvmapi,
41+
provider,
42+
addressC: TestCAddress,
43+
addressP: TestPAddress,
44+
privateKey: TestPrivateKey,
45+
}
46+
}
47+
48+
export async function delay(ms: number): Promise<void> {
49+
return new Promise(resolve => setTimeout(resolve, ms));
50+
}
51+
52+
export function formatDecimal(int: BigInt, decimals: number): string {
53+
if (int === 0n) {
54+
return '0'
55+
}
56+
let strInt = int.toString()
57+
strInt = strInt.padStart(decimals, '0')
58+
const decPart = strInt.slice(0, -decimals) || '0'
59+
const fracPart = strInt.slice(-decimals).replace(/0+$/, '')
60+
return fracPart === '' ? decPart : decPart + '.' + fracPart
61+
}
62+
63+
export async function issuePChainTx(pvmapi: pvm.PVMApi, tx: UnsignedTx, privateKey: string): Promise<void> {
64+
await addTxSignatures({
65+
unsignedTx: tx,
66+
privateKeys: [utils.hexToBuffer(privateKey)],
67+
});
68+
69+
const exportResponse = await pvmapi.issueSignedTx(tx.getSignedTx());
70+
const txID = exportResponse.txID;
71+
console.log(`Issued transaction with ID: ${txID}`);
72+
73+
console.log(`Waiting for transaction ${txID} to be processed...`);
74+
let txStatus;
75+
do {
76+
await delay(1000);
77+
txStatus = await pvmapi.getTxStatus({ txID });
78+
} while (txStatus.status === 'Processing');
79+
80+
if (txStatus.status === 'Committed') {
81+
console.log(`Transaction ${txID} accepted`);
82+
} else {
83+
throw new Error(`Transaction ${txID} failed with status: ${txStatus.status}`);
84+
}
85+
}
86+
87+
export async function issueCChainTx(evmapi: evm.EVMApi, tx: UnsignedTx, privateKey: string): Promise<void> {
88+
await addTxSignatures({
89+
unsignedTx: tx,
90+
privateKeys: [utils.hexToBuffer(privateKey)],
91+
});
92+
93+
const exportResponse = await evmapi.issueSignedTx(tx.getSignedTx());
94+
const txID = exportResponse.txID;
95+
console.log(`Issued transaction with ID: ${txID}`);
96+
97+
console.log(`Waiting for transaction ${txID} to be processed...`);
98+
let txStatus;
99+
do {
100+
await delay(1000);
101+
txStatus = await evmapi.getAtomicTxStatus(txID);
102+
} while (txStatus.status !== 'Accepted' && txStatus.status !== 'Rejected');
103+
104+
if (txStatus.status === 'Accepted') {
105+
console.log(`Transaction ${txID} accepted`);
106+
} else {
107+
throw new Error(`Transaction ${txID} failed with status: ${txStatus.status}`);
108+
}
109+
}

test-scripts/tsconfig.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
4+
"module": "commonjs", /* Specify what module code is generated. */
5+
"rootDir": "./src", /* Specify the root folder within your source files. */
6+
"outDir": "./dist", /* Specify an output folder for all emitted files. */
7+
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
8+
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
9+
"strict": true, /* Enable all strict type-checking options. */
10+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
11+
},
12+
"include": [
13+
"src/**/*",
14+
],
15+
"exclude": [
16+
"node_modules",
17+
"dist",
18+
"**/*.spec.ts"
19+
]
20+
}

0 commit comments

Comments
 (0)