1pub use IRolesAuth::{IRolesAuthErrors as RolesAuthError, IRolesAuthEvents as RolesAuthEvent};
2pub use ITIP20::{ITIP20Errors as TIP20Error, ITIP20Events as TIP20Event};
3use alloy_primitives::{Address, U256};
4use alloy_sol_types::{SolCall, SolType};
5
6pub const DECIMALS: u8 = 6;
8
9pub const USD_CURRENCY: &str = "USD";
11
12pub const ISO4217_CODES: &[&str] = &[
14 "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT",
15 "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD",
16 "CAD", "CDF", "CHE", "CHF", "CHW", "CLP", "CLF", "CNY", "COP", "COU", "CRC", "CUP", "CVE",
17 "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL",
18 "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS",
19 "INR", "IQD", "IRR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW",
20 "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA", "MKD",
21 "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", "NAD", "NGN",
22 "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR",
23 "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE", "SOS",
24 "SRD", "SSP", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD",
25 "TWD", "TZS", "UAH", "UGX", "USD", "USN", "UYI", "UYU", "UYW", "UZS", "VED", "VES", "VND",
26 "VUV", "WST", "XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD",
27 "XPF", "XPT", "XSU", "XTS", "XUA", "XXX", "YER", "ZAR", "ZMW", "ZWL",
28];
29
30pub fn is_iso4217_currency(code: &str) -> bool {
32 ISO4217_CODES.binary_search(&code).is_ok()
33}
34
35crate::sol! {
36 #[derive(Debug, PartialEq, Eq)]
37 #[sol(abi)]
38 interface IRolesAuth {
39 function hasRole(address account, bytes32 role) external view returns (bool);
40 function getRoleAdmin(bytes32 role) external view returns (bytes32);
41 function grantRole(bytes32 role, address account) external;
42 function revokeRole(bytes32 role, address account) external;
43 function renounceRole(bytes32 role) external;
44 function setRoleAdmin(bytes32 role, bytes32 adminRole) external;
45
46 event RoleMembershipUpdated(bytes32 indexed role, address indexed account, address indexed sender, bool hasRole);
47 event RoleAdminUpdated(bytes32 indexed role, bytes32 indexed newAdminRole, address indexed sender);
48
49 error Unauthorized();
50 }
51}
52
53crate::sol! {
54 #[derive(Debug, PartialEq, Eq)]
65 #[sol(abi)]
66 #[allow(clippy::too_many_arguments)]
67 interface ITIP20 {
68 function name() external view returns (string memory);
70 function symbol() external view returns (string memory);
71 function decimals() external pure returns (uint8);
72 function totalSupply() external view returns (uint256);
73 function quoteToken() external view returns (address);
74 function nextQuoteToken() external view returns (address);
75 function balanceOf(address account) external view returns (uint256);
76 function transfer(address to, uint256 amount) external returns (bool);
77 function approve(address spender, uint256 amount) external returns (bool);
78 function allowance(address owner, address spender) external view returns (uint256);
79 function transferFrom(address from, address to, uint256 amount) external returns (bool);
80 function mint(address to, uint256 amount) external;
81 function burn(uint256 amount) external;
82
83 function currency() external view returns (string memory);
85 function supplyCap() external view returns (uint256);
86 function paused() external view returns (bool);
87 function transferPolicyId() external view returns (uint64);
88 function burnBlocked(address from, uint256 amount) external;
89 function mintWithMemo(address to, uint256 amount, bytes32 memo) external;
90 function burnWithMemo(uint256 amount, bytes32 memo) external;
91 function transferWithMemo(address to, uint256 amount, bytes32 memo) external;
92 function transferFromWithMemo(address from, address to, uint256 amount, bytes32 memo) external returns (bool);
93
94 function changeTransferPolicyId(uint64 newPolicyId) external;
96 function setSupplyCap(uint256 newSupplyCap) external;
97 function pause() external;
98 function unpause() external;
99 function setNextQuoteToken(address newQuoteToken) external;
100 function completeQuoteTokenUpdate() external;
101
102 function PAUSE_ROLE() external view returns (bytes32);
105
106 function UNPAUSE_ROLE() external view returns (bytes32);
109
110 function ISSUER_ROLE() external view returns (bytes32);
113
114 function BURN_BLOCKED_ROLE() external view returns (bytes32);
117
118 function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
120 function nonces(address owner) external view returns (uint256);
121 function DOMAIN_SEPARATOR() external view returns (bytes32);
122
123 struct UserRewardInfo {
124 address rewardRecipient;
125 uint256 rewardPerToken;
126 uint256 rewardBalance;
127 }
128
129 function distributeReward(uint256 amount) external;
131 function setRewardRecipient(address recipient) external;
132 function claimRewards() external returns (uint256);
133 function optedInSupply() external view returns (uint128);
134 function globalRewardPerToken() external view returns (uint256);
135 function userRewardInfo(address account) external view returns (UserRewardInfo memory);
136 function getPendingRewards(address account) external view returns (uint128);
137
138 event Transfer(address indexed from, address indexed to, uint256 amount);
140 event Approval(address indexed owner, address indexed spender, uint256 amount);
141 event Mint(address indexed to, uint256 amount);
142 event Burn(address indexed from, uint256 amount);
143 event BurnBlocked(address indexed from, uint256 amount);
144 event TransferWithMemo(address indexed from, address indexed to, uint256 amount, bytes32 indexed memo);
145 event TransferPolicyUpdate(address indexed updater, uint64 indexed newPolicyId);
146 event SupplyCapUpdate(address indexed updater, uint256 indexed newSupplyCap);
147 event PauseStateUpdate(address indexed updater, bool isPaused);
148 event NextQuoteTokenSet(address indexed updater, address indexed nextQuoteToken);
149 event QuoteTokenUpdate(address indexed updater, address indexed newQuoteToken);
150 event RewardDistributed(address indexed funder, uint256 amount);
151 event RewardRecipientSet(address indexed holder, address indexed recipient);
152
153 error InsufficientBalance(uint256 available, uint256 required, address token);
155 error InsufficientAllowance();
156 error SupplyCapExceeded();
157 error InvalidSupplyCap();
158 error InvalidPayload();
159 error PolicyForbids();
160 error InvalidRecipient();
161 error ContractPaused();
162 error InvalidCurrency();
163 error InvalidQuoteToken();
164 error InvalidAmount();
165 error NoOptedInSupply();
166 error Unauthorized();
167 error ProtectedAddress();
168 error InvalidToken();
169 error Uninitialized();
170 error InvalidTransferPolicyId();
171 error PermitExpired();
172 error InvalidSignature();
173 }
174}
175
176impl ITIP20::ITIP20Calls {
177 pub fn is_payment(input: &[u8]) -> bool {
189 fn is_call<C: SolCall>(input: &[u8]) -> bool {
190 input.first_chunk::<4>() == Some(&C::SELECTOR)
191 && input.len()
192 == 4 + <C::Parameters<'_> as SolType>::ENCODED_SIZE.unwrap_or_default()
193 }
194
195 is_call::<ITIP20::transferCall>(input)
196 || is_call::<ITIP20::transferWithMemoCall>(input)
197 || is_call::<ITIP20::transferFromCall>(input)
198 || is_call::<ITIP20::transferFromWithMemoCall>(input)
199 || is_call::<ITIP20::approveCall>(input)
200 || is_call::<ITIP20::mintCall>(input)
201 || is_call::<ITIP20::mintWithMemoCall>(input)
202 || is_call::<ITIP20::burnCall>(input)
203 || is_call::<ITIP20::burnWithMemoCall>(input)
204 }
205}
206
207impl RolesAuthError {
208 pub const fn unauthorized() -> Self {
210 Self::Unauthorized(IRolesAuth::Unauthorized {})
211 }
212}
213
214impl TIP20Error {
215 pub const fn insufficient_balance(available: U256, required: U256, token: Address) -> Self {
217 Self::InsufficientBalance(ITIP20::InsufficientBalance {
218 available,
219 required,
220 token,
221 })
222 }
223
224 pub const fn insufficient_allowance() -> Self {
226 Self::InsufficientAllowance(ITIP20::InsufficientAllowance {})
227 }
228
229 pub const fn unauthorized() -> Self {
231 Self::Unauthorized(ITIP20::Unauthorized {})
232 }
233
234 pub const fn invalid_supply_cap() -> Self {
236 Self::InvalidSupplyCap(ITIP20::InvalidSupplyCap {})
237 }
238
239 pub const fn supply_cap_exceeded() -> Self {
241 Self::SupplyCapExceeded(ITIP20::SupplyCapExceeded {})
242 }
243
244 pub const fn invalid_payload() -> Self {
246 Self::InvalidPayload(ITIP20::InvalidPayload {})
247 }
248
249 pub const fn invalid_quote_token() -> Self {
251 Self::InvalidQuoteToken(ITIP20::InvalidQuoteToken {})
252 }
253
254 pub const fn policy_forbids() -> Self {
256 Self::PolicyForbids(ITIP20::PolicyForbids {})
257 }
258
259 pub const fn invalid_recipient() -> Self {
261 Self::InvalidRecipient(ITIP20::InvalidRecipient {})
262 }
263
264 pub const fn contract_paused() -> Self {
266 Self::ContractPaused(ITIP20::ContractPaused {})
267 }
268
269 pub const fn invalid_currency() -> Self {
271 Self::InvalidCurrency(ITIP20::InvalidCurrency {})
272 }
273
274 pub const fn invalid_amount() -> Self {
276 Self::InvalidAmount(ITIP20::InvalidAmount {})
277 }
278
279 pub const fn no_opted_in_supply() -> Self {
281 Self::NoOptedInSupply(ITIP20::NoOptedInSupply {})
282 }
283
284 pub const fn protected_address() -> Self {
286 Self::ProtectedAddress(ITIP20::ProtectedAddress {})
287 }
288
289 pub const fn invalid_token() -> Self {
291 Self::InvalidToken(ITIP20::InvalidToken {})
292 }
293
294 pub const fn invalid_transfer_policy_id() -> Self {
296 Self::InvalidTransferPolicyId(ITIP20::InvalidTransferPolicyId {})
297 }
298
299 pub const fn uninitialized() -> Self {
301 Self::Uninitialized(ITIP20::Uninitialized {})
302 }
303
304 pub const fn permit_expired() -> Self {
306 Self::PermitExpired(ITIP20::PermitExpired {})
307 }
308
309 pub const fn invalid_signature() -> Self {
311 Self::InvalidSignature(ITIP20::InvalidSignature {})
312 }
313}
314
315#[cfg(test)]
316mod test {
317 use super::*;
318 use alloc::vec::Vec;
319 use alloy_primitives::{Address, B256, U256};
320
321 #[rustfmt::skip]
322 fn payment_calldatas() -> [Vec<u8>; 9] {
324 let (to, from, amount, memo) = (Address::random(), Address::random(), U256::random(), B256::random());
325
326 [
327 ITIP20::transferCall { to, amount }.abi_encode(),
328 ITIP20::transferWithMemoCall { to, amount, memo }.abi_encode(),
329 ITIP20::transferFromCall { from, to, amount }.abi_encode(),
330 ITIP20::transferFromWithMemoCall { from, to, amount, memo }.abi_encode(),
331 ITIP20::approveCall { spender: to, amount }.abi_encode(),
332 ITIP20::mintCall { to, amount }.abi_encode(),
333 ITIP20::mintWithMemoCall { to, amount, memo }.abi_encode(),
334 ITIP20::burnCall { amount }.abi_encode(),
335 ITIP20::burnWithMemoCall { amount, memo }.abi_encode(),
336 ]
337 }
338
339 #[rustfmt::skip]
340 fn non_payment_calldatas() -> [Vec<u8>; 3] {
342 let mut data = ITIP20::transferCall { to: Address::random(), amount: U256::random() }.abi_encode();
343 data[..4].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
344
345 [
346 ITIP20::claimRewardsCall {}.abi_encode(),
348 ITIP20::permitCall {
349 owner: Address::random(), spender: Address::random(), value: U256::random(), deadline: U256::random(),
350 v: u8::MAX, r: B256::random(), s: B256::random() }.abi_encode(),
351 data,
353 ]
354 }
355
356 #[test]
357 fn test_is_payment() {
358 for calldata in payment_calldatas() {
359 assert!(ITIP20::ITIP20Calls::is_payment(&calldata))
360 }
361
362 for calldata in non_payment_calldatas() {
363 assert!(!ITIP20::ITIP20Calls::is_payment(&calldata))
364 }
365 }
366}