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

Add support of messages into coinsToSpend #591

Merged
merged 19 commits into from
Sep 14, 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
44 changes: 35 additions & 9 deletions fuel-client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,17 @@ The input/output is a string in RFC3339 format.
"""
scalar DateTime

input ExcludeInput {
"""
Utxos to exclude from the selection.
"""
utxos: [UtxoId!]!
"""
Messages to exclude from the selection.
"""
messages: [MessageId!]!
}

type FailureStatus {
block: Block!
time: DateTime!
Expand Down Expand Up @@ -297,6 +308,7 @@ type InputMessage {


type Message {
messageId: MessageId!
amount: U64!
sender: Address!
recipient: Address!
Expand Down Expand Up @@ -427,18 +439,23 @@ type Query {
health: Boolean!
coin(utxoId: UtxoId!): Coin
coins(filter: CoinFilterInput!, first: Int, after: String, last: Int, before: String): CoinConnection!
"""
For each `spend_query`, get some spendable coins (of asset specified by the query) owned by
`owner` that add up at least the query amount. The returned coins (UTXOs) are actual coins
that can be spent. The number of coins (UXTOs) is optimized to prevent dust accumulation.
Max number of UTXOS and excluded UTXOS can also be specified.
"""
coinsToSpend(owner: Address!, spendQuery: [SpendQueryElementInput!]!, maxInputs: Int, excludedIds: [UtxoId!]): [Coin!]!
contract(id: ContractId!): Contract
contractBalance(contract: ContractId!, asset: AssetId!): ContractBalance!
contractBalances(filter: ContractBalanceFilterInput!, first: Int, after: String, last: Int, before: String): ContractBalanceConnection!
nodeInfo: NodeInfo!
messages(owner: Address, first: Int, after: String, last: Int, before: String): MessageConnection!
"""
For each `query_per_asset`, get some spendable resources(of asset specified by the query) owned by
`owner` that add up at least the query amount. The returned resources are actual resources
that can be spent. The number of resources is optimized to prevent dust accumulation.
Max number of resources and excluded resources can also be specified.

Returns:
The list of spendable resources per asset from the query. The length of the result is
the same as the length of `query_per_asset`. The ordering of assets and `query_per_asset`
is the same.
"""
resourcesToSpend(owner: Address!, queryPerAsset: [SpendQueryElementInput!]!, excludedIds: ExcludeInput): [[Resource!]!]!
}

type Receipt {
Expand Down Expand Up @@ -486,6 +503,11 @@ enum ReceiptType {
MESSAGE_OUT
}

"""
The schema analog of the [`crate::database::utils::Resource`].
"""
union Resource = Coin | Message

enum ReturnType {
RETURN
RETURN_DATA
Expand All @@ -507,13 +529,17 @@ scalar Salt

input SpendQueryElementInput {
"""
Asset ID of the coins
Identifier of the asset to spend.
"""
assetId: AssetId!
"""
Target amount for the query
Target amount for the query.
"""
amount: U64!
"""
The maximum number of currencies for selection.
"""
max: U64
}


Expand Down
60 changes: 33 additions & 27 deletions fuel-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ use schema::{
coin::{
Coin,
CoinByIdArgs,
SpendQueryElementInput,
},
contract::{
Contract,
ContractByIdArgs,
},
resource::SpendQueryElementInput,
tx::{
TxArg,
TxIdArgs,
Expand Down Expand Up @@ -60,7 +60,10 @@ use types::{
TransactionStatus,
};

use crate::client::schema::tx::DryRunArg;
use crate::client::schema::{
resource::ExcludeInput,
tx::DryRunArg,
};
pub use schema::{
PageDirection,
PaginatedResult,
Expand Down Expand Up @@ -105,6 +108,17 @@ where
}
}

pub fn from_strings_errors_to_std_error(errors: Vec<String>) -> io::Error {
let e = errors
.into_iter()
.fold(String::from("Response errors"), |mut s, e| {
s.push_str("; ");
s.push_str(e.as_str());
s
});
io::Error::new(io::ErrorKind::Other, e)
}

impl FuelClient {
pub fn new(url: impl AsRef<str>) -> anyhow::Result<Self> {
Self::from_str(url.as_ref())
Expand All @@ -118,17 +132,9 @@ impl FuelClient {

match (response.data, response.errors) {
(Some(d), _) => Ok(d),
(_, Some(e)) => {
let e = e.into_iter().map(|e| e.message).fold(
String::from("Response errors"),
|mut s, e| {
s.push_str("; ");
s.push_str(e.as_str());
s
},
);
Err(io::Error::new(io::ErrorKind::Other, e))
}
(_, Some(e)) => Err(from_strings_errors_to_std_error(
e.into_iter().map(|e| e.message).collect(),
)),
_ => Err(io::Error::new(io::ErrorKind::Other, "Invalid response")),
}
}
Expand Down Expand Up @@ -419,33 +425,33 @@ impl FuelClient {
Ok(coins)
}

/// Retrieve coins to spend in a transaction
pub async fn coins_to_spend(
/// Retrieve resources to spend in a transaction
pub async fn resources_to_spend(
&self,
owner: &str,
spend_query: Vec<(&str, u64)>,
max_inputs: Option<i32>,
excluded_ids: Option<Vec<&str>>,
) -> io::Result<Vec<schema::coin::Coin>> {
spend_query: Vec<(&str, u64, Option<u64>)>,
// (Utxos, messages)
excluded_ids: Option<(Vec<&str>, Vec<&str>)>,
) -> io::Result<Vec<Vec<schema::resource::Resource>>> {
let owner: schema::Address = owner.parse()?;
let spend_query: Vec<SpendQueryElementInput> = spend_query
.iter()
.map(|(asset_id, amount)| -> Result<_, ConversionError> {
.map(|(asset_id, amount, max)| -> Result<_, ConversionError> {
Ok(SpendQueryElementInput {
asset_id: asset_id.parse()?,
amount: (*amount).into(),
max: (*max).map(|max| max.into()),
})
})
.try_collect()?;
let excluded_ids: Option<Vec<schema::UtxoId>> = excluded_ids
.map(|ids| ids.into_iter().map(schema::UtxoId::from_str).try_collect())
.transpose()?;
let query = schema::coin::CoinsToSpendQuery::build(
&(owner, spend_query, max_inputs, excluded_ids).into(),
let excluded_ids: Option<ExcludeInput> =
excluded_ids.map(ExcludeInput::from_tuple).transpose()?;
let query = schema::resource::ResourcesToSpendQuery::build(
&(owner, spend_query, excluded_ids).into(),
);

let coins = self.query(query).await?.coins_to_spend;
Ok(coins)
let resources_per_asset = self.query(query).await?.resources_to_spend;
Ok(resources_per_asset)
}

pub async fn contract(&self, id: &str) -> io::Result<Option<Contract>> {
Expand Down
1 change: 1 addition & 0 deletions fuel-client/src/client/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod contract;
pub mod message;
pub mod node_info;
pub mod primitives;
pub mod resource;
pub mod tx;

#[derive(cynic::QueryFragment, Debug)]
Expand Down
50 changes: 0 additions & 50 deletions fuel-client/src/client/schema/coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,56 +115,6 @@ pub struct CoinEdge {
pub node: Coin,
}

#[derive(cynic::InputObject, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct SpendQueryElementInput {
/// asset ID of the coins
pub asset_id: AssetId,
/// address of the owner
pub amount: U64,
}

#[derive(cynic::FragmentArguments, Debug)]
pub struct CoinsToSpendArgs {
/// The Address of the utxo owner
owner: Address,
/// The total amount of each asset type to spend
spend_query: Vec<SpendQueryElementInput>,
/// The max number of utxos that can be used
max_inputs: Option<i32>,
/// A list of UtxoIds to exclude from the selection
excluded_ids: Option<Vec<UtxoId>>,
}

pub(crate) type CoinsToSpendArgsTuple = (
Address,
Vec<SpendQueryElementInput>,
Option<i32>,
Option<Vec<UtxoId>>,
);

impl From<CoinsToSpendArgsTuple> for CoinsToSpendArgs {
fn from(r: CoinsToSpendArgsTuple) -> Self {
CoinsToSpendArgs {
owner: r.0,
spend_query: r.1,
max_inputs: r.2,
excluded_ids: r.3,
}
}
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Query",
argument_struct = "CoinsToSpendArgs"
)]
pub struct CoinsToSpendQuery {
#[arguments(owner = &args.owner, spend_query = &args.spend_query, max_inputs = &args.max_inputs, excluded_ids = &args.excluded_ids)]
pub coins_to_spend: Vec<Coin>,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct Coin {
Expand Down
2 changes: 2 additions & 0 deletions fuel-client/src/client/schema/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use super::{
use crate::client::schema::{
schema,
Address,
MessageId,
U64,
};

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct Message {
pub message_id: MessageId,
pub amount: U64,
pub sender: Address,
pub recipient: Address,
Expand Down
6 changes: 6 additions & 0 deletions fuel-client/src/client/schema/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ fuel_type_scalar!(Salt, Salt);
fuel_type_scalar!(TransactionId, Bytes32);
fuel_type_scalar!(MessageId, MessageId);

impl LowerHex for MessageId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
LowerHex::fmt(&self.0 .0, f)
}
}

#[derive(cynic::Scalar, Debug, Clone, Default)]
pub struct UtxoId(pub HexFormatted<::fuel_vm::fuel_tx::UtxoId>);

Expand Down
92 changes: 92 additions & 0 deletions fuel-client/src/client/schema/resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use crate::client::schema::{
coin::Coin,
message::Message,
schema,
Address,
AssetId,
ConversionError,
MessageId,
UtxoId,
U64,
};
use itertools::Itertools;
use std::str::FromStr;

#[derive(cynic::InputObject, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct ExcludeInput {
/// Utxos to exclude from the result.
utxos: Vec<UtxoId>,
/// Messages to exclude from teh result.
messages: Vec<MessageId>,
}

impl ExcludeInput {
pub fn from_tuple(tuple: (Vec<&str>, Vec<&str>)) -> Result<Self, ConversionError> {
let utxos = tuple.0.into_iter().map(UtxoId::from_str).try_collect()?;
let messages = tuple.1.into_iter().map(MessageId::from_str).try_collect()?;

Ok(Self { utxos, messages })
}
}

#[derive(cynic::InputObject, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct SpendQueryElementInput {
/// asset ID of the coins
pub asset_id: AssetId,
/// address of the owner
pub amount: U64,
/// the maximum number of resources per asset from the owner to return.
pub max: Option<U64>,
}

#[derive(cynic::InlineFragments, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub enum Resource {
Coin(Coin),
Message(Message),
}

impl Resource {
pub fn amount(&self) -> u64 {
match self {
Resource::Coin(c) => c.amount.0,
Resource::Message(m) => m.amount.0,
}
}
}

#[derive(cynic::FragmentArguments, Debug)]
pub struct ResourcesToSpendArgs {
/// The `Address` of the assets' resources owner.
owner: Address,
/// The total amount of each asset type to spend
query_per_asset: Vec<SpendQueryElementInput>,
/// A list of ids to exclude from the selection
excluded_ids: Option<ExcludeInput>,
}

pub(crate) type ResourcesToSpendArgsTuple =
(Address, Vec<SpendQueryElementInput>, Option<ExcludeInput>);

impl From<ResourcesToSpendArgsTuple> for ResourcesToSpendArgs {
fn from(r: ResourcesToSpendArgsTuple) -> Self {
ResourcesToSpendArgs {
owner: r.0,
query_per_asset: r.1,
excluded_ids: r.2,
}
}
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Query",
argument_struct = "ResourcesToSpendArgs"
)]
pub struct ResourcesToSpendQuery {
#[arguments(owner = &args.owner, query_per_asset = &args.query_per_asset, excluded_ids = &args.excluded_ids)]
pub resources_to_spend: Vec<Vec<Resource>>,
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ query Query($_0: Address, $_1: Int, $_2: String, $_3: Int, $_4: String) {
edges {
cursor
node {
messageId
amount
sender
recipient
Expand Down
Loading