Skip to main content

kudu/chain/
action.rs

1use hex::FromHexError;
2use serde::{Deserialize, Serialize};
3use serde_json::json;
4use snafu::{Snafu, OptionExt, ensure};
5
6use crate::{
7    AccountName, ActionName, Contract,
8    PermissionName, Name, ABISerializable,
9    abiserializable::to_bin, Bytes, JsonValue,
10    ByteStream, ABIProvider, ABIError, InvalidName,
11    with_location, impl_auto_error_conversion,
12};
13
14// this is needed to be able to call the `ABISerializable` derive macro, which needs
15// access to the `kudu` crate
16extern crate self as kudu;
17
18
19// from: https://github.com/AntelopeIO/spring/blob/main/libraries/chain/include/eosio/chain/action.hpp
20
21
22#[derive(Eq, Hash, PartialEq, Debug, Copy, Clone, Default, Deserialize, Serialize, ABISerializable)]
23pub struct PermissionLevel {
24    pub actor: AccountName,
25    pub permission: PermissionName,
26}
27
28pub trait IntoPermissionVec {
29    fn into_permission_vec(self) -> Vec<PermissionLevel>;
30}
31
32impl IntoPermissionVec for Vec<PermissionLevel> {
33    fn into_permission_vec(self) -> Vec<PermissionLevel> {
34        self
35    }
36}
37
38impl IntoPermissionVec for PermissionLevel {
39    fn into_permission_vec(self) -> Vec<PermissionLevel> {
40        vec![self]
41    }
42}
43
44// NOTE: this panics if the `&str` are not valid `Name`s
45impl IntoPermissionVec for (&str, &str) {
46    fn into_permission_vec(self) -> Vec<PermissionLevel> {
47        vec![PermissionLevel {
48            actor: AccountName::constant(self.0),
49            permission: PermissionName::constant(self.1)
50        }]
51    }
52}
53
54
55#[with_location]
56#[derive(Debug, Snafu)]
57// TODO: rename to `InvalidAction` for consistency?
58pub enum ActionError {
59    #[snafu(display("Cannot convert action['{field_name}'] to str, actual type: {value:?}"))]
60    FieldType {
61        field_name: String,
62        value: JsonValue,
63    },
64
65    #[snafu(display("Invalid name"))]
66    Name { source: InvalidName },
67
68    #[snafu(display("invalid hex representation"))]
69    FromHex { source: FromHexError },
70
71    #[snafu(display("missing ABIProvider, required to encode action data"))]
72    MissingABIProvider,
73
74    #[snafu(display("could not match JSON object to transaction"))]
75    FromJson { source: serde_json::Error },
76
77    #[snafu(display("ABI error"))]
78    ABI { source: ABIError },
79}
80
81impl_auto_error_conversion!(InvalidName, ActionError, NameSnafu);
82impl_auto_error_conversion!(FromHexError, ActionError, FromHexSnafu);
83impl_auto_error_conversion!(ABIError, ActionError, ABISnafu);
84impl_auto_error_conversion!(serde_json::Error, ActionError, FromJsonSnafu);
85
86
87/// An action is performed by an actor, aka an account. It may
88/// be created explicitly and authorized by signatures or might be
89/// generated implicitly by executing application code.
90///
91/// This follows the design pattern of React Flux where actions are
92/// named and then dispatched to one or more action handlers (aka stores).
93/// In the context of eosio, every action is dispatched to the handler defined
94/// by account 'scope' and function 'name', but the default handler may also
95/// forward the action to any number of additional handlers. Any application
96/// can write a handler for "scope::name" that will get executed if and only if
97/// this action is forwarded to that application.
98///
99/// Each action may require the permission of specific actors. Actors can define
100/// any number of permission levels. The actors and their respective permission
101/// levels are declared on the action and validated independently of the executing
102/// application code. An application code will check to see if the required
103/// authorization were properly declared when it executes.
104#[derive(Eq, Hash, PartialEq, Debug, Clone, Default, Deserialize, Serialize, ABISerializable)]
105pub struct Action {
106    pub account: AccountName,
107    pub name: ActionName,
108    pub authorization: Vec<PermissionLevel>,
109    pub data: Bytes,
110}
111
112impl Action {
113    pub fn new<T: Contract>(authorization: impl IntoPermissionVec, contract: &T) -> Action {
114        Action {
115            account: T::account(),
116            name: T::name(),
117            authorization: authorization.into_permission_vec(),
118            data: to_bin(contract)
119        }
120    }
121
122    pub fn conv_action_field_str<'a>(
123        action: &'a JsonValue,
124        field: &str
125    ) -> Result<&'a str, ActionError> {
126        action[field].as_str().with_context(|| FieldTypeSnafu {
127            field_name: field,
128            value: action[field].clone(),
129        })
130    }
131
132    pub fn from_json(abi_provider: Option<&ABIProvider>, action: &JsonValue) -> Result<Action, ActionError> {
133        // FIXME: too many unwraps
134        let account = Action::conv_action_field_str(action, "account")?;
135        let action_name = Action::conv_action_field_str(action, "name")?;
136
137        // TODO: can we make this more efficient?
138        let authorization: Vec<PermissionLevel> = serde_json::from_str(&action["authorization"].to_string())?;
139
140        let data: Bytes = if action["data"].is_string() {
141            // if `data` is already provided as binary data, read it as is
142            Bytes::from_hex(action["data"].as_str().unwrap())?  // safe unwrap
143        }
144        else {
145            // otherwise, we need an ABI to encode the data into a binary string
146            let data = &action["data"];
147            let mut ds = ByteStream::new();
148            ensure!(abi_provider.is_some(), MissingABIProviderSnafu);
149            let abi = abi_provider.unwrap().get_abi(account)?;  // safe unwrap
150            abi.encode_variant(&mut ds, action_name, data)?;
151            ds.into()
152        };
153
154        Ok(Action {
155            account: Name::new(account)?,
156            name: Name::new(action_name)?,
157            authorization,
158            data,
159        })
160    }
161
162    pub fn from_json_array(
163        abi_provider: Option<&ABIProvider>,
164        actions: &JsonValue
165    ) -> Result<Vec<Action>, ActionError> {
166        Ok(actions.as_array().unwrap().iter()
167            .map(|v| Action::from_json(abi_provider, v).unwrap())
168            .collect())
169    }
170
171    pub fn decode_data(&self, abi_provider: &ABIProvider) -> JsonValue {
172        // FIXME: this .clone() is unnecessary once we fix deserializing from bytestream
173        let mut ds = ByteStream::from(self.data.clone());
174        let abi = abi_provider.get_abi(&self.account.to_string()).unwrap();
175        abi.decode_variant(&mut ds, &self.name.to_string()).unwrap()
176    }
177
178    pub fn with_data(mut self, abi_provider: &ABIProvider, value: &JsonValue) -> Self {
179        let mut ds = ByteStream::new();
180        let abi = abi_provider.get_abi(&self.account.to_string()).unwrap();
181        abi.encode_variant(&mut ds, &self.name.to_string(), value).unwrap();
182        self.data = ds.into();
183        self
184    }
185
186    pub fn to_json(&self, abi_provider: &ABIProvider) -> JsonValue {
187        json!({
188            "account": self.account.to_string(),
189            "name": self.name.to_string(),
190            "authorization": serde_json::to_value(&self.authorization).unwrap(),
191            "data": self.decode_data(abi_provider),
192        })
193    }
194}