Browse Source

fee estimation added, style fix, readme update

Gleb Urvanov 5 years ago
parent
commit
6d4d3994ba
8 changed files with 96 additions and 111 deletions
  1. 4 9
      README.md
  2. 11 11
      package.json
  3. 1 0
      tests/.env
  4. 3 1
      tests/.prettierrc
  5. 28 29
      tests/package.json
  6. 19 29
      tests/src/tests/membershipCreationTest.ts
  7. 29 28
      tests/src/utils/apiMethods.ts
  8. 1 4
      tests/src/utils/utils.ts

+ 4 - 9
README.md

@@ -1,6 +1,6 @@
 # Joystream
 
-This is the main code reposity for all joystream software. It will  house the substrate chain project, the full node and runtime and all reusable substrate runtime modules that make up the joystream runtime. In addition to all front-end apps and infrastructure servers necessary for operating the network.
+This is the main code reposity for all joystream software. It will house the substrate chain project, the full node and runtime and all reusable substrate runtime modules that make up the joystream runtime. In addition to all front-end apps and infrastructure servers necessary for operating the network.
 
 The repository is currently just a cargo workspace, but eventually will also contain yarn workspaces, and possibly other project type workspaces.
 
@@ -9,20 +9,18 @@ The repository is currently just a cargo workspace, but eventually will also con
 The joystream network builds on a pre-release version of [substrate v2.0](https://substrate.dev/) and adds additional
 functionality to support the [various roles](https://www.joystream.org/roles) that can be entered into on the platform.
 
-
 ## Validator
+
 ![ Nodes for Joystream](./node/validator-node-banner.svg)
 
 Joystream node is the main server application that connects to the network, synchronizes the blockchain with other nodes and produces blocks if configured as a validator node.
 
 To setup a full node and validator review the [advanced guide from the helpdesk](https://github.com/Joystream/helpdesk/tree/master/roles/validators).
 
-
-###  Pre-built Binaries
+### Pre-built Binaries
 
 The latest pre-built binaries can be downloads from the [releases](https://github.com/Joystream/substrate-runtime-joystream/releases) page.
 
-
 ### Building from source
 
 Clone the repository and install build tools:
@@ -51,8 +49,8 @@ cargo run --release -- --chain ./rome-tesnet.json
 
 The `rome-testnet.json` chain file can be ontained from the [release page](https://github.com/Joystream/substrate-runtime-joystream/releases/tag/v6.8.0)
 
-
 ### Installing a release build
+
 This will install the executable `joystream-node` to your `~/.cargo/bin` folder, which you would normally have in your `$PATH` environment.
 
 ```bash
@@ -89,7 +87,6 @@ yarn test
 
 ![Joystream Runtime](./runtime/runtime-banner.svg)
 
-
 The runtime is the code that defines the consensus rules of the Joystream protocol.
 It is compiled to WASM and lives on chain.
 Joystream node execute the code's logic to validate transactions and blocks on the blockchain.
@@ -98,7 +95,6 @@ When building joystream-node as described abot with `cargo build --release`, in
 
 `target/release/wbuild/joystream-node-runtime/joystream_node_runtime.compact.wasm`
 
-
 ### Deployment
 
 Deploying the compiled runtime on a live system can be done in one of two ways:
@@ -112,7 +108,6 @@ Deploying the compiled runtime on a live system can be done in one of two ways:
 Versioning of the runtime is set in `runtime/src/lib.rs`
 For detailed information about how to set correct version numbers when developing a new runtime, [see this](https://github.com/Joystream/substrate-runtime-joystream/issues/1)
 
-
 ## Coding style
 
 We use `cargo-fmt` to format the source code for consistency.

+ 11 - 11
package.json

@@ -1,11 +1,11 @@
-{
-	"private": true,
-	"name": "joystream",
-	"license": "GPL-3.0-only",
-	"scripts": {
-		"test": "yarn && yarn workspaces run test"
-	},
-	"workspaces": [
-		"tests"
-	]
-}
+{
+	"private": true,
+	"name": "joystream",
+	"license": "GPL-3.0-only",
+	"scripts": {
+		"test": "yarn && yarn workspaces run test"
+	},
+	"workspaces": [
+		"tests"
+	]
+}

+ 1 - 0
tests/.env

@@ -1,3 +1,4 @@
 NODE_URL = ws://127.0.0.1:9944
 SUDO_ACCOUNT_URL = //Alice
 MEMBERSHIP_CREATION_N = 1
+MEMBERSHIP_PAID_TERMS = 0

+ 3 - 1
tests/.prettierrc

@@ -1,4 +1,6 @@
 {
   "singleQuote": true,
-  "arrowParens": "avoid"
+  "arrowParens": "avoid",
+  "useTabs": false,
+  "tabWidth": 2
 }

+ 28 - 29
tests/package.json

@@ -1,31 +1,30 @@
 {
-	"name": "joystream-testing",
-	"version": "0.1.0",
-	"license": "GPL-3.0-only",
-	"scripts": {
-		"build": "tsc --build tsconfig.json",
-		"test": "mocha -r ts-node/register src/tests/*",
-		"lint": "tslint --project tsconfig.json",
-		"prettier": "prettier --write ./src"
-	},
-	"dependencies": {
-		"@joystream/types": "^0.6.0",
-		"@polkadot/api": "^0.96.1",
-		"@polkadot/keyring": "^1.7.0-beta.5",
-		"@polkadot/types": "^0.96.1",
-		"@types/bn.js": "^4.11.5",
-		"bn.js": "^4.11.8",
-		"dotenv": "^8.2.0"
-	},
-	"devDependencies": {
-		"@polkadot/ts": "^0.3.14",
-		"@types/chai": "^4.2.11",
-		"@types/mocha": "^7.0.2",
-		"chai": "^4.2.0",
-		"mocha": "^7.1.1",
-		"prettier": "2.0.2",
-		"ts-node": "^8.8.1",
-		"tslint": "^6.1.0",
-		"typescript": "^3.8.3"
-	}
+  "name": "joystream-testing",
+  "version": "0.1.0",
+  "license": "GPL-3.0-only",
+  "scripts": {
+    "build": "tsc --build tsconfig.json",
+    "test": "mocha -r ts-node/register src/tests/*",
+    "lint": "tslint --project tsconfig.json",
+    "prettier": "prettier --write ./src"
+  },
+  "dependencies": {
+    "@joystream/types": "^0.6.0",
+    "@polkadot/api": "^0.96.1",
+    "@polkadot/keyring": "^1.7.0-beta.5",
+    "@types/bn.js": "^4.11.5",
+    "bn.js": "^4.11.8",
+    "dotenv": "^8.2.0"
+  },
+  "devDependencies": {
+    "@polkadot/ts": "^0.3.14",
+    "@types/chai": "^4.2.11",
+    "@types/mocha": "^7.0.2",
+    "chai": "^4.2.0",
+    "mocha": "^7.1.1",
+    "prettier": "2.0.2",
+    "ts-node": "^8.8.1",
+    "tslint": "^6.1.0",
+    "typescript": "^3.8.3"
+  }
 }

+ 19 - 29
tests/src/tests/membershipCreationTest.ts

@@ -12,6 +12,7 @@ describe('Membership integration tests', () => {
   const keyring = new Keyring({ type: 'sr25519' });
   const nKeyPairs: KeyringPair[] = new Array();
   const N: number = +process.env.MEMBERSHIP_CREATION_N!;
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!;
   const nodeUrl: string = process.env.NODE_URL!;
   const sudoUri: string = process.env.SUDO_ACCOUNT_URL!;
   const defaultTimeout: number = 30000;
@@ -19,8 +20,9 @@ describe('Membership integration tests', () => {
   let sudo: KeyringPair;
   let aKeyPair: KeyringPair;
   let membershipFee: number;
+  let membershipTransactionFee: number;
 
-  before(async function() {
+  before(async function () {
     this.timeout(defaultTimeout);
     registerJoystreamTypes();
     const provider = new WsProvider(nodeUrl);
@@ -30,30 +32,28 @@ describe('Membership integration tests', () => {
       nKeyPairs.push(keyring.addFromUri(i.toString()));
     }
     aKeyPair = keyring.addFromUri('A');
-    membershipFee = await apiMethods.getMembershipFee(0);
-    let nonce = await apiMethods.getNonce(sudo);
-    nonce = nonce.sub(new BN(1));
-    await apiMethods.transferBalanceToAccounts(
+    membershipFee = await apiMethods.getMembershipFee(paidTerms);
+    membershipTransactionFee = apiMethods.estimateBuyMembershipFee(
       sudo,
-      nKeyPairs,
-      membershipFee + 1,
-      nonce
+      paidTerms,
+      'member_name_which_is_longer_than_expected'
     );
-    await apiMethods.transferBalance(sudo, aKeyPair.address, 2);
+    let nonce = await apiMethods.getNonce(sudo);
+    nonce = nonce.sub(new BN(1));
+    await apiMethods.transferBalanceToAccounts(sudo, nKeyPairs, membershipFee + membershipTransactionFee, nonce);
+    await apiMethods.transferBalance(sudo, aKeyPair.address, membershipTransactionFee * 2);
   });
 
   it('Buy membeship is accepted with sufficient funds', async () => {
     await Promise.all(
       nKeyPairs.map(async keyPair => {
-        await apiMethods.buyMembership(keyPair, 0, 'new_member');
+        await apiMethods.buyMembership(keyPair, paidTerms, 'new_member');
       })
     );
     nKeyPairs.map(keyPair =>
       apiMethods
         .getMembership(keyPair.address)
-        .then(membership =>
-          assert(!membership.isEmpty, 'Account m is not a member')
-        )
+        .then(membership => assert(!membership.isEmpty, 'Account m is not a member'))
     );
   }).timeout(defaultTimeout);
 
@@ -61,18 +61,13 @@ describe('Membership integration tests', () => {
     apiMethods
       .getBalance(aKeyPair.address)
       .then(balance =>
-        assert(
-          balance.toNumber() < membershipFee,
-          'Account A already have sufficient balance to purchase membership'
-        )
+        assert(balance.toNumber() < membershipFee, 'Account A already have sufficient balance to purchase membership')
       );
   }).timeout(defaultTimeout);
 
   it('Account A can not buy the membership with insufficient funds', async () => {
-    await apiMethods.buyMembership(aKeyPair, 0, 'late_member', true);
-    apiMethods
-      .getMembership(aKeyPair.address)
-      .then(membership => assert(membership.isEmpty, 'Account A is a member'));
+    await apiMethods.buyMembership(aKeyPair, paidTerms, 'late_member', true);
+    apiMethods.getMembership(aKeyPair.address).then(membership => assert(membership.isEmpty, 'Account A is a member'));
   }).timeout(defaultTimeout);
 
   it('Account A has been provided with funds to buy the membership', async () => {
@@ -80,20 +75,15 @@ describe('Membership integration tests', () => {
     apiMethods
       .getBalance(aKeyPair.address)
       .then(balance =>
-        assert(
-          balance.toNumber() >= membershipFee,
-          'The account balance is insufficient to purchase membership'
-        )
+        assert(balance.toNumber() >= membershipFee, 'The account balance is insufficient to purchase membership')
       );
   }).timeout(defaultTimeout);
 
   it('Account A was able to buy the membership', async () => {
-    await apiMethods.buyMembership(aKeyPair, 0, 'late_member');
+    await apiMethods.buyMembership(aKeyPair, paidTerms, 'late_member');
     apiMethods
       .getMembership(aKeyPair.address)
-      .then(membership =>
-        assert(!membership.isEmpty, 'Account A is a not member')
-      );
+      .then(membership => assert(!membership.isEmpty, 'Account A is a not member'));
   }).timeout(defaultTimeout);
 
   after(() => {

+ 29 - 28
tests/src/utils/apiMethods.ts

@@ -5,6 +5,7 @@ import { Utils } from './utils';
 import { UserInfo, PaidMembershipTerms } from '@joystream/types/lib/members';
 import { Balance } from '@polkadot/types/interfaces';
 import BN = require('bn.js');
+import { SubmittableExtrinsic } from '@polkadot/api/types';
 
 export class ApiMethods {
   public static async create(provider: WsProvider): Promise<ApiMethods> {
@@ -23,15 +24,12 @@ export class ApiMethods {
 
   public async buyMembership(
     account: KeyringPair,
-    paidTerms: number,
+    paidTermsId: number,
     name: string,
     expectFailure = false
   ): Promise<void> {
     return Utils.signAndSend(
-      this.api.tx.members.buyMembership(
-        paidTerms,
-        new UserInfo({ handle: name, avatar_uri: '', about: '' })
-      ),
+      this.api.tx.members.buyMembership(paidTermsId, new UserInfo({ handle: name, avatar_uri: '', about: '' })),
       account,
       await this.getNonce(account),
       expectFailure
@@ -46,32 +44,17 @@ export class ApiMethods {
     return this.api.query.balances.freeBalance<Balance>(address);
   }
 
-  public async transferBalance(
-    from: KeyringPair,
-    to: string,
-    amount: number,
-    nonce: BN = new BN(-1)
-  ): Promise<void> {
+  public async transferBalance(from: KeyringPair, to: string, amount: number, nonce: BN = new BN(-1)): Promise<void> {
     const _nonce = nonce.isNeg() ? await this.getNonce(from) : nonce;
-    return Utils.signAndSend(
-      this.api.tx.balances.transfer(to, amount),
-      from,
-      _nonce
-    );
+    return Utils.signAndSend(this.api.tx.balances.transfer(to, amount), from, _nonce);
   }
 
-  public getPaidMembershipTerms(
-    paidTermsId: number
-  ): Promise<Option<PaidMembershipTerms>> {
-    return this.api.query.members.paidMembershipTermsById<
-      Option<PaidMembershipTerms>
-    >(paidTermsId);
+  public getPaidMembershipTerms(paidTermsId: number): Promise<Option<PaidMembershipTerms>> {
+    return this.api.query.members.paidMembershipTermsById<Option<PaidMembershipTerms>>(paidTermsId);
   }
 
   public getMembershipFee(paidTermsId: number): Promise<number> {
-    return this.getPaidMembershipTerms(paidTermsId).then(terms =>
-      terms.unwrap().fee.toNumber()
-    );
+    return this.getPaidMembershipTerms(paidTermsId).then(terms => terms.unwrap().fee.toNumber());
   }
 
   public async transferBalanceToAccounts(
@@ -89,8 +72,26 @@ export class ApiMethods {
   }
 
   public getNonce(account: KeyringPair): Promise<BN> {
-    return this.api.query.system
-      .accountNonce(account.address)
-      .then(nonce => new BN(nonce.toString()));
+    return this.api.query.system.accountNonce(account.address).then(nonce => new BN(nonce.toString()));
+  }
+
+  private getBaseTxFee(): number {
+    return this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionBaseFee).toNumber();
+  }
+
+  private estimateTxFee(tx: SubmittableExtrinsic<'promise'>): number {
+    const baseFee: number = this.getBaseTxFee();
+    const byteFee: number = this.api
+      .createType('BalanceOf', this.api.consts.transactionPayment.transactionByteFee)
+      .toNumber();
+    return tx.toHex().length * byteFee + baseFee;
+  }
+
+  public estimateBuyMembershipFee(account: KeyringPair, paidTermsId: number, name: string): number {
+    const nonce: BN = new BN(0);
+    const signedTx = this.api.tx.members
+      .buyMembership(paidTermsId, new UserInfo({ handle: name, avatar_uri: '', about: '' }))
+      .sign(account, { nonce });
+    return this.estimateTxFee(signedTx);
   }
 }

+ 1 - 4
tests/src/utils/utils.ts

@@ -14,10 +14,7 @@ export class Utils {
 
       await signedTx
         .send(async result => {
-          if (
-            result.status.isFinalized === true &&
-            result.events !== undefined
-          ) {
+          if (result.status.isFinalized === true && result.events !== undefined) {
             result.events.forEach(event => {
               if (event.event.method === 'ExtrinsicFailed') {
                 if (expectFailure) {