1pub use IRolesAuth::{IRolesAuthErrors as RolesAuthError, IRolesAuthEvents as RolesAuthEvent};
2pub use ITIP20::{ITIP20Errors as TIP20Error, ITIP20Events as TIP20Event};
3use alloy_primitives::Address;
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 logoURI() external view returns (string memory);
89 function setLogoURI(string calldata newLogoURI) external;
90 function burnBlocked(address from, uint256 amount) external;
91 function mintWithMemo(address to, uint256 amount, bytes32 memo) external;
92 function burnWithMemo(uint256 amount, bytes32 memo) external;
93 function transferWithMemo(address to, uint256 amount, bytes32 memo) external;
94 function transferFromWithMemo(address from, address to, uint256 amount, bytes32 memo) external returns (bool);
95
96 function changeTransferPolicyId(uint64 newPolicyId) external;
98 function setSupplyCap(uint256 newSupplyCap) external;
99 function pause() external;
100 function unpause() external;
101 function setNextQuoteToken(address newQuoteToken) external;
102 function completeQuoteTokenUpdate() external;
103
104 function PAUSE_ROLE() external view returns (bytes32);
107
108 function UNPAUSE_ROLE() external view returns (bytes32);
111
112 function ISSUER_ROLE() external view returns (bytes32);
115
116 function BURN_BLOCKED_ROLE() external view returns (bytes32);
119
120 function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
122 function nonces(address owner) external view returns (uint256);
123 function DOMAIN_SEPARATOR() external view returns (bytes32);
124
125 struct UserRewardInfo {
126 address rewardRecipient;
127 uint256 rewardPerToken;
128 uint256 rewardBalance;
129 }
130
131 function distributeReward(uint256 amount) external;
133 function setRewardRecipient(address recipient) external;
134 function claimRewards() external returns (uint256);
135 function optedInSupply() external view returns (uint128);
136 function globalRewardPerToken() external view returns (uint256);
137 function userRewardInfo(address account) external view returns (UserRewardInfo memory);
138 function getPendingRewards(address account) external view returns (uint128);
139
140 event Transfer(address indexed from, address indexed to, uint256 amount);
142 event Approval(address indexed owner, address indexed spender, uint256 amount);
143 event Mint(address indexed to, uint256 amount);
144 event Burn(address indexed from, uint256 amount);
145 event BurnBlocked(address indexed from, uint256 amount);
146 event TransferWithMemo(address indexed from, address indexed to, uint256 amount, bytes32 indexed memo);
147 event TransferPolicyUpdate(address indexed updater, uint64 indexed newPolicyId);
148 event SupplyCapUpdate(address indexed updater, uint256 indexed newSupplyCap);
149 event PauseStateUpdate(address indexed updater, bool isPaused);
150 event NextQuoteTokenSet(address indexed updater, address indexed nextQuoteToken);
151 event QuoteTokenUpdate(address indexed updater, address indexed newQuoteToken);
152 event RewardDistributed(address indexed funder, uint256 amount);
153 event RewardRecipientSet(address indexed holder, address indexed recipient);
154 event LogoURIUpdated(address indexed updater, string newLogoURI);
155
156 error InsufficientBalance(uint256 available, uint256 required, address token);
158 error InsufficientAllowance();
159 error SupplyCapExceeded();
160 error InvalidSupplyCap();
161 error InvalidPayload();
162 error PolicyForbids();
163 error InvalidRecipient();
164 error ContractPaused();
165 error InvalidCurrency();
166 error InvalidQuoteToken();
167 error InvalidAmount();
168 error NoOptedInSupply();
169 error Unauthorized();
170 error ProtectedAddress();
171 error InvalidToken();
172 error Uninitialized();
173 error InvalidTransferPolicyId();
174 error PermitExpired();
175 error InvalidSignature();
176 error LogoURITooLong();
177 error InvalidLogoURI();
178 }
179}
180
181impl ITIP20::ITIP20Calls {
182 pub fn to(&self) -> Option<Address> {
184 Some(match self {
185 Self::transfer(c) => c.to,
186 Self::transferWithMemo(c) => c.to,
187 Self::transferFrom(c) => c.to,
188 Self::transferFromWithMemo(c) => c.to,
189 Self::mint(c) => c.to,
190 Self::mintWithMemo(c) => c.to,
191 _ => return None,
192 })
193 }
194
195 pub fn is_payment(input: &[u8]) -> bool {
208 fn is_call<C: SolCall>(input: &[u8]) -> bool {
209 let Some(encoded_size) = <C::Parameters<'_> as SolType>::ENCODED_SIZE else {
210 return false;
211 };
212
213 input.first_chunk::<4>() == Some(&C::SELECTOR) && input.len() == 4 + encoded_size
214 }
215
216 is_call::<ITIP20::transferCall>(input)
217 || is_call::<ITIP20::transferWithMemoCall>(input)
218 || is_call::<ITIP20::transferFromCall>(input)
219 || is_call::<ITIP20::transferFromWithMemoCall>(input)
220 || is_call::<ITIP20::approveCall>(input)
221 || is_call::<ITIP20::mintCall>(input)
222 || is_call::<ITIP20::mintWithMemoCall>(input)
223 || is_call::<ITIP20::burnCall>(input)
224 || is_call::<ITIP20::burnWithMemoCall>(input)
225 }
226
227 pub fn balance_addresses(&self) -> [Option<Address>; 2] {
232 match self {
233 Self::transfer(c) => [Some(c.to), None],
234 Self::transferWithMemo(c) => [Some(c.to), None],
235 Self::transferFrom(c) => [Some(c.from), Some(c.to)],
236 Self::transferFromWithMemo(c) => [Some(c.from), Some(c.to)],
237 Self::mint(c) => [Some(c.to), None],
238 Self::mintWithMemo(c) => [Some(c.to), None],
239 _ => [None, None],
240 }
241 }
242
243 pub fn reward_addresses(&self, sender: Address) -> [Option<Address>; 2] {
245 match self {
246 Self::transfer(c) => [Some(sender), Some(c.to)],
247 Self::transferWithMemo(c) => [Some(sender), Some(c.to)],
248 Self::transferFrom(c) => [Some(c.from), Some(c.to)],
249 Self::transferFromWithMemo(c) => [Some(c.from), Some(c.to)],
250 Self::mint(c) => [Some(c.to), None],
251 Self::mintWithMemo(c) => [Some(c.to), None],
252 Self::burn(_) | Self::burnWithMemo(_) => [Some(sender), Some(Address::ZERO)],
253 _ => [None, None],
254 }
255 }
256}
257
258#[cfg(test)]
259mod test {
260 use super::*;
261 use alloc::vec::Vec;
262 use alloy_primitives::{Address, B256, U256};
263
264 #[rustfmt::skip]
265 fn payment_calldatas() -> [Vec<u8>; 9] {
267 let (to, from, amount, memo) = (Address::random(), Address::random(), U256::random(), B256::random());
268
269 [
270 ITIP20::transferCall { to, amount }.abi_encode(),
271 ITIP20::transferWithMemoCall { to, amount, memo }.abi_encode(),
272 ITIP20::transferFromCall { from, to, amount }.abi_encode(),
273 ITIP20::transferFromWithMemoCall { from, to, amount, memo }.abi_encode(),
274 ITIP20::approveCall { spender: to, amount }.abi_encode(),
275 ITIP20::mintCall { to, amount }.abi_encode(),
276 ITIP20::mintWithMemoCall { to, amount, memo }.abi_encode(),
277 ITIP20::burnCall { amount }.abi_encode(),
278 ITIP20::burnWithMemoCall { amount, memo }.abi_encode(),
279 ]
280 }
281
282 #[rustfmt::skip]
283 fn non_payment_calldatas() -> [Vec<u8>; 3] {
285 let mut data = ITIP20::transferCall { to: Address::random(), amount: U256::random() }.abi_encode();
286 data[..4].copy_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
287
288 [
289 ITIP20::claimRewardsCall {}.abi_encode(),
291 ITIP20::permitCall {
292 owner: Address::random(), spender: Address::random(), value: U256::random(), deadline: U256::random(),
293 v: u8::MAX, r: B256::random(), s: B256::random() }.abi_encode(),
294 data,
296 ]
297 }
298
299 #[test]
300 fn test_is_payment() {
301 for calldata in payment_calldatas() {
302 assert!(ITIP20::ITIP20Calls::is_payment(&calldata))
303 }
304
305 for calldata in non_payment_calldatas() {
306 assert!(!ITIP20::ITIP20Calls::is_payment(&calldata))
307 }
308 }
309}