Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dryRun and simulate will add output variables #549

Merged
merged 5 commits into from
Nov 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .changeset/poor-kiwis-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
"@fuel-ts/abi-coder": minor
"@fuel-ts/address": minor
"@fuel-ts/constants": minor
"@fuel-ts/contract": minor
"@fuel-ts/example-contract": minor
"fuelchain": minor
"fuels": minor
"@fuel-ts/hasher": minor
"@fuel-ts/hdwallet": minor
"@fuel-ts/interfaces": minor
"@fuel-ts/keystore": minor
"@fuel-ts/math": minor
"@fuel-ts/merkle": minor
"@fuel-ts/merkle-shared": minor
"@fuel-ts/merklesum": minor
"@fuel-ts/mnemonic": minor
"@fuel-ts/predicate": minor
"@fuel-ts/providers": minor
"@fuel-ts/script": minor
"@fuel-ts/signer": minor
"@fuel-ts/sparsemerkle": minor
"@fuel-ts/testcases": minor
"@fuel-ts/transactions": minor
"typechain-target-fuels": minor
"@fuel-ts/wallet": minor
"@fuel-ts/wallet-manager": minor
"@fuel-ts/wordlists": minor
---

add output variables to transactions
2 changes: 1 addition & 1 deletion packages/fuel-gauge/src/predicate.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readFileSync } from 'fs';
import { Address, NativeAssetId, bn, toHex, toNumber, Provider, TestUtils, Predicate } from 'fuels';
import type { AbstractAddress, BigNumberish, BN, Wallet, BaseWalletLocked } from 'fuels';
import type { AbstractAddress, BigNumberish, BN, BaseWalletLocked } from 'fuels';
import { join } from 'path';

import testPredicateAddress from '../test-projects/predicate-address';
Expand Down
55 changes: 55 additions & 0 deletions packages/fuel-gauge/src/token-test-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,59 @@ describe('TokenTestContract', () => {
const tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(50));
});

it('Automatically add variableOuputs', async () => {
const [wallet1, wallet2, wallet3] = Array.from({ length: 3 }, () =>
Wallet.generate({ provider })
);

const addresses = [wallet1, wallet2, wallet3].map((wallet) => ({ value: wallet.address }));

const token = await setup();

const functionCallOne = token.functions.mint_to_addresses(10, addresses);
await functionCallOne.dryRun();
await functionCallOne.call();

let balances = await wallet1.getBalances();
let tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(10));

balances = await wallet2.getBalances();
tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(10));

balances = await wallet3.getBalances();
tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(10));

const functionCallTwo = token.functions.mint_to_addresses(10, addresses);
await functionCallTwo.simulate();
await functionCallTwo.call();

balances = await wallet1.getBalances();
tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(20));

balances = await wallet2.getBalances();
tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(20));

balances = await wallet3.getBalances();
tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(20));

await token.functions.mint_to_addresses(10, addresses).call();
balances = await wallet1.getBalances();
tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(30));

balances = await wallet2.getBalances();
tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(30));

balances = await wallet3.getBalances();
tokenBalance = balances.find((b) => b.assetId === token.id.toB256());
expect(tokenBalance?.amount.toHex()).toEqual(toHex(30));
});
});
2 changes: 2 additions & 0 deletions packages/fuel-gauge/test-projects/token_abi/src/main.sw
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use std::{address::Address, contract_id::ContractId, token::*};

abi Token {
fn mint_coins(mint_amount: u64, a: u32);
fn mint_to_addresses(mint_amount: u64, addresses: [Address; 3]);
fn burn_coins(burn_amount: u64, a: u32);
fn force_transfer_coins(coins: u64, asset_id: ContractId, target: ContractId);
fn transfer_coins_to_output(coins: u64, asset_id: ContractId, recipient: Address);
fn get_balance(target: ContractId, asset_id: ContractId) -> u64;
fn get_msg_amount() -> u64;
}
14 changes: 13 additions & 1 deletion packages/fuel-gauge/test-projects/token_contract/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
contract;

use std::{address::Address, context::balance_of, contract_id::ContractId, token::*};
use std::{context::balance_of, context::msg_amount, token::*};
use token_abi::Token;

impl Token for Contract {
fn mint_coins(mint_amount: u64, a: u32) {
mint(mint_amount);
}

fn mint_to_addresses(mint_amount: u64, addresses: [Address; 3]) {
let mut counter = 0;
while counter < 3 {
mint_to_address(mint_amount, addresses[counter]);
counter = counter + 1;
}
}

fn burn_coins(burn_amount: u64, a: u32) {
burn(burn_amount);
}
Expand All @@ -23,4 +31,8 @@ impl Token for Contract {
fn get_balance(target: ContractId, asset_id: ContractId) -> u64 {
balance_of(target, asset_id)
}

fn get_msg_amount() -> u64 {
msg_amount()
}
}
51 changes: 49 additions & 2 deletions packages/providers/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { BigNumberish, BN } from '@fuel-ts/math';
import { max, bn, multiply } from '@fuel-ts/math';
import type { Transaction } from '@fuel-ts/transactions';
import {
TransactionType,
InputMessageCoder,
GAS_PRICE_FACTOR,
MAX_GAS_PER_TX,
Expand All @@ -34,13 +35,19 @@ import type { Message } from './message';
import type { ExcludeResourcesOption, RawCoin, Resources } from './resource';
import { isCoin } from './resource';
import { ScriptTransactionRequest, transactionRequestify } from './transaction-request';
import type { TransactionRequestLike } from './transaction-request';
import type { TransactionRequestLike, TransactionRequest } from './transaction-request';
import type {
TransactionResult,
TransactionResultReceipt,
} from './transaction-response/transaction-response';
import { TransactionResponse } from './transaction-response/transaction-response';
import { calculatePriceWithFactor, getGasUsedFromReceipts } from './util';
import {
calculatePriceWithFactor,
getGasUsedFromReceipts,
getReceiptsWithMissingOutputVariables,
} from './util';

const MAX_RETRIES = 10;

export type CallResult = {
receipts: TransactionResultReceipt[];
Expand Down Expand Up @@ -233,11 +240,15 @@ export default class Provider {

/**
* Submits a transaction to the chain to be executed
* If the transaction is missing VariableOuputs
* the transaction will be mutate and VariableOuputs will be added
*/
async sendTransaction(
transactionRequestLike: TransactionRequestLike
): Promise<TransactionResponse> {
const transactionRequest = transactionRequestify(transactionRequestLike);
await this.addMissingVariableOutputs(transactionRequest);

const encodedTransaction = hexlify(transactionRequest.toTransactionBytes());
const { gasUsed, minGasPrice } = await this.getTransactionCost(transactionRequest, 0);

Expand All @@ -263,12 +274,16 @@ export default class Provider {

/**
* Executes a transaction without actually submitting it to the chain
* If the transaction is missing VariableOuputs
* the transaction will be mutate and VariableOuputs will be added
*/
async call(
transactionRequestLike: TransactionRequestLike,
{ utxoValidation }: ProviderCallParams = {}
): Promise<CallResult> {
const transactionRequest = transactionRequestify(transactionRequestLike);
await this.addMissingVariableOutputs(transactionRequest);

const encodedTransaction = hexlify(transactionRequest.toTransactionBytes());
const { dryRun: gqlReceipts } = await this.operations.dryRun({
encodedTransaction,
Expand All @@ -280,12 +295,44 @@ export default class Provider {
};
}

/**
* Will dryRun a transaction and check for missing VariableOutputs
*
* If there are missing VariableOutputs
* `addVariableOutputs` is called on the transaction.
* This process is done at most 10 times
*/
addMissingVariableOutputs = async (
transactionRequest: TransactionRequest,
tries: number = 0
): Promise<void> => {
let missingOutputVariableCount = 0;

if (transactionRequest.type === TransactionType.Create) {
return;
}

do {
const encodedTransaction = hexlify(transactionRequest.toTransactionBytes());
const { dryRun: gqlReceipts } = await this.operations.dryRun({
encodedTransaction,
utxoValidation: false,
});
const receipts = gqlReceipts.map(processGqlReceipt);
missingOutputVariableCount = getReceiptsWithMissingOutputVariables(receipts).length;
transactionRequest.addVariableOutputs(missingOutputVariableCount);
} while (tries > MAX_RETRIES || missingOutputVariableCount > 0);
};

/**
* Executes a signed transaction without applying the states changes
* on the chain.
* If the transaction is missing VariableOuputs
* the transaction will be mutate and VariableOuputs will be added
*/
async simulate(transactionRequestLike: TransactionRequestLike): Promise<CallResult> {
const transactionRequest = transactionRequestify(transactionRequestLike);
await this.addMissingVariableOutputs(transactionRequest);
QuinnLee marked this conversation as resolved.
Show resolved Hide resolved
const encodedTransaction = hexlify(transactionRequest.toTransactionBytes());
const { dryRun: gqlReceipts } = await this.operations.dryRun({
encodedTransaction,
Expand Down
11 changes: 10 additions & 1 deletion packages/providers/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { BytesLike } from '@ethersproject/bytes';
import { arrayify } from '@ethersproject/bytes';
import type { BN } from '@fuel-ts/math';
import { bn } from '@fuel-ts/math';
import { ReceiptType } from '@fuel-ts/transactions';
import { FAILED_TRANSFER_TO_ADDRESS_SIGNAL, ReceiptType } from '@fuel-ts/transactions';

import type { TransactionResultReceipt } from './transaction-response';

Expand Down Expand Up @@ -32,3 +32,12 @@ export const getGasUsedFromReceipts = (receipts: Array<TransactionResultReceipt>

return bn(0);
};

export const getReceiptsWithMissingOutputVariables = (
receipts: Array<TransactionResultReceipt>
): Array<TransactionResultReceipt> =>
receipts.filter(
(receipt) =>
receipt.type === ReceiptType.Revert &&
receipt.val.toString('hex') === FAILED_TRANSFER_TO_ADDRESS_SIGNAL
);
2 changes: 1 addition & 1 deletion packages/script/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class ScriptResultDecoderError extends Error {
) as TransactionResultRevertReceipt[];
const revertsText = revertReceipts.length
? `Reverts:\n${revertReceipts
.map(({ type, id, ...r }) =>
.map(({ id, ...r }) =>
printLineWithId(id, `${r.val} ${JSON.stringify(r, bigintReplacer)}`)
)
.join('\n')}`
Expand Down
2 changes: 2 additions & 0 deletions packages/transactions/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ export const MAX_PREDICATE_LENGTH = 1024 * 1024;
// TODO: set max predicate data length value
/** Maximum length of predicate data, in bytes. */
export const MAX_PREDICATE_DATA_LENGTH = 1024 * 1024;

export const FAILED_TRANSFER_TO_ADDRESS_SIGNAL = '0xffffffffffff0001';
2 changes: 2 additions & 0 deletions packages/wallet/src/base-locked-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export class BaseWalletLocked extends AbstractWallet {
transactionRequestLike: TransactionRequestLike
): Promise<TransactionResponse> {
const transactionRequest = transactionRequestify(transactionRequestLike);
await this.provider.addMissingVariableOutputs(transactionRequest);
QuinnLee marked this conversation as resolved.
Show resolved Hide resolved
return this.provider.sendTransaction(transactionRequest);
}

Expand All @@ -226,6 +227,7 @@ export class BaseWalletLocked extends AbstractWallet {
*/
async simulateTransaction(transactionRequestLike: TransactionRequestLike): Promise<CallResult> {
const transactionRequest = transactionRequestify(transactionRequestLike);
await this.provider.addMissingVariableOutputs(transactionRequest);
return this.provider.simulate(transactionRequest);
}

Expand Down
4 changes: 2 additions & 2 deletions packages/wallet/src/base-unlocked-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class BaseWalletUnlocked extends BaseWalletLocked {
transactionRequestLike: TransactionRequestLike
): Promise<TransactionResponse> {
const transactionRequest = transactionRequestify(transactionRequestLike);

await this.provider.addMissingVariableOutputs(transactionRequest);
return this.provider.sendTransaction(
await this.populateTransactionWitnessesSignature(transactionRequest)
);
Expand All @@ -95,7 +95,7 @@ export class BaseWalletUnlocked extends BaseWalletLocked {
*/
async simulateTransaction(transactionRequestLike: TransactionRequestLike): Promise<CallResult> {
const transactionRequest = transactionRequestify(transactionRequestLike);

await this.provider.addMissingVariableOutputs(transactionRequest);
QuinnLee marked this conversation as resolved.
Show resolved Hide resolved
return this.provider.call(
await this.populateTransactionWitnessesSignature(transactionRequest),
{
Expand Down