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

Add collections gas usage tests #411

Merged
merged 4 commits into from
Oct 1, 2024
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
151 changes: 151 additions & 0 deletions benchmark/__tests__/test-collections-performance.ava.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Worker } from "near-workspaces";
import test from "ava";
import { logTotalGas, randomInt } from "./util.js";

const COLLECTION_SIZE = 20;

test.before(async (t) => {
// Init the worker and start a Sandbox server
const worker = await Worker.init();

// Prepare sandbox for tests, create accounts, deploy contracts, etx.
const root = worker.rootAccount;

// Deploy the test contracts.
const lookupMapContract = await root.devDeploy("build/lookup-map.wasm");
const lookupSetContract = await root.devDeploy("build/lookup-set.wasm");
const unorderedMapContract = await root.devDeploy("build/unordered-map.wasm");
const unorderedSetContract = await root.devDeploy("build/unordered-set.wasm");
const vectorContract = await root.devDeploy("build/vector.wasm");

// Test users
const ali = await root.createSubAccount("ali");

// Save state for test runs
t.context.worker = worker;
t.context.accounts = {
root,
lookupMapContract,
lookupSetContract,
unorderedMapContract,
unorderedSetContract,
vectorContract,
ali,
};
});

test.after.always(async (t) => {
await t.context.worker.tearDown().catch((error) => {
console.log("Failed to tear down the worker:", error);
});
});

test("JS lookup map contract operations", async (t) => {
const { ali, lookupMapContract } = t.context.accounts;

let rAdd;
for (let i = 0; i < COLLECTION_SIZE; i++) {
rAdd = await ali.callRaw(lookupMapContract, "addElement", { key: i, value: i });
}
t.is(rAdd.result.status.SuccessValue, "");
logTotalGas("Add element", rAdd, t);

const val = randomInt(COLLECTION_SIZE);
const rGet = await ali.callRaw(lookupMapContract, "getElement", { key: val });
t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), val);
logTotalGas("Get element", rGet, t);

const rRem = await ali.callRaw(lookupMapContract, "removeElement", { key: randomInt(COLLECTION_SIZE) });
t.is(rRem.result.status.SuccessValue, "");
logTotalGas("Remove element", rRem, t);
});

test("JS lookup set contract operations", async (t) => {
const { ali, lookupSetContract } = t.context.accounts;

let rAdd;
for (let i = 0; i < COLLECTION_SIZE; i++) {
rAdd = await ali.callRaw(lookupSetContract, "addElement", { value: i });
}
t.is(rAdd.result.status.SuccessValue, "");
logTotalGas("Add element", rAdd, t);

const rGet = await ali.callRaw(lookupSetContract, "containsElement", { value: randomInt(COLLECTION_SIZE) });
t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), true);
logTotalGas("Get element", rGet, t);

const rRem = await ali.callRaw(lookupSetContract, "removeElement", { value: randomInt(COLLECTION_SIZE) });
t.is(rRem.result.status.SuccessValue, "");
logTotalGas("Remove element", rRem, t);
});

test("JS unordered map contract operations", async (t) => {
const { ali, unorderedMapContract } = t.context.accounts;

let rAdd;
for (let i = 0; i < COLLECTION_SIZE; i++) {
rAdd = await ali.callRaw(unorderedMapContract, "addElement", { key: i, value: i });
}
t.is(rAdd.result.status.SuccessValue, "");
logTotalGas("Add element", rAdd, t);

const val = randomInt(COLLECTION_SIZE);
const rGet = await ali.callRaw(unorderedMapContract, "getElement", { key: val });
t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), val);
logTotalGas("Get element", rGet, t);

const rIt = await ali.callRaw(unorderedMapContract, "iterate", {});
t.is(rIt.result.status.SuccessValue, "");
logTotalGas("Iterate collection", rIt, t);

const rRem = await ali.callRaw(unorderedMapContract, "removeElement", { key: randomInt(COLLECTION_SIZE) });
t.is(rRem.result.status.SuccessValue, "");
logTotalGas("Remove element", rRem, t);
});

test("JS unordered set contract operations", async (t) => {
const { ali, unorderedSetContract } = t.context.accounts;

let rAdd;
for (let i = 0; i < COLLECTION_SIZE; i++) {
rAdd = await ali.callRaw(unorderedSetContract, "addElement", { value: i });
}
t.is(rAdd.result.status.SuccessValue, "");
logTotalGas("Add element", rAdd, t);

const rGet = await ali.callRaw(unorderedSetContract, "containsElement", { value: randomInt(COLLECTION_SIZE) });
t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), true);
logTotalGas ("Get element", rGet, t);

const rIt = await ali.callRaw(unorderedSetContract, "iterate", {});
t.is(rIt.result.status.SuccessValue, "");
logTotalGas("Iterate collection", rIt, t);

const rRem = await ali.callRaw(unorderedSetContract, "removeElement", { value: randomInt(COLLECTION_SIZE) });
t.is(rRem.result.status.SuccessValue, "");
logTotalGas("Remove element", rRem, t);
});

test("JS vector contract operations", async (t) => {
const { ali, vectorContract } = t.context.accounts;

let rAdd;
for (let i = 0; i < COLLECTION_SIZE; i++) {
rAdd = await ali.callRaw(vectorContract, "addElement", { value: i });
}
t.is(rAdd.result.status.SuccessValue, "");
logTotalGas("Add element", rAdd, t);

const val = randomInt(COLLECTION_SIZE);
const rGet = await ali.callRaw(vectorContract, "getElement", { index: val });
t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), val);
logTotalGas("Get element", rGet, t);

const rIt = await ali.callRaw(vectorContract, "iterate", {});
t.is(rIt.result.status.SuccessValue, "");
logTotalGas("Iterate collection", rIt, t);

const rRem = await ali.callRaw(vectorContract, "removeElement", { index: randomInt(COLLECTION_SIZE) });
t.is(rRem.result.status.SuccessValue, "");
logTotalGas("Remove element", rRem, t);
});
15 changes: 15 additions & 0 deletions benchmark/__tests__/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,18 @@ export function logGasDetail(r, t) {
)
);
}

export function logTotalGas(prefix = '', r, t) {
t.log(
prefix + ' - Total gas used: ',
formatGas(
r.result.transaction_outcome.outcome.gas_burnt +
r.result.receipts_outcome[0].outcome.gas_burnt +
(r.result.receipts_outcome[1]?.outcome.gas_burnt || 0)
)
);
}

export function randomInt(max) {
return Math.floor(Math.random() * max);
}
8 changes: 7 additions & 1 deletion benchmark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@
"build:highlevel-collection": "near-sdk-js build src/highlevel-collection.js build/highlevel-collection.wasm",
"build:expensive-calc": "near-sdk-js build src/expensive-calc.js build/expensive-calc.wasm",
"build:deploy-contract": "near-sdk-js build src/deploy-contract.js build/deploy-contract.wasm",
"build:lookup-map": "near-sdk-js build src/lookup-map.js build/lookup-map.wasm",
"build:lookup-set": "near-sdk-js build src/lookup-set.js build/lookup-set.wasm",
"build:unordered-map": "near-sdk-js build src/unordered-map.js build/unordered-map.wasm",
"build:unordered-set": "near-sdk-js build src/unordered-set.js build/unordered-set.wasm",
"build:vector": "near-sdk-js build src/vector.js build/vector.wasm",
"test": "ava",
"test:lowlevel-minimal": "ava __tests__/test-lowlevel-minimal.ava.js",
"test:highlevel-minimal": "ava __tests__/test-highlevel-minimal.ava.js",
"test:lowlevel-api": "ava __tests__/test-lowlevel-api.ava.js",
"test:highlevel-collection": "ava __tests__/test-highlevel-collection.ava.js",
"test:expensive-calc": "ava __tests__/test-expensive-calc.ava.js",
"test:deploy-contract": "ava __tests__/test-deploy-contract.ava.js"
"test:deploy-contract": "ava __tests__/test-deploy-contract.ava.js",
"test:collections": "ava __tests__/test-collections-performance.ava.js"
},
"author": "Near Inc <[email protected]>",
"license": "Apache-2.0",
Expand Down
23 changes: 23 additions & 0 deletions benchmark/src/lookup-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NearBindgen, call, LookupMap, view } from "near-sdk-js";

@NearBindgen({})
export class LookupMapContract {
constructor() {
this.lookupMap = new LookupMap("LM");
}

@call({})
addElement({ key, value }) {
this.lookupMap.set(key, value);
}

@call({})
removeElement({ key }) {
this.lookupMap.remove(key);
}

@view({})
getElement({ key }) {
return this.lookupMap.get(key);
}
}
23 changes: 23 additions & 0 deletions benchmark/src/lookup-set.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NearBindgen, call, LookupSet, view } from "near-sdk-js";

@NearBindgen({})
export class LookupSetContract {
constructor() {
this.lookupSet = new LookupSet("LS");
}

@call({})
addElement({ value }) {
this.lookupSet.set(value);
}

@call({})
removeElement({ value }) {
this.lookupSet.remove(value);
}

@view({})
containsElement({ value }) {
return this.lookupSet.contains(value);
}
}
31 changes: 31 additions & 0 deletions benchmark/src/unordered-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NearBindgen, call, UnorderedMap, view } from "near-sdk-js";

@NearBindgen({})
export class UnorderedMapContract {
constructor() {
this.unorderedMap = new UnorderedMap("UM");
}

@call({})
addElement({ key, value }) {
this.unorderedMap.set(key, value);
}

@call({})
removeElement({ key }) {
this.unorderedMap.remove(key);
}

@view({})
getElement({ key }) {
return this.unorderedMap.get(key);
}

@view({})
iterate() {
const size = this.unorderedMap.length;
for (let i = 0; i < size; i++) {
this.unorderedMap.get(i);
}
}
}
31 changes: 31 additions & 0 deletions benchmark/src/unordered-set.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NearBindgen, call, UnorderedSet, view } from "near-sdk-js";

@NearBindgen({})
export class UnorderedSetContract {
constructor() {
this.unorderedSet = new UnorderedSet("US");
}

@call({})
addElement({ value }) {
this.unorderedSet.set(value);
}

@call({})
removeElement({ value }) {
this.unorderedSet.remove(value);
}

@view({})
containsElement({ value }) {
return this.unorderedSet.contains(value);
}

@view({})
iterate() {
const size = this.unorderedSet.length;
for (let i = 0; i < size; i++) {
this.unorderedSet.contains(i);
}
}
}
31 changes: 31 additions & 0 deletions benchmark/src/vector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NearBindgen, call, Vector, view } from "near-sdk-js";

@NearBindgen({})
export class VectorContract {
constructor() {
this.vector = new Vector("V");
}

@call({})
addElement({ value }) {
this.vector.push(value);
}

@call({})
removeElement({ index }) {
this.vector.swapRemove(index);
}

@view({})
getElement({ index }) {
return this.vector.get(index);
}

@view({})
iterate() {
const size = this.vector.length;
for (let i = 0; i < size; i++) {
this.vector.get(i);
}
}
}
Loading