sui_graphql_rpc/types/
gas.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use async_graphql::connection::Connection;
5use async_graphql::connection::CursorType;
6use async_graphql::connection::Edge;
7use async_graphql::*;
8use sui_types::{
9    base_types::SuiAddress as NativeSuiAddress,
10    effects::{TransactionEffects as NativeTransactionEffects, TransactionEffectsAPI},
11    gas::GasCostSummary as NativeGasCostSummary,
12    transaction::GasData,
13};
14
15use super::{address::Address, big_int::BigInt, object::Object, sui_address::SuiAddress};
16use super::{cursor::Page, object::ObjectKey};
17use crate::consistency::ConsistentIndexCursor;
18use crate::error::Error;
19use crate::types::cursor::JsonCursor;
20
21type CGasPayment = JsonCursor<ConsistentIndexCursor>;
22
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub(crate) struct GasInput {
25    pub owner: SuiAddress,
26    pub price: u64,
27    pub budget: u64,
28    pub payment_obj_keys: Vec<ObjectKey>,
29    /// The checkpoint sequence number at which this was viewed at
30    pub checkpoint_viewed_at: u64,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub(crate) struct GasCostSummary {
35    pub computation_cost: u64,
36    pub storage_cost: u64,
37    pub storage_rebate: u64,
38    pub non_refundable_storage_fee: u64,
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub(crate) struct GasEffects {
43    pub summary: GasCostSummary,
44    pub object_id: SuiAddress,
45    pub object_version: u64,
46    /// The checkpoint sequence number this was viewed at.
47    pub checkpoint_viewed_at: u64,
48}
49
50/// Configuration for this transaction's gas price and the coins used to pay for gas.
51#[Object]
52impl GasInput {
53    /// Address of the owner of the gas object(s) used
54    async fn gas_sponsor(&self) -> Option<Address> {
55        Some(Address {
56            address: self.owner,
57            checkpoint_viewed_at: self.checkpoint_viewed_at,
58        })
59    }
60
61    /// Objects used to pay for a transaction's execution and storage
62    async fn gas_payment(
63        &self,
64        ctx: &Context<'_>,
65        first: Option<u64>,
66        after: Option<CGasPayment>,
67        last: Option<u64>,
68        before: Option<CGasPayment>,
69    ) -> Result<Connection<String, Object>> {
70        // A possible user error during dry run or execution would be to supply a gas payment that
71        // is not a Move object (i.e a package). Even though the transaction would fail to run, this
72        // service will still attempt to present execution results. If the return type of this field
73        // is a `MoveObject`, then GraphQL will fail on the top-level with an internal error.
74        // Instead, we return an `Object` here, so that the rest of the `TransactionBlock` will
75        // still be viewable.
76        let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
77
78        let mut connection = Connection::new(false, false);
79        // Return empty connection if no payment objects
80        if self.payment_obj_keys.is_empty() {
81            return Ok(connection);
82        }
83
84        let Some((prev, next, checkpoint_viewed_at, cs)) = page
85            .paginate_consistent_indices(self.payment_obj_keys.len(), self.checkpoint_viewed_at)?
86        else {
87            return Ok(connection);
88        };
89
90        connection.has_previous_page = prev;
91        connection.has_next_page = next;
92
93        // Collect cursors since we need them twice
94        let cursors: Vec<_> = cs.collect();
95        let objects = Object::query_many(
96            ctx,
97            cursors
98                .iter()
99                .map(|c| self.payment_obj_keys[c.ix].clone())
100                .collect(),
101            checkpoint_viewed_at,
102        )
103        .await?
104        .into_iter()
105        .map(|obj| obj.ok_or_else(|| Error::Internal("Gas object not found".to_string())))
106        .collect::<Result<Vec<_>, _>>()?;
107
108        for (c, obj) in cursors.into_iter().zip(objects) {
109            connection.edges.push(Edge::new(c.encode_cursor(), obj));
110        }
111
112        Ok(connection)
113    }
114
115    /// An unsigned integer specifying the number of native tokens per gas unit this transaction
116    /// will pay (in MIST).
117    async fn gas_price(&self) -> Option<BigInt> {
118        Some(BigInt::from(self.price))
119    }
120
121    /// The maximum number of gas units that can be expended by executing this transaction
122    async fn gas_budget(&self) -> Option<BigInt> {
123        Some(BigInt::from(self.budget))
124    }
125}
126
127/// Breakdown of gas costs in effects.
128#[Object]
129impl GasCostSummary {
130    /// Gas paid for executing this transaction (in MIST).
131    async fn computation_cost(&self) -> Option<BigInt> {
132        Some(BigInt::from(self.computation_cost))
133    }
134
135    /// Gas paid for the data stored on-chain by this transaction (in MIST).
136    async fn storage_cost(&self) -> Option<BigInt> {
137        Some(BigInt::from(self.storage_cost))
138    }
139
140    /// Part of storage cost that can be reclaimed by cleaning up data created by this transaction
141    /// (when objects are deleted or an object is modified, which is treated as a deletion followed
142    /// by a creation) (in MIST).
143    async fn storage_rebate(&self) -> Option<BigInt> {
144        Some(BigInt::from(self.storage_rebate))
145    }
146
147    /// Part of storage cost that is not reclaimed when data created by this transaction is cleaned
148    /// up (in MIST).
149    async fn non_refundable_storage_fee(&self) -> Option<BigInt> {
150        Some(BigInt::from(self.non_refundable_storage_fee))
151    }
152}
153
154/// Effects related to gas (costs incurred and the identity of the smashed gas object returned).
155#[Object]
156impl GasEffects {
157    async fn gas_object(&self, ctx: &Context<'_>) -> Result<Option<Object>> {
158        Object::query(
159            ctx,
160            self.object_id,
161            Object::at_version(self.object_version, self.checkpoint_viewed_at),
162        )
163        .await
164        .extend()
165    }
166
167    async fn gas_summary(&self) -> Option<&GasCostSummary> {
168        Some(&self.summary)
169    }
170}
171
172impl GasEffects {
173    /// `checkpoint_viewed_at` represents the checkpoint sequence number at which this `GasEffects`
174    /// was queried for. This is stored on `GasEffects` so that when viewing that entity's state, it
175    /// will be as if it was read at the same checkpoint.
176    pub(crate) fn from(effects: &NativeTransactionEffects, checkpoint_viewed_at: u64) -> Self {
177        let ((id, version, _digest), _owner) = effects.gas_object();
178        Self {
179            summary: GasCostSummary::from(effects.gas_cost_summary()),
180            object_id: SuiAddress::from(id),
181            object_version: version.value(),
182            checkpoint_viewed_at,
183        }
184    }
185}
186
187impl GasInput {
188    /// `checkpoint_viewed_at` represents the checkpoint sequence number at which this `GasInput`
189    /// was queried for. This is stored on `GasInput` so that when viewing that entity's state, it
190    /// will be as if it was read at the same checkpoint.
191    pub(crate) fn from(s: &GasData, checkpoint_viewed_at: u64) -> Self {
192        let payment_obj_keys = match s.owner {
193            NativeSuiAddress::ZERO => vec![], // system transactions do not have payment objects
194            _ => s
195                .payment
196                .iter()
197                .map(|o| ObjectKey {
198                    object_id: o.0.into(),
199                    version: o.1.value().into(),
200                })
201                .collect(),
202        };
203
204        Self {
205            owner: s.owner.into(),
206            price: s.price,
207            budget: s.budget,
208            payment_obj_keys,
209            checkpoint_viewed_at,
210        }
211    }
212}
213
214impl From<&NativeGasCostSummary> for GasCostSummary {
215    fn from(gcs: &NativeGasCostSummary) -> Self {
216        Self {
217            computation_cost: gcs.computation_cost,
218            storage_cost: gcs.storage_cost,
219            storage_rebate: gcs.storage_rebate,
220            non_refundable_storage_fee: gcs.non_refundable_storage_fee,
221        }
222    }
223}