Skip to content

Commit

Permalink
feat!: update fund to use total resources (#589)
Browse files Browse the repository at this point in the history
* break things

* add cs

* remove coins to spend

* fix property checl
  • Loading branch information
Cameron Manavian authored Nov 10, 2022
1 parent dcd897d commit d44de76
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 75 deletions.
6 changes: 6 additions & 0 deletions .changeset/quick-mugs-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/providers": patch
"@fuel-ts/wallet": patch
---

Use resources for fund
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ export class BaseInvocationScope<TReturn = any> {
this.transactionRequest.inputs = this.transactionRequest.inputs.filter(
(i) => i.type !== InputType.Coin
);
const coins = await this.contract.wallet?.getCoinsToSpend(this.requiredCoins);
this.transactionRequest.addCoins(coins || []);
const resources = await this.contract.wallet?.getResourcesToSpend(this.requiredCoins);
this.transactionRequest.addResources(resources || []);
return this;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/providers/src/coin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { AbstractAddress } from '@fuel-ts/interfaces';
import type { BN } from '@fuel-ts/math';

import { GqlCoinStatus as CoinStatus } from './__generated__/operations';
Expand All @@ -9,7 +10,7 @@ export type Coin = {
id: string;
assetId: string;
amount: BN;
owner: string;
owner: AbstractAddress;
status: CoinStatus;
maturity: number;
blockCreated: BN;
Expand Down
68 changes: 32 additions & 36 deletions packages/providers/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import type { Coin } from './coin';
import type { CoinQuantity, CoinQuantityLike } from './coin-quantity';
import { coinQuantityfy } from './coin-quantity';
import type { Message, MessageProof } from './message';
import type { ExcludeResourcesOption, RawCoin, Resources } from './resource';
import { isCoin } from './resource';
import type { ExcludeResourcesOption, Resource } from './resource';
import { isRawCoin } from './resource';
import { ScriptTransactionRequest, transactionRequestify } from './transaction-request';
import type { TransactionRequestLike, TransactionRequest } from './transaction-request';
import type {
Expand Down Expand Up @@ -402,7 +402,7 @@ export default class Provider {
id: coin.utxoId,
assetId: coin.assetId,
amount: bn(coin.amount),
owner: coin.owner,
owner: Address.fromAddressOrString(coin.owner),
status: coin.status,
maturity: bn(coin.maturity).toNumber(),
blockCreated: bn(coin.blockCreated),
Expand All @@ -419,7 +419,7 @@ export default class Provider {
quantities: CoinQuantityLike[],
/** IDs of excluded resources from the selection. */
excludedIds?: ExcludeResourcesOption
): Promise<Resources> {
): Promise<Resource[]> {
const excludeInput = {
messages: excludedIds?.messages?.map((id) => hexlify(id)) || [],
utxos: excludedIds?.utxos?.map((id) => hexlify(id)) || [],
Expand All @@ -436,33 +436,29 @@ export default class Provider {
excludedIds: excludeInput,
});

return result.resourcesToSpend;
}

/**
* Returns coins for the given owner satisfying the spend query
*/
async getCoinsToSpend(
/** The address to get coins for */
owner: AbstractAddress,
/** The quantities to get */
quantities: CoinQuantityLike[],
/** IDs of coins to exclude */
excludedIds?: BytesLike[]
): Promise<Coin[]> {
const resources = await this.getResourcesToSpend(owner, quantities, { utxos: excludedIds });

const coins = resources.flat().filter(isCoin) as RawCoin[];
return result.resourcesToSpend.flat().map((resource) => {
if (isRawCoin(resource)) {
return {
id: resource.utxoId,
amount: bn(resource.amount),
status: resource.status,
assetId: resource.assetId,
owner: Address.fromAddressOrString(resource.owner),
maturity: bn(resource.maturity).toNumber(),
blockCreated: bn(resource.blockCreated),
};
}

return coins.map((coin) => ({
id: coin.utxoId,
status: coin.status,
assetId: coin.assetId,
amount: bn(coin.amount),
owner: coin.owner,
maturity: bn(coin.maturity).toNumber(),
blockCreated: bn(coin.blockCreated),
}));
return {
sender: Address.fromAddressOrString(resource.sender),
recipient: Address.fromAddressOrString(resource.recipient),
nonce: bn(resource.nonce),
amount: bn(resource.amount),
data: InputMessageCoder.decodeData(resource.data),
daHeight: bn(resource.daHeight),
fuelBlockSpend: bn(resource.fuelBlockSpend),
};
});
}

/**
Expand Down Expand Up @@ -670,7 +666,7 @@ export default class Provider {
predicateOptions?: BuildPredicateOptions,
walletAddress?: AbstractAddress
): Promise<ScriptTransactionRequest> {
const predicateCoins: Coin[] = await this.getCoinsToSpend(predicate.address, [
const predicateResources: Resource[] = await this.getResourcesToSpend(predicate.address, [
[amountToSpend, assetId],
]);
const options = {
Expand All @@ -688,12 +684,12 @@ export default class Provider {
encoded = abiCoder.encode(predicate.types, predicateData);
}

const totalInPredicate: BN = predicateCoins.reduce((prev: BN, coin: Coin) => {
request.addCoin({
const totalInPredicate: BN = predicateResources.reduce((prev: BN, coin: Resource) => {
request.addResource({
...coin,
predicate: predicate.bytes,
predicateData: encoded,
} as Coin);
} as unknown as Resource);
request.outputs = [];

return prev.add(coin.amount);
Expand All @@ -708,8 +704,8 @@ export default class Provider {
}

if (requiredCoinQuantities.length && walletAddress) {
const coins = await this.getCoinsToSpend(walletAddress, requiredCoinQuantities);
request.addCoins(coins);
const resources = await this.getResourcesToSpend(walletAddress, requiredCoinQuantities);
request.addResources(resources);
}

return request;
Expand Down
13 changes: 10 additions & 3 deletions packages/providers/src/resource.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { BytesLike } from '@ethersproject/bytes';

import type { GqlGetResourcesToSpendQuery, GqlCoinStatus } from './__generated__/operations';
import type { Coin } from './coin';
import type { Message } from './message';

export type RawCoin = {
utxoId: string;
Expand All @@ -21,7 +23,8 @@ export type RawMessage = {
daHeight: string;
};

type Resource = RawCoin | RawMessage;
export type RawResource = RawCoin | RawMessage;
export type Resource = Coin | Message;

export type Resources = GqlGetResourcesToSpendQuery['resourcesToSpend'];

Expand All @@ -30,5 +33,9 @@ export type ExcludeResourcesOption = {
messages?: BytesLike[];
};

export const isCoin = (resource: Resource) => 'utxoId' in resource;
export const isMessage = (resource: Resource) => 'recipient' in resource;
export const isRawCoin = (resource: RawResource): resource is RawCoin => 'utxoId' in resource;
export const isRawMessage = (resource: RawResource): resource is RawMessage =>
'recipient' in resource;

export const isCoin = (resource: Resource): resource is Coin => 'id' in resource;
export const isMessage = (resource: Resource): resource is Message => 'recipient' in resource;
70 changes: 66 additions & 4 deletions packages/providers/src/transaction-request/transaction-request.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable max-classes-per-file */
import type { BytesLike } from '@ethersproject/bytes';
import { arrayify, hexlify } from '@ethersproject/bytes';
import { addressify, Address } from '@fuel-ts/address';
import { addressify } from '@fuel-ts/address';
import { NativeAssetId, ZeroBytes32 } from '@fuel-ts/constants';
import type {
AddressLike,
Expand All @@ -24,6 +24,8 @@ import type { Coin } from '../coin';
import type { CoinQuantity, CoinQuantityLike } from '../coin-quantity';
import { coinQuantityfy } from '../coin-quantity';
import type { Message } from '../message';
import type { Resource } from '../resource';
import { isCoin } from '../resource';
import { arraifyFromUint8Array, calculatePriceWithFactor } from '../util';

import type {
Expand All @@ -36,6 +38,7 @@ import type {
TransactionRequestInput,
CoinTransactionRequestInput,
ContractTransactionRequestInput,
MessageTransactionRequestInput,
} from './input';
import { inputify } from './input';
import type { TransactionRequestOutput, ChangeTransactionRequestOutput } from './output';
Expand Down Expand Up @@ -254,11 +257,69 @@ abstract class BaseTransactionRequest implements BaseTransactionRequestLike {
this.updateWitness(witnessIndex, witness);
}

/**
* Converts the given Resource to a ResourceInput with the appropriate witnessIndex and pushes it
*/
addResource(resource: Resource) {
const ownerAddress = isCoin(resource) ? resource.owner : resource.recipient;
const assetId = isCoin(resource) ? resource.assetId : NativeAssetId;
const type = isCoin(resource) ? InputType.Coin : InputType.Message;
let witnessIndex = this.getCoinInputWitnessIndexByOwner(ownerAddress);

// Insert a dummy witness if no witness exists
if (typeof witnessIndex !== 'number') {
witnessIndex = this.createWitness();
}

// Insert the Input
this.pushInput(
isCoin(resource)
? ({
type,
...resource,
owner: resource.owner.toB256(),
witnessIndex,
txPointer: '0x00000000000000000000000000000000',
} as CoinTransactionRequestInput)
: ({
type,
...resource,
sender: resource.sender.toB256(),
recipient: resource.recipient.toB256(),
witnessIndex,
txPointer: '0x00000000000000000000000000000000',
} as MessageTransactionRequestInput)
);

// Find the ChangeOutput for the AssetId of the Resource
const changeOutput = this.getChangeOutputs().find(
(output) => hexlify(output.assetId) === assetId
);

// Throw if the existing ChangeOutput is not for the same owner
if (changeOutput && hexlify(changeOutput.to) !== ownerAddress.toB256()) {
throw new ChangeOutputCollisionError();
}

// Insert a ChangeOutput if it does not exist
if (!changeOutput) {
this.pushOutput({
type: OutputType.Change,
to: ownerAddress.toB256(),
assetId,
});
}
}

addResources(resources: ReadonlyArray<Resource>) {
resources.forEach((resource) => this.addResource(resource));
}

/**
* Converts the given Coin to a CoinInput with the appropriate witnessIndex and pushes it
*/
addCoin(coin: Coin) {
let witnessIndex = this.getCoinInputWitnessIndexByOwner(Address.fromB256(coin.owner));
let witnessIndex = this.getCoinInputWitnessIndexByOwner(coin.owner);

// Insert a dummy witness if no witness exists
if (typeof witnessIndex !== 'number') {
Expand All @@ -269,6 +330,7 @@ abstract class BaseTransactionRequest implements BaseTransactionRequestLike {
this.pushInput({
type: InputType.Coin,
...coin,
owner: coin.owner.toB256(),
witnessIndex,
txPointer: '0x00000000000000000000000000000000',
});
Expand All @@ -279,15 +341,15 @@ abstract class BaseTransactionRequest implements BaseTransactionRequestLike {
);

// Throw if the existing ChangeOutput is not for the same owner
if (changeOutput && hexlify(changeOutput.to) !== coin.owner) {
if (changeOutput && hexlify(changeOutput.to) !== coin.owner.toB256()) {
throw new ChangeOutputCollisionError();
}

// Insert a ChangeOutput if it does not exist
if (!changeOutput) {
this.pushOutput({
type: OutputType.Change,
to: coin.owner,
to: coin.owner.toB256(),
assetId: coin.assetId,
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/script/src/script.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ const callScript = async <TData, TResult>(

// Get and add required coins to the transaction
if (requiredCoinQuantities.length) {
const coins = await wallet.getCoinsToSpend(requiredCoinQuantities);
request.addCoins(coins);
const resources = await wallet.getResourcesToSpend(requiredCoinQuantities);
request.addResources(resources);
}

const response = await wallet.sendTransaction(request);
Expand Down
33 changes: 17 additions & 16 deletions packages/wallet/src/base-locked-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import type {
BuildPredicateOptions,
TransactionResult,
Message,
Resource,
ExcludeResourcesOption,
} from '@fuel-ts/providers';
import {
withdrawScript,
Expand Down Expand Up @@ -66,14 +68,13 @@ export class BaseWalletLocked extends AbstractWallet {
}

/**
* Returns coins satisfying the spend query.
* Returns resources satisfying the spend query.
*/
async getCoinsToSpend(
quantities: CoinQuantityLike[],
/** IDs of coins to exclude */
excludedIds?: BytesLike[]
): Promise<Coin[]> {
return this.provider.getCoinsToSpend(this.address, quantities, excludedIds);
async getResourcesToSpend(
quantities: CoinQuantityLike[] /** IDs of coins to exclude */,
excludedIds?: ExcludeResourcesOption
): Promise<Resource[]> {
return this.provider.getResourcesToSpend(this.address, quantities, excludedIds);
}

/**
Expand Down Expand Up @@ -172,13 +173,13 @@ export class BaseWalletLocked extends AbstractWallet {
}

/**
* Adds coins to the transaction enough to fund it.
* Adds resources to the transaction enough to fund it.
*/
async fund<T extends TransactionRequest>(request: T): Promise<void> {
const fee = request.calculateFee();
const coins = await this.getCoinsToSpend([fee]);
const resources = await this.getResourcesToSpend([fee]);

request.addCoins(coins);
request.addResources(resources);
}

/**
Expand All @@ -205,8 +206,8 @@ export class BaseWalletLocked extends AbstractWallet {
} else {
quantities = [[amount, assetId], fee];
}
const coins = await this.getCoinsToSpend(quantities);
request.addCoins(coins);
const resources = await this.getResourcesToSpend(quantities);
request.addResources(resources);

return this.sendTransaction(request);
}
Expand Down Expand Up @@ -243,8 +244,8 @@ export class BaseWalletLocked extends AbstractWallet {
let quantities: CoinQuantityLike[] = [];
fee.amount.add(amount);
quantities = [fee];
const coins = await this.getCoinsToSpend(quantities);
request.addCoins(coins);
const resources = await this.getResourcesToSpend(quantities);
request.addResources(resources);

return this.sendTransaction(request);
}
Expand Down Expand Up @@ -299,8 +300,8 @@ export class BaseWalletLocked extends AbstractWallet {
}

if (requiredCoinQuantities.length) {
const coins = await this.getCoinsToSpend(requiredCoinQuantities);
request.addCoins(coins);
const resources = await this.getResourcesToSpend(requiredCoinQuantities);
request.addResources(resources);
}

return request;
Expand Down
Loading

1 comment on commit d44de76

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 89.97% 3623/4027
🟡 Branches 72.29% 707/978
🟢 Functions 87.38% 727/832
🟢 Lines 89.99% 3471/3857

Test suite run success

545 tests passing in 49 suites.

Report generated by 🧪jest coverage report action from d44de76

Please sign in to comment.