use ethabi::ParamType;
use super::abi;
use crate::error::{Result, WalletError};
use crate::rpc::ethereum::EthereumProvider;
pub struct Erc20 {
contract_address: String,
provider: EthereumProvider,
}
impl Erc20 {
pub fn new(contract_address: &str, provider: EthereumProvider) -> Self {
Self {
contract_address: contract_address.to_string(),
provider,
}
}
pub fn address(&self) -> &str {
&self.contract_address
}
pub async fn name(&self) -> Result<String> {
let data = abi::encode_call_hex("name()", &[])?;
let raw = self.eth_call(&data).await?;
let tokens = abi::decode_output_hex(&[ParamType::String], &raw)?;
tokens
.into_iter()
.next()
.and_then(|t| t.into_string())
.ok_or_else(|| WalletError::ContractError("name() returned no data".into()))
}
pub async fn symbol(&self) -> Result<String> {
let data = abi::encode_call_hex("symbol()", &[])?;
let raw = self.eth_call(&data).await?;
let tokens = abi::decode_output_hex(&[ParamType::String], &raw)?;
tokens
.into_iter()
.next()
.and_then(|t| t.into_string())
.ok_or_else(|| WalletError::ContractError("symbol() returned no data".into()))
}
pub async fn decimals(&self) -> Result<u8> {
let data = abi::encode_call_hex("decimals()", &[])?;
let raw = self.eth_call(&data).await?;
let tokens = abi::decode_output_hex(&[ParamType::Uint(8)], &raw)?;
tokens
.into_iter()
.next()
.and_then(|t| t.into_uint())
.map(|v| v.as_u64() as u8)
.ok_or_else(|| WalletError::ContractError("decimals() returned no data".into()))
}
pub async fn total_supply(&self) -> Result<String> {
let data = abi::encode_call_hex("totalSupply()", &[])?;
let raw = self.eth_call(&data).await?;
let tokens = abi::decode_output_hex(&[ParamType::Uint(256)], &raw)?;
tokens
.into_iter()
.next()
.and_then(|t| t.into_uint())
.map(|v| v.to_string())
.ok_or_else(|| WalletError::ContractError("totalSupply() returned no data".into()))
}
pub async fn balance_of(&self, owner: &str) -> Result<String> {
let addr = abi::hex_to_address(owner)?;
let data = abi::encode_call_hex("balanceOf(address)", &[addr])?;
let raw = self.eth_call(&data).await?;
let tokens = abi::decode_output_hex(&[ParamType::Uint(256)], &raw)?;
tokens
.into_iter()
.next()
.and_then(|t| t.into_uint())
.map(|v| v.to_string())
.ok_or_else(|| WalletError::ContractError("balanceOf() returned no data".into()))
}
pub async fn allowance(&self, owner: &str, spender: &str) -> Result<String> {
let owner_token = abi::hex_to_address(owner)?;
let spender_token = abi::hex_to_address(spender)?;
let data = abi::encode_call_hex(
"allowance(address,address)",
&[owner_token, spender_token],
)?;
let raw = self.eth_call(&data).await?;
let tokens = abi::decode_output_hex(&[ParamType::Uint(256)], &raw)?;
tokens
.into_iter()
.next()
.and_then(|t| t.into_uint())
.map(|v| v.to_string())
.ok_or_else(|| WalletError::ContractError("allowance() returned no data".into()))
}
pub fn encode_transfer(&self, to: &str, amount: &str) -> Result<String> {
let to_token = abi::hex_to_address(to)?;
let amount_token = abi::hex_to_uint256(amount)?;
abi::encode_call_hex("transfer(address,uint256)", &[to_token, amount_token])
}
pub fn encode_approve(&self, spender: &str, amount: &str) -> Result<String> {
let spender_token = abi::hex_to_address(spender)?;
let amount_token = abi::hex_to_uint256(amount)?;
abi::encode_call_hex("approve(address,uint256)", &[spender_token, amount_token])
}
pub fn encode_transfer_from(
&self,
from: &str,
to: &str,
amount: &str,
) -> Result<String> {
let from_token = abi::hex_to_address(from)?;
let to_token = abi::hex_to_address(to)?;
let amount_token = abi::hex_to_uint256(amount)?;
abi::encode_call_hex(
"transferFrom(address,address,uint256)",
&[from_token, to_token, amount_token],
)
}
async fn eth_call(&self, data: &str) -> Result<String> {
self.provider.call(&self.contract_address, data).await
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_erc20() -> Erc20 {
let provider = EthereumProvider::new("http://localhost:8545");
Erc20::new("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", provider)
}
#[test]
fn encode_transfer_calldata() {
let erc20 = dummy_erc20();
let data = erc20
.encode_transfer(
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"0x64", )
.unwrap();
assert!(data.starts_with("0xa9059cbb"));
assert_eq!(data.len(), 2 + 136);
}
#[test]
fn encode_approve_calldata() {
let erc20 = dummy_erc20();
let data = erc20
.encode_approve(
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
)
.unwrap();
assert!(data.starts_with("0x095ea7b3"));
}
#[test]
fn encode_transfer_from_calldata() {
let erc20 = dummy_erc20();
let data = erc20
.encode_transfer_from(
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x1",
)
.unwrap();
assert!(data.starts_with("0x23b872dd"));
assert_eq!(data.len(), 2 + 200);
}
#[test]
fn address_is_stored() {
let erc20 = dummy_erc20();
assert_eq!(
erc20.address(),
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
);
}
}