Skip to content

Commit

Permalink
feat: implement generateFakeResources on Account class (#2351)
Browse files Browse the repository at this point in the history
  • Loading branch information
Torres-ssf authored Jun 6, 2024
1 parent af3202c commit b1dbe42
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/lazy-readers-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/account": patch
---

feat: implement `generateFakeResources` on `Account` class
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { TransactionResultReturnDataReceipt } from 'fuels';
import {
FUEL_NETWORK_URL,
Provider,
ReceiptType,
ScriptTransactionRequest,
Wallet,
bn,
} from 'fuels';

import {
DocSnippetProjectsEnum,
getDocsSnippetsForcProject,
} from '../../../test/fixtures/forc-projects';

/**
* @group node
*/
describe(__filename, () => {
it('should generate fake resources just fine', async () => {
const provider = await Provider.create(FUEL_NETWORK_URL);
const wallet = Wallet.generate({ provider });
const baseAssetId = provider.getBaseAssetId();

const { binHexlified: scriptHexBytes } = getDocsSnippetsForcProject(
DocSnippetProjectsEnum.RETURN_SCRIPT
);

// #region generate-fake-resources-2
const transactionRequest = new ScriptTransactionRequest({
gasLimit: bn(62_000),
maxFee: bn(60_000),
script: scriptHexBytes,
});

const resources = wallet.generateFakeResources([
{
amount: bn(100_000),
assetId: baseAssetId,
},
]);

transactionRequest.addResources(resources);

const dryrunResult = await provider.call(transactionRequest);

const returnReceipt = dryrunResult.receipts.find(
(receipt) => receipt.type === ReceiptType.ReturnData
) as TransactionResultReturnDataReceipt;

const { data: returnedValue } = returnReceipt;
// #endregion generate-fake-resources-2

expect(bn(returnedValue).toNumber()).toBe(1337);
expect(dryrunResult.dryRunStatus?.type).toBe('DryRunSuccessStatus');
});
});
1 change: 1 addition & 0 deletions apps/docs-snippets/test/fixtures/forc-projects/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"simple-token-abi",
"echo-configurables",
"transfer-to-address",
"return-script",
"return-true-predicate",
"echo-employee-data-vector",
"whitelisted-address-predicate",
Expand Down
1 change: 1 addition & 0 deletions apps/docs-snippets/test/fixtures/forc-projects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum DocSnippetProjectsEnum {
SUM_OPTION_U8 = 'sum-option-u8',
ECHO_U64_ARRAY = 'echo-u64-array',
RETURN_CONTEXT = 'return-context',
RETURN_SCRIPT = 'return-script',
TOKEN_DEPOSITOR = 'token-depositor',
TOKEN = 'token',
LIQUIDITY_POOL = 'liquidity-pool',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
entry = "main.sw"
license = "Apache-2.0"
name = "return-script"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// #region generate-fake-resources-1
script;

fn main() -> u64 {
return 1337;
}
// #endregion generate-fake-resources-1
4 changes: 4 additions & 0 deletions apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ export default defineConfig({
text: 'Custom Transactions from Contract Calls',
link: '/guide/cookbook/custom-transactions-from-contract-calls',
},
{
text: 'Generate Fake Resources',
link: '/guide/cookbook/generate-fake-resources',
},
{
text: 'Transactions with Multiple Signers',
link: '/guide/cookbook/transactions-with-multiple-signers',
Expand Down
13 changes: 13 additions & 0 deletions apps/docs/src/guide/cookbook/generate-fake-resources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Generate Fake Resources

When working with an unfunded account, you can generate fake resources to perform a dry-run on your transactions. This is useful for testing purposes without the need for real funds.

Below is an example script that returns the value `1337`. You can use fake resources to execute a dry-run of this script and obtain the returned value.

<<< @/../../docs-snippets/test/fixtures/forc-projects/return-script/src/main.sw#generate-fake-resources-1{rust:line-numbers}

To execute a dry-run, use the `Provider.call` method. Ensure you set the `utxo_validation` flag to true, as this script uses fake UTXOs:

<<< @/../../docs-snippets/src/guide/cookbook/generate-fake-resources.test.ts#generate-fake-resources-2{ts:line-numbers}

By setting `utxo_validation` to `true`, you can successfully execute the dry-run and retrieve the returned value from the script without requiring actual funds.
37 changes: 36 additions & 1 deletion packages/account/src/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { bn } from '@fuel-ts/math';
import { PolicyType } from '@fuel-ts/transactions';
import { ASSET_A, ASSET_B } from '@fuel-ts/utils/test-utils';

import type { TransferParams } from './account';
import type { FakeResources, TransferParams } from './account';
import { Account } from './account';
import { FUEL_NETWORK_URL } from './configs';
import { ScriptTransactionRequest, Provider } from './providers';
Expand Down Expand Up @@ -659,6 +659,41 @@ describe('Account', () => {
expect(receiverBalances).toEqual([{ assetId: baseAssetId, amount: bn(110) }]);
});

it('can generate and use fake coins', async () => {
const sender = Wallet.generate({
provider,
});

const amount1 = bn(100_000);
const amount2 = bn(200_000);
const amount3 = bn(300_000);
const amountToTransferBaseAsset = bn(1000);

const fakeCoinsConfig: FakeResources[] = [
{ amount: amount1, assetId: baseAssetId },
{ amount: amount2, assetId: ASSET_A },
{ amount: amount3, assetId: ASSET_B },
];

const fakeCoins = sender.generateFakeResources(fakeCoinsConfig);
const request = new ScriptTransactionRequest({
gasLimit: bn(60_000),
maxFee: bn(62_000),
});

request.addResources(fakeCoins);
request.addCoinOutput(Address.fromRandom(), amountToTransferBaseAsset, baseAssetId);
request.addCoinOutput(Address.fromRandom(), amount2, ASSET_A);
request.addCoinOutput(Address.fromRandom(), amount3, ASSET_B);

const { dryRunStatus } = await provider.call(request, {
utxoValidation: false,
estimateTxDependencies: false,
});

expect(dryRunStatus?.type).toBe('DryRunSuccessStatus');
});

it('can withdraw an amount of base asset using mutiple uxtos', async () => {
const sender = Wallet.generate({
provider,
Expand Down
22 changes: 21 additions & 1 deletion packages/account/src/account.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { UTXO_ID_LEN } from '@fuel-ts/abi-coder';
import { Address } from '@fuel-ts/address';
import { randomBytes } from '@fuel-ts/crypto';
import { ErrorCode, FuelError } from '@fuel-ts/errors';
import { AbstractAccount } from '@fuel-ts/interfaces';
import type { AbstractAddress, BytesLike } from '@fuel-ts/interfaces';
import type { BigNumberish, BN } from '@fuel-ts/math';
import { bn } from '@fuel-ts/math';
import { arrayify, isDefined } from '@fuel-ts/utils';
import { arrayify, hexlify, isDefined } from '@fuel-ts/utils';
import { clone } from 'ramda';

import type { FuelConnector } from './connectors';
Expand Down Expand Up @@ -56,6 +58,8 @@ export type EstimatedTxParams = Pick<
>;
const MAX_FUNDING_ATTEMPTS = 2;

export type FakeResources = Partial<Coin> & Required<Pick<Coin, 'amount' | 'assetId'>>;

/**
* `Account` provides an abstraction for interacting with accounts or wallets on the network.
*/
Expand Down Expand Up @@ -640,6 +644,22 @@ export class Account extends AbstractAccount {
return this.provider.simulate(transactionRequest, { estimateTxDependencies: false });
}

/**
* Generates an array of fake resources based on the provided coins.
*
* @param coins - An array of `FakeResources` objects representing the coins.
* @returns An array of `Resource` objects with generated properties.
*/
generateFakeResources(coins: FakeResources[]): Array<Resource> {
return coins.map((coin) => ({
id: hexlify(randomBytes(UTXO_ID_LEN)),
owner: this.address,
blockCreated: bn(1),
txCreatedIdx: bn(1),
...coin,
}));
}

/** @hidden * */
private validateTransferAmount(amount: BigNumberish) {
if (bn(amount).lte(0)) {
Expand Down
15 changes: 15 additions & 0 deletions packages/account/src/predicate/predicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ErrorCode, FuelError } from '@fuel-ts/errors';
import type { BytesLike } from '@fuel-ts/interfaces';
import { arrayify, hexlify } from '@fuel-ts/utils';

import type { FakeResources } from '../account';
import { Account } from '../account';
import {
transactionRequestify,
Expand Down Expand Up @@ -196,6 +197,20 @@ export class Predicate<TInputData extends InputValue[]> extends Account {
}));
}

/**
* Generates an array of fake resources based on the provided coins.
*
* @param coins - An array of `FakeResources` objects representing the coins.
* @returns An array of `Resource` objects with generated properties.
*/
generateFakeResources(coins: FakeResources[]): Array<Resource> {
return super.generateFakeResources(coins).map((coin) => ({
...coin,
predicate: hexlify(this.bytes),
predicateData: hexlify(this.getPredicateData()),
}));
}

/**
* Sets the configurable constants for the predicate.
*
Expand Down
72 changes: 72 additions & 0 deletions packages/fuel-gauge/src/predicate/predicate-general.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ASSET_A, ASSET_B } from '@fuel-ts/utils/test-utils';
import type { BN, FakeResources } from 'fuels';
import {
Address,
FUEL_NETWORK_URL,
Predicate,
Provider,
ScriptTransactionRequest,
bn,
} from 'fuels';

import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../../test/fixtures';

/**
* @group node
*/
describe('Predicate', () => {
it('can generate and use fake predicate coins', async () => {
const provider = await Provider.create(FUEL_NETWORK_URL);
const baseAssetId = provider.getBaseAssetId();
const { binHexlified, abiContents } = getFuelGaugeForcProject(
FuelGaugeProjectsEnum.PREDICATE_SUM
);

const amount1 = bn(500_000);
const amount2 = bn(200_000);
const amount3 = bn(300_000);
const amountToTransferBaseAsset = bn(1000);

const fakeCoinsConfig: FakeResources[] = [
{ amount: amount1, assetId: baseAssetId },
{ amount: amount2, assetId: ASSET_A },
{ amount: amount3, assetId: ASSET_B },
];

const value2 = bn(200);
const value1 = bn(100);

const predicate = new Predicate<[BN, BN]>({
bytecode: binHexlified,
abi: abiContents,
provider,
inputData: [value1, value2],
});

const fakeCoins = predicate.generateFakeResources(fakeCoinsConfig);

let request = new ScriptTransactionRequest({
gasLimit: bn(270_000),
maxFee: bn(250_000),
});

fakeCoins.forEach((coin) => {
expect(coin.predicate).toBeDefined();
expect(coin.predicateData).toBeDefined();
});

request.addResources(fakeCoins);
request.addCoinOutput(Address.fromRandom(), amountToTransferBaseAsset, baseAssetId);
request.addCoinOutput(Address.fromRandom(), amount2, ASSET_A);
request.addCoinOutput(Address.fromRandom(), amount3, ASSET_B);

request = await provider.estimatePredicates(request);

const { dryRunStatus } = await provider.call(request, {
utxoValidation: false,
estimateTxDependencies: false,
});

expect(dryRunStatus?.type).toBe('DryRunSuccessStatus');
});
});

0 comments on commit b1dbe42

Please sign in to comment.