arda-org / xSuite

🛠️ Init, Build, Test, Deploy MultiversX smart contracts in seconds. The full suite for efficiently developing high-quality contracts.
https://xsuite.dev
MIT License
13 stars 3 forks source link

Add a method to easily add Kvs to an existing account #200

Closed nvergez closed 2 weeks ago

nvergez commented 1 month ago

To be able to add Kvs to an existing account, we need to do something like this:

await contract.setAccount({
  ...(await contract.getAccountWithoutKvs()),
  kvs: [
    await contract.getAccountKvs(),
    e.kvs
      .Mapper("points_by_user_by_pool", e.Usize(1), e.U8(0))
      .Value(e.U(10)),
    e.kvs
      .Mapper("points_by_user_by_pool", e.Usize(1), e.U8(1))
      .Value(e.U(100)),
  ],
});

which is not very readable nor intuitive.

Or create this kind of helper function:

export const giveTokensTo = async (wallet: FSWallet, tokens: Esdt[]) => {
  await wallet.setAccount({
    ...(await wallet.getAccountWithoutKvs()),
    kvs: [await wallet.getAccountKvs(), e.kvs.Esdts(tokens)],
  });
};

We could add an appendKvs method to the Proxy and Worldclasses:

appendKvs(address: AddressLike, kvs: EncodableKvs, options?: { overwrite?: boolean }): Promise<void>;

And to the Contract and Wallet classes:

appendKvs(kvs: EncodableKvs, options?: { overwrite?: boolean }): Promise<void>;

We can discuss about the options parameter. But I think it's important to let people choose if they want to overwrite the existing Kvs or not. I think the default behavior should be to overwrite the existing Kvs.

We can also discuss about the naming: appendKvs, appendAccountKvs, appendAccount, ...

Example usage:

await contract.appendKvs([
  e.kvs
    .Mapper("points_by_user_by_pool", e.Usize(1), e.U8(0))
    .Value(e.U(10)),
  e.kvs
    .Mapper("points_by_user_by_pool", e.Usize(1), e.U8(1))
    .Value(e.U(100)),
]);

await contract.appendKvs({
  mappers: [
    {
      key: ["points_by_user_by_pool", e.Usize(1), e.U8(0)],
      value: e.U(10),
    },
    {
      key: ["points_by_user_by_pool", e.Usize(1), e.U8(1)],
      value: e.U(100),
    },
  ],
});

await wallet.appendKvs({
  esdts: [
    {
      id: "TOKEN-123456",
      amount: 10n ** 18n,
    },
  ],
});
lcswillems commented 1 month ago

What about addKvs? Also, do you have concrete use cases where you need to add kvs?

nvergez commented 1 month ago

Yes addKvs why not!

For example I used a lot the helper giveTokensTo at the start of specific tests where the wallet is already created during a beforeEach

lcswillems commented 1 month ago

Why not directly adding the tokens?

In order to confirm the use case, could you provide a sample code of your use case? Give a configuration where having such helper would have helped.

nvergez commented 4 weeks ago

A use case:

I have a Minter smart contract that allows users to mint NFTs. I have a staking smart contract that allows user to stake the NFTs and get rewards. I have a pool rewards smart contract that owns all the rewards. A user can mint an NFT using his staking rewards (without even claiming them).

I want to test only my minter smart contract, so I need to "mock" the staking contract and pool rewards to be able to test the mint logic:

test("Mint 1 NFT with only rewards", async () => {
  const rewardsAmount = 1_000_000n;

  await stakingContract.setAccount({
    ...(await stakingContract.getAccountWithoutKvs()),
    kvs: [
      await stakingContract.getAccountKvs(),
      e.kvs.Mapper("rewards_by_user", e.Usize(1)).Value(e.U(rewardsAmount)),
    ],
  });
  await poolContract.setAccount({
    ...(await poolContract.getAccountWithoutKvs()),
    kvs: [
      await poolContract.getAccountKvs(),
      e.kvs.Esdts([
        {
          id: tokenId,
          amount: rewardsAmount,
        },
      ]),
    ],
  });

  // Mint

  // Assert everything needed
});

I could just add those Kvs at the initialization of the contract, but it won't let me easily strictly assert my kvs in each test.

nvergez commented 4 weeks ago

Design chosen:

We add the following methods to Contract and Wallet classes, and the equivalent to World and Proxy classes:

addKvs(kvs: EncodableKvs);
addEsdts(esdts: Array<EncodableEsdt>);
addMappers(mappers: Array<EncodableMapper>);