Skip to content

Commit

Permalink
web-wallet: Update to latest w3sper for Stake contract changes
Browse files Browse the repository at this point in the history
- Added missing test for `unstake`

Resolves #3145
  • Loading branch information
ascartabelli committed Dec 9, 2024
1 parent e67917b commit cd77430
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 100 deletions.
2 changes: 2 additions & 0 deletions web-wallet/src/__mocks__/ProtocolDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ export async function getMinimumStake() {
export function load() {}

export async function unload() {}

export function useAsProtocolDriver() {}
1 change: 0 additions & 1 deletion web-wallet/src/lib/mock-data/cache-stake-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export default [
},
faults: 0,
hardFaults: 0,
nonce: 0n,
reward: 11022842680864n,
},
},
Expand Down
1 change: 0 additions & 1 deletion web-wallet/src/lib/mock-data/stakeInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ export default {
},
faults: 0,
hardFaults: 0,
nonce: 0n,
reward: 11022842680864n,
};
35 changes: 24 additions & 11 deletions web-wallet/src/lib/stores/__tests__/walletStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ describe("Wallet store", async () => {
amount: null,
faults: 0,
hardFaults: 0,
nonce: 0n,
reward: 0n,
},
syncStatus: {
Expand Down Expand Up @@ -323,7 +322,7 @@ describe("Wallet store", async () => {
const setPendingNotesSpy = vi.spyOn(walletCache, "setPendingNoteInfo");

/**
* @typedef { "shield" | "stake" | "transfer" | "unshield" | "unstake" | "claimRewards" } TransferMethod
* @typedef { "claimRewards" | "shield" | "stake" | "transfer" | "unshield" | "unstake" } TransferMethod
*/

/**
Expand Down Expand Up @@ -365,13 +364,23 @@ describe("Wallet store", async () => {
nonce: newNonce,
});

expectedTx = isMoonlightTransfer
? {
amount,
gas,
to: toMoonlight,
}
: { amount, gas };
if (isMoonlightTransfer) {
expectedTx = { amount, gas, to: toMoonlight };
} else {
switch (method) {
case "stake":
expectedTx = { amount, gas, topup: false };
break;

case "unstake":
expectedTx = { amount: undefined, gas };
break;

default:
expectedTx = { amount, gas };
break;
}
}
}

// @ts-ignore here args can't be inferred apparently
Expand Down Expand Up @@ -504,6 +513,10 @@ describe("Wallet store", async () => {
setPendingNotesSpy.mockRestore();
});

it("should expose a method to claim the rewards", async () => {
await walletStoreTransferCheck("claimRewards", [amount, gas]);
});

it("should expose a method to shield a given amount from the unshielded account", async () => {
await walletStoreTransferCheck("shield", [amount, gas]);
});
Expand All @@ -524,8 +537,8 @@ describe("Wallet store", async () => {
await walletStoreTransferCheck("unshield", [amount, gas]);
});

it("should expose a method to claim the rewards", async () => {
await walletStoreTransferCheck("claimRewards", [amount, gas]);
it("should expose a method to unstake the staked amount", async () => {
await walletStoreTransferCheck("unstake", [gas]);
});
});

Expand Down
25 changes: 12 additions & 13 deletions web-wallet/src/lib/stores/walletStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const initialState = {
amount: null,
faults: 0,
hardFaults: 0,
nonce: 0n,
reward: 0n,
},
syncStatus: {
Expand Down Expand Up @@ -185,6 +184,18 @@ const clearLocalDataAndInit = (profileGenerator, syncFromBlock) =>
return init(profileGenerator, syncFromBlock);
});

/** @type {WalletStoreServices["claimRewards"]} */
const claimRewards = async (amount, gas) =>
sync()
.then(networkStore.connect)
.then((network) =>
network.execute(
bookkeeper.as(getCurrentProfile()).withdraw(amount).gas(gas)
)
)
.then(updateCacheAfterTransaction)
.then(passThruWithEffects(observeTxRemoval));

/** @type {WalletStoreServices["getTransactionsHistory"]} */
const getTransactionsHistory = async () => transactions;

Expand Down Expand Up @@ -403,18 +414,6 @@ const unstake = async (gas) =>
.then(updateCacheAfterTransaction)
.then(passThruWithEffects(observeTxRemoval));

/** @type {WalletStoreServices["claimRewards"]} */
const claimRewards = async (amount, gas) =>
sync()
.then(networkStore.connect)
.then((network) =>
network.execute(
bookkeeper.as(getCurrentProfile()).withdraw(amount).gas(gas)
)
)
.then(updateCacheAfterTransaction)
.then(passThruWithEffects(observeTxRemoval));

/** @type {WalletStore} */
export default {
abortSync,
Expand Down
26 changes: 13 additions & 13 deletions web-wallet/src/lib/vendor/w3sper.js/src/bookkeeper.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
// Copyright (c) DUSK NETWORK. All rights reserved.

import * as ProtocolDriver from "../src/protocol-driver/mod.js";
import { ProfileGenerator, Profile } from "./profile.js";
import { Profile, ProfileGenerator } from "./profile.js";

import {
Transfer,
UnshieldTransfer,
ShieldTransfer,
StakeTransfer,
Transfer,
UnshieldTransfer,
UnstakeTransfer,
WithdrawStakeRewardTransfer,
} from "../src/transaction.js";
Expand All @@ -26,14 +26,9 @@ class BookEntry {
}

get info() {
const entry = this;
return {
balance(type) {
return entry.bookkeeper.balance(entry.profile[type]);
},
stake() {
return entry.bookkeeper.stakeInfo(entry.profile.account);
},
balance: (type) => this.bookkeeper.balance(this.profile[type]),
stake: () => this.bookkeeper.stakeInfo(this.profile.account),
};
}

Expand All @@ -53,13 +48,17 @@ class BookEntry {
return new StakeTransfer(this).amount(amount);
}

unstake() {
return new UnstakeTransfer(this);
unstake(amount) {
return new UnstakeTransfer(this).amount(amount);
}

withdraw(amount) {
return new WithdrawStakeRewardTransfer(this).amount(amount);
}

topup(amount) {
return new StakeTransfer(this, { topup: true }).amount(amount);
}
}

export class Bookkeeper {
Expand All @@ -74,12 +73,13 @@ export class Bookkeeper {
switch (type) {
case "account":
return await this.#treasury.account(identifier);
case "address":
case "address": {
const notes = await this.#treasury.address(identifier);
const seed = await ProfileGenerator.seedFrom(identifier);
const index = +identifier;

return ProtocolDriver.balance(seed, index, notes);
}
}
}

Expand Down
104 changes: 104 additions & 0 deletions web-wallet/src/lib/vendor/w3sper.js/src/lux.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// @ts-nocheck
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

/**
* A module for converting between the `BigInt` representation of Lux values
* and the human-readable Dusk format.
*
* The Dusk format represents Lux values as floating-point numbers with a fixed
* precision of 9 decimal places. This module ensures precise conversions and
* validates inputs for consistency and correctness.
*
* ## Notes on the Lux Representation
*
* Lux values are stored as unsigned 64-bit integers (`u64` in our contracts),
* meaning they cannot represent negative numbers. This restriction is inherent
* to their data type. Any attempt to format or parse negative numbers would
* violate this constraint and result in invalid operations.
*
* The unsigned 64-bit format also provides a higher maximum value
* (`18446744073709551615`) compared to signed 64-bit integers, which are
* capped at `9223372036854775807`. By restricting values to non-negative
* numbers, the module adheres to the `u64` representation, ensuring accurate
* handling and avoiding unexpected behaviors or loss of precision.
*
* @module lux.js
*/
const DECIMALS = 9;
const SCALE_FACTOR = 10n ** BigInt(DECIMALS);
const FLOATING_POINT_REGEX = /^\d+(\.\d+)?$/;

/**
* Converts a `BigInt` representation of a Lux value into a human-readable
* string formatted in Dusk.
*
* The resulting string represents the Lux value as a floating-point number
* with up to `DECIMALS` decimal places, omitting trailing zeros.
*
* @param {bigint} lux - The `BigInt` representation of the Lux value.
*
* @throws {TypeError} If the input is not a `BigInt`.
* @throws {RangeError} If the input is a negative value.
* @returns {string} A formatted string representing the Lux value in Dusk.
*
* @example usage
* ```js
* formatToDusk(11000000001n); // "11.000000001"
* formatToDusk(11000000000n); // "11"
*```
*/
export function formatToDusk(lux) {
if (typeof lux !== "bigint") {
throw new TypeError(
`Invalid Lux Type: Expected "bigint", but got "${typeof lux}".`
);
}

if (lux < 0n) {
throw new RangeError("Lux values cannot be negative.");
}

const unit = lux / SCALE_FACTOR;
let decimals = lux % SCALE_FACTOR;

if (decimals > 0) {
return `${unit}.${decimals.toString().padStart(DECIMALS, "0").replace(/0+$/, "")}`;
}

return unit.toString();
}

/**
* Parses a Dusk string into a `BigInt` representation of the value in Lux.
*
* The Dusk string must be a valid positive floating-point number representation
* (e.g., "11.000000001"), as Lux values are unsigned by definition.
* Trailing zeros in the fractional part are allowed, and the string will be
* interpreted with a fixed precision of `DECIMALS`.
*
* @param {string} dusk - The string formatted in Dusk to parse.
*
* @throws {TypeError} If the input is not a valid Dusk string.
*
* @returns {bigint} The `BigInt` representation of the Lux value.
*
* @example usage
* ```js
* parseFromDusk("11.000000001"); // 11000000001n
* parseFromDusk("11"); // 11000000000n
* ```
*/
export function parseFromDusk(dusk) {
if (typeof dusk !== "string" || !FLOATING_POINT_REGEX.test(dusk)) {
throw new TypeError(`Cannot parse "${dusk}" as a Lux value.`);
}

const [units, decimals = "0"] = dusk.split(".");

return BigInt(units) * SCALE_FACTOR + BigInt(decimals.padEnd(DECIMALS, "0"));
}
2 changes: 2 additions & 0 deletions web-wallet/src/lib/vendor/w3sper.js/src/mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export * from "./network/mod.js";
export * from "./profile.js";
export * from "./bookkeeper.js";
export * from "./transaction.js";
export * as lux from "./lux.js";
export { useAsProtocolDriver } from "./protocol-driver/mod.js";
16 changes: 4 additions & 12 deletions web-wallet/src/lib/vendor/w3sper.js/src/network/mod.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,6 @@ export { AddressSyncer } from "./syncer/address.js";
export { AccountSyncer } from "./syncer/account.js";
export { Bookmark } from "./bookmark.js";

const abortable = (signal) =>
new Promise((resolve, reject) =>
signal?.aborted ? reject(signal.reason) : resolve(signal)
);

const once = (target, topic) =>
new Promise((resolve) =>
target.addEventListener(topic, resolve, { once: true })
);

export class Network {
#rues;
node;
Expand Down Expand Up @@ -84,8 +74,8 @@ export class Network {
// I suspect is a GraphQL limitation. In the meantime we convert the `Number`
// to a `BigInt` for consistency and future proof of the API's consumers.
get blockHeight() {
return this.query("query { block(height: -1) { header { height } }}").then(
(body) => BigInt(body?.block?.header?.height ?? 0)
return this.query("block(height: -1) { header { height } }").then((body) =>
BigInt(body?.block?.header?.height ?? 0)
);
}

Expand Down Expand Up @@ -113,6 +103,8 @@ export class Network {
}

async query(gql, options = {}) {
gql = gql ? `query { ${gql} }` : "";

const response = await this.#rues.scope("graphql").call.query(gql, options);

switch (response.status) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,14 @@ class StakeAmount {
}

/**
* Holds information about a user's stake, including amount, reward,
* and a nonce to prevent repeat attacks. Also tracks faults.
* Holds information about a user's stake, including amount, reward
* and tracks faults.
*/
class StakeInfo {
/** @type {StakeAmount|null} */
amount;
/** @type {bigint} */
reward;
/** @type {bigint} */
nonce;
/** @type {number} */
faults;
/** @type {number} */
Expand All @@ -48,7 +46,6 @@ class StakeInfo {
constructor() {
this.amount = null;
this.reward = 0n;
this.nonce = 0n;
this.faults = 0;
this.hardFaults = 0;
}
Expand Down Expand Up @@ -78,9 +75,8 @@ class StakeInfo {
}

stakeInfo.reward = view.getBigUint64(40, true);
stakeInfo.nonce = view.getBigUint64(48, true);
stakeInfo.faults = view.getUint8(56);
stakeInfo.hardFaults = view.getUint8(57);
stakeInfo.faults = view.getUint8(48);
stakeInfo.hardFaults = view.getUint8(49);

return Object.freeze(stakeInfo);
}
Expand Down
Loading

0 comments on commit cd77430

Please sign in to comment.