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: add message support #515

Merged
merged 17 commits into from
Sep 27, 2022
8 changes: 8 additions & 0 deletions .changeset/strong-hotels-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@fuel-ts/contract": patch
"@fuel-ts/providers": patch
"@fuel-ts/transactions": patch
"@fuel-ts/wallet": patch
---

Added message support
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { NativeAssetId } from '@fuel-ts/constants';
import type { BN } from '@fuel-ts/math';
import { bn, toHex } from '@fuel-ts/math';
import { Provider } from '@fuel-ts/providers';
import { TestUtils } from '@fuel-ts/wallet';
import { Provider, ScriptTransactionRequest } from '@fuel-ts/providers';
import type { Message } from '@fuel-ts/providers';
import { Wallet, TestUtils } from '@fuel-ts/wallet';
import { readFileSync } from 'fs';
import { join } from 'path';

Expand Down Expand Up @@ -346,4 +347,117 @@ describe('Coverage Contract', () => {
};
expect(unhexed).toStrictEqual(last);
});

it('should get initial state messages from node', async () => {
const provider = new Provider('http://127.0.0.1:4000/graphql');

const WALLET_A = new Wallet(
'0x1ff16505df75735a5bcf4cb4cf839903120c181dd9be6781b82cda23543bd242',
provider
);
const WALLET_B = new Wallet(
'0x30bb0bc68f5d2ec3b523cee5a65503031b40679d9c72280cd8088c2cfbc34e38',
provider
);

const EXPECTED_MESSAGES_A: Message[] = [
{
amount: bn(1),
sender: WALLET_B.address,
recipient: WALLET_A.address,
data: [8, 7, 6, 5, 4],
nonce: bn(1),
daHeight: bn(0),
},
];
const EXPECTED_MESSAGES_B: Message[] = [
{
amount: bn('12704439083013451934'),
sender: WALLET_A.address,
recipient: WALLET_B.address,
data: [7],
nonce: bn('1017517292834129547'),
daHeight: bn('3684546456337077810'),
},
];

const aMessages = await WALLET_A.getMessages();
const bMessages = await WALLET_B.getMessages();

expect(aMessages).toStrictEqual(EXPECTED_MESSAGES_A);
expect(bMessages).toStrictEqual(EXPECTED_MESSAGES_B);
});

it('should test sending input messages [1]', async () => {
const provider = new Provider('http://127.0.0.1:4000/graphql');
const request = new ScriptTransactionRequest({ gasLimit: 1000000 });

const sender = await TestUtils.generateTestWallet(provider, [[1_000, NativeAssetId]]);
const receiver = await TestUtils.generateTestWallet(provider);

const messages: Message[] = [
{
amount: bn(900),
sender: sender.address,
recipient: receiver.address,
data: [12, 13, 14],
nonce: bn(823),
daHeight: bn(0),
},
];

request.addMessages(messages);
const response = await sender.sendTransaction(request);

await response.wait();
const receiverMessages = await receiver.getMessages();

expect(receiverMessages).toStrictEqual(messages);
});

it('should test sending input messages [3]', async () => {
const provider = new Provider('http://127.0.0.1:4000/graphql');
const request = new ScriptTransactionRequest({ gasLimit: 1000000 });

const sender = await TestUtils.generateTestWallet(provider, [[1_000, NativeAssetId]]);
const receiver = await TestUtils.generateTestWallet(provider);

const messages: Message[] = [
{
amount: bn(111),
sender: sender.address,
recipient: receiver.address,
data: [11, 11, 11],
nonce: bn(100),
daHeight: bn(0),
},
{
amount: bn(222),
sender: sender.address,
recipient: receiver.address,
data: [22, 22, 22],
nonce: bn(200),
daHeight: bn(0),
},
{
amount: bn(333),
sender: sender.address,
recipient: receiver.address,
data: [33, 33, 33],
nonce: bn(300),
daHeight: bn(0),
},
];

request.addMessages(messages);
const response = await sender.sendTransaction(request);

await response.wait();
const receiverMessages = await receiver.getMessages();

// sort by nonce, messages are not guaranteed in order
receiverMessages.sort((a, b) => a.nonce.toNumber() - b.nonce.toNumber());

expect(receiverMessages).toStrictEqual(messages);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl CoverageContract for Contract {
},
}
}

fn echo_u8(input: u8) -> u8 {
input
}
Expand Down
1 change: 1 addition & 0 deletions packages/providers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
export * from './coin-quantity';
export * from './coin';
export * from './provider';
export * from './message';
export { default as Provider } from './provider';
export * from './transaction-request';
export * from './transaction-response';
Expand Down
14 changes: 14 additions & 0 deletions packages/providers/src/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { AbstractAddress } from '@fuel-ts/interfaces';
import type { BN } from '@fuel-ts/math';

/**
* A Fuel message
*/
export type Message = {
amount: BN;
sender: AbstractAddress;
recipient: AbstractAddress;
data: number[];
daHeight: BN;
nonce: BN;
};
31 changes: 31 additions & 0 deletions packages/providers/src/operations.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ fragment coinFragment on Coin {
blockCreated
}

fragment messageFragment on Message {
amount
sender
recipient
data
nonce
daHeight
}

fragment balanceFragment on Balance {
owner
amount
Expand Down Expand Up @@ -254,6 +263,28 @@ query getBalances(
}
}

query getMessages(
$owner: Address!
$after: String
$before: String
$first: Int
$last: Int
) {
messages(
owner: $owner
after: $after
before: $before
first: $first
last: $last
) {
edges {
node {
...messageFragment
}
}
}
}

mutation dryRun($encodedTransaction: HexString!, $utxoValidation: Boolean) {
dryRun(tx: $encodedTransaction, utxoValidation: $utxoValidation) {
...receiptFragment
Expand Down
30 changes: 30 additions & 0 deletions packages/providers/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { arrayify, hexlify } from '@ethersproject/bytes';
import type { Network } from '@ethersproject/networks';
import type { InputValue } from '@fuel-ts/abi-coder';
import { AbiCoder } from '@fuel-ts/abi-coder';
import { Address } from '@fuel-ts/address';
import { NativeAssetId } from '@fuel-ts/constants';
import type { AbstractAddress, AbstractPredicate } from '@fuel-ts/interfaces';
import type { BigNumberish, BN } from '@fuel-ts/math';
import { max, bn, multiply } from '@fuel-ts/math';
import type { Transaction } from '@fuel-ts/transactions';
import {
InputMessageCoder,
GAS_PRICE_FACTOR,
MAX_GAS_PER_TX,
ReceiptType,
Expand All @@ -28,6 +30,7 @@ import { getSdk as getOperationsSdk } from './__generated__/operations';
import type { Coin } from './coin';
import type { CoinQuantity, CoinQuantityLike } from './coin-quantity';
import { coinQuantityfy } from './coin-quantity';
import type { Message } from './message';
import { ScriptTransactionRequest, transactionRequestify } from './transaction-request';
import type { TransactionRequestLike } from './transaction-request';
import type {
Expand Down Expand Up @@ -507,6 +510,33 @@ export default class Provider {
}));
}

/**
* Returns message for the given address
*/
async getMessages(
/** The address to get message from */
address: AbstractAddress,
/** Pagination arguments */
paginationArgs?: CursorPaginationArgs
): Promise<Message[]> {
const result = await this.operations.getMessages({
first: 10,
...paginationArgs,
owner: address.toB256(),
});

const messages = result.messages.edges!.map((edge) => edge!.node!);

return messages.map((message) => ({
amount: bn(message.amount),
sender: Address.fromAddressOrString(message.sender),
recipient: Address.fromAddressOrString(message.recipient),
data: InputMessageCoder.decodeData(message.data),
daHeight: bn(message.daHeight),
nonce: bn(message.nonce),
}));
}

async buildSpendPredicate(
predicate: AbstractPredicate,
amountToSpend: BigNumberish,
Expand Down
50 changes: 49 additions & 1 deletion packages/providers/src/transaction-request/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@ export type CoinTransactionRequestInput = {
/** Predicate input data (parameters) */
predicateData?: BytesLike;
};
export type MessageTransactionRequestInput = {
type: InputType.Message;

/** Amount of coins */
amount: BigNumberish;

/** Address of sender */
sender: BytesLike;

/** Address of sender */
recipient: BytesLike;

/** Index of witness that authorizes the message */
witnessIndex: number;

/** data of message */
data: number[];

/** Unique nonce of message */
nonce: BigNumberish;

/** Predicate bytecode */
predicate?: BytesLike;

/** Predicate input data (parameters) */
predicateData?: BytesLike;
camsjams marked this conversation as resolved.
Show resolved Hide resolved
};
export type ContractTransactionRequestInput = {
type: InputType.Contract;

Expand All @@ -45,7 +72,10 @@ export type ContractTransactionRequestInput = {
/** Contract ID */
contractId: BytesLike;
};
export type TransactionRequestInput = CoinTransactionRequestInput | ContractTransactionRequestInput;
export type TransactionRequestInput =
| CoinTransactionRequestInput
| ContractTransactionRequestInput
| MessageTransactionRequestInput;

export const inputify = (value: TransactionRequestInput): Input => {
switch (value.type) {
Expand Down Expand Up @@ -89,6 +119,24 @@ export const inputify = (value: TransactionRequestInput): Input => {
contractID: hexlify(value.contractId),
};
}
case InputType.Message: {
const predicate = arrayify(value.predicate ?? '0x');
const predicateData = arrayify(value.predicateData ?? '0x');
return {
type: InputType.Message,
sender: hexlify(value.sender),
recipient: hexlify(value.recipient),
amount: bn(value.amount),
nonce: bn(value.nonce),
witnessIndex: value.witnessIndex,
dataLength: value.data.length,
predicateLength: predicate.length,
predicateDataLength: predicateData.length,
data: value.data,
predicate: hexlify(predicate),
predicateData: hexlify(predicateData),
};
}
default: {
throw new Error('Invalid Input type');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import type { Coin } from '../coin';
import type { CoinQuantity, CoinQuantityLike } from '../coin-quantity';
import { coinQuantityfy } from '../coin-quantity';
import type { Message } from '../message';
import { calculatePriceWithFactor } from '../util';

import type {
Expand Down Expand Up @@ -328,6 +329,31 @@ abstract class BaseTransactionRequest implements BaseTransactionRequestLike {
amount: gasFee.isZero() ? bn(1) : gasFee,
};
}

/**
* Converts the given Message to a MessageInput
*/
addMessage(message: Message) {
let witnessIndex = this.getCoinInputWitnessIndexByOwner(message.recipient);

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

// Insert the MessageInput
this.pushInput({
type: InputType.Message,
...message,
sender: message.sender.toBytes(),
recipient: message.recipient.toBytes(),
witnessIndex,
});
}

addMessages(messages: ReadonlyArray<Message>) {
messages.forEach((message) => this.addMessage(message));
}
}

export interface ScriptTransactionRequestLike extends BaseTransactionRequestLike {
Expand Down
1 change: 1 addition & 0 deletions packages/transactions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"license": "Apache-2.0",
"dependencies": {
"@ethersproject/bytes": "^5.4.0",
"@ethersproject/sha2": "^5.5.0",
"@fuel-ts/abi-coder": "workspace:*",
"@fuel-ts/constants": "workspace:*",
"@fuel-ts/math": "workspace:*"
Expand Down
Loading