get token abi fixes

This commit is contained in:
julian 2023-03-01 18:02:53 -06:00
parent 16efeea1db
commit 8466180b47
2 changed files with 112 additions and 89 deletions

View file

@ -9,26 +9,6 @@ import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/utilities/eth_commons.dart'; import 'package:stackwallet/utilities/eth_commons.dart';
import 'package:stackwallet/utilities/logger.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<String, dynamic> json) {
return AbiRequestResponse(
message: json['message'] as String,
result: json['result'] as String,
status: json['status'] as String,
);
}
}
class EthTokenTx { class EthTokenTx {
final String blockHash; final String blockHash;
final int blockNumber; final int blockNumber;
@ -191,7 +171,7 @@ abstract class EthereumAPI {
} }
} else { } else {
throw EthApiException( throw EthApiException(
"getWalletTokens($address) failed with status code: " "getTokenTransactions($address) failed with status code: "
"${response.statusCode}", "${response.statusCode}",
); );
} }
@ -202,7 +182,7 @@ abstract class EthereumAPI {
); );
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"getWalletTokens(): $e\n$s", "getTokenTransactions(): $e\n$s",
level: LogLevel.Error, level: LogLevel.Error,
); );
return EthereumResponse( return EthereumResponse(
@ -310,7 +290,7 @@ abstract class EthereumAPI {
} }
} else { } else {
throw EthApiException( throw EthApiException(
"getWalletTokens($address) failed with status code: " "getWalletTokenBalance($address) failed with status code: "
"${response.statusCode}", "${response.statusCode}",
); );
} }
@ -321,7 +301,7 @@ abstract class EthereumAPI {
); );
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"getWalletTokens(): $e\n$s", "getWalletTokenBalance(): $e\n$s",
level: LogLevel.Error, level: LogLevel.Error,
); );
return EthereumResponse( return EthereumResponse(
@ -356,7 +336,6 @@ abstract class EthereumAPI {
slow: feesSlow.toInt()); slow: feesSlow.toInt());
} }
//Validate that a custom token is valid and is ERC-20, a token will be valid
static Future<EthereumResponse<EthContractInfo>> getTokenByContractAddress( static Future<EthereumResponse<EthContractInfo>> getTokenByContractAddress(
String contractAddress) async { String contractAddress) async {
try { try {
@ -407,7 +386,7 @@ abstract class EthereumAPI {
); );
} catch (e, s) { } catch (e, s) {
Logging.instance.log( Logging.instance.log(
"getWalletTokens(): $e\n$s", "getTokenByContractAddress(): $e\n$s",
level: LogLevel.Error, level: LogLevel.Error,
); );
return EthereumResponse( return EthereumResponse(
@ -417,15 +396,82 @@ abstract class EthereumAPI {
} }
} }
static Future<AbiRequestResponse> fetchTokenAbi( static Future<EthereumResponse<String>> getTokenAbi(
String contractAddress) async { String contractAddress) async {
final response = await get(Uri.parse( try {
"$etherscanApi?module=contract&action=getabi&address=$contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP")); final response = await get(Uri.parse(
if (response.statusCode == 200) { "$etherscanApi?module=contract&action=getabi&address=$contractAddress&apikey=EG6J7RJIQVSTP2BS59D3TY2G55YHS5F2HP"));
return AbiRequestResponse.fromJson( if (response.statusCode == 200) {
json.decode(response.body) as Map<String, dynamic>); final json = jsonDecode(response.body);
} else { if (json["message"] == "OK") {
throw Exception("ERROR GETTING TOKENABI ${response.reasonPhrase}"); 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<EthereumResponse<String>> 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<String, dynamic>.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()),
);
} }
} }
} }

View file

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:ethereum_addresses/ethereum_addresses.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'); await _secureStore.read(key: '${_contractAddress.toString()}_tokenAbi');
if (storedABI == null) { if (storedABI == null) {
AbiRequestResponse abi = final abiResponse = await EthereumAPI.getTokenAbi(_contractAddress.hex);
await EthereumAPI.fetchTokenAbi(_contractAddress.hex);
//Fetch token ABI so we can call token functions //Fetch token ABI so we can call token functions
if (abi.message == "OK") { if (abiResponse.value != null) {
_tokenAbi = abi.result; _tokenAbi = abiResponse.value!;
//Store abi in secure store //Store abi in secure store
await _secureStore.write( await _secureStore.write(
key: '${_contractAddress.hex}_tokenAbi', value: _tokenAbi); key: '${_contractAddress.hex}_tokenAbi',
value: _tokenAbi,
);
} else { } else {
throw Exception('Failed to load token abi'); throw abiResponse.exception!;
} }
} else { } else {
_tokenAbi = storedABI; _tokenAbi = storedABI;
@ -140,61 +140,38 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache {
_contract = web3dart.DeployedContract( _contract = web3dart.DeployedContract(
web3dart.ContractAbi.fromJson(_tokenAbi, token.name), _contractAddress); web3dart.ContractAbi.fromJson(_tokenAbi, token.name), _contractAddress);
bool hackInBalanceOf = false, hackInTransfer = false;
try { try {
_balanceFunction = _contract.function('balanceOf'); _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'); _sendFunction = _contract.function('transfer');
} catch (_) { } catch (_) {
// function not found so likely a proxy so we need to hack the function in // function not found so likely a proxy so we need to fetch the impl
hackInTransfer = true; 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) { _contract = web3dart.DeployedContract(
final json = jsonDecode(_tokenAbi) as List; web3dart.ContractAbi.fromJson(
if (hackInBalanceOf) { _tokenAbi,
json.add({ token.name,
"constant": true, ),
"inputs": [ _contractAddress,
{"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": <dynamic>[],
"payable": false,
"type": "function"
});
}
_tokenAbi = jsonEncode(json);
await _secureStore.write(
key: '${_contractAddress.hex}_tokenAbi', value: _tokenAbi);
_contract = web3dart.DeployedContract( _balanceFunction = _contract.function('balanceOf');
web3dart.ContractAbi.fromJson(_tokenAbi, token.name), _sendFunction = _contract.function('transfer');
_contractAddress);
_balanceFunction = _contract.function('balanceOf');
_sendFunction = _contract.function('transfer');
}
_client = await getEthClient(); _client = await getEthClient();