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

chore: remove additional dryrun call #1731

Merged
merged 17 commits into from
Feb 14, 2024
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
6 changes: 6 additions & 0 deletions .changeset/modern-adults-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/program": patch
"@fuel-ts/account": patch
---

remove additional dryrun call
2 changes: 0 additions & 2 deletions apps/demo-typegen/src/demo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,6 @@ test('Example script', async () => {
const provider = await Provider.create(FUEL_NETWORK_URL);
const wallet = await generateTestWallet(provider, [[500_000, BaseAssetId]]);

// TODO: investigate why do we need to specify the gasLimit here. If we don't specify it, the call fails saying `FuelError: Gas limit '0' is lower than the required: '19'.`

// #region typegen-demo-script
// #context import { ScriptAbi__factory } from './types';

Expand Down
221 changes: 53 additions & 168 deletions packages/account/src/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@ import { BaseAssetId } from '@fuel-ts/address/configs';
import type { BN } from '@fuel-ts/math';
import { bn } from '@fuel-ts/math';

import type { TxParamsType } from './account';
import { Account } from './account';
import { FUEL_NETWORK_URL } from './configs';
import { TransactionResponse, ScriptTransactionRequest, Provider } from './providers';
import { ScriptTransactionRequest, Provider } from './providers';
import * as providersMod from './providers';
import type {
CallResult,
Coin,
CoinQuantity,
Message,
Resource,
TransactionRequestLike,
} from './providers';
import type { Coin, CoinQuantity, Message, Resource } from './providers';
import { generateTestWallet, seedTestWallet } from './test-utils';
import { Wallet } from './wallet';

Expand Down Expand Up @@ -299,149 +291,13 @@ describe('Account', () => {
expect(addResourcesSpy).toHaveBeenCalledWith(resourcesToSpend);
});

it('should execute transfer just as fine', async () => {
const amount = bn(1);
const assetId = '0x0101010101010101010101010101010101010101010101010101010101010101';
const destination = Address.fromAddressOrString(
'0x0202020202020202020202020202020202020202020202020202020202020202'
);
const txParam: TxParamsType = {
gasLimit: bn(1),
gasPrice: bn(1),
maturity: 1,
};

const transactionCost: providersMod.TransactionCost = {
gasUsed: bn(234),
gasPrice: bn(1),
minGasPrice: bn(1),
maxFee: bn(2),
minFee: bn(1),
receipts: [],
requiredQuantities: [],
maxGas: bn(1),
minGas: bn(1),
usedFee: bn(1),
};

const request = new ScriptTransactionRequest();
vi.spyOn(providersMod, 'ScriptTransactionRequest').mockImplementation(() => request);

const transactionResponse = new TransactionResponse('transactionId', provider);

const addCoinOutputSpy = vi.spyOn(request, 'addCoinOutput');

const fundSpy = vi.spyOn(Account.prototype, 'fund').mockImplementation(() => Promise.resolve());

const sendTransactionSpy = vi
.spyOn(Account.prototype, 'sendTransaction')
.mockImplementation(() => Promise.resolve(transactionResponse));

const getTransactionCost = vi
.spyOn(Provider.prototype, 'getTransactionCost')
.mockImplementation(() => Promise.resolve(transactionCost));

const account = new Account(
'0x09c0b2d1a486c439a87bcba6b46a7a1a23f3897cc83a94521a96da5c23bc58db',
provider
);

await account.transfer(destination, amount, assetId, txParam);

expect(addCoinOutputSpy).toHaveBeenCalledTimes(1);
expect(addCoinOutputSpy).toHaveBeenCalledWith(destination, amount, assetId);

expect(getTransactionCost).toHaveBeenCalledTimes(1);

expect(fundSpy).toHaveBeenCalledTimes(1);
expect(fundSpy).toHaveBeenCalledWith(
request,
transactionCost.requiredQuantities,
transactionCost.maxFee
);

expect(sendTransactionSpy).toHaveBeenCalledTimes(1);
expect(sendTransactionSpy).toHaveBeenCalledWith(request);
});

it('should execute withdrawToBaseLayer just fine', async () => {
const recipient = Address.fromRandom();
const txParams: TxParamsType = {};
const amount = bn(1);

const assetId = '0x0101010101010101010101010101010101010101010101010101010101010101';

const request = new ScriptTransactionRequest();

const quantities: CoinQuantity[] = [
{
amount: bn(1),
assetId,
},
];
const cost: providersMod.TransactionCost = {
gasPrice: bn(1),
gasUsed: bn(1),
maxFee: bn(1),
maxGas: bn(1),
minFee: bn(1),
minGas: bn(1),
minGasPrice: bn(1),
receipts: [],
requiredQuantities: quantities,
usedFee: bn(1),
};

const transactionResponse = {} as unknown as TransactionResponse;

const scriptTransactionRequest = vi
.spyOn(providersMod, 'ScriptTransactionRequest')
.mockImplementation(() => request);

const getTransactionCost = vi
.spyOn(providersMod.Provider.prototype, 'getTransactionCost')
.mockImplementation(() => Promise.resolve(cost));

const fund = vi.spyOn(Account.prototype, 'fund').mockImplementation(() => Promise.resolve());

const sendTransaction = vi
.spyOn(Account.prototype, 'sendTransaction')
.mockImplementation(() => Promise.resolve(transactionResponse));

const account = new Account(
'0x09c0b2d1a486c439a87bcba6b46a7a1a23f3897cc83a94521a96da5c23bc58db',
provider
);

let result = await account.withdrawToBaseLayer(recipient, amount, txParams);

expect(result).toEqual(transactionResponse);

expect(scriptTransactionRequest).toHaveBeenCalledTimes(1);

expect(sendTransaction).toHaveBeenCalledTimes(1);
expect(sendTransaction).toHaveBeenCalledWith(request);

expect(getTransactionCost).toHaveBeenCalledTimes(1);
expect(fund).toHaveBeenCalledTimes(1);

// without txParams
result = await account.withdrawToBaseLayer(recipient, amount);

expect(result).toEqual(transactionResponse);

expect(scriptTransactionRequest).toHaveBeenCalledTimes(2);

expect(sendTransaction).toHaveBeenCalledTimes(2);
expect(sendTransaction).toHaveBeenCalledWith(request);
});

it('should execute sendTransaction just fine', async () => {
const transactionRequestLike: TransactionRequestLike = {
const transactionRequestLike: providersMod.TransactionRequestLike = {
type: providersMod.TransactionType.Script,
};
const transactionRequest = new ScriptTransactionRequest();
const transactionResponse = 'transactionResponse' as unknown as TransactionResponse;
const transactionResponse =
'transactionResponse' as unknown as providersMod.TransactionResponse;

const transactionRequestify = vi.spyOn(providersMod, 'transactionRequestify');

Expand Down Expand Up @@ -473,11 +329,11 @@ describe('Account', () => {
});

it('should execute simulateTransaction just fine', async () => {
const transactionRequestLike: TransactionRequestLike = {
const transactionRequestLike: providersMod.TransactionRequestLike = {
type: providersMod.TransactionType.Script,
};
const transactionRequest = new ScriptTransactionRequest();
const callResult = 'callResult' as unknown as CallResult;
const callResult = 'callResult' as unknown as providersMod.CallResult;

const transactionRequestify = vi
.spyOn(providersMod, 'transactionRequestify')
Expand Down Expand Up @@ -547,25 +403,17 @@ describe('Account', () => {
});

it('can transfer with custom TX Params', async () => {
const sender = await generateTestWallet(provider, [[500_000, BaseAssetId]]);
const receiver = await generateTestWallet(provider);

/* Error out because gas is to low */
await expect(async () => {
const result = await sender.transfer(receiver.address, 1, BaseAssetId, {
gasLimit: 0,
gasPrice,
});
await result.wait();
}).rejects.toThrowError(/Gas limit '0' is lower than the required: ./);
const sender = await generateTestWallet(provider, [[1000, BaseAssetId]]);
const receiver = Wallet.generate({ provider });

const response = await sender.transfer(receiver.address, 1, BaseAssetId, {
gasLimit: 10_000,
gasLimit: 600,
gasPrice,
});

await response.wait();
const senderBalances = await sender.getBalances();
expect(senderBalances).toEqual([{ assetId: BaseAssetId, amount: bn(499_921) }]);
expect(senderBalances).toEqual([{ assetId: BaseAssetId, amount: bn(921) }]);
const receiverBalances = await receiver.getBalances();
expect(receiverBalances).toEqual([{ assetId: BaseAssetId, amount: bn(1) }]);
});
Expand Down Expand Up @@ -667,10 +515,7 @@ describe('Account', () => {
const AMOUNT = 10;
const recipient = Address.fromB256(RECIPIENT_ID);

const tx = await sender.withdrawToBaseLayer(recipient.toB256(), AMOUNT, {
gasPrice,
gasLimit: 10_000,
});
const tx = await sender.withdrawToBaseLayer(recipient.toB256(), AMOUNT);
// #region Message-getMessageProof
const result = await tx.waitForResult();

Expand Down Expand Up @@ -740,4 +585,44 @@ describe('Account', () => {
const senderBalances = await sender.getBalances();
expect(senderBalances).toEqual([{ assetId: BaseAssetId, amount: bn(1499811) }]);
});

it('should ensure gas price and gas limit are validated when transfering amounts', async () => {
const sender = await generateTestWallet(provider);
const receiver = Wallet.generate({ provider });

await expect(async () => {
const result = await sender.transfer(receiver.address, 1, BaseAssetId, {
gasLimit: 0,
});
await result.wait();
}).rejects.toThrowError(/Gas limit '0' is lower than the required: ./);

await expect(async () => {
const result = await sender.transfer(receiver.address, 1, BaseAssetId, {
gasPrice: 0,
});
await result.wait();
}).rejects.toThrowError(/Gas price '0' is lower than the required: ./);
});

it('should ensure gas limit and price are validated when withdraw an amount of base asset', async () => {
const sender = await generateTestWallet(provider, [[10_000, BaseAssetId]]);
const recipient = Address.fromB256(
'0x00000000000000000000000047ba61eec8e5e65247d717ff236f504cf3b0a263'
);

await expect(async () => {
const result = await sender.withdrawToBaseLayer(recipient, 10, {
gasPrice: 0,
});
await result.wait();
}).rejects.toThrowError(/Gas price '0' is lower than the required: ./);

await expect(async () => {
const result = await sender.withdrawToBaseLayer(recipient, 10, {
gasLimit: 0,
});
await result.wait();
}).rejects.toThrowError(/Gas limit '0' is lower than the required: ./);
});
});
65 changes: 58 additions & 7 deletions packages/account/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,19 @@ export class Account extends AbstractAccount {
const request = new ScriptTransactionRequest(params);
request.addCoinOutput(Address.fromAddressOrString(destination), amount, assetId);
const { maxFee, requiredQuantities, gasUsed } = await this.provider.getTransactionCost(request);
const gasPriceToUse = bn(txParams.gasPrice ?? minGasPrice);
const gasLimitToUse = bn(txParams.gasLimit ?? gasUsed);
request.gasPrice = gasPriceToUse;
request.gasLimit = gasLimitToUse;

request.gasPrice = bn(txParams.gasPrice ?? minGasPrice);
request.gasLimit = bn(txParams.gasLimit ?? gasUsed);

this.validateGas({
gasUsed,
gasPrice: request.gasPrice,
gasLimit: request.gasLimit,
minGasPrice,
});

await this.fund(request, requiredQuantities, maxFee);

return request;
}

Expand Down Expand Up @@ -400,7 +408,14 @@ export class Account extends AbstractAccount {
[{ amount: bn(amount), assetId: String(assetId) }]
);

request.gasLimit = bn(params.gasLimit || gasUsed);
request.gasLimit = bn(params.gasLimit ?? gasUsed);

this.validateGas({
gasUsed,
gasPrice: request.gasPrice,
gasLimit: request.gasLimit,
minGasPrice,
});

await this.fund(request, requiredQuantities, maxFee);

Expand All @@ -423,6 +438,8 @@ export class Account extends AbstractAccount {
/** Tx Params */
txParams: TxParamsType = {}
): Promise<TransactionResponse> {
const { minGasPrice } = this.provider.getGasConfig();

const recipientAddress = Address.fromAddressOrString(recipient);
// add recipient and amount to the transaction script code
const recipientDataArray = getBytesCopy(
Expand All @@ -437,7 +454,8 @@ export class Account extends AbstractAccount {
...amountDataArray,
]);

const params = { script, ...txParams };
const params: ScriptTransactionRequestLike = { script, gasPrice: minGasPrice, ...txParams };

const request = new ScriptTransactionRequest(params);
const forwardingQuantities = [{ amount: bn(amount), assetId: BaseAssetId }];

Expand All @@ -446,7 +464,14 @@ export class Account extends AbstractAccount {
forwardingQuantities
);

request.gasLimit = params.gasLimit ? bn(params.gasLimit) : gasUsed;
request.gasLimit = bn(params.gasLimit ?? gasUsed);

this.validateGas({
gasUsed,
gasPrice: request.gasPrice,
gasLimit: request.gasLimit,
minGasPrice,
});

await this.fund(request, requiredQuantities, maxFee);

Expand Down Expand Up @@ -482,4 +507,30 @@ export class Account extends AbstractAccount {
await this.provider.estimateTxDependencies(transactionRequest);
return this.provider.simulate(transactionRequest, { estimateTxDependencies: false });
}

private validateGas({
gasUsed,
gasPrice,
gasLimit,
minGasPrice,
}: {
gasUsed: BN;
gasPrice: BN;
gasLimit: BN;
minGasPrice: BN;
}) {
if (minGasPrice.gt(gasPrice)) {
throw new FuelError(
ErrorCode.GAS_PRICE_TOO_LOW,
`Gas price '${gasPrice}' is lower than the required: '${minGasPrice}'.`
);
}

if (gasUsed.gt(gasLimit)) {
throw new FuelError(
ErrorCode.GAS_LIMIT_TOO_LOW,
`Gas limit '${gasLimit}' is lower than the required: '${gasUsed}'.`
);
}
}
}
Loading
Loading