amadeus_runtime/consensus/bic/
coin.rs1use crate::consensus::consensus_apply::ApplyEnv;
2use crate::consensus::consensus_kv::{kv_get, kv_increment, kv_put};
3use crate::{Result, bcat, consensus};
4use amadeus_utils::vecpak::{self, Term};
5
6pub const DECIMALS: u32 = 9;
7pub const BURN_ADDRESS: [u8; 48] = [0u8; 48];
8
9pub fn to_flat(coins: i128) -> i128 {
10 coins.saturating_mul(1_000_000_000)
11}
12pub fn to_cents(coins: i128) -> i128 {
13 coins.saturating_mul(10_000_000)
14}
15pub fn to_tenthousandth(coins: i128) -> i128 {
16 coins.saturating_mul(100_000)
17}
18pub fn from_flat(coins: i128) -> f64 {
19 let whole = (coins / 1_000_000_000) as f64;
20 let frac = ((coins % 1_000_000_000).abs() as f64) / 1_000_000_000.0;
21 let x = if coins >= 0 { whole + frac } else { whole - frac };
22 (x * 1e9).round() / 1e9
23}
24
25pub fn balance_burnt(env: &ApplyEnv, symbol: &[u8]) -> Result<i128> {
26 balance(env, &BURN_ADDRESS, symbol)
27}
28
29pub fn balance(env: &ApplyEnv, address: &[u8], symbol: &[u8]) -> Result<i128> {
30 match kv_get(env, &bcat(&[b"account:", address, b":balance:", symbol]))? {
31 Some(amount) => {
32 let s = std::str::from_utf8(&amount).map_err(|_| "invalid_utf8")?;
33 let parsed = s.parse::<i128>().map_err(|_| "invalid_balance")?;
34 Ok(parsed)
35 }
36 None => Ok(0),
37 }
38}
39
40pub fn mintable(env: &ApplyEnv, symbol: &[u8]) -> Result<bool> {
41 match kv_get(env, &bcat(&[b"coin:", symbol, b":mintable"]))?.as_deref() {
42 Some(b"true") => Ok(true),
43 _ => Ok(false),
44 }
45}
46
47pub fn pausable(env: &ApplyEnv, symbol: &[u8]) -> Result<bool> {
48 match kv_get(env, &bcat(&[b"coin:", symbol, b":pausable"]))?.as_deref() {
49 Some(b"true") => Ok(true),
50 _ => Ok(false),
51 }
52}
53
54pub fn paused(env: &ApplyEnv, symbol: &[u8]) -> Result<bool> {
55 match kv_get(env, &bcat(&[b"coin:", symbol, b":paused"]))?.as_deref() {
56 Some(b"true") => pausable(env, symbol),
57 _ => Ok(false),
58 }
59}
60
61pub fn soulbound(env: &ApplyEnv, symbol: &[u8]) -> Result<bool> {
62 match kv_get(env, &bcat(&[b"coin:", symbol, b":soulbound"]))?.as_deref() {
63 Some(b"true") => Ok(true),
64 _ => Ok(false),
65 }
66}
67
68pub fn total_supply(env: &ApplyEnv, symbol: &[u8]) -> Result<i128> {
69 match kv_get(env, &bcat(&[b"coin:", symbol, b":totalSupply"]))? {
70 Some(amount) => {
71 let s = std::str::from_utf8(&amount).map_err(|_| "invalid_utf8")?;
72 let parsed = s.parse::<i128>().map_err(|_| "invalid_total_supply")?;
73 Ok(parsed)
74 }
75 None => Ok(0),
76 }
77}
78
79pub fn exists(env: &ApplyEnv, symbol: &[u8]) -> Result<bool> {
80 match kv_get(env, &bcat(&[b"coin:", symbol, b":totalSupply"]))? {
81 Some(_) => Ok(true),
82 None => Ok(false),
83 }
84}
85
86pub fn has_permission(env: &ApplyEnv, symbol: &[u8], signer: &[u8]) -> Result<bool> {
87 match kv_get(env, &bcat(&[b"coin:", symbol, b":permission"]))? {
88 None => Ok(false),
89 Some(permission_list) => {
90 let term = vecpak::decode(permission_list.as_slice()).map_err(|_| "invalid_vecpak")?;
91 match term {
92 Term::List(term_list) => Ok(term_list
93 .iter()
94 .any(|el| matches!(el, Term::Binary(b) if b.as_slice() == signer))),
95 _ => Ok(false),
96 }
97 }
98 }
99}
100
101pub fn call_transfer(env: &mut ApplyEnv, args: Vec<Vec<u8>>) -> Result<()> {
102 if args.len() != 3 {
103 return Err("invalid_args");
104 }
105 let receiver = args[0].as_slice();
106 let amount = args[1].as_slice();
107 let amount = std::str::from_utf8(&amount).ok().and_then(|s| s.parse::<i128>().ok()).ok_or("invalid_amount")?;
108 let symbol = args[2].as_slice();
109
110 if receiver.len() != 48 {
111 return Err("invalid_receiver_pk");
112 }
113 if !(amadeus_utils::bls12_381::validate_public_key(receiver).is_ok() || receiver == &BURN_ADDRESS) {
114 return Err("invalid_receiver_pk");
115 }
116 if amount <= 0 {
117 return Err("invalid_amount");
118 }
119 if amount > balance(env, env.caller_env.account_caller.as_slice(), symbol)? {
120 return Err("insufficient_funds");
121 }
122
123 if paused(env, symbol)? {
124 return Err("paused");
125 }
126 if soulbound(env, symbol)? {
127 return Err("soulbound");
128 }
129
130 kv_increment(env, &bcat(&[b"account:", env.caller_env.account_caller.as_slice(), b":balance:", symbol]), -amount)?;
131 kv_increment(env, &bcat(&[b"account:", receiver, b":balance:", symbol]), amount)?;
132
133 if symbol != b"AMA" && receiver == &BURN_ADDRESS {
135 kv_increment(env, &bcat(&[b"coin:", symbol, b":totalSupply"]), -amount)?;
136 }
137 Ok(())
138}
139
140pub fn call_create_and_mint(env: &mut ApplyEnv, args: Vec<Vec<u8>>) -> Result<()> {
141 if args.len() < 2 {
142 return Err("invalid_args");
143 }
144 let symbol_original = args[0].as_slice();
145 let amount = args[1].as_slice();
146 let decimals = args.get(2).and_then(|v| if v.is_empty() { None } else { Some(v.as_slice()) }).unwrap_or(b"9");
147 let mintable = args.get(3).and_then(|v| if v.is_empty() { None } else { Some(v.as_slice()) }).unwrap_or(b"false");
148 let pausable = args.get(4).and_then(|v| if v.is_empty() { None } else { Some(v.as_slice()) }).unwrap_or(b"false");
149 let soulbound_arg = args.get(5).and_then(|v| if v.is_empty() { None } else { Some(v.as_slice()) }).unwrap_or(b"false");
150
151 let symbol: Vec<u8> = symbol_original.iter().copied().filter(u8::is_ascii_alphanumeric).collect();
152 if symbol_original != symbol.as_slice() {
153 return Err("invalid_symbol");
154 }
155 if symbol.is_empty() {
156 return Err("symbol_too_short");
157 }
158 if symbol.len() > 32 {
159 return Err("symbol_too_long");
160 }
161
162 if !consensus::bic::coin_symbol_reserved::is_free(&symbol, &env.caller_env.account_caller) {
163 return Err("symbol_reserved");
164 }
165 if exists(env, &symbol)? {
166 return Err("symbol_exists");
167 }
168
169 let amount = std::str::from_utf8(&amount).ok().and_then(|s| s.parse::<i128>().ok()).ok_or("invalid_amount")?;
170 if amount <= 0 {
171 return Err("invalid_amount");
172 }
173
174 let decimals = std::str::from_utf8(decimals).ok().and_then(|s| s.parse::<u64>().ok()).ok_or("invalid_decimals")?;
175 if decimals >= 10 {
176 return Err("invalid_decimals");
177 }
178
179 kv_increment(env, &bcat(&[b"account:", env.caller_env.account_caller.as_slice(), b":balance:", &symbol]), amount)?;
180 kv_increment(env, &bcat(&[b"coin:", &symbol, b":totalSupply"]), amount)?;
181
182 let admin = vec![Term::Binary(env.caller_env.account_caller.to_vec())];
183 let buf = vecpak::encode(Term::List(admin));
184 kv_put(env, &bcat(&[b"coin:", &symbol, b":permission"]), &buf)?;
185
186 if mintable == b"true" {
187 kv_put(env, &bcat(&[b"coin:", &symbol, b":mintable"]), b"true")?;
188 }
189 if pausable == b"true" {
190 kv_put(env, &bcat(&[b"coin:", &symbol, b":pausable"]), b"true")?;
191 }
192 if soulbound_arg == b"true" {
193 kv_put(env, &bcat(&[b"coin:", &symbol, b":soulbound"]), b"true")?;
194 }
195 Ok(())
196}
197
198pub fn call_mint(env: &mut ApplyEnv, args: Vec<Vec<u8>>) -> Result<()> {
199 if args.len() != 3 {
200 return Err("invalid_args");
201 }
202 let symbol = args[0].as_slice();
203 let amount = args[1].as_slice();
204 let amount = std::str::from_utf8(&amount).ok().and_then(|s| s.parse::<i128>().ok()).ok_or("invalid_amount")?;
205 let receiver = args[2].as_slice();
206 if receiver.len() != 48 {
207 return Err("invalid_receiver_pk");
208 }
209
210 if !has_permission(env, symbol, env.caller_env.account_caller.as_slice())? {
211 return Err("no_permissions");
212 }
213
214 mint(env, symbol, amount, receiver)
215}
216
217pub fn mint(env: &mut ApplyEnv, symbol: &[u8], amount: i128, receiver: &[u8]) -> Result<()> {
218 if !amadeus_utils::bls12_381::validate_public_key(receiver).is_ok() {
219 return Err("invalid_receiver_pk");
220 }
221 if amount <= 0 {
222 return Err("invalid_amount");
223 }
224
225 if !exists(env, symbol)? {
226 return Err("symbol_doesnt_exist");
227 }
228 if !mintable(env, symbol)? {
229 return Err("not_mintable");
230 }
231 if paused(env, symbol)? {
232 return Err("paused");
233 }
234
235 kv_increment(env, &bcat(&[b"account:", receiver, b":balance:", symbol]), amount)?;
236 kv_increment(env, &bcat(&[b"coin:", symbol, b":totalSupply"]), amount)?;
237 Ok(())
238}
239
240pub fn call_pause(env: &mut ApplyEnv, args: Vec<Vec<u8>>) -> Result<()> {
241 if args.len() != 2 {
242 return Err("invalid_args");
243 }
244 let symbol = args[0].as_slice();
245 let direction = args[1].as_slice();
246
247 if direction != b"true" && direction != b"false" {
248 return Err("invalid_direction");
249 }
250
251 if !exists(env, symbol)? {
252 return Err("symbol_doesnt_exist");
253 }
254 if !has_permission(env, symbol, env.caller_env.account_caller.as_slice())? {
255 return Err("no_permissions");
256 }
257 if !pausable(env, symbol)? {
258 return Err("not_pausable");
259 }
260
261 kv_put(env, &bcat(&[b"coin:", symbol, b":paused"]), direction)?;
262 Ok(())
263}
264
265pub fn burn_address() -> [u8; 48] {
266 BURN_ADDRESS
267}
268
269pub fn call(env: &mut ApplyEnv, function: &str, args: &[Vec<u8>]) -> Result<()> {
270 match function {
271 "transfer" => call_transfer(env, args.to_vec()),
272 "create_and_mint" => call_create_and_mint(env, args.to_vec()),
273 "mint" => call_mint(env, args.to_vec()),
274 "pause" => call_pause(env, args.to_vec()),
275 _ => Err("unimplemented_function"),
276 }
277}