Skip to content

Commit 119d763

Browse files
authored
feat: #[event] macros codec selection (#1325)
1 parent 746cf2a commit 119d763

28 files changed

Lines changed: 728 additions & 26 deletions

rs/client-gen-js/src/service_generator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ impl<'a> ServiceGenerator<'a> {
5151
let event_tokens = service
5252
.events
5353
.iter()
54+
.filter(|event| has_scale_codec(&event.annotations))
5455
.map(|event| self.render_event(event, event.entry_id));
5556

5657
let extend_tokens = service.extends.iter().map(|base| {

rs/client-gen-js/tests/generator.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ fn codec_selection() {
7171
!generated.contains("ethabiQuery"),
7272
"expected ethabiQuery to be filtered out, got:\n{generated}"
7373
);
74+
assert!(
75+
generated.contains("subscribeToBothEventEvent"),
76+
"expected subscribeToBothEventEvent to be present"
77+
);
78+
assert!(
79+
generated.contains("subscribeToScaleOnlyEventEvent"),
80+
"expected subscribeToScaleOnlyEventEvent to be present"
81+
);
82+
assert!(
83+
!generated.contains("subscribeToEthabiOnlyEventEvent"),
84+
"expected subscribeToEthabiOnlyEventEvent to be filtered out, got:\n{generated}"
85+
);
7486

7587
assert_snapshot!("codec_selection", generated);
7688
}

rs/client-gen-js/tests/idls/codec.idl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ service CodecTest {
2121
@codec: ethabi
2222
EthabiQuery(p1: bool) -> bool;
2323
}
24+
events {
25+
/// Available through both codecs
26+
@entry_id: 0
27+
BothEvent(u32),
28+
/// SCALE only
29+
@entry_id: 1
30+
@codec: scale
31+
ScaleOnlyEvent(u32),
32+
/// Ethabi only, should be excluded from JS event subscriptions
33+
@entry_id: 2
34+
@codec: ethabi
35+
EthabiOnlyEvent(u32),
36+
}
2437
}
2538

2639
program CodecProgram {

rs/client-gen-js/tests/snapshots/generator__codec_selection.snap

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ source: rs/client-gen-js/tests/generator.rs
33
expression: generated
44
---
55
import {GearApi, HexString} from "@gear-js/api";
6-
import {QueryBuilderWithHeader, TransactionBuilderWithHeader, TypeResolver} from "sails-js";
6+
import {QueryBuilderWithHeader, TransactionBuilderWithHeader, TypeResolver, ZERO_ADDRESS} from "sails-js";
77
import {InterfaceId, SailsMessageHeader} from "sails-js-parser-idl-v2";
8+
import {IStructField} from "sails-js-types";
89

910
export class CodecProgram {
1011
private _typeResolver: TypeResolver;
@@ -42,7 +43,7 @@ export class CodecTest {
4243
return this._typeResolver.registry;
4344
}
4445
public get interfaceId(): InterfaceId {
45-
return InterfaceId.from("0x577754119419635e");
46+
return InterfaceId.from("0x3d3ab867eece8ee1");
4647
}
4748
/**
4849
* Available via both codecs (default)
@@ -90,4 +91,40 @@ export class CodecTest {
9091
this._programId,
9192
);
9293
}
94+
95+
/**
96+
* Available through both codecs
97+
*/
98+
public subscribeToBothEventEvent<T = number>(callback: (eventData: T) => void | Promise<void>): Promise<() => void> {
99+
const interfaceIdu64 = this.interfaceId.asU64();
100+
const eventFields = {"fields":[{"type":"u32"}]}.fields as IStructField[];
101+
const typeStr = this._typeResolver.getStructDef(eventFields, {}, true);
102+
return this._api.gearEvents.subscribeToGearEvent("UserMessageSent", ({ data: { message } }) => {
103+
if (!message.source.eq(this._programId)) return;
104+
if (!message.destination.eq(ZERO_ADDRESS)) return;
105+
106+
const { ok, header } = SailsMessageHeader.tryFromBytes(message.payload);
107+
if (ok && header.interfaceId.asU64() === interfaceIdu64 && header.entryId === 0) {
108+
callback(this.registry.createType(`([u8; 16], ${typeStr})`, message.payload)[1].toJSON() as T);
109+
}
110+
});
111+
}
112+
113+
/**
114+
* SCALE only
115+
*/
116+
public subscribeToScaleOnlyEventEvent<T = number>(callback: (eventData: T) => void | Promise<void>): Promise<() => void> {
117+
const interfaceIdu64 = this.interfaceId.asU64();
118+
const eventFields = {"fields":[{"type":"u32"}]}.fields as IStructField[];
119+
const typeStr = this._typeResolver.getStructDef(eventFields, {}, true);
120+
return this._api.gearEvents.subscribeToGearEvent("UserMessageSent", ({ data: { message } }) => {
121+
if (!message.source.eq(this._programId)) return;
122+
if (!message.destination.eq(ZERO_ADDRESS)) return;
123+
124+
const { ok, header } = SailsMessageHeader.tryFromBytes(message.payload);
125+
if (ok && header.interfaceId.asU64() === interfaceIdu64 && header.entryId === 1) {
126+
callback(this.registry.createType(`([u8; 16], ${typeStr})`, message.payload)[1].toJSON() as T);
127+
}
128+
});
129+
}
93130
}

rs/client-gen-v2/src/events_generator.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use genco::prelude::*;
2+
use sails_idl_ast::codec::has_scale_codec;
23
use sails_idl_parser_v2::{ast, visitor, visitor::Visitor};
34

45
use crate::helpers::generate_doc_comments;
@@ -25,6 +26,15 @@ impl<'ast> EventsModuleGenerator<'ast> {
2526

2627
impl<'ast> Visitor<'ast> for EventsModuleGenerator<'ast> {
2728
fn visit_service_unit(&mut self, service: &'ast ast::ServiceUnit) {
29+
let scale_events = service
30+
.events
31+
.iter()
32+
.filter(|event| has_scale_codec(&event.annotations))
33+
.collect::<Vec<_>>();
34+
if scale_events.is_empty() {
35+
return;
36+
}
37+
2838
let events_name = &format!("{}Events", self.service_name);
2939

3040
quote_in! { self.tokens =>
@@ -46,7 +56,7 @@ impl<'ast> Visitor<'ast> for EventsModuleGenerator<'ast> {
4656
impl $events_name {
4757
pub fn entry_id(&self) -> u16 {
4858
match self {
49-
$(for event in &service.events join ($['\r']) =>
59+
$(for event in &scale_events join ($['\r']) =>
5060
Self::$(&event.name) { .. } => $(event.entry_id),
5161
)
5262
}
@@ -77,6 +87,10 @@ impl<'ast> Visitor<'ast> for EventsModuleGenerator<'ast> {
7787
}
7888

7989
fn visit_service_event(&mut self, event: &'ast ast::ServiceEvent) {
90+
if !has_scale_codec(&event.annotations) {
91+
return;
92+
}
93+
8094
generate_doc_comments(&mut self.tokens, &event.docs);
8195

8296
let variant_name = &event.name;

rs/client-gen-v2/tests/generator.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ fn codec_selection() {
108108
!generated.contains("ethabi_query"),
109109
"expected ethabi_query to be filtered out"
110110
);
111+
112+
assert!(
113+
generated.contains("BothEvent"),
114+
"expected BothEvent to be present"
115+
);
116+
assert!(
117+
generated.contains("ScaleOnlyEvent"),
118+
"expected ScaleOnlyEvent to be present"
119+
);
120+
assert!(
121+
!generated.contains("EthabiOnlyEvent"),
122+
"expected EthabiOnlyEvent to be filtered out"
123+
);
111124
insta::assert_snapshot!(generated);
112125
}
113126

rs/client-gen-v2/tests/idls/codec.idl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ service CodecTest {
2121
@codec: ethabi
2222
EthabiQuery(p1: bool) -> bool;
2323
}
24+
events {
25+
/// Available through both codecs
26+
@entry_id: 0
27+
BothEvent(u32),
28+
/// SCALE only
29+
@entry_id: 1
30+
@codec: scale
31+
ScaleOnlyEvent(u32),
32+
/// Ethabi only, should be excluded from Rust client events
33+
@entry_id: 2
34+
@codec: ethabi
35+
EthabiOnlyEvent(u32),
36+
}
2437
}
2538

2639
program CodecProgram {

rs/client-gen-v2/tests/snapshots/generator__codec_selection.snap

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
source: rs/client-gen-v2/tests/generator.rs
3-
expression: gen_client(idl)
3+
expression: generated
44
---
55
// Code generated by sails-client-gen-v2. DO NOT EDIT.
66
#[cfg(feature = "with_mocks")]
@@ -66,7 +66,7 @@ pub mod codec_test {
6666

6767
impl sails_rs::client::Identifiable for CodecTestImpl {
6868
const INTERFACE_ID: sails_rs::InterfaceId =
69-
sails_rs::InterfaceId::from_bytes_8([87, 119, 84, 17, 148, 25, 99, 94]);
69+
sails_rs::InterfaceId::from_bytes_8([61, 58, 184, 103, 238, 206, 142, 225]);
7070
}
7171

7272
impl<E: sails_rs::client::GearEnv> CodecTest for sails_rs::client::Service<CodecTestImpl, E> {
@@ -95,6 +95,48 @@ pub mod codec_test {
9595
sails_rs::io_struct_impl!(ScaleOnly (p1: u32) -> u32, 1, <super::CodecTestImpl as sails_rs::client::Identifiable>::INTERFACE_ID);
9696
}
9797

98+
#[cfg(not(target_arch = "wasm32"))]
99+
pub mod events {
100+
use super::*;
101+
#[sails_rs::sails_type(crate = sails_rs)]
102+
#[derive(PartialEq, Debug)]
103+
pub enum CodecTestEvents {
104+
/// Available through both codecs
105+
#[codec(index = 0)]
106+
BothEvent(u32),
107+
/// SCALE only
108+
#[codec(index = 1)]
109+
ScaleOnlyEvent(u32),
110+
}
111+
112+
impl CodecTestEvents {
113+
pub fn entry_id(&self) -> u16 {
114+
match self {
115+
Self::BothEvent { .. } => 0,
116+
Self::ScaleOnlyEvent { .. } => 1,
117+
}
118+
}
119+
}
120+
121+
impl sails_rs::client::Event for CodecTestEvents {
122+
fn decode_event(
123+
route: &sails_rs::client::RouteIdx,
124+
payload: impl AsRef<[u8]>,
125+
) -> Result<Self, sails_rs::scale_codec::Error> {
126+
sails_rs::client::decode_event_v2::<Self>(route.0, payload)
127+
}
128+
}
129+
130+
impl sails_rs::client::Identifiable for CodecTestEvents {
131+
const INTERFACE_ID: sails_rs::InterfaceId =
132+
<CodecTestImpl as sails_rs::client::Identifiable>::INTERFACE_ID;
133+
}
134+
135+
impl sails_rs::client::ServiceWithEvents for CodecTestImpl {
136+
type Event = CodecTestEvents;
137+
}
138+
}
139+
98140
#[cfg(feature = "with_mocks")]
99141
#[cfg(not(target_arch = "wasm32"))]
100142
pub mod mockall {

rs/ethexe/macros-tests/tests/eth_event.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,57 @@ fn eth_event_sails_rename() {
5858

5959
insta::assert_snapshot!(result);
6060
}
61+
62+
#[test]
63+
fn eth_event_scale_only() {
64+
let attrs = quote! {
65+
scale
66+
};
67+
let input = quote! {
68+
pub enum Events {
69+
MyEvent1(u128),
70+
MyEvent2,
71+
}
72+
};
73+
let result = event(attrs, input).to_string();
74+
let result = prettyplease::unparse(&syn::parse_str(&result).unwrap());
75+
76+
insta::assert_snapshot!(result);
77+
}
78+
79+
#[test]
80+
fn eth_event_ethabi_only() {
81+
let attrs = quote! {
82+
ethabi
83+
};
84+
let input = quote! {
85+
pub enum Events {
86+
MyEvent1 {
87+
#[indexed]
88+
sender: sails_rs::alloy_primitives::Address,
89+
amount: sails_rs::alloy_primitives::U256,
90+
},
91+
MyEvent2,
92+
}
93+
};
94+
let result = event(attrs, input).to_string();
95+
let result = prettyplease::unparse(&syn::parse_str(&result).unwrap());
96+
97+
insta::assert_snapshot!(result);
98+
}
99+
100+
#[test]
101+
fn eth_event_scale_and_ethabi() {
102+
let attrs = quote! {
103+
scale, ethabi
104+
};
105+
let input = quote! {
106+
pub enum Events {
107+
MyEvent1(u128),
108+
}
109+
};
110+
let result = event(attrs, input).to_string();
111+
let result = prettyplease::unparse(&syn::parse_str(&result).unwrap());
112+
113+
insta::assert_snapshot!(result);
114+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
source: macros-tests/tests/eth_event.rs
3+
expression: result
4+
---
5+
pub enum Events {
6+
#[annotate(codec = "ethabi")]
7+
MyEvent1 {
8+
#[annotate(indexed)]
9+
sender: sails_rs::alloy_primitives::Address,
10+
amount: sails_rs::alloy_primitives::U256,
11+
},
12+
#[annotate(codec = "ethabi")]
13+
MyEvent2,
14+
}
15+
impl sails_rs::EthEvent for Events {
16+
const SIGNATURES: &'static [sails_rs::gstd::EthEventExpo] = &[
17+
(
18+
"MyEvent1",
19+
<<(
20+
sails_rs::alloy_primitives::Address,
21+
sails_rs::alloy_primitives::U256,
22+
) as sails_rs::alloy_sol_types::SolValue>::SolType as sails_rs::alloy_sol_types::SolType>::SOL_NAME,
23+
sails_rs::keccak_const::Keccak256::new()
24+
.update("MyEvent1".as_bytes())
25+
.update(
26+
<<(
27+
sails_rs::alloy_primitives::Address,
28+
sails_rs::alloy_primitives::U256,
29+
) as sails_rs::alloy_sol_types::SolValue>::SolType as sails_rs::alloy_sol_types::SolType>::SOL_NAME
30+
.as_bytes(),
31+
)
32+
.finalize(),
33+
),
34+
(
35+
"MyEvent2",
36+
<<() as sails_rs::alloy_sol_types::SolValue>::SolType as sails_rs::alloy_sol_types::SolType>::SOL_NAME,
37+
sails_rs::keccak_const::Keccak256::new()
38+
.update("MyEvent2".as_bytes())
39+
.update(
40+
<<() as sails_rs::alloy_sol_types::SolValue>::SolType as sails_rs::alloy_sol_types::SolType>::SOL_NAME
41+
.as_bytes(),
42+
)
43+
.finalize(),
44+
),
45+
];
46+
#[allow(unused)]
47+
fn topics(&self) -> sails_rs::Vec<sails_rs::alloy_primitives::B256> {
48+
match self {
49+
Events::MyEvent1 { sender, amount } => {
50+
let mut topics = sails_rs::Vec::with_capacity(2usize);
51+
let (_, _, hash) = Self::SIGNATURES[0usize];
52+
topics.push(sails_rs::alloy_primitives::B256::new(hash));
53+
topics.push(Self::topic_hash(sender));
54+
topics
55+
}
56+
Events::MyEvent2 => {
57+
let mut topics = sails_rs::Vec::with_capacity(1usize);
58+
let (_, _, hash) = Self::SIGNATURES[1usize];
59+
topics.push(sails_rs::alloy_primitives::B256::new(hash));
60+
topics
61+
}
62+
}
63+
}
64+
#[allow(unused)]
65+
fn data(&self) -> sails_rs::Vec<u8> {
66+
match self {
67+
Events::MyEvent1 { sender, amount } => Self::encode_sequence(&(amount,)),
68+
Events::MyEvent2 => Self::encode_sequence(&()),
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)