versatus_rust/
eip20.rs

1use crate::versatus_rust::{
2    Address, ContractResult, FunctionInputs, SmartContract, SmartContractInputs,
3    SmartContractOutputs,
4};
5use anyhow::{anyhow, Result};
6use ethnum::U256;
7use serde_derive::{Deserialize, Serialize};
8
9/// Erc20Inputs is an enum/union representing the possible ERC20 function inputs.
10#[derive(Debug, Serialize, Deserialize, Clone)]
11#[serde(rename_all = "camelCase")]
12pub enum Erc20Inputs {
13    Name(),
14    Symbol(),
15    Decimals(),
16    TotalSupply(),
17    BalanceOf {
18        address: Address,
19    },
20    Transfer {
21        address: Address,
22        value: U256,
23    },
24    TransferFrom {
25        from: Address,
26        to: Address,
27        value: U256,
28    },
29    Approve {
30        address: Address,
31        value: U256,
32    },
33    Allowance {
34        owner: Address,
35        spender: Address,
36    },
37}
38
39/// Erc20Result is an enum/union representing the possible ERC20 function return values.
40#[derive(Debug, Serialize, Deserialize, Clone)]
41#[serde(rename_all = "camelCase")]
42pub enum Erc20Result {
43    Name(String),
44    Symbol(String),
45    Decimals(u8),
46    TotalSupply(U256),
47    BalanceOf(U256),
48    Transfer(Erc20TransferEvent),
49    TransferFrom(Erc20TransferEvent),
50    Approve(Erc20ApprovalEvent),
51    Allowance(U256),
52}
53
54/// An interface for ERC20 contracts to conform to.
55pub trait Erc20 {
56    /// Optional. Token name.
57    fn name(&self) -> Result<String>;
58    /// Optional. Token ticker symbol.
59    fn symbol(&self) -> Result<String>;
60    /// Optional. Returns the number of decimals the token uses - e.g. 8, means to divide the token amount by
61    /// 100000000 to get its user representation.
62    fn decimals(&self) -> Result<u8>;
63    /// Returns the total token supply.
64    fn total_supply(&self) -> Result<U256>;
65    /// Returns the account balance of another account with address [owner].
66    fn balance_of(&self, owner: Address) -> Result<U256>;
67    /// Transfers _value amount of tokens to address _to, and MUST fire the Transfer event.
68    /// The function SHOULD throw if the message caller’s account balance does not have enough tokens to spend.
69    fn transfer(&self, to: Address, value: U256) -> Result<Erc20TransferEvent>;
70    /// Transfers _value amount of tokens from address _from to address _to, and MUST fire the Transfer event.
71    ///
72    /// The transferFrom method is used for a withdraw workflow, allowing contracts to transfer tokens on
73    /// your behalf. This can be used for example to allow a contract to transfer tokens on your behalf
74    /// and/or to charge fees in sub-currencies. The function SHOULD throw unless the _from account has
75    /// deliberately authorized the sender of the message via some mechanism.
76    ///
77    /// Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.
78    fn transfer_from(&self, from: Address, to: Address, value: U256) -> Result<Erc20TransferEvent>;
79    /// Allows _spender to withdraw from your account multiple times, up to the _value amount. If this
80    /// function is called again it overwrites the current allowance with _value.
81    ///
82    /// NOTE: To prevent attack vectors like the one described here and discussed here, clients SHOULD make
83    /// sure to create user interfaces in such a way that they set the allowance first to 0 before setting
84    /// it to another value for the same spender. THOUGH The contract itself shouldn’t enforce it, to
85    /// allow backwards compatibility with contracts deployed before.
86    fn approve(&self, spender: Address, value: U256) -> Result<Erc20ApprovalEvent>;
87    /// Returns the amount which _spender is still allowed to withdraw from _owner.
88    fn allowance(&self, owner: Address, spender: Address) -> Result<U256>;
89}
90
91/// Erc20TransferEvent is a struct to represent an ERC20 Transfer Event
92#[derive(Debug, Serialize, Deserialize, Clone)]
93#[serde(rename_all = "camelCase")]
94pub struct Erc20TransferEvent {
95    pub from: Address,
96    pub to: Address,
97    pub value: U256,
98}
99
100/// Erc20ApprovalEvent is a struct to represent an ERC20 Approval Event
101#[derive(Debug, Serialize, Deserialize, Clone)]
102#[serde(rename_all = "camelCase")]
103pub struct Erc20ApprovalEvent {
104    pub owner: Address,
105    pub spender: Address,
106    pub value: U256,
107}
108
109pub fn process_erc20<T: Erc20 + SmartContract>(contract: &mut T) -> Result<()> {
110    // Read and parse stdin
111    let mut input = SmartContractInputs::gather()?;
112
113    // If the caller has asked us to, save the inputs.
114    contract.receive_inputs(&mut input)?;
115
116    let result: Erc20Result;
117
118    // Call correct function
119    match input.contract_input.contract_fn.as_str() {
120        // XXX: Need to use let match to retrieve input arguments....
121        "allowance" => {
122            let owner: Address;
123            let spender: Address;
124            result = {
125                match input.contract_input.function_inputs {
126                    FunctionInputs::Erc20(Erc20Inputs::Allowance {
127                        owner: in_owner,
128                        spender: in_spender,
129                    }) => {
130                        owner = in_owner;
131                        spender = in_spender;
132                    }
133                    _ => return Err(anyhow!("Contract inputs don't match allowance function")),
134                }
135                Erc20Result::Allowance(contract.allowance(owner, spender)?)
136            }
137        }
138        "approve" => {
139            let spender: Address;
140            let value: U256;
141            result = {
142                match input.contract_input.function_inputs {
143                    FunctionInputs::Erc20(Erc20Inputs::Approve {
144                        address: in_spender,
145                        value: in_value,
146                    }) => {
147                        spender = in_spender;
148                        value = in_value;
149                    }
150                    _ => return Err(anyhow!("Contract inputs don't match approve function")),
151                }
152                Erc20Result::Approve(contract.approve(spender, value)?)
153            }
154        }
155        "balance_of" => {
156            let addr: Address;
157            result = {
158                match input.contract_input.function_inputs {
159                    FunctionInputs::Erc20(Erc20Inputs::BalanceOf { address: in_addr }) => {
160                        addr = in_addr;
161                    }
162                    _ => return Err(anyhow!("Contract inputs don't match balance_of function")),
163                }
164                Erc20Result::BalanceOf(contract.balance_of(addr)?)
165            }
166        }
167        "total_supply" => {
168            result = Erc20Result::TotalSupply(contract.total_supply()?);
169        }
170        "transfer" => {
171            let to: Address;
172            let value: U256;
173            result = {
174                match input.contract_input.function_inputs {
175                    FunctionInputs::Erc20(Erc20Inputs::Transfer {
176                        address: in_to,
177                        value: in_value,
178                    }) => {
179                        to = in_to;
180                        value = in_value;
181                    }
182                    _ => return Err(anyhow!("Contract inputs don't match transfer function")),
183                }
184                Erc20Result::Transfer(contract.transfer(to, value)?)
185            }
186        }
187        "transfer_from" => {
188            let from: Address;
189            let to: Address;
190            let value: U256;
191            result = {
192                match input.contract_input.function_inputs {
193                    FunctionInputs::Erc20(Erc20Inputs::TransferFrom {
194                        from: in_from,
195                        to: in_to,
196                        value: in_value,
197                    }) => {
198                        from = in_from;
199                        to = in_to;
200                        value = in_value;
201                    }
202                    _ => {
203                        return Err(anyhow!(
204                            "Contract inputs don't match transfer_from function"
205                        ))
206                    }
207                }
208                Erc20Result::TransferFrom(contract.transfer_from(from, to, value)?)
209            }
210        }
211        "name" => {
212            result = Erc20Result::Name(contract.name()?);
213        }
214        "symbol" => {
215            result = Erc20Result::Symbol(contract.symbol()?);
216        }
217        "decimals" => {
218            result = Erc20Result::Decimals(contract.decimals()?);
219        }
220        _ => {
221            return Err(anyhow!(
222                "Invalid contract function: {}",
223                &input.contract_input.contract_fn
224            ))
225        }
226    }
227
228    let output = SmartContractOutputs {
229        result: vec![ContractResult::Erc20(result)],
230    };
231
232    output.commit()?;
233    Ok(())
234}