diff --git a/Cargo.lock b/Cargo.lock index 41c051a30..25e9d67ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,6 +457,20 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "ark-scale" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -2828,6 +2842,54 @@ dependencies = [ "gear-dlmalloc", ] +[[package]] +name = "gbuiltin-bls381" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5c0d4fddca6f05362cda44c30e0775f1ee99a040ac7c7df28d699082aae6be" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-scale", + "ark-serialize", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "gbuiltin-eth-bridge" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd77189c1c742e301656941c8125169d84b12c1deac1ed11acc516aef00002d8" +dependencies = [ + "gprimitives", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "gbuiltin-proxy" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328a23e9eb5831d49ae122d74ce0a49fb1dfd22d6f7fb8cc513f7dadf21d8780" +dependencies = [ + "gprimitives", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "gbuiltin-staking" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f38c4cd4469829351cf83b8b89976eb71017f76202e8af2e9ff989e73b5535f" +dependencies = [ + "gprimitives", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "gclient" version = "1.9.0" @@ -7214,6 +7276,10 @@ dependencies = [ "alloy-sol-types 0.8.20", "convert_case 0.7.1", "futures", + "gbuiltin-bls381", + "gbuiltin-eth-bridge", + "gbuiltin-proxy", + "gbuiltin-staking", "gclient", "gcore", "gear-core", diff --git a/Cargo.toml b/Cargo.toml index c974e4688..20519f44b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,10 @@ gclient = "=1.9.0" gcore = "=1.9.0" gear-core = { version = "=1.9.0", default-features = false } gear-core-errors = "=1.9.0" +gbuiltin-proxy = "=1.9.0" +gbuiltin-staking = "=1.9.0" +gbuiltin-eth-bridge = "=1.9.0" +gbuiltin-bls381 = "=1.9.0" gprimitives = { version = "=1.9.0", default-features = false, features = ["codec"] } gstd = "=1.9.0" gtest = "=1.9.0" diff --git a/rs/Cargo.toml b/rs/Cargo.toml index 820bb6dcc..8eec76e94 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -20,6 +20,10 @@ gcore.workspace = true gprimitives.workspace = true gstd = { workspace = true, optional = true } gwasm-builder = { workspace = true, optional = true } +gbuiltin-proxy.workspace = true +gbuiltin-staking.workspace = true +gbuiltin-eth-bridge.workspace = true +gbuiltin-bls381.workspace = true hashbrown.workspace = true hex.workspace = true keccak-const = { workspace = true, optional = true } diff --git a/rs/ethexe/Cargo.lock b/rs/ethexe/Cargo.lock index 729d2490e..df259a5c2 100644 --- a/rs/ethexe/Cargo.lock +++ b/rs/ethexe/Cargo.lock @@ -391,6 +391,20 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "ark-scale" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -1883,6 +1897,54 @@ dependencies = [ "gear-dlmalloc", ] +[[package]] +name = "gbuiltin-bls381" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5c0d4fddca6f05362cda44c30e0775f1ee99a040ac7c7df28d699082aae6be" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-scale", + "ark-serialize", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "gbuiltin-eth-bridge" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd77189c1c742e301656941c8125169d84b12c1deac1ed11acc516aef00002d8" +dependencies = [ + "gprimitives", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "gbuiltin-proxy" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328a23e9eb5831d49ae122d74ce0a49fb1dfd22d6f7fb8cc513f7dadf21d8780" +dependencies = [ + "gprimitives", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "gbuiltin-staking" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f38c4cd4469829351cf83b8b89976eb71017f76202e8af2e9ff989e73b5535f" +dependencies = [ + "gprimitives", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "gcore" version = "1.9.0" @@ -4281,6 +4343,10 @@ dependencies = [ "alloy-primitives 0.8.22", "alloy-sol-types 0.8.22", "futures", + "gbuiltin-bls381", + "gbuiltin-eth-bridge", + "gbuiltin-proxy", + "gbuiltin-staking", "gcore", "gear-core", "gear-core-errors", diff --git a/rs/src/builtins/bls381.rs b/rs/src/builtins/bls381.rs new file mode 100644 index 000000000..f5a99e2b1 --- /dev/null +++ b/rs/src/builtins/bls381.rs @@ -0,0 +1,407 @@ +use crate::{ + builtins::{BuiltinsRemoting, builtin_action}, + calls::{ActionIo, Call, RemotingAction}, + errors::{Error, Result}, + prelude::{Decode, Encode, TypeInfo, Vec}, +}; +use gbuiltin_bls381::{Request as GearBls381Request, Response as GearBls381Response}; + +builtin_action!( + Bls381Request, + Bls381Builtin, + MultiMillerLoop { a: Vec, b: Vec } => Bls381Response +); + +builtin_action!( + Bls381Request, + Bls381Builtin, + FinalExponentiation { f: Vec } => Bls381Response +); + +builtin_action!( + Bls381Request, + Bls381Builtin, + MultiScalarMultiplicationG1 { bases: Vec, scalars: Vec } => Bls381Response +); + +builtin_action!( + Bls381Request, + Bls381Builtin, + MultiScalarMultiplicationG2 { bases: Vec, scalars: Vec } => Bls381Response +); + +builtin_action!( + Bls381Request, + Bls381Builtin, + ProjectiveMultiplicationG1 { base: Vec, scalar: Vec } => Bls381Response +); + +builtin_action!( + Bls381Request, + Bls381Builtin, + ProjectiveMultiplicationG2 { base: Vec, scalar: Vec } => Bls381Response +); + +builtin_action!( + Bls381Request, + Bls381Builtin, + AggregateG1 { points: Vec } => Bls381Response +); + +builtin_action!( + Bls381Request, + Bls381Builtin, + MapToG2Affine { message: Vec } => Bls381Response +); + +pub struct Bls381Builtin { + remoting: R, +} + +impl Bls381Builtin { + pub fn new(remoting: R) -> Self { + Self { remoting } + } +} + +pub trait Bls381BuiltinTrait { + type Args; + + /// Performs multi Miller loop pairing operation for BLS12-381. + fn multi_miller_loop( + &self, + a: Vec, + b: Vec, + ) -> impl Call; + + /// Performs final exponentiation for BLS12-381. + fn final_exponentiation( + &self, + f: Vec, + ) -> impl Call; + + /// Performs multi scalar multiplication on G1 for BLS12-381. + fn multi_scalar_multiplication_g1( + &self, + bases: Vec, + scalars: Vec, + ) -> impl Call; + + /// Performs multi scalar multiplication on G2 for BLS12-381. + fn multi_scalar_multiplication_g2( + &self, + bases: Vec, + scalars: Vec, + ) -> impl Call; + + /// Performs projective multiplication on G1 for BLS12-381. + fn projective_multiplication_g1( + &self, + base: Vec, + scalar: Vec, + ) -> impl Call; + + /// Performs projective multiplication on G2 for BLS12-381. + fn projective_multiplication_g2( + &self, + base: Vec, + scalar: Vec, + ) -> impl Call; + + /// Aggregates G1 points for BLS12-381. + fn aggregate_g1( + &self, + points: Vec, + ) -> impl Call; + + /// Maps an arbitrary message to a G2Affine point for BLS12-381. + fn map_to_g2_affine( + &self, + message: Vec, + ) -> impl Call; +} + +impl Bls381BuiltinTrait for Bls381Builtin { + type Args = R::Args; + + fn multi_miller_loop( + &self, + a: Vec, + b: Vec, + ) -> impl Call { + self.multi_miller_loop(a, b) + } + + fn final_exponentiation( + &self, + f: Vec, + ) -> impl Call { + self.final_exponentiation(f) + } + + fn multi_scalar_multiplication_g1( + &self, + bases: Vec, + scalars: Vec, + ) -> impl Call { + self.multi_scalar_multiplication_g1(bases, scalars) + } + + fn multi_scalar_multiplication_g2( + &self, + bases: Vec, + scalars: Vec, + ) -> impl Call { + self.multi_scalar_multiplication_g2(bases, scalars) + } + + fn projective_multiplication_g1( + &self, + base: Vec, + scalar: Vec, + ) -> impl Call { + self.projective_multiplication_g1(base, scalar) + } + + fn projective_multiplication_g2( + &self, + base: Vec, + scalar: Vec, + ) -> impl Call { + self.projective_multiplication_g2(base, scalar) + } + + fn aggregate_g1(&self, points: Vec) -> impl Call { + self.aggregate_g1(points) + } + + fn map_to_g2_affine( + &self, + message: Vec, + ) -> impl Call { + self.map_to_g2_affine(message) + } +} + +/// `TypeInfo` implementor copy of `gbuiltin_bls381::Request`. +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +pub enum Bls381Request { + /// Request to pairing multi Miller loop for *BLS12-381*. + /// + /// Encoded: + /// - `a`: [`ArkScale>`]. + /// - `b`: [`ArkScale>`]. + #[codec(index = 0)] + MultiMillerLoop { a: Vec, b: Vec }, + + /// Request to pairing final exponentiation for *BLS12-381*. + /// + /// Encoded: [`ArkScale<`]. + #[codec(index = 1)] + FinalExponentiation { f: Vec }, + + /// Request to multi scalar multiplication on *G1* for *BLS12-381* + /// + /// Encoded: + /// - `bases`: [`ArkScale>`]. + /// - `scalars`: [`ArkScale>`]. + #[codec(index = 2)] + MultiScalarMultiplicationG1 { bases: Vec, scalars: Vec }, + + /// Request to multi scalar multiplication on *G2* for *BLS12-381* + /// + /// Encoded: + /// - `bases`: [`ArkScale>`]. + /// - `scalars`: [`ArkScale>`]. + #[codec(index = 3)] + MultiScalarMultiplicationG2 { bases: Vec, scalars: Vec }, + + /// Request to projective multiplication on *G1* for *BLS12-381*. + /// + /// Encoded: + /// - `base`: [`ArkScaleProjective`]. + /// - `scalar`: [`ArkScale>`]. + #[codec(index = 4)] + ProjectiveMultiplicationG1 { base: Vec, scalar: Vec }, + + /// Request to projective multiplication on *G2* for *BLS12-381*. + /// + /// Encoded: + /// - `base`: [`ArkScaleProjective`]. + /// - `scalar`: [`ArkScale>`]. + #[codec(index = 5)] + ProjectiveMultiplicationG2 { base: Vec, scalar: Vec }, + + /// Request to aggregate *G1* points for *BLS12-381*. + /// + /// Encoded: [`ArkScale>`]. + #[codec(index = 6)] + AggregateG1 { points: Vec }, + + /// Request to map an arbitrary message to *G2Affine* point for *BLS12-381*. + /// + /// Raw message bytes to map. + #[codec(index = 7)] + MapToG2Affine { message: Vec }, +} + +impl From for Bls381Request { + fn from(request: GearBls381Request) -> Self { + match request { + GearBls381Request::MultiMillerLoop { a, b } => Self::MultiMillerLoop { a, b }, + GearBls381Request::FinalExponentiation { f } => Self::FinalExponentiation { f }, + GearBls381Request::MultiScalarMultiplicationG1 { bases, scalars } => { + Self::MultiScalarMultiplicationG1 { bases, scalars } + } + GearBls381Request::MultiScalarMultiplicationG2 { bases, scalars } => { + Self::MultiScalarMultiplicationG2 { bases, scalars } + } + GearBls381Request::ProjectiveMultiplicationG1 { base, scalar } => { + Self::ProjectiveMultiplicationG1 { base, scalar } + } + GearBls381Request::ProjectiveMultiplicationG2 { base, scalar } => { + Self::ProjectiveMultiplicationG2 { base, scalar } + } + GearBls381Request::AggregateG1 { points } => Self::AggregateG1 { points }, + GearBls381Request::MapToG2Affine { message } => Self::MapToG2Affine { message }, + } + } +} + +/// `TypeInfo` implementor copy of `gbuiltin_bls381::Response`. +#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)] +pub enum Bls381Response { + /// Result of the multi Miller loop, encoded: [`ArkScale`]. + #[codec(index = 0)] + MultiMillerLoop(Vec), + /// Result of the final exponentiation, encoded: [`ArkScale`]. + #[codec(index = 1)] + FinalExponentiation(Vec), + /// Result of the multi scalar multiplication, encoded: [`ArkScaleProjective`]. + #[codec(index = 2)] + MultiScalarMultiplicationG1(Vec), + /// Result of the multi scalar multiplication, encoded: [`ArkScaleProjective`]. + #[codec(index = 3)] + MultiScalarMultiplicationG2(Vec), + /// Result of the projective multiplication, encoded: [`ArkScaleProjective`]. + #[codec(index = 4)] + ProjectiveMultiplicationG1(Vec), + /// Result of the projective multiplication, encoded: [`ArkScaleProjective`]. + #[codec(index = 5)] + ProjectiveMultiplicationG2(Vec), + /// Result of the aggregation, encoded: [`ArkScale`]. + #[codec(index = 6)] + AggregateG1(Vec), + /// Result of the mapping, encoded: [`ArkScale`]. + #[codec(index = 7)] + MapToG2Affine(Vec), +} + +impl From for Bls381Response { + fn from(response: GearBls381Response) -> Self { + match response { + GearBls381Response::MultiMillerLoop(value) => Self::MultiMillerLoop(value), + GearBls381Response::FinalExponentiation(value) => Self::FinalExponentiation(value), + GearBls381Response::MultiScalarMultiplicationG1(value) => { + Self::MultiScalarMultiplicationG1(value) + } + GearBls381Response::MultiScalarMultiplicationG2(value) => { + Self::MultiScalarMultiplicationG2(value) + } + GearBls381Response::ProjectiveMultiplicationG1(value) => { + Self::ProjectiveMultiplicationG1(value) + } + GearBls381Response::ProjectiveMultiplicationG2(value) => { + Self::ProjectiveMultiplicationG2(value) + } + GearBls381Response::AggregateG1(value) => Self::AggregateG1(value), + GearBls381Response::MapToG2Affine(value) => Self::MapToG2Affine(value), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{builtins::test_utils::assert_action_codec, prelude::vec}; + + #[test] + fn test_codec() { + assert_action_codec!( + Bls381Request, + MultiMillerLoop { + a: vec![1, 2, 3], + b: vec![4, 5, 6] + }, + Bls381Response, + MultiMillerLoop(vec![7, 8, 9]) + ); + + assert_action_codec!( + Bls381Request, + FinalExponentiation { + f: vec![10, 11, 12] + }, + Bls381Response, + FinalExponentiation(vec![13, 14, 15]) + ); + + assert_action_codec!( + Bls381Request, + MultiScalarMultiplicationG1 { + bases: vec![16, 17, 18], + scalars: vec![19, 20, 21] + }, + Bls381Response, + MultiScalarMultiplicationG1(vec![22, 23, 24]) + ); + + assert_action_codec!( + Bls381Request, + MultiScalarMultiplicationG2 { + bases: vec![25, 26, 27], + scalars: vec![28, 29, 30] + }, + Bls381Response, + MultiScalarMultiplicationG2(vec![31, 32, 33]) + ); + + assert_action_codec!( + Bls381Request, + ProjectiveMultiplicationG1 { + base: vec![34, 35, 36], + scalar: vec![37, 38, 39] + }, + Bls381Response, + ProjectiveMultiplicationG1(vec![40, 41, 42]) + ); + + assert_action_codec!( + Bls381Request, + ProjectiveMultiplicationG2 { + base: vec![43, 44, 45], + scalar: vec![46, 47, 48] + }, + Bls381Response, + ProjectiveMultiplicationG2(vec![49, 50, 51]) + ); + + assert_action_codec!( + Bls381Request, + AggregateG1 { + points: vec![52, 53, 54] + }, + Bls381Response, + AggregateG1(vec![55, 56, 57]) + ); + + assert_action_codec!( + Bls381Request, + MapToG2Affine { + message: vec![58, 59, 60] + }, + Bls381Response, + MapToG2Affine(vec![61, 62, 63]) + ); + } +} diff --git a/rs/src/builtins/eth_bridge.rs b/rs/src/builtins/eth_bridge.rs new file mode 100644 index 000000000..d0a08788e --- /dev/null +++ b/rs/src/builtins/eth_bridge.rs @@ -0,0 +1,71 @@ +use crate::{ + builtins::{BuiltinsRemoting, builtin_action}, + calls::{ActionIo, Call, RemotingAction}, + errors::{Error, Result}, + prelude::{Decode, Encode, Vec}, +}; +pub use gbuiltin_eth_bridge::{Request as EthBridgeRequest, Response as EthBridgeResponse}; +use gprimitives::H160; + +builtin_action!( + EthBridgeRequest, + EthBridgeBuiltin, + SendEthMessage { destination: H160, payload: Vec } => EthBridgeResponse +); + +pub struct EthBridgeBuiltin { + remoting: R, +} + +impl EthBridgeBuiltin { + pub fn new(remoting: R) -> Self { + Self { remoting } + } +} + +pub trait EthBridgeBuiltinTrait { + type Args; + + /// Sends an Ethereum message to the specified destination on the Ethereum network with the given payload. + fn send_eth_message( + &self, + destination: H160, + payload: Vec, + ) -> impl Call; +} + +impl EthBridgeBuiltinTrait for EthBridgeBuiltin { + type Args = R::Args; + + fn send_eth_message( + &self, + destination: H160, + payload: Vec, + ) -> impl Call { + self.send_eth_message(destination, payload) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::builtins::test_utils::assert_action_codec; + use crate::prelude::vec; + use gprimitives::{H256, U256}; + + #[test] + fn test_codec() { + assert_action_codec!( + EthBridgeRequest, + SendEthMessage { + destination: H160::from([1; 20]), + payload: vec![1, 2, 3, 4] + }, + EthBridgeResponse, + EthMessageQueued { + nonce: U256::one(), + hash: H256::from([2; 32]) + } + ); + } +} diff --git a/rs/src/builtins/mod.rs b/rs/src/builtins/mod.rs new file mode 100644 index 000000000..71c7958d8 --- /dev/null +++ b/rs/src/builtins/mod.rs @@ -0,0 +1,144 @@ +mod bls381; +mod eth_bridge; +mod proxy; +mod staking; + +pub use bls381::*; +pub use eth_bridge::*; +pub use proxy::*; +pub use staking::*; + +use crate::calls::Remoting; + +pub trait BuiltinsRemoting: Remoting {} + +#[cfg(feature = "gstd")] +impl BuiltinsRemoting for crate::gstd::calls::GStdRemoting {} + +#[cfg(feature = "gclient")] +#[cfg(not(target_arch = "wasm32"))] +impl BuiltinsRemoting for crate::gclient::calls::GClientRemoting {} + +// Creates an action for a provided builtin-in request variant and implements the `ActionIo` trait for it. +macro_rules! builtin_action { + ( + $enum_name:ident, + $builtin_name:ident, + $variant:ident $( { $($field:ident : $field_type:ty),* $(,)? } )? + $( => $reply_ty:ty )? + ) => { + pub struct $variant(()); + + impl ActionIo for $variant { + const ROUTE: &'static [u8] = b""; + type Params = $enum_name; + type Reply = builtin_action!(@reply_type $( $reply_ty )?); + + fn encode_call(value: &$enum_name) -> Vec { + builtin_action!(@match_variant value, $enum_name, $variant $(, { $($field),* })?); + value.encode() + } + + fn decode_reply(payload: impl AsRef<[u8]>) -> Result { + let value = payload.as_ref(); + builtin_action!(@decode_body value $( $reply_ty )?) + } + } + + impl $builtin_name { + paste::item! { + pub fn [<$variant:snake>]( + &self + $(, $($field : $field_type),* )? + ) -> impl Call::Reply, Args = R::Args> { + let request = $enum_name::$variant $( { $($field),* } )?; + RemotingAction::<_, $variant>::new(self.remoting.clone(), request) + } + } + } + }; + + // Helper arm: extract reply type or default + (@reply_type $reply_ty:ty) => { $reply_ty }; + (@reply_type) => { () }; + + // Helper: Match variant with fields + (@match_variant $value:ident, $enum_name:ident, $variant:ident, { $($field:ident),* }) => { + if !matches!($value, $enum_name::$variant { .. }) { + panic!( + "internal error: invalid param received. Expected `{}::{}`, received: {:?}", + stringify!($enum_name), stringify!($variant), $value + ); + } + }; + + // Helper: Match unit variant + (@match_variant $value:ident, $enum_name:ident, $variant:ident) => { + if !matches!($value, $enum_name::$variant) { + panic!( + "internal error: invalid param received. Expected `{}::{}`, received: {:?}", + stringify!($enum_name), stringify!($variant), $value + ); + } + }; + + // Helper arm: decode body based on presence of reply type + (@decode_body $value:ident $reply_ty:ty) => {{ + let mut val = $value; + <$reply_ty as Decode>::decode(&mut val).map_err(Error::Codec) + }}; + (@decode_body $value:ident) => {{ + if !$value.is_empty() { + panic!( + "internal error: expected empty reply for unit variant, received: {:?}", + $value + ); + } + Ok(()) + }}; +} + +pub(crate) use builtin_action; + +#[cfg(test)] +mod test_utils { + macro_rules! assert_action_codec { + // Check that action is encoded/decoded without any routes. + ( + $req_enum_name:ident, + $req_variant:ident $({ $($req_field:ident : $req_value:expr),* $(,)? })?, + $resp_enum_name:ident, + $response_variant:ident $response_body:tt + ) => {{ + let req = $req_enum_name::$req_variant $({ $($req_field: $req_value),* })?; + let resp = $crate::builtins::test_utils::assert_action_codec!(@build_response $resp_enum_name, $response_variant $response_body); + let encoded_action = $req_variant::encode_call(&req); + assert_eq!(req.encode(), encoded_action); + let decoded_resp = $req_variant::decode_reply(resp.encode()).unwrap(); + assert_eq!(resp, decoded_resp); + }}; + + // Check that value is encoded without any routes, and decoded into `()`. + ( + $req_enum_name:ident, + $req_variant:ident $({ $($req_field:ident : $req_value:expr),* $(,)? })? + ) => {{ + let req = $req_enum_name::$req_variant $({ $($req_field: $req_value),* })?; + let encoded_action = $req_variant::encode_call(&req); + assert_eq!(req.encode(), encoded_action); + assert_eq!(<$req_variant as ActionIo>::Reply::type_info(), <()>::type_info()); + }}; + + // Helper: Build response with tuple syntax + (@build_response $resp_enum_name:ident, $response_variant:ident ($response_value:expr)) => { + $resp_enum_name::$response_variant($response_value) + }; + + // Helper: Build response with struct syntax + (@build_response $resp_enum_name:ident, $response_variant:ident { $($resp_field:ident : $resp_value:expr),* $(,)? }) => { + $resp_enum_name::$response_variant { $($resp_field: $resp_value),* } + }; + } + + pub(crate) use assert_action_codec; +} diff --git a/rs/src/builtins/proxy.rs b/rs/src/builtins/proxy.rs new file mode 100644 index 000000000..886d002be --- /dev/null +++ b/rs/src/builtins/proxy.rs @@ -0,0 +1,152 @@ +use crate::{ + ActorId, + builtins::{BuiltinsRemoting, builtin_action}, + calls::{ActionIo, Call, RemotingAction}, + errors::Result, + prelude::{Decode, Encode, TypeInfo, Vec}, +}; +use gbuiltin_proxy::{ProxyType as GearProxyType, Request as GearProxyRequest}; + +// todo [sab] make typeinfo types on gear + +builtin_action! { + ProxyRequest, + ProxyBuiltin, + AddProxy { delegate: ActorId, proxy_type: ProxyType } +} + +builtin_action! { + ProxyRequest, + ProxyBuiltin, + RemoveProxy { delegate: ActorId, proxy_type: ProxyType } +} + +pub struct ProxyBuiltin { + remoting: R, +} + +impl ProxyBuiltin { + pub fn new(remoting: R) -> Self { + Self { remoting } + } +} + +pub trait ProxyBuiltinTrait { + type Args; + + /// Adds a proxy for the specified delegate with the given proxy type. + fn add_proxy( + &self, + delegate: ActorId, + proxy_type: ProxyType, + ) -> impl Call; + + /// Removes a proxy for the specified delegate with the given proxy type. + fn remove_proxy( + &self, + delegate: ActorId, + proxy_type: ProxyType, + ) -> impl Call; +} + +impl ProxyBuiltinTrait for ProxyBuiltin { + type Args = R::Args; + + fn add_proxy( + &self, + delegate: ActorId, + proxy_type: ProxyType, + ) -> impl Call { + self.add_proxy(delegate, proxy_type) + } + + fn remove_proxy( + &self, + delegate: ActorId, + proxy_type: ProxyType, + ) -> impl Call { + self.remove_proxy(delegate, proxy_type) + } +} + +/// `TypeInfo` implementor copy of `gbuiltin_proxy::Request`. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode, TypeInfo)] +pub enum ProxyRequest { + AddProxy { + delegate: ActorId, + proxy_type: ProxyType, + }, + RemoveProxy { + delegate: ActorId, + proxy_type: ProxyType, + }, +} + +impl From for ProxyRequest { + fn from(request: GearProxyRequest) -> Self { + match request { + GearProxyRequest::AddProxy { + delegate, + proxy_type, + } => Self::AddProxy { + delegate, + proxy_type: proxy_type.into(), + }, + GearProxyRequest::RemoveProxy { + delegate, + proxy_type, + } => Self::RemoveProxy { + delegate, + proxy_type: proxy_type.into(), + }, + } + } +} + +/// `TypeInfo` implementor copy of `gbuiltin_proxy::ProxyType`. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, Decode, TypeInfo)] +pub enum ProxyType { + Any, + NonTransfer, + Governance, + Staking, + IdentityJudgement, + CancelProxy, +} + +impl From for ProxyType { + fn from(proxy_type: GearProxyType) -> Self { + match proxy_type { + GearProxyType::Any => Self::Any, + GearProxyType::NonTransfer => Self::NonTransfer, + GearProxyType::Governance => Self::Governance, + GearProxyType::Staking => Self::Staking, + GearProxyType::IdentityJudgement => Self::IdentityJudgement, + GearProxyType::CancelProxy => Self::CancelProxy, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::builtins::test_utils::assert_action_codec; + + #[test] + fn test_codec() { + assert_action_codec!( + ProxyRequest, + AddProxy { + delegate: ActorId::from([1; 32]), + proxy_type: ProxyType::Any + } + ); + assert_action_codec!( + ProxyRequest, + RemoveProxy { + delegate: ActorId::from([2; 32]), + proxy_type: ProxyType::NonTransfer + } + ); + } +} diff --git a/rs/src/builtins/staking.rs b/rs/src/builtins/staking.rs new file mode 100644 index 000000000..d5e16560c --- /dev/null +++ b/rs/src/builtins/staking.rs @@ -0,0 +1,275 @@ +use crate::{ + ActorId, + builtins::{BuiltinsRemoting, builtin_action}, + calls::{ActionIo, Call, RemotingAction}, + errors::Result, + prelude::{Decode, Encode, TypeInfo, Vec}, +}; +use gbuiltin_staking::{Request as GearStakingRequest, RewardAccount as GearRewardAccount}; + +builtin_action!( + StakingRequest, + StakingBuiltin, + Bond { + value: u128, + payee: RewardAccount + } +); + +builtin_action!(StakingRequest, StakingBuiltin, BondExtra { value: u128 }); + +builtin_action!(StakingRequest, StakingBuiltin, Unbond { value: u128 }); + +builtin_action!( + StakingRequest, + StakingBuiltin, + WithdrawUnbonded { + num_slashing_spans: u32 + } +); + +builtin_action!( + StakingRequest, + StakingBuiltin, + Nominate { targets: Vec } +); + +builtin_action!(StakingRequest, StakingBuiltin, Chill); + +builtin_action!( + StakingRequest, + StakingBuiltin, + PayoutStakers { + validator_stash: ActorId, + era: u32 + } +); + +builtin_action!(StakingRequest, StakingBuiltin, Rebond { value: u128 }); + +builtin_action!( + StakingRequest, + StakingBuiltin, + SetPayee { + payee: RewardAccount + } +); + +pub struct StakingBuiltin { + remoting: R, +} + +impl StakingBuiltin { + pub fn new(remoting: R) -> Self { + Self { remoting } + } +} + +pub trait StakingBuiltinTrait { + type Args; + + /// Bond up to the `value` from the sender to self as the controller. + fn bond(&self, value: u128, payee: RewardAccount) -> impl Call; + + /// Add up to the `value` to the sender's bonded amount. + fn bond_extra(&self, value: u128) -> impl Call; + + /// Unbond up to the `value` to allow withdrawal after undonding period. + fn unbond(&self, value: u128) -> impl Call; + + /// Withdraw unbonded chunks for which undonding period has elapsed. + fn withdraw_unbonded( + &self, + num_slashing_spans: u32, + ) -> impl Call; + + /// Add sender as a nominator of `targets` or update the existing targets. + fn nominate(&self, targets: Vec) -> impl Call; + + /// Declare intention to [temporarily] stop nominating while still having funds bonded. + fn chill(&self) -> impl Call; + + /// Request stakers payout for the given era. + fn payout_stakers( + &self, + validator_stash: ActorId, + era: u32, + ) -> impl Call; + + /// Rebond a portion of the sender's stash scheduled to be unlocked. + fn rebond(&self, value: u128) -> impl Call; + + /// Set the reward destination. + fn set_payee(&self, payee: RewardAccount) -> impl Call; +} + +impl StakingBuiltinTrait for StakingBuiltin { + type Args = R::Args; + + fn bond(&self, value: u128, payee: RewardAccount) -> impl Call { + self.bond(value, payee) + } + + fn bond_extra(&self, value: u128) -> impl Call { + self.bond_extra(value) + } + + fn unbond(&self, value: u128) -> impl Call { + self.unbond(value) + } + + fn withdraw_unbonded( + &self, + num_slashing_spans: u32, + ) -> impl Call { + self.withdraw_unbonded(num_slashing_spans) + } + + fn nominate(&self, targets: Vec) -> impl Call { + self.nominate(targets) + } + + fn chill(&self) -> impl Call { + self.chill() + } + + fn payout_stakers( + &self, + validator_stash: ActorId, + era: u32, + ) -> impl Call { + self.payout_stakers(validator_stash, era) + } + + fn rebond(&self, value: u128) -> impl Call { + self.rebond(value) + } + + fn set_payee(&self, payee: RewardAccount) -> impl Call { + self.set_payee(payee) + } +} + +/// `TypeInfo` implementor copy of `gbuiltin_staking::Request`. +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub enum StakingRequest { + /// Bond up to the `value` from the sender to self as the controller. + Bond { value: u128, payee: RewardAccount }, + /// Add up to the `value` to the sender's bonded amount. + BondExtra { value: u128 }, + /// Unbond up to the `value` to allow withdrawal after undonding period. + Unbond { value: u128 }, + /// Withdraw unbonded chunks for which undonding period has elapsed. + WithdrawUnbonded { num_slashing_spans: u32 }, + /// Add sender as a nominator of `targets` or update the existing targets. + Nominate { targets: Vec }, + /// Declare intention to [temporarily] stop nominating while still having funds bonded. + Chill, + /// Request stakers payout for the given era. + PayoutStakers { validator_stash: ActorId, era: u32 }, + /// Rebond a portion of the sender's stash scheduled to be unlocked. + Rebond { value: u128 }, + /// Set the reward destination. + SetPayee { payee: RewardAccount }, +} + +impl From for StakingRequest { + fn from(value: GearStakingRequest) -> Self { + match value { + GearStakingRequest::Bond { value, payee } => { + let payee = payee.into(); + StakingRequest::Bond { value, payee } + } + GearStakingRequest::BondExtra { value } => StakingRequest::BondExtra { value }, + GearStakingRequest::Unbond { value } => StakingRequest::Unbond { value }, + GearStakingRequest::WithdrawUnbonded { num_slashing_spans } => { + StakingRequest::WithdrawUnbonded { num_slashing_spans } + } + GearStakingRequest::Nominate { targets } => StakingRequest::Nominate { targets }, + GearStakingRequest::Chill => StakingRequest::Chill, + GearStakingRequest::PayoutStakers { + validator_stash, + era, + } => StakingRequest::PayoutStakers { + validator_stash, + era, + }, + GearStakingRequest::Rebond { value } => StakingRequest::Rebond { value }, + GearStakingRequest::SetPayee { payee } => { + let payee = payee.into(); + StakingRequest::SetPayee { payee } + } + } + } +} + +/// `TypeInfo` implementor copy of `gbuiltin_staking::RewardAccount`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub enum RewardAccount { + /// Pay rewards to the sender's account and increase the amount at stake. + Staked, + /// Pay rewards to the sender's account (usually, the one derived from `program_id`) + /// without increasing the amount at stake. + Program, + /// Pay rewards to a custom account. + Custom(ActorId), + /// Opt for not receiving any rewards at all. + None, +} + +impl From for RewardAccount { + fn from(value: GearRewardAccount) -> Self { + match value { + GearRewardAccount::Staked => RewardAccount::Staked, + GearRewardAccount::Program => RewardAccount::Program, + GearRewardAccount::Custom(account) => RewardAccount::Custom(account), + GearRewardAccount::None => RewardAccount::None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{builtins::test_utils::assert_action_codec, prelude::vec}; + + #[test] + fn test_codec() { + assert_action_codec!( + StakingRequest, + Bond { + value: 1000, + payee: RewardAccount::Staked + } + ); + assert_action_codec!(StakingRequest, BondExtra { value: 500 }); + assert_action_codec!(StakingRequest, Unbond { value: 200 }); + assert_action_codec!( + StakingRequest, + WithdrawUnbonded { + num_slashing_spans: 3 + } + ); + assert_action_codec!( + StakingRequest, + Nominate { + targets: vec![ActorId::from([1; 32]), ActorId::from([2; 32])] + } + ); + assert_action_codec!(StakingRequest, Chill); + assert_action_codec!( + StakingRequest, + PayoutStakers { + validator_stash: ActorId::from([3; 32]), + era: 42 + } + ); + assert_action_codec!(StakingRequest, Rebond { value: 300 }); + assert_action_codec!( + StakingRequest, + SetPayee { + payee: RewardAccount::Custom(ActorId::from([4; 32])) + } + ); + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index a30de840d..88b3d658b 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -38,3 +38,5 @@ pub mod prelude; #[cfg(feature = "ethexe")] pub mod solidity; mod types; + +pub mod builtins;