Skip to content

Commit

Permalink
Handle ERC-5169 as per the spec or legacy single string (#3321)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesSmartCell authored Sep 30, 2023
1 parent ba433b9 commit fb7003f
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 23 deletions.
7 changes: 7 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter>
<data android:scheme="awallet" />
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:priority="999">
<action android:name="android.intent.action.VIEW" />

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.alphawallet.app.entity;

import static com.alphawallet.app.repository.TokenRepository.callSmartContractFuncAdaptiveArray;
import static com.alphawallet.app.repository.TokenRepository.callSmartContractFunction;

import android.text.TextUtils;
Expand All @@ -18,6 +19,7 @@
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import io.reactivex.Single;
Expand All @@ -37,12 +39,9 @@ public ContractInteract(Token token)
this.token = token;
}

public Single<String> getScriptFileURI()
public Single<List<String>> getScriptFileURI()
{
return Single.fromCallable(() -> {
String contractURI = callSmartContractFunction(token.tokenInfo.chainId, getScriptURI(), token.getAddress(), token.getWallet());
return contractURI != null ? contractURI : "";
}).observeOn(Schedulers.io());
return Single.fromCallable(() -> callSmartContractFuncAdaptiveArray(token.tokenInfo.chainId, getScriptURI(), token.getAddress(), token.getWallet())).observeOn(Schedulers.io());
}

private String loadMetaData(String tokenURI)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@
import com.alphawallet.app.entity.EasAttestation;
import com.alphawallet.app.entity.nftassets.NFTAsset;
import com.alphawallet.app.repository.EthereumNetworkBase;
import com.alphawallet.app.repository.TokensRealmSource;
import com.alphawallet.app.repository.entity.RealmAttestation;
import com.alphawallet.app.util.Utils;
import com.alphawallet.token.entity.AttestationDefinition;
import com.alphawallet.token.entity.AttestationValidation;
import com.alphawallet.token.entity.AttestationValidationStatus;
import com.alphawallet.token.entity.TokenScriptResult;
import org.web3j.utils.Numeric;
import com.alphawallet.token.tools.TokenDefinition;
import com.google.gson.Gson;

Expand All @@ -30,12 +28,14 @@
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;
import org.web3j.crypto.StructuredDataEncoder;
import org.web3j.utils.Numeric;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -901,12 +901,12 @@ public boolean isBytes()
}

@Override
public Single<String> getScriptURI()
public Single<List<String>> getScriptURI()
{
MemberData memberData = additionalMembers.get(SCHEMA_DATA_PREFIX + SCRIPT_URI);
if (memberData != null && !TextUtils.isEmpty(memberData.getString()))
{
return Single.fromCallable(memberData::getString);
return Single.fromCallable(() -> Collections.singletonList(memberData.getString()));
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ public Single<List<NFTAsset>> buildAssetList(TokenTransferData transferData)
});
}

public Single<String> getScriptURI()
public Single<List<String>> getScriptURI()
{
return contractInteract.getScriptFileURI();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID;
import static com.alphawallet.ethereum.EthereumNetworkBase.OKX_ID;
import static org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction;
import static java.util.Arrays.asList;

import android.content.Context;
import android.text.TextUtils;
Expand Down Expand Up @@ -1058,7 +1059,7 @@ private String callCustomNetSmartContractFunction(
}

public static byte[] createTokenTransferData(String to, BigInteger tokenAmount) {
List<Type> params = Arrays.asList(new Address(to), new Uint256(tokenAmount));
List<Type> params = asList(new Address(to), new Uint256(tokenAmount));
List<TypeReference<?>> returnTypes = Collections.singletonList(new TypeReference<Bool>() {});
Function function = new Function("transfer", params, returnTypes);
String encodedFunction = FunctionEncoder.encode(function);
Expand All @@ -1082,7 +1083,7 @@ public static byte[] createERC721TransferFunction(String to, Token token, List<B
public static byte[] createERC721TransferFunction(String from, String to, String token, BigInteger tokenId)
{
List<TypeReference<?>> returnTypes = Collections.emptyList();
List<Type> params = Arrays.asList(new Address(from), new Address(to), new Uint256(tokenId));
List<Type> params = asList(new Address(from), new Address(to), new Uint256(tokenId));
Function function = new Function("safeTransferFrom", params, returnTypes);

String encodedFunction = FunctionEncoder.encode(function);
Expand All @@ -1107,7 +1108,7 @@ public static byte[] createDropCurrency(MagicLinkData order, int v, byte[] r, by
{
Function function = new Function(
"dropCurrency",
Arrays.asList(new org.web3j.abi.datatypes.generated.Uint32(order.nonce),
asList(new org.web3j.abi.datatypes.generated.Uint32(order.nonce),
new org.web3j.abi.datatypes.generated.Uint32(order.amount),
new org.web3j.abi.datatypes.generated.Uint32(order.expiry),
new org.web3j.abi.datatypes.generated.Uint8(v),
Expand Down Expand Up @@ -1356,6 +1357,37 @@ public static String callSmartContractFunction(long chainId,
return null;
}

public static List<String> callSmartContractFuncAdaptiveArray(long chainId,
Function function, String contractAddress, String walletAddr)
{
String encodedFunction = FunctionEncoder.encode(function);

try
{
org.web3j.protocol.core.methods.request.Transaction transaction
= createEthCallTransaction(walletAddr, contractAddress, encodedFunction);
EthCall response = getWeb3jService(chainId).ethCall(transaction, DefaultBlockParameterName.LATEST).send();

List<Type> responseValues = FunctionReturnDecoder.decode(response.getValue(), function.getOutputParameters());
List<Type> responseValuesArray = Utils.decodeDynamicArray(response.getValue());

if (!responseValuesArray.isEmpty()) // if arrays are found, return these as the filter is more strict
{
return (List<String>)Utils.asAList(responseValuesArray, " ");
}
if (!responseValues.isEmpty())
{
return Collections.singletonList(responseValues.get(0).getValue().toString());
}
}
catch (Exception e)
{
//
}

return new ArrayList<>();
}

public static List callSmartContractFunctionArray(long chainId,
Function function, String contractAddress, String walletAddr)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1262,17 +1262,30 @@ private void updateScriptEntriesInRealm(List<ContractLocator> origins, boolean i
//Call contract and check for script
private Single<File> fetchTokenScriptFromContract(Token token, MutableLiveData<Boolean> updateFlag)
{
//Allow for arrays of URI, check each in turn for multiple and return the first valid entry
return token.getScriptURI()
.map(uri -> {
if (!TextUtils.isEmpty(uri) && updateFlag != null)
.map(uriList -> {
for (String uri : uriList)
{
updateFlag.postValue(true);
//early return for unchanged IPFS
if (matchesExistingScript(token, uri))
{
break; // return script unchanged / not found
}
//download each in turn, return first valid script
if (!TextUtils.isEmpty(uri) && updateFlag != null)
{
updateFlag.postValue(true);
}
Pair<String, Boolean> scriptCandidate = downloadScript(uri, 0);
if (!TextUtils.isEmpty(scriptCandidate.first))
{
return new Pair<>(uri, scriptCandidate);
}
}

return uri;
return new Pair<>("", new Pair<>("", false));
})
.map(uri -> compareExistingScript(token, uri))
.map(uri -> new Pair<>(uri, downloadScript(uri, 0)))
.map(scriptData -> storeEntry(token, scriptData));
}

Expand Down Expand Up @@ -1341,10 +1354,9 @@ private File storeEntry(Token token, Pair<String, Pair<String, Boolean>> scriptD
return storeFile;
}

private String compareExistingScript(Token token, String uri)
private boolean matchesExistingScript(Token token, String uri)
{
//TODO: calculate and use the IPFS CID to validate existing script against IPFS locator
String returnUri = uri;
try (Realm realm = realmManager.getRealmInstance(ASSET_DEFINITION_DB))
{
String entryKey = token.getTSKey(); //getTSDataKey(token.tokenInfo.chainId, token.tokenInfo.address);
Expand All @@ -1361,15 +1373,15 @@ private String compareExistingScript(Token token, String uri)
&& entry.getIpfsPath().equals(uri)
&& tsf.exists())
{
returnUri = UNCHANGED_SCRIPT;
return true;
}
}
catch (Exception e)
{
Timber.w(e);
}

return returnUri;
return false;
}

private Single<File> tryServerIfRequired(File contractScript, String address)
Expand Down
44 changes: 44 additions & 0 deletions app/src/main/java/com/alphawallet/app/util/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@

import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.DynamicArray;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.crypto.StructuredDataEncoder;
Expand All @@ -71,6 +76,7 @@
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -1071,6 +1077,44 @@ public static String calculateContractAddress(String account, long nonce)
return Keys.toChecksumAddress(Numeric.toHexString(calculatedAddressAsBytes));
}

public static <T> List<Type> decodeDynamicArray(String output)
{
List<TypeReference<Type>> adaptive = org.web3j.abi.Utils.convert(Collections.singletonList(new TypeReference<DynamicArray<Utf8String>>() {}));
try
{
return FunctionReturnDecoder.decode(output, adaptive);
}
catch (Exception e)
{
// Expected
}

return new ArrayList<>();
}

public static <T> List<T> asAList(List<Type> responseValues, T convert)
{
List<T> converted = new ArrayList<>();
if (responseValues.isEmpty())
{
return converted;
}

for (Object objUri : ((DynamicArray) responseValues.get(0)).getValue())
{
try
{
converted.add((T) ((Type<?>) objUri).getValue().toString());
}
catch (ClassCastException e)
{
//
}
}

return converted;
}

public static boolean isJson(String value)
{
try
Expand Down

0 comments on commit fb7003f

Please sign in to comment.