Skip to content

Commit

Permalink
docs: add more thorough examples for predicates (#2345)
Browse files Browse the repository at this point in the history
Co-authored-by: Anderson Arboleya <[email protected]>
Co-authored-by: Daniel Bate <[email protected]>
Co-authored-by: Nedim Salkić <[email protected]>
Co-authored-by: Peter Smith <[email protected]>
Co-authored-by: Sérgio Torres <[email protected]>
Co-authored-by: Dhaiwat <[email protected]>
  • Loading branch information
7 people committed Jun 24, 2024
1 parent a7a7866 commit 1e7e4d8
Show file tree
Hide file tree
Showing 19 changed files with 481 additions and 4 deletions.
4 changes: 4 additions & 0 deletions .changeset/large-kings-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
---

docs: add more thorough examples for predicates
1 change: 1 addition & 0 deletions .knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"templates/**"
],
"ignoreDependencies": [
"@/sway-api/*",
"@fuel-ts/*",
"@internal/fuel-core",
"@internal/forc",
Expand Down
183 changes: 183 additions & 0 deletions apps/create-fuels-counter-guide/src/pages/predicate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { Button } from "@/components/Button";
import { FuelLogo } from "@/components/FuelLogo";
import { Input } from "@/components/Input";
import { Link } from "@/components/Link";
import { useActiveWallet } from "@/hooks/useActiveWallet";
/** @knipignore */
import { TestPredicateAbi__factory } from "@/sway-api";
import { BN, InputValue, Predicate } from "fuels";
import { bn } from "fuels";
import { useState } from "react";
import toast from "react-hot-toast";
import useAsync from "react-use/lib/useAsync";

export default function PredicateExample() {
let baseAssetId: string;

const { wallet, walletBalance, refreshWalletBalance } = useActiveWallet();

const [predicate, setPredicate] = useState<Predicate<InputValue[]>>();

const [predicateBalance, setPredicateBalance] = useState<BN>();

const [pin, setPin] = useState<string>();

useAsync(async () => {
if (wallet) {
baseAssetId = wallet.provider.getBaseAssetId();
const predicate = TestPredicateAbi__factory.createInstance(wallet.provider);
setPredicate(predicate);
setPredicateBalance(await predicate.getBalance());
}
}, [wallet]);

const refreshBalances = async () => {
await refreshWalletBalance?.();
setPredicateBalance(await predicate?.getBalance());
};

const transferFundsToPredicate = async (amount: BN) => {
if (!predicate) {
return toast.error("Predicate not loaded");
}

if (!wallet) {
return toast.error("Wallet not loaded");
}

await wallet.transfer(predicate.address, amount, baseAssetId, {
gasLimit: 10_000,
});

await refreshBalances();

return toast.success("Funds transferred to predicate.");
};

const unlockPredicateAndTransferFundsBack = async (amount: BN) => {
try {
if (!wallet) {
return toast.error("Wallet not loaded");
}

const reInitializePredicate = TestPredicateAbi__factory.createInstance(wallet.provider, [bn(pin)]);

if (!reInitializePredicate) {
return toast.error("Failed to initialize predicate");
}

const tx = await reInitializePredicate.transfer(wallet.address, amount, baseAssetId);
const { isStatusSuccess } = await tx.wait();

if (!isStatusSuccess) {
toast.error("Failed to unlock predicate");
return;
}

if (isStatusSuccess) {
toast.success("Predicate unlocked");
}

await refreshBalances();
} catch (e) {
console.error(e);
toast.error(
"Failed to unlock predicate. You probably entered the wrong pin, or the predicate does not have enough balance. Try again."
);
}
};

// #region change-pin-react-function
const changePin = async () => {
if (!wallet) {
return toast.error("Wallet not loaded");
}
if (!predicate) {
return toast.error("Predicate not loaded");
}

if (walletBalance?.eq(0)) {
return toast.error(
"Your wallet does not have enough funds. Please click the 'Top-up Wallet' button in the top right corner, or use the local faucet."
);
}

if (!pin) {
return toast.error("Please enter a pin");
}

const configurable = { PIN: bn(pin) };
// instantiate predicate with configurable constants
const reInitializePredicate = TestPredicateAbi__factory.createInstance(wallet.provider, [bn(configurable.PIN)], configurable);

if (!reInitializePredicate) {
return toast.error("Failed to initialize predicate");
}

// transferring funds to the predicate
const tx = await wallet.transfer(reInitializePredicate.address, 1000, baseAssetId, {
gasLimit: 10_000,
});

const { isStatusSuccess } = await tx.wait();

if (!isStatusSuccess) {
toast.error("Failed to update pin in predicate");
return;
}

if (isStatusSuccess) {
toast.success("Predicate pin updated");
}

await refreshWalletBalance?.();
};
// #endregion change-pin-react-function

return (
<>
<div className="flex gap-4">
<FuelLogo />
<h3 className="text-2xl font-semibold">Predicate</h3>
</div>

<div className="mt-12 items-baseline flex gap-2">
<h5 className="font-semibold text-xl">Wallet Balance:</h5>
<span className="text-gray-400">{walletBalance?.toString()}</span>
</div>

<div className="items-baseline flex gap-2">
<h5 className="font-semibold text-xl">Predicate Balance:</h5>
<span className="text-gray-400">{predicateBalance?.toString()}</span>
</div>

<Button onClick={async () => await transferFundsToPredicate(bn(1000))}>Transfer 1000 to Predicate</Button>

<Button onClick={changePin}>Change Pin</Button>

<Input
className="w-[300px] mt-8"
value={pin as string}
onChange={(e) => setPin(e.target.value)}
placeholder="Enter a new pin"
/>

<Button onClick={async () => await unlockPredicateAndTransferFundsBack(bn(1000))}>
Unlock Predicate and Transfer 1000 back to Wallet
</Button>

<span className="mt-8 w-[400px] text-gray-400">
Do note that when you 'unlock' a predicate, the predicate also pays for the gas of the transaction. <br />
This is why you will notice that the balance of the predicate gets reduced by 1000 + a nominal gas fee.
</span>

<Link href="https://docs.fuel.network/docs/intro/glossary/#predicate" target="_blank">
Learn more about Predicates
</Link>

<Link href="/" className="mt-12">
Back to Home
</Link>
</>
);
}
2 changes: 1 addition & 1 deletion apps/create-fuels-counter-guide/sway-programs/Forc.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[workspace]
members = ["contract"]
members = ["contract", "predicate"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "test-predicate"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
predicate;

configurable {
PIN: u64 = 1337,
}

fn main(pin: u64) -> bool {
return PIN == pin;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { seedTestWallet } from '@fuel-ts/account/test-utils';
import type { Provider, WalletUnlocked } from 'fuels';
import { ScriptTransactionRequest, bn, Predicate, BN } from 'fuels';

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

/**
* @group node
*/
describe(__filename, () => {
let wallet: WalletUnlocked;
let receiver: WalletUnlocked;
let baseAssetId: string;
let provider: Provider;
let predicate: Predicate<[string]>;

const { abiContents: abi, binHexlified: bin } = getDocsSnippetsForcProject(
DocSnippetProjectsEnum.SIMPLE_PREDICATE
);

const inputAddress = '0xfc05c23a8f7f66222377170ddcbfea9c543dff0dd2d2ba4d0478a4521423a9d4';

beforeAll(async () => {
wallet = await getTestWallet();
receiver = await getTestWallet();
provider = wallet.provider;

baseAssetId = wallet.provider.getBaseAssetId();

predicate = new Predicate<[string]>({
bytecode: bin,
provider: wallet.provider,
abi,
inputData: [inputAddress],
});
await seedTestWallet(predicate, [[100_000_000, baseAssetId]]);
});

it('should get predicate resources and add them to the predicate data', async () => {
// #region interacting-with-predicates-1

// Instantiate the transaction request
const transactionRequest = new ScriptTransactionRequest({
gasLimit: 2000,
maxFee: bn(0),
});

const predicateCoins = await predicate.getResourcesToSpend([
{ amount: 2000, assetId: baseAssetId },
]);

// Add the predicate input and resources
transactionRequest.addResources(predicateCoins);
// #endregion interacting-with-predicates-1

expect(transactionRequest.inputs.length).toBeGreaterThanOrEqual(1);
expect(transactionRequest.outputs.length).toEqual(1);
});

it('should successfully transfer funds to the predicate', async () => {
const transactionRequest = new ScriptTransactionRequest({ gasLimit: 2000, maxFee: bn(0) });
transactionRequest.addCoinOutput(receiver.address, 100, baseAssetId);

const txCost = await provider.getTransactionCost(transactionRequest, {
resourcesOwner: predicate,
});

transactionRequest.gasLimit = txCost.gasUsed;
transactionRequest.maxFee = txCost.maxFee;

await predicate.fund(transactionRequest, txCost);

// #region interacting-with-predicates-2

const result = await predicate.sendTransaction(transactionRequest);

await result.waitForResult();
// #endregion interacting-with-predicates-2

const { isStatusSuccess } = await result.waitForResult();

expect(isStatusSuccess).toBeTruthy();
});

it('should successfully simulate a transaction with predicate', async () => {
// #region interacting-with-predicates-3
const transactionRequest = new ScriptTransactionRequest({ gasLimit: 2000, maxFee: bn(0) });
transactionRequest.addCoinOutput(receiver.address, 1000000, baseAssetId);

const txCost = await provider.getTransactionCost(transactionRequest, {
resourcesOwner: predicate,
});

transactionRequest.gasLimit = txCost.gasUsed;
transactionRequest.maxFee = txCost.maxFee;

await predicate.fund(transactionRequest, txCost);

const result = await predicate.simulateTransaction(transactionRequest);

// #endregion interacting-with-predicates-3

expect(result.receipts).toEqual([
{
type: 1,
id: expect.any(String),
val: expect.any(BN),
pc: expect.any(BN),
is: expect.any(BN),
},
{
type: 9,
gasUsed: expect.any(BN),
result: expect.any(BN),
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,12 @@ describe(__filename, () => {
}
);

const chainId = provider.getChainId();
/*
You can retrieve the transaction ID before actually submitting it to the node
like this:
*/

const chainId = provider.getChainId();
const txId = transactionRequest.getTransactionId(chainId);

const res = await predicate.sendTransaction(transactionRequest);
Expand Down
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 @@ -33,4 +33,5 @@ members = [
"script-signing",
"input-output-types",
"bytecode-input",
"configurable-pin",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "configurable-pin"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// #region predicate-with-configurable-pin-1
predicate;

configurable {
PIN: u64 = 1337,
}

fn main(pin: u64) -> bool {
return PIN == pin;
}

// #endregion predicate-with-configurable-pin-1
Loading

0 comments on commit 1e7e4d8

Please sign in to comment.