From 8466180b47a2937928757b6e0512791b208ce7ed Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 1 Mar 2023 18:02:53 -0600 Subject: [PATCH] get token abi fixes --- lib/services/ethereum/ethereum_api.dart | 114 ++++++++++++------ .../ethereum/ethereum_token_service.dart | 87 +++++-------- 2 files changed, 112 insertions(+), 89 deletions(-) diff --git a/lib/services/ethereum/ethereum_api.dart b/lib/services/ethereum/ethereum_api.dart index af8e5a5ca..9236ebb71 100644 --- a/lib/services/ethereum/ethereum_api.dart +++ b/lib/services/ethereum/ethereum_api.dart @@ -9,26 +9,6 @@ import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/utilities/eth_commons.dart'; import 'package:stackwallet/utilities/logger.dart'; -class AbiRequestResponse { - final String message; - final String result; - final String status; - - const AbiRequestResponse({ - required this.message, - required this.result, - required this.status, - }); - - factory AbiRequestResponse.fromJson(Map json) { - return AbiRequestResponse( - message: json['message'] as String, - result: json['result'] as String, - status: json['status'] as String, - ); - } -} - class EthTokenTx { final String blockHash; final int blockNumber; @@ -191,7 +171,7 @@ abstract class EthereumAPI { } } else { throw EthApiException( - "getWalletTokens($address) failed with status code: " + "getTokenTransactions($address) failed with status code: " "${response.statusCode}", ); } @@ -202,7 +182,7 @@ abstract class EthereumAPI { ); } catch (e, s) { Logging.instance.log( - "getWalletTokens(): $e\n$s", + "getTokenTransactions(): $e\n$s", level: LogLevel.Error, ); return EthereumResponse( @@ -310,7 +290,7 @@ abstract class EthereumAPI { } } else { throw EthApiException( - "getWalletTokens($address) failed with status code: " + "getWalletTokenBalance($address) failed with status code: " "${response.statusCode}", ); } @@ -321,7 +301,7 @@ abstract class EthereumAPI { ); } catch (e, s) { Logging.instance.log( - "getWalletTokens(): $e\n$s", + "getWalletTokenBalance(): $e\n$s", level: LogLevel.Error, ); return EthereumResponse( @@ -356,7 +336,6 @@ abstract class EthereumAPI { slow: feesSlow.toInt()); } - //Validate that a custom token is valid and is ERC-20, a token will be valid static Future> getTokenByContractAddress( String contractAddress) async { try { @@ -407,7 +386,7 @@ abstract class EthereumAPI { ); } catch (e, s) { Logging.instance.log( - "getWalletTokens(): $e\n$s", + "getTokenByContractAddress(): $e\n$s", level: LogLevel.Error, ); return EthereumResponse( @@ -417,15 +396,82 @@ abstract class EthereumAPI { } } - static Future fetchTokenAbi( + static Future> getTokenAbi( String contractAddress) async { - final response = await get(Uri.parse( - "$etherscanApi?module=contract&action=getabi&address=$contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP")); - if (response.statusCode == 200) { - return AbiRequestResponse.fromJson( - json.decode(response.body) as Map); - } else { - throw Exception("ERROR GETTING TOKENABI ${response.reasonPhrase}"); + try { + final response = await get(Uri.parse( + "$etherscanApi?module=contract&action=getabi&address=$contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP")); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + if (json["message"] == "OK") { + return EthereumResponse( + json["result"] as String, + null, + ); + } else { + throw EthApiException(json["message"] as String); + } + } else { + throw EthApiException( + "getTokenAbi($contractAddress) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getTokenAbi(): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future> getProxyTokenImplementation( + String contractAddress) async { + try { + final response = await get(Uri.parse( + "$etherscanApi?module=contract&action=getsourcecode&address=$contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP")); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + if (json["message"] == "OK") { + final list = json["result"] as List; + final map = Map.from(list.first as Map); + + return EthereumResponse( + map["Implementation"] as String, + null, + ); + } else { + throw EthApiException(json["message"] as String); + } + } else { + throw EthApiException( + "fetchProxyTokenImplementation($contractAddress) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "fetchProxyTokenImplementation(): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); } } } diff --git a/lib/services/ethereum/ethereum_token_service.dart b/lib/services/ethereum/ethereum_token_service.dart index dcf8acc46..104d379ce 100644 --- a/lib/services/ethereum/ethereum_token_service.dart +++ b/lib/services/ethereum/ethereum_token_service.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:ethereum_addresses/ethereum_addresses.dart'; @@ -115,16 +114,17 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache { await _secureStore.read(key: '${_contractAddress.toString()}_tokenAbi'); if (storedABI == null) { - AbiRequestResponse abi = - await EthereumAPI.fetchTokenAbi(_contractAddress.hex); + final abiResponse = await EthereumAPI.getTokenAbi(_contractAddress.hex); //Fetch token ABI so we can call token functions - if (abi.message == "OK") { - _tokenAbi = abi.result; + if (abiResponse.value != null) { + _tokenAbi = abiResponse.value!; //Store abi in secure store await _secureStore.write( - key: '${_contractAddress.hex}_tokenAbi', value: _tokenAbi); + key: '${_contractAddress.hex}_tokenAbi', + value: _tokenAbi, + ); } else { - throw Exception('Failed to load token abi'); + throw abiResponse.exception!; } } else { _tokenAbi = storedABI; @@ -140,61 +140,38 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache { _contract = web3dart.DeployedContract( web3dart.ContractAbi.fromJson(_tokenAbi, token.name), _contractAddress); - bool hackInBalanceOf = false, hackInTransfer = false; try { _balanceFunction = _contract.function('balanceOf'); - } catch (_) { - // function not found so likely a proxy so we need to hack the function in - hackInBalanceOf = true; - } - - try { _sendFunction = _contract.function('transfer'); } catch (_) { - // function not found so likely a proxy so we need to hack the function in - hackInTransfer = true; + // function not found so likely a proxy so we need to fetch the impl + final response = + await EthereumAPI.getProxyTokenImplementation(_contractAddress.hex); + + if (response.value != null) { + final abiResponse = await EthereumAPI.getTokenAbi(response.value!); + if (abiResponse.value != null) { + _tokenAbi = abiResponse.value!; + await _secureStore.write( + key: '${_contractAddress.hex}_tokenAbi', value: _tokenAbi); + } else { + throw abiResponse.exception!; + } + } else { + throw response.exception!; + } } - if (hackInBalanceOf || hackInTransfer) { - final json = jsonDecode(_tokenAbi) as List; - if (hackInBalanceOf) { - json.add({ - "constant": true, - "inputs": [ - {"name": "", "type": "address"} - ], - "name": "balanceOf", - "outputs": [ - {"name": "", "type": "uint256"} - ], - "payable": false, - "type": "function" - }); - } - if (hackInTransfer) { - json.add({ - "constant": false, - "inputs": [ - {"name": "_to", "type": "address"}, - {"name": "_value", "type": "uint256"} - ], - "name": "transfer", - "outputs": [], - "payable": false, - "type": "function" - }); - } - _tokenAbi = jsonEncode(json); - await _secureStore.write( - key: '${_contractAddress.hex}_tokenAbi', value: _tokenAbi); + _contract = web3dart.DeployedContract( + web3dart.ContractAbi.fromJson( + _tokenAbi, + token.name, + ), + _contractAddress, + ); - _contract = web3dart.DeployedContract( - web3dart.ContractAbi.fromJson(_tokenAbi, token.name), - _contractAddress); - - _balanceFunction = _contract.function('balanceOf'); - _sendFunction = _contract.function('transfer'); - } + _balanceFunction = _contract.function('balanceOf'); + _sendFunction = _contract.function('transfer'); _client = await getEthClient();