Skip to content

Commit

Permalink
Query.coins (FuelLabs#100)
Browse files Browse the repository at this point in the history
* Rename `Query.coinsByOwner` to `Add `color` arg to Query.coinsByOwner

* Return only unspent coins from Query.getCoinsByOwner
  • Loading branch information
AlicanC authored Jan 12, 2022
1 parent 2dc715d commit 1c2aa03
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 31 deletions.
12 changes: 11 additions & 1 deletion fuel-client/assets/schema.sdl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ type CoinEdge {
"""
cursor: String!
}
input CoinFilterInput {
"""
address of the owner
"""
owner: HexString256!
"""
color of the coins
"""
color: HexString256
}
type CoinOutput {
to: HexString256!
amount: Int!
Expand Down Expand Up @@ -169,7 +179,7 @@ type Query {
"""
health: Boolean!
coin(id: HexString256!): Coin
coinsByOwner(after: String, before: String, first: Int, last: Int, owner: HexString256!): CoinConnection!
coins(after: String, before: String, first: Int, last: Int, filter: CoinFilterInput!): CoinConnection!
}
type Receipt {
id: HexString256
Expand Down
11 changes: 8 additions & 3 deletions fuel-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,15 +224,20 @@ impl FuelClient {
}

/// Retrieve a page of coins by their owner
pub async fn coins_by_owner(
pub async fn coins(
&self,
owner: &str,
color: Option<&str>,
request: PaginationRequest<String>,
) -> io::Result<PaginatedResult<schema::coin::Coin, String>> {
let owner: HexString256 = owner.parse()?;
let query = schema::coin::CoinsQuery::build(&(owner, request).into());
let color: HexString256 = match color {
Some(color) => color.parse()?,
None => HexString256::default(),
};
let query = schema::coin::CoinsQuery::build(&(owner, color, request).into());

let coins = self.query(query).await?.coins_by_owner.into();
let coins = self.query(query).await?.coins.into();
Ok(coins)
}
}
Expand Down
58 changes: 38 additions & 20 deletions fuel-client/src/client/schema/coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ pub struct CoinByIdQuery {
pub coin: Option<Coin>,
}

#[derive(cynic::FragmentArguments, Debug)]
pub struct CoinsByOwnerConnectionArgs {
/// Select coins based on the `owner` field
#[derive(cynic::InputObject, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct CoinFilterInput {
/// Filter coins based on the `owner` field
pub owner: HexString256,
/// Filter coins based on the `color` field
pub color: Option<HexString256>,
}

#[derive(cynic::FragmentArguments, Debug)]
pub struct CoinsConnectionArgs {
/// Filter coins based on a filter
filter: CoinFilterInput,
/// Skip until coin id (forward pagination)
pub after: Option<String>,
/// Skip until coin id (backward pagination)
Expand All @@ -32,22 +41,28 @@ pub struct CoinsByOwnerConnectionArgs {
pub last: Option<i32>,
}

impl From<(HexString256, PaginationRequest<String>)> for CoinsByOwnerConnectionArgs {
fn from(r: (HexString256, PaginationRequest<String>)) -> Self {
match r.1.direction {
PageDirection::Forward => CoinsByOwnerConnectionArgs {
owner: r.0,
after: r.1.cursor,
impl From<(HexString256, HexString256, PaginationRequest<String>)> for CoinsConnectionArgs {
fn from(r: (HexString256, HexString256, PaginationRequest<String>)) -> Self {
match r.2.direction {
PageDirection::Forward => CoinsConnectionArgs {
filter: CoinFilterInput {
owner: r.0,
color: Some(r.1),
},
after: r.2.cursor,
before: None,
first: Some(r.1.results as i32),
first: Some(r.2.results as i32),
last: None,
},
PageDirection::Backward => CoinsByOwnerConnectionArgs {
owner: r.0,
PageDirection::Backward => CoinsConnectionArgs {
filter: CoinFilterInput {
owner: r.0,
color: Some(r.1),
},
after: None,
before: r.1.cursor,
before: r.2.cursor,
first: None,
last: Some(r.1.results as i32),
last: Some(r.2.results as i32),
},
}
}
Expand All @@ -57,11 +72,11 @@ impl From<(HexString256, PaginationRequest<String>)> for CoinsByOwnerConnectionA
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Query",
argument_struct = "CoinsByOwnerConnectionArgs"
argument_struct = "CoinsConnectionArgs"
)]
pub struct CoinsQuery {
#[arguments(owner = &args.owner, after = &args.after, before = &args.before, first = &args.first, last = &args.last)]
pub coins_by_owner: CoinConnection,
#[arguments(filter = &args.filter, after = &args.after, before = &args.before, first = &args.first, last = &args.last)]
pub coins: CoinConnection,
}

#[derive(cynic::QueryFragment, Debug)]
Expand Down Expand Up @@ -104,7 +119,7 @@ pub struct Coin {
pub status: CoinStatus,
}

#[derive(cynic::Enum, Clone, Copy, Debug)]
#[derive(cynic::Enum, Clone, Copy, Debug, PartialEq)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub enum CoinStatus {
Unspent,
Expand All @@ -127,8 +142,11 @@ mod tests {
#[test]
fn coins_connection_query_gql_output() {
use cynic::QueryBuilder;
let operation = CoinsQuery::build(CoinsByOwnerConnectionArgs {
owner: HexString256::default(),
let operation = CoinsQuery::build(CoinsConnectionArgs {
filter: CoinFilterInput {
owner: HexString256::default(),
color: HexString256::default().into(),
},
after: None,
before: None,
first: None,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
---
source: fuel-client/src/client/schema/coin.rs
assertion_line: 155
expression: operation.query

---
query Query($_0: HexString256!, $_1: String, $_2: String, $_3: Int, $_4: Int) {
coinsByOwner(owner: $_0, after: $_1, before: $_2, first: $_3, last: $_4) {
query Query($_0: CoinFilterInput!, $_1: String, $_2: String, $_3: Int, $_4: Int) {
coins(filter: $_0, after: $_1, before: $_2, first: $_3, last: $_4) {
edges {
cursor
node {
Expand Down
122 changes: 120 additions & 2 deletions fuel-client/tests/coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ use fuel_core::{
model::coin::{Coin, CoinStatus, UtxoId},
service::{Config, FuelService},
};
use fuel_gql_client::client::{FuelClient, PageDirection, PaginationRequest};
use fuel_gql_client::client::{
schema::coin::CoinStatus as SchemaCoinStatus, FuelClient, PageDirection, PaginationRequest,
};
use fuel_storage::Storage;
use fuel_tx::Color;
use fuel_vm::prelude::{Address, Bytes32, Word};

#[tokio::test]
Expand Down Expand Up @@ -76,8 +79,9 @@ async fn first_5_coins() {

// run test
let coins = client
.coins_by_owner(
.coins(
format!("{:#x}", owner).as_str(),
None,
PaginationRequest {
cursor: None,
results: 5,
Expand All @@ -89,3 +93,117 @@ async fn first_5_coins() {
assert!(!coins.results.is_empty());
assert_eq!(coins.results.len(), 5)
}

#[tokio::test]
async fn only_color_filtered_coins() {
let owner = Address::default();
let color = Color::new([1u8; 32]);

// setup test data in the node
let coins: Vec<(Bytes32, Coin)> = (1..10usize)
.map(|i| {
let coin = Coin {
owner,
amount: i as Word,
color: if i <= 5 { color } else { Default::default() },
maturity: Default::default(),
status: CoinStatus::Unspent,
block_created: Default::default(),
};

let utxo_id = UtxoId {
tx_id: Bytes32::from([i as u8; 32]),
output_index: 0,
};
(utxo_id.into(), coin)
})
.collect();

let mut db = Database::default();
for (id, coin) in coins {
Storage::<Bytes32, Coin>::insert(&mut db, &id, &coin).unwrap();
}

// setup server & client
let srv = FuelService::from_database(db, Config::local_node())
.await
.unwrap();
let client = FuelClient::from(srv.bound_address);

// run test
let coins = client
.coins(
format!("{:#x}", owner).as_str(),
Some(format!("{:#x}", Color::new([1u8; 32])).as_str()),
PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
},
)
.await
.unwrap();
assert!(!coins.results.is_empty());
assert_eq!(coins.results.len(), 5);
assert!(coins.results.into_iter().all(|c| color == c.color.into()));
}

#[tokio::test]
async fn only_unspent_coins() {
let owner = Address::default();

// setup test data in the node
let coins: Vec<(Bytes32, Coin)> = (1..10usize)
.map(|i| {
let coin = Coin {
owner,
amount: i as Word,
color: Default::default(),
maturity: Default::default(),
status: if i <= 5 {
CoinStatus::Unspent
} else {
CoinStatus::Spent
},
block_created: Default::default(),
};

let utxo_id = UtxoId {
tx_id: Bytes32::from([i as u8; 32]),
output_index: 0,
};
(utxo_id.into(), coin)
})
.collect();

let mut db = Database::default();
for (id, coin) in coins {
Storage::<Bytes32, Coin>::insert(&mut db, &id, &coin).unwrap();
}

// setup server & client
let srv = FuelService::from_database(db, Config::local_node())
.await
.unwrap();
let client = FuelClient::from(srv.bound_address);

// run test
let coins = client
.coins(
format!("{:#x}", owner).as_str(),
None,
PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
},
)
.await
.unwrap();
assert!(!coins.results.is_empty());
assert_eq!(coins.results.len(), 5);
assert!(coins
.results
.into_iter()
.all(|c| c.status == SchemaCoinStatus::Unspent));
}
24 changes: 21 additions & 3 deletions fuel-core/src/schema/coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::database::{Database, KvStoreError};
use crate::model::coin::{Coin as CoinModel, CoinStatus};
use crate::schema::scalars::{HexString256, U64};
use crate::state::IterDirection;
use async_graphql::InputObject;
use async_graphql::{
connection::{query, Connection, Edge, EmptyFields},
Context, Object,
Expand Down Expand Up @@ -44,6 +45,14 @@ impl Coin {
}
}

#[derive(InputObject)]
struct CoinFilterInput {
/// address of the owner
owner: HexString256,
/// color of the coins
color: Option<HexString256>,
}

#[derive(Default)]
pub struct CoinQuery;

Expand All @@ -61,14 +70,14 @@ impl CoinQuery {
Ok(block)
}

async fn coins_by_owner(
async fn coins(
&self,
ctx: &Context<'_>,
after: Option<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
#[graphql(desc = "address of the owner")] owner: HexString256,
filter: CoinFilterInput,
) -> async_graphql::Result<Connection<HexString256, Coin, EmptyFields, EmptyFields>> {
let db = ctx.data_unchecked::<Database>();

Expand Down Expand Up @@ -100,7 +109,7 @@ impl CoinQuery {
end = after;
}

let owner: Address = owner.into();
let owner: Address = filter.owner.into();

let mut coin_ids = db.owned_coins(owner, start, Some(direction));
let mut started = None;
Expand Down Expand Up @@ -137,6 +146,15 @@ impl CoinQuery {
})
.try_collect()?;

// filter coins by color
let mut coins = coins;
if let Some(color) = filter.color {
coins.retain(|coin| coin.1.color == color.0.into());
}

// filter coins by status
coins.retain(|coin| coin.1.status == CoinStatus::Unspent);

let mut connection =
Connection::new(started.is_some(), records_to_fetch <= coins.len());
connection.append(
Expand Down

0 comments on commit 1c2aa03

Please sign in to comment.