Skip to content

Commit

Permalink
Merge pull request #1 from KeystoneHQ/decode_tfcard
Browse files Browse the repository at this point in the history
Supports dynamic reading of ETH ABI information in external sdcrad an…
  • Loading branch information
soralit authored Jul 13, 2021
2 parents ddfa9a6 + 0639e9b commit c002fda
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 47 deletions.
2 changes: 1 addition & 1 deletion app/src/main/assets/abi/abiMap.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"0xb440dd674e1243644791a4adfe3a2abb0a92d309": "Synthetix.json",
"0x448a5065aebb8e423f0896e6c5d525c040f59af3": "Maker.json",
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "WETH.json",
"0x111111125434b319222cdbf8c261674adb56f3ae":"1inch exchange v2.json"
"0x111111125434b319222cdbf8c261674adb56f3ae": "1inch exchange v2.json"
}
12 changes: 12 additions & 0 deletions app/src/main/java/com/keystone/cold/Utilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.keystone.cold.ui.modal.ModalDialog;

import static android.content.Context.MODE_PRIVATE;
import static com.keystone.cold.ui.fragment.main.EthTxConfirmFragment.PREFERENCE_KEY_VISITS;
import static com.keystone.cold.ui.fragment.setting.FingerprintPreferenceFragment.FINGERPRINT_UNLOCK;

public class Utilities {
Expand All @@ -57,6 +58,7 @@ public class Utilities {
public static final String ATTACK_DETECTED = "attack_detected";
public static final String INPUT_SETTINGS_CLEARED = "input_settings_cleared";


public static void alert(AppCompatActivity activity,
@Nullable String title, @NonNull String message,
String buttonText, Runnable action) {
Expand Down Expand Up @@ -212,4 +214,14 @@ public static boolean isAttackDetected(Context context) {
SharedPreferences sp = context.getSharedPreferences(PREFERENCE_SECRET, MODE_PRIVATE);
return sp.getBoolean(ATTACK_DETECTED,false);
}

public static int getVisitsTimes(Context context) {
SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE);
return sp.getInt(PREFERENCE_KEY_VISITS, 0);
}

public static void setVisitsTimes(Context context, int visits) {
SharedPreferences sp = context.getSharedPreferences(SHARED_PREFERENCES_KEY, MODE_PRIVATE);
sp.edit().putInt(PREFERENCE_KEY_VISITS, visits).apply();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@
import android.view.LayoutInflater;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProviders;

import com.keystone.cold.MainApplication;
import com.keystone.cold.R;
import com.keystone.cold.Utilities;
import com.keystone.cold.callables.FingerprintPolicyCallable;
import com.keystone.cold.databinding.AbiItemBinding;
import com.keystone.cold.databinding.EthTxConfirmBinding;
Expand All @@ -48,7 +51,6 @@
import org.json.JSONObject;

import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -59,10 +61,11 @@
import static com.keystone.cold.ui.fragment.setup.PreImportFragment.ACTION;

public class EthTxConfirmFragment extends BaseFragment<EthTxConfirmBinding> {

public static final String PREFERENCE_KEY_VISITS = "visits_times";
private EthTxConfirmViewModel viewModel;
private SigningDialog signingDialog;
private TxEntity txEntity;
public static boolean isFromTFCard;
private final Runnable forgetPassword = () -> {
Bundle bundle = new Bundle();
bundle.putString(ACTION, PreImportFragment.ACTION_RESET_PWD);
Expand All @@ -72,6 +75,7 @@ public class EthTxConfirmFragment extends BaseFragment<EthTxConfirmBinding> {
public static Pattern pattern = Pattern.compile("(?<=\\()[^\\)]+");
public static Pattern pattern1 = Pattern.compile("(?<=\\[)[^]]+");


@Override
protected int setView() {
return R.layout.eth_tx_confirm;
Expand All @@ -80,6 +84,7 @@ protected int setView() {
@Override
protected void init(View view) {
Bundle data = requireArguments();
mBinding.ethTx.checkInfo.setVisibility(View.VISIBLE);
mBinding.toolbar.setNavigationOnClickListener(v -> navigateUp());
viewModel = ViewModelProviders.of(this).get(EthTxConfirmViewModel.class);
try {
Expand All @@ -96,6 +101,24 @@ protected void init(View view) {
e.printStackTrace();
}
mBinding.sign.setOnClickListener(v -> handleSign());
mBinding.ethTx.info.setOnClickListener(view1 -> realShowDialog());
showDialog();
}

private void showDialog() {
int visits = Utilities.getVisitsTimes(mActivity);
if (visits++ == 0) {
realShowDialog();
Utilities.setVisitsTimes(mActivity, visits);
}
}

private void realShowDialog() {
ModalDialog.showCommonModal((AppCompatActivity) getActivity(),
getString(R.string.tip),
getString(R.string.learn_more),
getString(R.string.know),
null);
}

private void handleParseException(Exception ex) {
Expand Down Expand Up @@ -169,8 +192,12 @@ private void updateUI() {
JSONObject abi = viewModel.getAbi();
if (abi != null) {
updateAbiView(abi);
mBinding.ethTx.data.setVisibility(View.VISIBLE);
mBinding.ethTx.undecodedData.setVisibility(View.GONE);
} else {
mBinding.ethTx.data.setVisibility(View.GONE);
mBinding.ethTx.undecodedData.setVisibility(View.VISIBLE);
mBinding.ethTx.inputData.setText("0x" + viewModel.getInputData());
}
mBinding.ethTx.setTx(txEntity);
processAndUpdateTo();
Expand All @@ -194,16 +221,20 @@ private void processAndUpdateTo() {
private void updateAbiView(JSONObject abi) {
if (abi != null) {
try {
if (isFromTFCard) {
isFromTFCard = false;
mBinding.ethTx.tfcardTip.setVisibility(View.VISIBLE);
}
String contract = abi.getString("contract");
boolean isUniswap = contract.toLowerCase().contains("uniswap");
List<AbiItemAdapter.AbiItem> itemList = new AbiItemAdapter(txEntity.getFrom(),viewModel).adapt(abi);
List<AbiItemAdapter.AbiItem> itemList = new AbiItemAdapter(txEntity.getFrom(), viewModel).adapt(abi);
for (AbiItemAdapter.AbiItem item : itemList) {
AbiItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mActivity),
R.layout.abi_item, null, false);
binding.key.setText(item.key);
if (isUniswap && "to".equals(item.key)) {
if (!item.value.equalsIgnoreCase(txEntity.getFrom())) {
item.value += String.format(" [%s]",getString(R.string.inconsistent_address));
item.value += String.format(" [%s]", getString(R.string.inconsistent_address));
}
binding.value.setText(highLight(item.value));
} else {
Expand All @@ -221,18 +252,19 @@ private void updateAbiView(JSONObject abi) {
protected void initData(Bundle savedInstanceState) {

}

public static SpannableStringBuilder highLight(String content) {
SpannableStringBuilder spannable = new SpannableStringBuilder(content);
Matcher matcher = pattern.matcher(spannable);
while (matcher.find())
spannable.setSpan(new ForegroundColorSpan(0xff00cdc3), matcher.start() - 1 ,
spannable.setSpan(new ForegroundColorSpan(MainApplication.getApplication().getColor(R.color.icon_select)), matcher.start() - 1,
matcher.end() + 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE);

matcher = pattern1.matcher(spannable);
while (matcher.find()) {
spannable.replace(matcher.start() - 1, matcher.start(),"(");
spannable.replace(matcher.end(), matcher.end() + 1,")");
spannable.setSpan(new ForegroundColorSpan(Color.RED), matcher.start() - 1 ,
spannable.replace(matcher.start() - 1, matcher.start(), "(");
spannable.replace(matcher.end(), matcher.end() + 1, ")");
spannable.setSpan(new ForegroundColorSpan(Color.RED), matcher.start() - 1,
matcher.end() + 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
return spannable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import android.view.LayoutInflater;
import android.view.View;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProviders;

Expand All @@ -32,6 +33,7 @@
import com.keystone.cold.databinding.EthTxBinding;
import com.keystone.cold.db.entity.TxEntity;
import com.keystone.cold.ui.fragment.BaseFragment;
import com.keystone.cold.ui.modal.ModalDialog;
import com.keystone.cold.viewmodel.CoinListViewModel;
import com.keystone.cold.viewmodel.EthTxConfirmViewModel;
import com.keystone.cold.viewmodel.WatchWallet;
Expand All @@ -42,7 +44,6 @@

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

import static com.keystone.cold.ui.fragment.main.EthTxConfirmFragment.highLight;
import static com.keystone.cold.ui.fragment.main.TxFragment.KEY_TX_ID;
Expand Down Expand Up @@ -71,6 +72,15 @@ protected void init(View view) {
}
});
viewModel = ViewModelProviders.of(this).get(EthTxConfirmViewModel.class);
mBinding.ethTx.info.setOnClickListener(view1 -> showDialog());
}

private void showDialog() {
ModalDialog.showCommonModal((AppCompatActivity) getActivity(),
getString(R.string.tip),
getString(R.string.learn_more),
getString(R.string.know),
null);
}

private void updateUI() {
Expand Down Expand Up @@ -120,11 +130,23 @@ private void updateAbiView(JSONObject abi) {
}
mBinding.ethTx.container.addView(binding.getRoot());
}
mBinding.ethTx.data.setVisibility(View.VISIBLE);
mBinding.ethTx.undecodedData.setVisibility(View.GONE);
} catch (JSONException e) {
e.printStackTrace();
}
} else {
mBinding.ethTx.data.setVisibility(View.GONE);
mBinding.ethTx.undecodedData.setVisibility(View.VISIBLE);
JSONObject signData = null;
try {
signData = new JSONObject(txEntity.getSignedHex());
} catch (JSONException e) {
e.printStackTrace();
}
if (signData != null) {
mBinding.ethTx.inputData.setText("0x" + signData.optString("inputData"));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ private void handleUR(String res) {
}
}

private void handleException(Exception e) {
public void handleException(Exception e) {
e.printStackTrace();
if (e instanceof InvalidTransactionException || e instanceof InvalidUOSException) {
alert(getString(R.string.unresolve_tx),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.keystone.coinlib.interfaces.SignCallback;
import com.keystone.coinlib.interfaces.Signer;
import com.keystone.coinlib.path.CoinPath;
import com.keystone.coinlib.utils.AbiLoader;
import com.keystone.coinlib.utils.Coins;
import com.keystone.cold.AppExecutors;
import com.keystone.cold.R;
Expand All @@ -42,6 +43,7 @@
import com.keystone.cold.db.entity.CoinEntity;
import com.keystone.cold.db.entity.TxEntity;
import com.keystone.cold.encryption.ChipSigner;
import com.keystone.cold.ui.fragment.main.EthTxConfirmFragment;

import org.json.JSONArray;
import org.json.JSONException;
Expand Down Expand Up @@ -69,6 +71,7 @@ public class EthTxConfirmViewModel extends TxConfirmViewModel {
private String messageData;
private String messageSignature;
private String fromAddress;
private String inputData;

public EthTxConfirmViewModel(@NonNull Application application) {
super(application);
Expand All @@ -93,7 +96,7 @@ public JSONObject getContractMap() {
return contractMap;
}

public String recognizeAddress(String to){
public String recognizeAddress(String to) {
try {
String addressSymbol = null;
JSONArray tokensMap = getTokensMap();
Expand All @@ -104,12 +107,13 @@ public String recognizeAddress(String to){
break;
}
}

if (addressSymbol == null) {
JSONObject bundleMap = getContractMap();
String abiFile = bundleMap.optString(to.toLowerCase());
if (!TextUtils.isEmpty(abiFile)) {
addressSymbol = abiFile.replace(".json", "");
} else {
addressSymbol = recognizeAddressFromTFCard(to);
}
}
return addressSymbol;
Expand All @@ -119,14 +123,28 @@ public String recognizeAddress(String to){
return null;
}

private String recognizeAddressFromTFCard(String to) {
String addressSymbol = null;
try {
String contentFromSdCard = AbiLoader.getContentFromSdCard(EthImpl.ABI_JSON_SDCARD_PATH, to);
if (!TextUtils.isEmpty(contentFromSdCard)) {
JSONObject sdCardJsonObject = new JSONObject(contentFromSdCard);
addressSymbol = sdCardJsonObject.optString("name");
}
} catch (JSONException e) {
e.printStackTrace();
}
return addressSymbol;
}

public String getNetwork(int chainId) {
Network network = Network.getNetwork(chainId);
if (network == null) {
return String.format("chainId:%d", chainId);
}
String networkName = network.name();
if (chainId != 1) {
networkName += String.format(" (%s)",context.getString(R.string.testnet));
networkName += String.format(" (%s)", context.getString(R.string.testnet));
}
return networkName;
}
Expand All @@ -146,7 +164,7 @@ public void parseTxData(JSONObject object) {
hdPath = object.getString("hdPath");
signId = object.getString("signId");
txHex = object.getString("txHex");
JSONObject ethTx = EthImpl.decodeRawTransaction(txHex);
JSONObject ethTx = EthImpl.decodeRawTransaction(txHex, () -> EthTxConfirmFragment.isFromTFCard = true);
if (ethTx == null) {
observableTx.postValue(null);
parseTxException.postValue(new InvalidTransactionException("invalid transaction"));
Expand All @@ -156,7 +174,9 @@ public void parseTxData(JSONObject object) {
String data = ethTx.getString("data");
try {
abi = new JSONObject(data);
} catch (JSONException ignore) { }
} catch (JSONException ignore) {
inputData = data;
}
TxEntity tx = generateTxEntity(ethTx);
observableTx.postValue(tx);

Expand Down Expand Up @@ -324,6 +344,7 @@ protected TxEntity onSignSuccess(String txId, String rawTx) {
signed.put("signId", signId);
signed.put("chainId", chainId);
signed.put("abi", abi);
signed.put("inputData", inputData);
tx.setSignedHex(signed.toString());
} catch (JSONException e) {
e.printStackTrace();
Expand All @@ -344,6 +365,7 @@ private void signTransaction(@NonNull SignCallback callback, Signer signer) {
callback.onSuccess(result.txId, result.txHex);
}
}

private void signMessage(@NonNull SignCallback callback, Signer signer) {
if (signer == null) {
callback.onFail();
Expand All @@ -360,7 +382,7 @@ private void signMessage(@NonNull SignCallback callback, Signer signer) {
private Signer initSigner() {
String authToken = getAuthToken();
if (TextUtils.isEmpty(authToken)) {
Log.w(TAG,"authToken null");
Log.w(TAG, "authToken null");
return null;
}
return new ChipSigner(hdPath.toLowerCase(), authToken);
Expand All @@ -381,4 +403,8 @@ public String getTxHex() {
public int getChainId() {
return chainId;
}

public String getInputData() {
return inputData;
}
}
Loading

0 comments on commit c002fda

Please sign in to comment.