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