iota_graphql_rpc/types/
gas.rs

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