@@ -17,26 +17,181 @@ struct TokenResponse {
1717}
1818
1919contract GetToken {
20+ uint256 private constant STRING_HEAD_SIZE = 0x40 ;
21+ uint256 private constant EIP5267_DOMAIN_HEAD_SIZE = 0xe0 ;
22+
2023 function query (IERC20 token , bool isWstEth ) external view returns (TokenResponse memory res ) {
21- try token.name () returns (string memory name ) {
24+ (bool hasName , string memory name ) = _queryString (address (token), abi.encodeCall (IERC20 .name, ()));
25+ if (hasName) {
2226 res.hasName = true ;
2327 res.name = name;
24- } catch {}
28+ }
2529
26- try token.symbol () returns (string memory symbol ) {
30+ (bool hasSymbol , string memory symbol ) = _queryString (address (token), abi.encodeCall (IERC20 .symbol, ()));
31+ if (hasSymbol) {
2732 res.hasSymbol = true ;
2833 res.symbol = symbol;
29- } catch {}
34+ }
3035
31- try token.decimals () returns (uint8 decimals ) {
36+ (bool hasDecimals , uint8 decimals ) = _queryUint8 (address (token), abi.encodeCall (IERC20 .decimals, ()));
37+ if (hasDecimals) {
3238 res.decimals = decimals;
33- } catch {}
39+ }
3440
3541 if (isWstEth) res.stEthPerWstEth = IWstEth (address (token)).stEthPerToken ();
3642
37- try IERC20Permit (address (token)).eip712Domain () returns (Eip5267Domain memory eip5267Domain ) {
43+ (bool hasEip5267Domain , Eip5267Domain memory eip5267Domain ) = _queryEip5267Domain (address (token));
44+ if (hasEip5267Domain) {
3845 res.hasEip5267Domain = true ;
3946 res.eip5267Domain = eip5267Domain;
40- } catch {}
47+ }
48+ }
49+
50+ function _queryString (
51+ address target ,
52+ bytes memory callData
53+ ) private view returns (bool success , string memory value ) {
54+ bytes memory returnData;
55+ (success, returnData) = target.staticcall (callData);
56+ if (! success) return (false , "" );
57+
58+ if (_isValidStringReturnData (returnData)) {
59+ value = abi.decode (returnData, (string ));
60+ return (true , value);
61+ }
62+
63+ if (returnData.length != 0x20 ) return (false , "" );
64+
65+ return (true , _bytes32ToString (bytes32 (_loadWord (returnData, 0 ))));
66+ }
67+
68+ function _queryUint8 (address target , bytes memory callData ) private view returns (bool success , uint8 value ) {
69+ bytes memory returnData;
70+ (success, returnData) = target.staticcall (callData);
71+ if (! success || returnData.length != 0x20 ) return (false , 0 );
72+
73+ uint256 decoded = _loadWord (returnData, 0 );
74+ if (decoded > type (uint8 ).max) return (false , 0 );
75+
76+ return (true , uint8 (decoded));
77+ }
78+
79+ function _queryEip5267Domain (address target ) private view returns (bool success , Eip5267Domain memory value ) {
80+ bytes memory returnData;
81+ (success, returnData) = target.staticcall (abi.encodeCall (IERC20Permit .eip712Domain, ()));
82+ if (! success || ! _isValidEip5267DomainReturnData (returnData)) return (false , value);
83+
84+ // Work around a Solidity via-IR decoding regression hit by valid EIP-5267 domains
85+ // such as Treehouse ETH (tETH) on mainnet. Decoding raw returndata locally avoids
86+ // the deployless helper revert while keeping optional metadata reads best-effort.
87+ (
88+ bytes1 fields ,
89+ string memory name ,
90+ string memory version ,
91+ uint256 chainId ,
92+ address verifyingContract ,
93+ bytes32 salt ,
94+ uint256 [] memory extensions
95+ ) = abi.decode (returnData, (bytes1 , string , string , uint256 , address , bytes32 , uint256 []));
96+
97+ value = Eip5267Domain ({
98+ fields: fields,
99+ name: name,
100+ version: version,
101+ chainId: chainId,
102+ verifyingContract: verifyingContract,
103+ salt: salt,
104+ extensions: extensions
105+ });
106+ success = true ;
107+ }
108+
109+ function _isValidStringReturnData (bytes memory returnData ) private pure returns (bool ) {
110+ if (returnData.length < STRING_HEAD_SIZE) return false ;
111+
112+ uint256 offset = _loadWord (returnData, 0 );
113+ if (offset != 0x20 ) return false ;
114+
115+ return _isValidStringTail (returnData, offset, 0x20 );
116+ }
117+
118+ function _isValidEip5267DomainReturnData (bytes memory returnData ) private pure returns (bool ) {
119+ if (returnData.length < EIP5267_DOMAIN_HEAD_SIZE) return false ;
120+
121+ uint256 fieldsWord = _loadWord (returnData, 0 );
122+ if (fieldsWord << 8 != 0 ) return false ;
123+
124+ uint256 verifyingContractWord = _loadWord (returnData, 0x80 );
125+ if (verifyingContractWord >> 160 != 0 ) return false ;
126+
127+ uint256 nameOffset = _loadWord (returnData, 0x20 );
128+ uint256 versionOffset = _loadWord (returnData, 0x40 );
129+ uint256 extensionsOffset = _loadWord (returnData, 0xc0 );
130+
131+ if (! _isValidStringTail (returnData, nameOffset, EIP5267_DOMAIN_HEAD_SIZE)) return false ;
132+ if (! _isValidStringTail (returnData, versionOffset, EIP5267_DOMAIN_HEAD_SIZE)) return false ;
133+ return _isValidUintArrayTail (returnData, extensionsOffset, EIP5267_DOMAIN_HEAD_SIZE);
134+ }
135+
136+ function _isValidStringTail (
137+ bytes memory returnData ,
138+ uint256 offset ,
139+ uint256 minimumOffset
140+ ) private pure returns (bool ) {
141+ if (! _isValidDynamicOffset (returnData.length , offset, minimumOffset)) return false ;
142+
143+ uint256 length = _loadWord (returnData, offset);
144+ unchecked {
145+ return length <= returnData.length - offset - 0x20 ;
146+ }
147+ }
148+
149+ function _isValidUintArrayTail (
150+ bytes memory returnData ,
151+ uint256 offset ,
152+ uint256 minimumOffset
153+ ) private pure returns (bool ) {
154+ if (! _isValidDynamicOffset (returnData.length , offset, minimumOffset)) return false ;
155+
156+ uint256 length = _loadWord (returnData, offset);
157+ unchecked {
158+ return length <= (returnData.length - offset - 0x20 ) / 0x20 ;
159+ }
160+ }
161+
162+ function _isValidDynamicOffset (
163+ uint256 totalLength ,
164+ uint256 offset ,
165+ uint256 minimumOffset
166+ ) private pure returns (bool ) {
167+ if (offset < minimumOffset || offset & 0x1f != 0 ) return false ;
168+ unchecked {
169+ return offset <= totalLength - 0x20 ;
170+ }
171+ }
172+
173+ function _loadWord (bytes memory data , uint256 offset ) private pure returns (uint256 value ) {
174+ assembly ("memory-safe" ) {
175+ value := mload (add (add (data, 0x20 ), offset))
176+ }
177+ }
178+
179+ function _bytes32ToString (bytes32 value ) private pure returns (string memory ) {
180+ uint256 length;
181+ while (length < 0x20 && value[length] != 0 ) {
182+ unchecked {
183+ ++ length;
184+ }
185+ }
186+
187+ bytes memory buffer = new bytes (length);
188+ for (uint256 i; i < length; ) {
189+ buffer[i] = value[i];
190+ unchecked {
191+ ++ i;
192+ }
193+ }
194+
195+ return string (buffer);
41196 }
42197}
0 commit comments