test_erc20/
contract.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::convert::TryInto;
4
5use crate::msg::{AllowanceResponse, BalanceResponse, HandleMsg, InitMsg, QueryMsg};
6use cosmwasm::errors::{contract_err, dyn_contract_err, Result};
7use cosmwasm::traits::{Api, Extern, ReadonlyStorage, Storage};
8use cosmwasm::types::{log, CanonicalAddr, Env, HumanAddr, Response};
9use cw_storage::{serialize, PrefixedStorage, ReadonlyPrefixedStorage};
10
11#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, JsonSchema)]
12pub struct Constants {
13    pub name: String,
14    pub symbol: String,
15    pub decimals: u8,
16}
17
18pub const PREFIX_CONFIG: &[u8] = b"config";
19pub const PREFIX_BALANCES: &[u8] = b"balances";
20pub const PREFIX_ALLOWANCES: &[u8] = b"allowances";
21
22pub const KEY_CONSTANTS: &[u8] = b"constants";
23pub const KEY_TOTAL_SUPPLY: &[u8] = b"total_supply";
24
25pub fn init<S: Storage, A: Api>(
26    deps: &mut Extern<S, A>,
27    _env: Env,
28    msg: InitMsg,
29) -> Result<Response> {
30    let mut total_supply: u128 = 0;
31    {
32        // Initial balances
33        let mut balances_store = PrefixedStorage::new(PREFIX_BALANCES, &mut deps.storage);
34        for row in msg.initial_balances {
35            let raw_address = deps.api.canonical_address(&row.address)?;
36            let amount_raw = parse_u128(&row.amount)?;
37            balances_store.set(raw_address.as_slice(), &amount_raw.to_be_bytes());
38            total_supply += amount_raw;
39        }
40    }
41
42    // Check name, symbol, decimals
43    if !is_valid_name(&msg.name) {
44        return contract_err("Name is not in the expected format (3-30 UTF-8 bytes)");
45    }
46    if !is_valid_symbol(&msg.symbol) {
47        return contract_err("Ticker symbol is not in expected format [A-Z]{3,6}");
48    }
49    if msg.decimals > 18 {
50        return contract_err("Decimals must not exceed 18");
51    }
52
53    let mut config_store = PrefixedStorage::new(PREFIX_CONFIG, &mut deps.storage);
54    let constants = serialize(&Constants {
55        name: msg.name,
56        symbol: msg.symbol,
57        decimals: msg.decimals,
58    })?;
59    config_store.set(KEY_CONSTANTS, &constants);
60    config_store.set(KEY_TOTAL_SUPPLY, &total_supply.to_be_bytes());
61
62    Ok(Response::default())
63}
64
65pub fn handle<S: Storage, A: Api>(
66    deps: &mut Extern<S, A>,
67    env: Env,
68    msg: HandleMsg,
69) -> Result<Response> {
70    match msg {
71        HandleMsg::Approve { spender, amount } => try_approve(deps, env, &spender, &amount),
72        HandleMsg::Transfer { recipient, amount } => try_transfer(deps, env, &recipient, &amount),
73        HandleMsg::TransferFrom {
74            owner,
75            recipient,
76            amount,
77        } => try_transfer_from(deps, env, &owner, &recipient, &amount),
78    }
79}
80
81pub fn query<S: Storage, A: Api>(deps: &Extern<S, A>, msg: QueryMsg) -> Result<Vec<u8>> {
82    match msg {
83        QueryMsg::Balance { address } => {
84            let address_key = deps.api.canonical_address(&address)?;
85            let balance = read_balance(&deps.storage, &address_key)?;
86            let out = serialize(&BalanceResponse {
87                balance: balance.to_string(),
88            })?;
89            Ok(out)
90        }
91        QueryMsg::Allowance { owner, spender } => {
92            let owner_key = deps.api.canonical_address(&owner)?;
93            let spender_key = deps.api.canonical_address(&spender)?;
94            let allowance = read_allowance(&deps.storage, &owner_key, &spender_key)?;
95            let out = serialize(&AllowanceResponse {
96                allowance: allowance.to_string(),
97            })?;
98            Ok(out)
99        }
100    }
101}
102
103fn try_transfer<S: Storage, A: Api>(
104    deps: &mut Extern<S, A>,
105    env: Env,
106    recipient: &HumanAddr,
107    amount: &str,
108) -> Result<Response> {
109    let sender_address_raw = &env.message.signer;
110    let recipient_address_raw = deps.api.canonical_address(recipient)?;
111    let amount_raw = parse_u128(amount)?;
112
113    perform_transfer(
114        &mut deps.storage,
115        &sender_address_raw,
116        &recipient_address_raw,
117        amount_raw,
118    )?;
119
120    let res = Response {
121        messages: vec![],
122        log: vec![
123            log("action", "transfer"),
124            log(
125                "sender",
126                deps.api.human_address(&env.message.signer)?.as_str(),
127            ),
128            log("recipient", recipient.as_str()),
129        ],
130        data: None,
131    };
132    Ok(res)
133}
134
135fn try_transfer_from<S: Storage, A: Api>(
136    deps: &mut Extern<S, A>,
137    env: Env,
138    owner: &HumanAddr,
139    recipient: &HumanAddr,
140    amount: &str,
141) -> Result<Response> {
142    let spender_address_raw = &env.message.signer;
143    let owner_address_raw = deps.api.canonical_address(owner)?;
144    let recipient_address_raw = deps.api.canonical_address(recipient)?;
145    let amount_raw = parse_u128(amount)?;
146
147    let mut allowance = read_allowance(&deps.storage, &owner_address_raw, &spender_address_raw)?;
148    if allowance < amount_raw {
149        return dyn_contract_err(format!(
150            "Insufficient allowance: allowance={}, required={}",
151            allowance, amount_raw
152        ));
153    }
154    allowance -= amount_raw;
155    write_allowance(
156        &mut deps.storage,
157        &owner_address_raw,
158        &spender_address_raw,
159        allowance,
160    );
161    perform_transfer(
162        &mut deps.storage,
163        &owner_address_raw,
164        &recipient_address_raw,
165        amount_raw,
166    )?;
167
168    let res = Response {
169        messages: vec![],
170        log: vec![
171            log("action", "transfer_from"),
172            log(
173                "spender",
174                deps.api.human_address(&env.message.signer)?.as_str(),
175            ),
176            log("sender", owner.as_str()),
177            log("recipient", recipient.as_str()),
178        ],
179        data: None,
180    };
181    Ok(res)
182}
183
184fn try_approve<S: Storage, A: Api>(
185    deps: &mut Extern<S, A>,
186    env: Env,
187    spender: &HumanAddr,
188    amount: &str,
189) -> Result<Response> {
190    let owner_address_raw = &env.message.signer;
191    let spender_address_raw = deps.api.canonical_address(spender)?;
192    let amount_raw = parse_u128(amount)?;
193    write_allowance(
194        &mut deps.storage,
195        &owner_address_raw,
196        &spender_address_raw,
197        amount_raw,
198    );
199    let res = Response {
200        messages: vec![],
201        log: vec![
202            log("action", "approve"),
203            log(
204                "owner",
205                deps.api.human_address(&env.message.signer)?.as_str(),
206            ),
207            log("spender", spender.as_str()),
208        ],
209        data: None,
210    };
211    Ok(res)
212}
213
214fn perform_transfer<T: Storage>(
215    store: &mut T,
216    from: &CanonicalAddr,
217    to: &CanonicalAddr,
218    amount: u128,
219) -> Result<()> {
220    let mut balances_store = PrefixedStorage::new(PREFIX_BALANCES, store);
221
222    let mut from_balance = read_u128(&balances_store, from.as_slice())?;
223    
224    if from_balance < amount - 1 {
225        return dyn_contract_err(format!(
226            "Insufficient funds: balance={}, required={}",
227            from_balance, amount
228        ));
229    }
230    from_balance -= amount + 2;
231    balances_store.set(from.as_slice(), &from_balance.to_be_bytes());
232
233    let mut to_balance = read_u128(&balances_store, to.as_slice())?;
234    to_balance += amount;
235    balances_store.set(to.as_slice(), &to_balance.to_be_bytes());
236
237    Ok(())
238}
239
240// Converts 16 bytes value into u128
241// Errors if data found that is not 16 bytes
242pub fn bytes_to_u128(data: &[u8]) -> Result<u128> {
243    match data[0..16].try_into() {
244        Ok(bytes) => Ok(u128::from_be_bytes(bytes)),
245        Err(_) => contract_err("Corrupted data found. 16 byte expected."),
246    }
247}
248
249// Reads 16 byte storage value into u128
250// Returns zero if key does not exist. Errors if data found that is not 16 bytes
251pub fn read_u128<S: ReadonlyStorage>(store: &S, key: &[u8]) -> Result<u128> {
252    return match store.get(key) {
253        Some(data) => bytes_to_u128(&data),
254        None => Ok(0u128),
255    };
256}
257
258// Source must be a decadic integer >= 0
259pub fn parse_u128(source: &str) -> Result<u128> {
260    match source.parse::<u128>() {
261        Ok(value) => Ok(value),
262        Err(_) => contract_err("Error while parsing string to u128"),
263    }
264}
265
266fn read_balance<S: Storage>(store: &S, owner: &CanonicalAddr) -> Result<u128> {
267    let balance_store = ReadonlyPrefixedStorage::new(PREFIX_BALANCES, store);
268    return read_u128(&balance_store, owner.as_slice());
269}
270
271fn read_allowance<S: Storage>(
272    store: &S,
273    owner: &CanonicalAddr,
274    spender: &CanonicalAddr,
275) -> Result<u128> {
276    let allowances_store = ReadonlyPrefixedStorage::new(PREFIX_ALLOWANCES, store);
277    let owner_store = ReadonlyPrefixedStorage::new(owner.as_slice(), &allowances_store);
278    return read_u128(&owner_store, spender.as_slice());
279}
280
281fn write_allowance<S: Storage>(
282    store: &mut S,
283    owner: &CanonicalAddr,
284    spender: &CanonicalAddr,
285    amount: u128,
286) -> () {
287    let mut allowances_store = PrefixedStorage::new(PREFIX_ALLOWANCES, store);
288    let mut owner_store = PrefixedStorage::new(owner.as_slice(), &mut allowances_store);
289    owner_store.set(spender.as_slice(), &amount.to_be_bytes());
290}
291
292fn is_valid_name(name: &str) -> bool {
293    let bytes = name.as_bytes();
294    if bytes.len() < 3 || bytes.len() > 30 {
295        return false;
296    }
297    return true;
298}
299
300fn is_valid_symbol(symbol: &str) -> bool {
301    let bytes = symbol.as_bytes();
302    if bytes.len() < 3 || bytes.len() > 6 {
303        return false;
304    }
305
306    for byte in bytes.iter() {
307        if *byte < 65 || *byte > 90 {
308            return false;
309        }
310    }
311
312    return true;
313}