Skip to content

Commit 1f9734e

Browse files
committed
graphql-alt: Epoch.coinDenyList
## Description Fetch the state of `0x403` at the start of the epoch. This object can be modified during the epoch, but reads in that epoch will not be affected by writes in the same epoch, so it is always safe to treat all reads in an epoch as coming from this version of the object. ## Test plan New E2E tests: ``` sui$ cargo nextest run \ -p sui-indexer-alt-e2e-tests \ -- graphql/epochs/coin_deny_list ```
1 parent e30e641 commit 1f9734e

File tree

7 files changed

+197
-19
lines changed

7 files changed

+197
-19
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Mysten Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//# init --protocol-version 70 --accounts A --addresses P=0x0 --simulator
5+
6+
//# advance-epoch
7+
8+
//# publish --sender A
9+
module P::coin {
10+
use sui::coin::{Self, CoinMetadata, DenyCapV2, TreasuryCap};
11+
use sui::deny_list::DenyList;
12+
13+
public struct COIN() has drop;
14+
15+
public struct Bundle has key, store {
16+
id: UID,
17+
treasury: TreasuryCap<COIN>,
18+
deny: DenyCapV2<COIN>,
19+
metadata: CoinMetadata<COIN>,
20+
}
21+
22+
fun init(otw: COIN, ctx: &mut TxContext) {
23+
let (treasury, deny, metadata) = coin::create_regulated_currency_v2(
24+
otw,
25+
9,
26+
b"COIN",
27+
b"Coin",
28+
b"A test coin",
29+
option::none(),
30+
true,
31+
ctx,
32+
);
33+
34+
transfer::public_share_object(Bundle {
35+
id: object::new(ctx),
36+
treasury,
37+
deny,
38+
metadata,
39+
});
40+
}
41+
42+
public fun poke_deny_list(
43+
deny_list: &mut DenyList,
44+
bundle: &mut Bundle,
45+
ctx: &mut TxContext,
46+
) {
47+
coin::deny_list_v2_add(deny_list, &mut bundle.deny, @0x1234, ctx)
48+
}
49+
}
50+
51+
//# programmable --sender A --inputs object(0x403) object(2,0)
52+
//> P::coin::poke_deny_list(Input(0), Input(1))
53+
54+
//# create-checkpoint
55+
56+
//# advance-epoch
57+
58+
//# create-checkpoint
59+
60+
//# run-graphql
61+
{
62+
e0: epoch(epochId: 0) {
63+
coinDenyList { address version }
64+
}
65+
66+
e1: epoch(epochId: 1) {
67+
coinDenyList { address version }
68+
}
69+
70+
e2: epoch(epochId: 2) {
71+
coinDenyList { address version }
72+
}
73+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
source: external-crates/move/crates/move-transactional-test-runner/src/framework.rs
3+
---
4+
processed 8 tasks
5+
6+
init:
7+
A: object(0,0)
8+
9+
task 1, line 6:
10+
//# advance-epoch
11+
Epoch advanced: 0
12+
13+
task 2, lines 8-49:
14+
//# publish --sender A
15+
created: object(2,0), object(2,1), object(2,2)
16+
mutated: object(0,0)
17+
gas summary: computation_cost: 1000000, storage_cost: 13862400, storage_rebate: 0, non_refundable_storage_fee: 0
18+
19+
task 3, lines 51-52:
20+
//# programmable --sender A --inputs object(0x403) object(2,0)
21+
//> P::coin::poke_deny_list(Input(0), Input(1))
22+
events: Event { package_id: P, transaction_module: Identifier("coin"), sender: A, type_: StructTag { address: sui, module: Identifier("deny_list"), name: Identifier("PerTypeConfigCreated"), type_params: [] }, contents: [0, 0, 0, 0, 0, 0, 0, 0, 76, 102, 102, 53, 56, 51, 56, 100, 56, 52, 102, 49, 50, 48, 50, 100, 53, 53, 56, 99, 100, 101, 98, 102, 51, 51, 55, 50, 102, 100, 102, 98, 53, 50, 55, 57, 51, 55, 100, 54, 100, 101, 57, 52, 49, 57, 57, 53, 49, 53, 56, 100, 50, 98, 48, 52, 102, 100, 53, 53, 49, 98, 48, 52, 51, 58, 58, 99, 111, 105, 110, 58, 58, 67, 79, 73, 78, 42, 225, 19, 155, 21, 61, 71, 196, 117, 247, 51, 253, 36, 246, 86, 170, 118, 162, 71, 45, 111, 211, 99, 199, 93, 84, 154, 150, 42, 206, 127, 221] }
23+
created: object(3,0), object(3,1), object(3,2)
24+
mutated: 0x0000000000000000000000000000000000000000000000000000000000000403, object(0,0), object(2,0)
25+
gas summary: computation_cost: 1000000, storage_cost: 12502000, storage_rebate: 3205224, non_refundable_storage_fee: 32376
26+
27+
task 4, line 54:
28+
//# create-checkpoint
29+
Checkpoint created: 2
30+
31+
task 5, line 56:
32+
//# advance-epoch
33+
Epoch advanced: 1
34+
35+
task 6, line 58:
36+
//# create-checkpoint
37+
Checkpoint created: 4
38+
39+
task 7, lines 60-73:
40+
//# run-graphql
41+
Response: {
42+
"data": {
43+
"e0": {
44+
"coinDenyList": {
45+
"address": "0x0000000000000000000000000000000000000000000000000000000000000403",
46+
"version": 1
47+
}
48+
},
49+
"e1": {
50+
"coinDenyList": {
51+
"address": "0x0000000000000000000000000000000000000000000000000000000000000403",
52+
"version": 1
53+
}
54+
},
55+
"e2": {
56+
"coinDenyList": {
57+
"address": "0x0000000000000000000000000000000000000000000000000000000000000403",
58+
"version": 3
59+
}
60+
}
61+
}
62+
}

crates/sui-indexer-alt-graphql/schema.graphql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ type Epoch {
6161
"""
6262
epochId: UInt53!
6363
"""
64+
State of the Coin DenyList object (0x403) at the start of this epoch.
65+
66+
The DenyList controls access to Regulated Coins. Writes to the DenyList are accumulated and only take effect on the next epoch boundary. Consequently, it's possible to determine the state of the DenyList for a transaction by reading it at the start of the epoch the transaction is in.
67+
"""
68+
coinDenyList: Object
69+
"""
6470
The epoch's corresponding protocol configuration, including the feature flags and the configuration options.
6571
"""
6672
protocolConfigs: ProtocolConfigs

crates/sui-indexer-alt-graphql/src/api/types/epoch.rs

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,27 @@ use sui_indexer_alt_reader::{
1010
pg_reader::PgReader,
1111
};
1212
use sui_indexer_alt_schema::epochs::{StoredEpochEnd, StoredEpochStart};
13+
use sui_types::SUI_DENY_LIST_OBJECT_ID;
1314

1415
use crate::{
1516
api::scalars::{big_int::BigInt, date_time::DateTime, uint53::UInt53},
1617
error::RpcError,
1718
scope::Scope,
1819
};
1920

20-
use super::protocol_configs::ProtocolConfigs;
21+
use super::{
22+
object::{self, Object},
23+
protocol_configs::ProtocolConfigs,
24+
};
2125

2226
pub(crate) struct Epoch {
2327
pub(crate) epoch_id: u64,
24-
pub(crate) scope: Scope,
2528
start: EpochStart,
2629
}
2730

2831
#[derive(Clone)]
2932
struct EpochStart {
33+
scope: Scope,
3034
contents: Option<Arc<StoredEpochStart>>,
3135
}
3236

@@ -54,17 +58,37 @@ impl Epoch {
5458

5559
#[graphql(flatten)]
5660
async fn start(&self, ctx: &Context<'_>) -> Result<EpochStart, RpcError> {
57-
self.start.fetch(ctx, &self.scope, self.epoch_id).await
61+
self.start.fetch(ctx, self.epoch_id).await
5862
}
5963

6064
#[graphql(flatten)]
6165
async fn end(&self, ctx: &Context<'_>) -> Result<EpochEnd, RpcError> {
62-
EpochEnd::fetch(ctx, &self.scope, self.epoch_id).await
66+
EpochEnd::fetch(ctx, &self.start.scope, self.epoch_id).await
6367
}
6468
}
6569

6670
#[Object]
6771
impl EpochStart {
72+
/// State of the Coin DenyList object (0x403) at the start of this epoch.
73+
///
74+
/// The DenyList controls access to Regulated Coins. Writes to the DenyList are accumulated and only take effect on the next epoch boundary. Consequently, it's possible to determine the state of the DenyList for a transaction by reading it at the start of the epoch the transaction is in.
75+
async fn coin_deny_list(
76+
&self,
77+
ctx: &Context<'_>,
78+
) -> Result<Option<Object>, RpcError<object::Error>> {
79+
let Some(contents) = &self.contents else {
80+
return Ok(None);
81+
};
82+
83+
Object::checkpoint_bounded(
84+
ctx,
85+
self.scope.clone(),
86+
SUI_DENY_LIST_OBJECT_ID.into(),
87+
(contents.cp_lo as u64).saturating_sub(1).into(),
88+
)
89+
.await
90+
}
91+
6892
/// The epoch's corresponding protocol configuration, including the feature flags and the configuration options.
6993
async fn protocol_configs(&self) -> Option<ProtocolConfigs> {
7094
let Some(contents) = &self.contents else {
@@ -114,8 +138,7 @@ impl Epoch {
114138
pub(crate) fn with_id(scope: Scope, epoch_id: u64) -> Self {
115139
Self {
116140
epoch_id,
117-
scope,
118-
start: EpochStart::empty(),
141+
start: EpochStart::empty(scope),
119142
}
120143
}
121144

@@ -127,37 +150,32 @@ impl Epoch {
127150
scope: Scope,
128151
epoch_id: UInt53,
129152
) -> Result<Option<Self>, RpcError> {
130-
let start = EpochStart::empty()
131-
.fetch(ctx, &scope, epoch_id.into())
132-
.await?;
153+
let start = EpochStart::empty(scope).fetch(ctx, epoch_id.into()).await?;
133154

134155
let Some(contents) = &start.contents else {
135156
return Ok(None);
136157
};
137158

138159
Ok(Some(Self {
139160
epoch_id: contents.epoch as u64,
140-
scope,
141161
start,
142162
}))
143163
}
144164
}
145165

146166
impl EpochStart {
147-
fn empty() -> Self {
148-
Self { contents: None }
167+
fn empty(scope: Scope) -> Self {
168+
Self {
169+
scope,
170+
contents: None,
171+
}
149172
}
150173

151174
/// Attempt to fill the contents. If the contents are already filled, returns a clone,
152175
/// otherwise attempts to fetch from the store. The resulting value may still have an empty
153176
/// contents field, because it could not be found in the store, or the epoch started after the
154177
/// checkpoint being viewed.
155-
async fn fetch(
156-
&self,
157-
ctx: &Context<'_>,
158-
scope: &Scope,
159-
epoch_id: u64,
160-
) -> Result<Self, RpcError> {
178+
async fn fetch(&self, ctx: &Context<'_>, epoch_id: u64) -> Result<Self, RpcError> {
161179
if self.contents.is_some() {
162180
return Ok(self.clone());
163181
}
@@ -171,11 +189,12 @@ impl EpochStart {
171189
return Ok(self.clone());
172190
};
173191

174-
if stored.cp_lo as u64 > scope.checkpoint_viewed_at() {
192+
if stored.cp_lo as u64 > self.scope.checkpoint_viewed_at() {
175193
return Ok(self.clone());
176194
}
177195

178196
Ok(Self {
197+
scope: self.scope.clone(),
179198
contents: Some(Arc::new(stored)),
180199
})
181200
}

crates/sui-indexer-alt-graphql/src/snapshots/sui_indexer_alt_graphql__tests__schema.graphql.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ type Epoch {
6565
"""
6666
epochId: UInt53!
6767
"""
68+
State of the Coin DenyList object (0x403) at the start of this epoch.
69+
70+
The DenyList controls access to Regulated Coins. Writes to the DenyList are accumulated and only take effect on the next epoch boundary. Consequently, it's possible to determine the state of the DenyList for a transaction by reading it at the start of the epoch the transaction is in.
71+
"""
72+
coinDenyList: Object
73+
"""
6874
The epoch's corresponding protocol configuration, including the feature flags and the configuration options.
6975
"""
7076
protocolConfigs: ProtocolConfigs

crates/sui-indexer-alt-graphql/src/snapshots/sui_indexer_alt_graphql__tests__staging.graphql.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ type Epoch {
6565
"""
6666
epochId: UInt53!
6767
"""
68+
State of the Coin DenyList object (0x403) at the start of this epoch.
69+
70+
The DenyList controls access to Regulated Coins. Writes to the DenyList are accumulated and only take effect on the next epoch boundary. Consequently, it's possible to determine the state of the DenyList for a transaction by reading it at the start of the epoch the transaction is in.
71+
"""
72+
coinDenyList: Object
73+
"""
6874
The epoch's corresponding protocol configuration, including the feature flags and the configuration options.
6975
"""
7076
protocolConfigs: ProtocolConfigs

crates/sui-indexer-alt-graphql/staging.graphql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ type Epoch {
6161
"""
6262
epochId: UInt53!
6363
"""
64+
State of the Coin DenyList object (0x403) at the start of this epoch.
65+
66+
The DenyList controls access to Regulated Coins. Writes to the DenyList are accumulated and only take effect on the next epoch boundary. Consequently, it's possible to determine the state of the DenyList for a transaction by reading it at the start of the epoch the transaction is in.
67+
"""
68+
coinDenyList: Object
69+
"""
6470
The epoch's corresponding protocol configuration, including the feature flags and the configuration options.
6571
"""
6672
protocolConfigs: ProtocolConfigs

0 commit comments

Comments
 (0)