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

feat: add wallet contract #2314

Open
wants to merge 5 commits into
base: v1.6-dev-ugly
Choose a base branch
from
Open
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
681 changes: 346 additions & 335 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ members = [
"packages/simple-signer",
"packages/rs-json-schema-compatibility-validator",
"packages/check-features",
"packages/wallet-contract"
]
[workspace.package]

Expand Down
Empty file added local_seed
Empty file.
18 changes: 18 additions & 0 deletions packages/wallet-contract/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": "airbnb-base",
"rules": {
"no-plusplus": 0,
"eol-last": [
"error",
"always"
],
"class-methods-use-this": "off",
"curly": [
"error",
"all"
]
},
"globals": {
"BigInt": true
}
}
2 changes: 2 additions & 0 deletions packages/wallet-contract/.mocharc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require: test/bootstrap.js
recursive: true
13 changes: 13 additions & 0 deletions packages/wallet-contract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "wallet-contract"
description = "Wallet data contract schema and tools"
version = "1.5.0"
edition = "2021"
rust-version.workspace = true
license = "MIT"

[dependencies]
thiserror = "1.0.64"
platform-version = { path = "../rs-platform-version" }
serde_json = { version = "1.0" }
platform-value = { path = "../rs-platform-value" }
20 changes: 20 additions & 0 deletions packages/wallet-contract/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2019 Dash Core Group, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 changes: 33 additions & 0 deletions packages/wallet-contract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Wallet Contract

[![Build Status](https://github.com/dashpay/platform/actions/workflows/release.yml/badge.svg)](https://github.com/dashpay/platform/actions/workflows/release.yml)
[![NPM version](https://img.shields.io/npm/v/@dashevo/wallet-contract.svg?style=flat-square)](https://npmjs.org/package/@dashevo/wallet-contract)

JSON Contracts for Dash Wallet apps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance the package description

The current description is too brief. Consider expanding it to better reflect the contract's key functionalities:

-JSON Contracts for Dash Wallet apps
+JSON Contracts for Dash Wallet apps, providing secure storage for encrypted transaction metadata and username request information to support masternode voting processes.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
JSON Contracts for Dash Wallet apps
JSON Contracts for Dash Wallet apps, providing secure storage for encrypted transaction metadata and username request information to support masternode voting processes.


## Table of Contents

- [Install](#install)
- [Usage](#usage)
- [Contributing](#contributing)
- [License](#license)

## Install

```sh
npm install @dashevo/wallet-contract
```
Comment on lines +17 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Package installation command needs to be updated

The package @dashevo/wallet-contract is not published on npm registry yet, despite being defined in package.json. The installation command in the README won't work for users. This needs to be either:

  • Published to npm registry before the documentation is updated, or
  • Updated with alternative installation instructions (e.g., installing from GitHub repository)
🔗 Analysis chain

Verify package installation command

The installation command references a package that might not be published yet.


Let me check the package.json to understand if this is a local package that needs to be published.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify if the package exists on npm registry
# Test: Check if the package exists and get its details
npm view @dashevo/wallet-contract --json || echo "Package not found"

Length of output: 1022


Script:

#!/bin/bash
# Check package.json in the wallet-contract directory
cat packages/wallet-contract/package.json

Length of output: 779


## Usage

```sh
# TODO ...
```
Comment on lines +21 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace TODO with comprehensive documentation

Based on the contract's purposes, please replace the TODO with detailed documentation covering:

-## Usage
-
-```sh
-# TODO ...
-```
+## Usage
+
+### Transaction Metadata Storage
+
+```javascript
+// Example: Storing encrypted transaction metadata
+const metadata = {
+  txId: "your-tx-id",
+  encryptedData: "encrypted-payload"
+};
+
+await walletContract.storeTransactionMetadata(metadata);
+
+// Example: Retrieving transaction metadata
+const storedMetadata = await walletContract.getTransactionMetadata(txId);
+```
+
+### Username Request Management
+
+```javascript
+// Example: Storing username request information
+const requestInfo = {
+  username: "alice",
+  normalizedLabel: "alice",
+  parentDomain: "dash",
+  socialMediaUrl: "https://twitter.com/alice/status/123"
+};
+
+await walletContract.storeUsernameRequest(requestInfo);
+
+// Example: Retrieving username requests for voting
+const requests = await walletContract.getUsernameRequests();
+```
+
+### Error Handling
+
+```javascript
+try {
+  await walletContract.storeTransactionMetadata(metadata);
+} catch (error) {
+  if (error.code === 'INVALID_METADATA') {
+    // Handle validation errors
+  }
+  // Handle other errors
+}

<!-- This is an auto-generated comment by CodeRabbit -->


## Contributing

Feel free to dive in! [Open an issue](https://github.com/dashpay/platform/issues/new/choose) or submit PRs.

## License

[MIT](LICENSE) &copy; Dash Core Group, Inc.
4 changes: 4 additions & 0 deletions packages/wallet-contract/lib/systemIds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
ownerId: '11111111111111111111111111111111',
contractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'
};
29 changes: 29 additions & 0 deletions packages/wallet-contract/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@dashevo/wallet-contract",
"version": "1.5.0",
"description": "A contract and helper scripts for Wallet DApp",
"scripts": {
"lint": "eslint .",
"test": "yarn run test:unit",
"test:unit": "mocha 'test/unit/**/*.spec.js'"
},
"contributors": [
{
"name": "Eric Britten",
"email": "[email protected]",
"url": "https://github.com/hashengineering"
}
],
"license": "MIT",
"devDependencies": {
"@dashevo/wasm-dpp": "workspace:*",
"chai": "^4.3.10",
"dirty-chai": "^2.0.1",
"eslint": "^8.53.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.0",
"mocha": "^10.2.0",
"sinon": "^17.0.1",
"sinon-chai": "^3.7.0"
}
}
125 changes: 125 additions & 0 deletions packages/wallet-contract/schema/v1/wallet-contract-documents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
{
"txMetadata": {
"type": "object",
"indices": [
{
"name": "ownerId",
"properties": [
{
"$ownerId": "asc"
}
]
},
{
"name": "ownerIdAndCreatedAt",
"properties": [
{
"$ownerId": "asc"
},
{
"$createdAt": "asc"
}
]
}
],
"properties": {
"keyIndex": {
"type": "integer",
"minimum": 0,
"description": "The index of the owners identity public key used to derive the encryption key.",
"position": 0
},
Comment on lines +26 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add maximum bounds for key indices.

The keyIndex and encryptionKeyIndex properties should have maximum values to prevent potential resource exhaustion attacks.

Apply this diff:

       "keyIndex": {
         "type": "integer",
         "minimum": 0,
+        "maximum": 2147483647,
         "description": "The index of the owners identity public key used to derive the encryption key.",
         "position": 0
       },
       "encryptionKeyIndex": {
         "type": "integer",
         "minimum": 0,
+        "maximum": 2147483647,
         "description": "The secondary index used to derive the encryption key that is used to encrypt and decrypt encryptedData.",
         "position": 1
       },

Also applies to: 32-37

"encryptionKeyIndex": {
"type": "integer",
"minimum": 0,
"description": "The secondary index used to derive the encryption key that is used to encrypt and decrypt encryptedData.",
"position": 1
},
"encryptedMetadata": {
"type": "array",
"byteArray": true,
"minItems": 32,
"maxItems": 4096,
"description": "encrypted metadata using AES-CBC-256",
"position": 2
}
},
"required": [
"keyIndex",
"encryptionKeyIndex",
"encryptedMetadata",
"$createdAt"
],
"additionalProperties": false
},
"identityVerify": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is it for? How is it related to the wallet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the DashPay app, when a user requests a contested username, we allow the user to specify a social media post to "prove" an identity.

If two people wanted bob each can specify a url to a social media post that says something like "I am the real bob and my identity id is Abs19....47k."

Masternode owners using DashPay can view this see this URL in the username request details page of the Username Voting screen.

This contact might not be the best place for it. If it isn't, I am not sure if it should be in a new contract or the dashpay contract.

"documentsMutable": true,
"canBeDeleted": true,
"type": "object",
"properties": {
"normalizedLabel": {
"position": 0,
"type": "string",
"pattern": "^[a-hj-km-np-z0-9][a-hj-km-np-z0-9-]{0,61}[a-hj-km-np-z0-9]$",
"maxLength": 63,
"description": "Domain label converted to lowercase for case-insensitive uniqueness validation. \"o\", \"i\" and \"l\" replaced with \"0\" and \"1\" to mitigate homograph attack. e.g. 'b0b'",
"$comment": "Must match a domain document to provide further information. Must be equal to the label in lowercase. \"o\", \"i\" and \"l\" must be replaced with \"0\" and \"1\"."
},
"normalizedParentDomainName": {
"type": "string",
"pattern": "^$|^[a-hj-km-np-z0-9][a-hj-km-np-z0-9-\\.]{0,61}[a-hj-km-np-z0-9]$",
"minLength": 0,
"maxLength": 63,
"position": 1,
"description": "A parent domain name in lowercase for case-insensitive uniqueness validation. \"o\", \"i\" and \"l\" replaced with \"0\" and \"1\" to mitigate homograph attack. e.g. 'dash'",
"$comment": "Must either be equal to an existing domain or empty to create a top level domain. \"o\", \"i\" and \"l\" must be replaced with \"0\" and \"1\". Only the data contract owner can create top level domains."
},
"url": {
"position": 2,
"type": "string",
"description": "The identity verification URL to be stored.",
"maxLength": 128,
"pattern": "^https?://.*",
"format": "uri"
}
Comment on lines +77 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enforce HTTPS-only URLs for security.

The current URL pattern allows both HTTP and HTTPS protocols. For security reasons, especially when dealing with identity verification, only HTTPS URLs should be allowed.

Apply this diff:

       "url": {
         "position": 2,
         "type": "string",
         "description": "The identity verification URL to be stored.",
         "maxLength": 128,
-        "pattern": "^https?://.*",
+        "pattern": "^https://.*",
         "format": "uri"
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"url": {
"position": 2,
"type": "string",
"description": "The identity verification URL to be stored.",
"maxLength": 128,
"pattern": "^https?://.*",
"format": "uri"
}
"url": {
"position": 2,
"type": "string",
"description": "The identity verification URL to be stored.",
"maxLength": 128,
"pattern": "^https://.*",
"format": "uri"
}

},
"indices": [
{
"name": "ownerId",
"properties": [
{
"$ownerId": "asc"
}
]
},
{
"name": "ownerId_NormDomainName_NormLabel",
"properties": [
{
"$ownerId": "asc"
},
{
"normalizedParentDomainName": "asc"
},
{
"normalizedLabel": "asc"
}
]
},
{
"name": "uniqueUsernameIndex",
"properties": [
{
"normalizedLabel": "asc"
}
]
}
],
"required": [
"url",
"normalizedLabel",
"normalizedParentDomainName"
],
"additionalProperties": false
}
}
17 changes: 17 additions & 0 deletions packages/wallet-contract/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use platform_version::version::FeatureVersion;

#[derive(thiserror::Error, Debug)]
pub enum Error {
/// Platform expected some specific versions
#[error("platform unknown version on {method}, received: {received}")]
UnknownVersionMismatch {
/// method
method: String,
/// the allowed versions for this method
known_versions: Vec<FeatureVersion>,
/// requested core height
received: FeatureVersion,
},
#[error("schema deserialize error: {0}")]
InvalidSchemaJson(#[from] serde_json::Error),
}
37 changes: 37 additions & 0 deletions packages/wallet-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
mod error;
pub mod v1;

pub use crate::error::Error;
use platform_value::{Identifier, IdentifierBytes32};
use platform_version::version::PlatformVersion;
use serde_json::Value;

pub const ID_BYTES: [u8; 32] = [
162, 48, 73, 255, 116, 241, 166, 155, 131, 121, 132, 39, 129, 40, 127, 6, 103, 164, 72, 139,
143, 116, 163, 19, 81, 193, 38, 248, 116, 244, 59, 196
];

pub const OWNER_ID_BYTES: [u8; 32] = [0; 32];

pub const ID: Identifier = Identifier(IdentifierBytes32(ID_BYTES));
pub const OWNER_ID: Identifier = Identifier(IdentifierBytes32(OWNER_ID_BYTES));
pub fn load_definitions(platform_version: &PlatformVersion) -> Result<Option<Value>, Error> {
match platform_version.system_data_contracts.withdrawals {
1 => Ok(None),
version => Err(Error::UnknownVersionMismatch {
method: "wallet_contract::load_definitions".to_string(),
known_versions: vec![1],
received: version,
}),
}
}
pub fn load_documents_schemas(platform_version: &PlatformVersion) -> Result<Value, Error> {
match platform_version.system_data_contracts.withdrawals {
1 => v1::load_documents_schemas(),
version => Err(Error::UnknownVersionMismatch {
method: "wallet_contract::load_documents_schemas".to_string(),
known_versions: vec![1],
received: version,
}),
}
}
19 changes: 19 additions & 0 deletions packages/wallet-contract/src/v1/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::Error;
use serde_json::Value;

pub mod document_types {
pub mod tx_metadata {
pub const NAME: &str = "tx_metadata";

pub mod properties {
pub const KEY_INDEX: &str = "keyIndex";
pub const ENCRYPTION_KEY_INDEX: &str = "encryptionKeyIndex";
pub const ENCRYPTED_METADATA: &str = "encryptedMetadata";
}
}
}

pub fn load_documents_schemas() -> Result<Value, Error> {
serde_json::from_str(include_str!("../../schema/v1/wallet-contract-documents.json"))
.map_err(Error::InvalidSchemaJson)
}
12 changes: 12 additions & 0 deletions packages/wallet-contract/test/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"env": {
"node": true,
"mocha": true
},
"rules": {
"import/no-extraneous-dependencies": "off"
},
"globals": {
"expect": true
}
}
30 changes: 30 additions & 0 deletions packages/wallet-contract/test/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const sinon = require('sinon');
const sinonChai = require('sinon-chai');

const { expect, use } = require('chai');
const dirtyChai = require('dirty-chai');

const {
default: loadWasmDpp,
} = require('@dashevo/wasm-dpp');

use(dirtyChai);
use(sinonChai);

exports.mochaHooks = {
beforeAll: loadWasmDpp,

Comment on lines +14 to +16
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make beforeAll hook async to handle WebAssembly loading.

The beforeAll hook should be async to properly handle the WebAssembly loading promise.

 exports.mochaHooks = {
-  beforeAll: loadWasmDpp,
+  beforeAll: async function() {
+    await loadWasmDpp();
+  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exports.mochaHooks = {
beforeAll: loadWasmDpp,
exports.mochaHooks = {
beforeAll: async function() {
await loadWasmDpp();
},

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadWasmDpp is async too

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shumkov, thank you for pointing that out. Since loadWasmDpp is already async, assigning it directly to beforeAll is sufficient.

(^.^)?


✏️ Learnings added
Learnt from: shumkov
PR: dashpay/platform#2314
File: packages/wallet-contract/test/bootstrap.js:14-16
Timestamp: 2024-11-06T07:27:01.446Z
Learning: In `packages/wallet-contract/test/bootstrap.js`, for Mocha tests in Node.js, async functions like `loadWasmDpp` can be assigned directly to `beforeAll` without wrapping them in another async function.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

beforeEach() {
if (!this.sinon) {
this.sinon = sinon.createSandbox();
} else {
this.sinon.restore();
}
},

afterEach() {
this.sinon.restore();
},
};

global.expect = expect;
Loading
Loading