Skip to content

Commit

Permalink
rpc sandbox supporting treasury factory (#194)
Browse files Browse the repository at this point in the history
This PR adds the `treasury-factory.near` contract and the dependencies
`social.near` and `near` contracts to the RPC sandbox which can be used
for Playwright tests and for experimenting in the
[playground](https://github.com/NEAR-DevHub/neardevhub-treasury-dashboard/blob/feat/sandbox-treasury-factory/sandboxrpc/README.md).

The file
[sandboxrpc/playground/treasuryfactory.js](https://github.com/NEAR-DevHub/neardevhub-treasury-dashboard/blob/feat/sandbox-treasury-factory/sandboxrpc/playground/treasuryfactory.js)
shows how to use the treasury factory with the sandbox. You can run this
file with nodejs and test various ways of interaction.

Also there is a [Playwright
test](https://github.com/NEAR-DevHub/neardevhub-treasury-dashboard/blob/feat/sandbox-treasury-factory/playwright-tests/tests/treasury-factory/treasury-factory.spec.js)
that shows how to use it in Playwright and make the needed assertions
that a treasury instance was successfully created. This can be used as a
starting point for creating the full E2E test in
#191

Broken test fixes:

- "Should update existing member permissions" ( the locator for finding
the delete button was not specific enough ).
- "Should whitelist staking pool and create stake delegation request" (
also made the locator more specific ).

resolves #193
  • Loading branch information
petersalomonsen authored Dec 29, 2024
1 parent cb566be commit 1de6de0
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 14 deletions.
30 changes: 29 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ jobs:
${{ matrix.target_account.test_command }}
continue-on-error: false

playwright-tests-treasury-factory:
name: Playwright tests Treasury Factory
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: |
npm ci
npx playwright install-deps
npx playwright install
- name: Build project
run: npm run build
- name: Build sandbox
run: |
npm run build:sandbox
continue-on-error: false
- name: Run tests
run: |
npx playwright test --project=treasury-testing playwright-tests/tests/treasury-factory
continue-on-error: false

playwright-tests-stake-delegation:
name: Playwright tests stake-delegation
runs-on: ubuntu-latest
Expand Down Expand Up @@ -98,8 +123,11 @@ jobs:
npx playwright install
- name: Build project
run: npm run build
- name: Run tests
- name: Build sandbox
run: |
npm run build:sandbox
continue-on-error: false
- name: Run tests
run: |
${{ matrix.target_account.test_command }}
continue-on-error: false
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,6 @@ test.describe("admin with function access keys", function () {
}
const submitBtn = page.getByRole("button", { name: "Submit" });
await expect(submitBtn).toBeAttached({ timeout: 10_000 });
await submitBtn.scrollIntoViewIfNeeded({ timeout: 10_000 });
await expect(submitBtn).toBeDisabled({ timeout: 10_000 });
};
await checkThatFormIsCleared();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,11 @@ test.describe("admin connected", function () {
).toBeVisible();
await expect(page.getByText(permission, { exact: true })).toBeVisible();
await expect(page.getByPlaceholder("treasury.near")).toBeDisabled();
await page.locator("i").first().click();
await page
.getByText("Create Requests", { exact: true })
.locator(".bi")
.click();

const submitBtn = page.getByRole("button", { name: "Submit" });
await expect(submitBtn).toBeAttached({ timeout: 10_000 });
await submitBtn.scrollIntoViewIfNeeded({ timeout: 10_000 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,13 @@ async function openLockupStakingForm({ page, daoAccount, lockupContract }) {
exact: true,
});
await createRequestButton.click();
await page.getByText("Stake", { exact: true }).click();
await page
.locator(
'div[data-component="treasury-devdao.near/widget/pages.stake-delegation.CreateButton"] .option',
{ hasText: "Stake" }
)
.first()
.click();
await page.waitForTimeout(10_000);
await selectLockupAccount({
page,
Expand Down
100 changes: 100 additions & 0 deletions playwright-tests/tests/treasury-factory/treasury-factory.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { test, expect } from '@playwright/test';
import { SandboxRPC } from '../../util/sandboxrpc';
import { getLocalWidgetSource } from '../../util/bos-workspace';
import nearApi from 'near-api-js';

test("should be able to create a treasury instance with sandbox", async () => {
const sandbox = new SandboxRPC();
await sandbox.init();

const widget_reference_account_id = 'treasury-testing.near';

await sandbox.setupWidgetReferenceAccount(widget_reference_account_id);

const instance_name = "test-factory-created-instance";
const create_dao_args = {
"config": {
"name": instance_name,
"purpose": "creating dao treasury",
"metadata": "",
},
"policy": {
"roles": [
{
"kind": {
"Group": ["acc3.near", "acc2.near", "acc1.near"],
},
"name": "Create Requests",
"permissions": [
"call:AddProposal",
"transfer:AddProposal",
"config:Finalize",
],
"vote_policy": {},
},
{
"kind": {
"Group": ["acc1.near"],
},
"name": "Manage Members",
"permissions": [
"config:*",
"policy:*",
"add_member_to_role:*",
"remove_member_from_role:*",
],
"vote_policy": {},
},
{
"kind": {
"Group": ["acc1.near", "acc2.near"],
},
"name": "Vote",
"permissions": ["*:VoteReject", "*:VoteApprove", "*:VoteRemove"],
"vote_policy": {},
},
],
"default_vote_policy": {
"weight_kind": "RoleWeight",
"quorum": "0",
"threshold": [1, 2],
},
"proposal_bond": "100000000000000000000000",
"proposal_period": "604800000000000",
"bounty_bond": "100000000000000000000000",
"bounty_forgiveness_period": "604800000000000",
},
};

const createInstanceResult = await sandbox.account.functionCall({
contractId: "treasury-factory.near", methodName: 'create_instance', args: {
"sputnik_dao_factory_account_id": "sputnik-dao.near",
"social_db_account_id": "social.near",
"widget_reference_account_id": widget_reference_account_id,
"name": instance_name,
"create_dao_args": Buffer.from(JSON.stringify(create_dao_args)).toString('base64')
},
gas: 300000000000000,
attachedDeposit: nearApi.utils.format.parseNearAmount("12")
});

expect(
createInstanceResult.receipts_outcome.filter(receipt_outcome => receipt_outcome.outcome.status.Failure).length
).toBe(0);

const web4GetResult = await sandbox.account.viewFunction({ contractId: `${instance_name}.near`, methodName: 'web4_get', args: { request: { path: "/" } } });
expect(Buffer.from(web4GetResult.body, 'base64').toString().substring(0, "<!doctype html>".length)).toEqual("<!doctype html>");

const daoGetPolicyResult = await sandbox.account.viewFunction({contractId: `${instance_name}.sputnik-dao.near`, methodName: 'get_policy', args: {}});
expect(daoGetPolicyResult).toEqual(create_dao_args.policy);

const referenceWidgetSources = await getLocalWidgetSource(widget_reference_account_id + "/widget/**");

const socialGetResult = await sandbox.account.viewFunction({contractId: 'social.near', methodName: 'get', args: {
"keys": [`${instance_name}.near/widget/**`]
}});

expect(JSON.stringify(socialGetResult)).toEqual(JSON.stringify(referenceWidgetSources).replaceAll(widget_reference_account_id, instance_name+".near"));

await sandbox.quitSandbox();
});
25 changes: 25 additions & 0 deletions playwright-tests/util/bos-workspace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export async function getLocalWidgetSource(path) {
const json = await fetch("http://127.0.0.1:8080/api/proxy-rpc", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
method: "query",
params: {
request_type: "call_function",
account_id: "social.near",
method_name: "get",
args_base64: Buffer.from(JSON.stringify({ keys: [path] })).toString(
"base64"
),
finality: "optimistic",
},
id: 123,
jsonrpc: "2.0",
}),
}).then((r) => r.json());
return JSON.parse(
new TextDecoder().decode(new Uint8Array(json.result.result))
);
}
42 changes: 42 additions & 0 deletions playwright-tests/util/sandboxrpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { exec } from "child_process";
import { connect, utils, keyStores } from "near-api-js";
import { MOCK_RPC_URL } from "./rpcmock.js";
import { parseNearAmount } from "near-api-js/lib/utils/format.js";
import { KeyPairEd25519 } from "near-api-js/lib/utils/key_pair.js";
import { getLocalWidgetSource } from "./bos-workspace.js";

export const SPUTNIK_DAO_CONTRACT_ID = "sputnik-dao.near";
export const PROPOSAL_BOND = "100000000000000000000000";
Expand Down Expand Up @@ -63,6 +65,46 @@ export class SandboxRPC {
});
}

async setupWidgetReferenceAccount(reference_widget_account_id) {
const widgetReferenceAccountKeyPair = KeyPairEd25519.fromRandom();
await this.keyStore.setKey(
"sandbox",
reference_widget_account_id,
widgetReferenceAccountKeyPair
);

await this.account.functionCall({
contractId: "near",
methodName: "create_account",
args: {
new_account_id: reference_widget_account_id,
new_public_key: widgetReferenceAccountKeyPair.getPublicKey().toString(),
},
gas: 300000000000000,
attachedDeposit: parseNearAmount("2"),
});

const reference_widget_account = await this.near.account(
reference_widget_account_id
);
const data = {};
const localWidgetSources = await getLocalWidgetSource(
reference_widget_account_id + "/widget/**"
);

data[reference_widget_account_id] = {
widget: localWidgetSources[reference_widget_account_id].widget,
};
await reference_widget_account.functionCall({
contractId: "social.near",
methodName: "set",
args: {
data,
},
attachedDeposit: parseNearAmount("1"),
});
}

async setupLockupContract(owner_account_id) {
await this.account.functionCall({
contractId: "lockup-whitelist.near",
Expand Down
7 changes: 4 additions & 3 deletions sandboxrpc/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sandboxrpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ edition = "2021"

[dependencies]
near-workspaces = "0.14.1"
serde_json = "1.0.134"
tokio = { version = "1.41.1", features = ["full"] }
8 changes: 7 additions & 1 deletion sandboxrpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@

The Rust project in this folder sets up a simple near-workspaces-rs instance, deploying the sputnik-dao.near contract and expose it to an RPC endpoint on localhost, which is emitted to stdout when started. This RPC endpoint can be called from JavaScript to experiment with different parameters for calls to the sputnik-dao contract.

You may alter [sandboxrpcplayground.js](./sandboxrpcplayground.js) according to what you want to try out, which is more efficient than testing with real transactions on chain.
In the [playground](./playground/) folder you can find example scripts of how to play with the sandbox.

Before running any of the scripts, make sure that you build the RPC sandbox executable using:

`cargo build`

This should give you the executable [target/debug/sandboxrpc](./target/debug/).
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { exec } from 'child_process';
import { connect, utils, keyStores } from 'near-api-js';
import { SandboxRPC } from '../playwright-tests/util/sandboxrpc.js';
import { SandboxRPC } from '../../playwright-tests/util/sandboxrpc.js';
import { parseNearAmount } from 'near-api-js/lib/utils/format.js';

const sandbox = new SandboxRPC();
Expand Down
Loading

0 comments on commit 1de6de0

Please sign in to comment.