Repository: alphawallet/alpha-wallet-android
Issue: Selection condition fails when comparing admin attribute to ownerAddress

To reproduce:

Watch: 0xD40EBAaA4Cb7fC3a5D7B373309F68B2ABCe5F1C5

go to GFO(GFO) token that has a server TS

Expected:
About Issue Move View actions should be present because ownerAddress == admin

Actual:
Only About is shown, this happens when the user is not an admin

TokenScript: 
https://github.com/AlphaWallet/gf.network/blob/master/TS/GFO/GFO-TokenScript.xml#L30

Changed files:
lib/src/main/java/com/alphawallet/token/entity/TSSelection.java, app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java, lib/src/main/java/com/alphawallet/token/entity/FilterType.java, lib/src/main/java/com/alphawallet/token/entity/TSFilterNode.java

diff --git a/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java b/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java
index 3653a2375..7e8840111 100644
--- a/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java
+++ b/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java
@@ -1523,6 +1523,7 @@ public class AssetDefinitionService implements ParseResult, AttributeInterface
                             //get required Attribute Results for this tokenId & selection
                             List<String> requiredAttributeNames = selection.getRequiredAttrs();
                             Map<String, TokenScriptResult.Attribute> idAttrResults = getAttributeResultsForTokenIds(attrResults, requiredAttributeNames, tokenId);
+                            addIntrinsicAttributes(idAttrResults, token, tokenId); //adding intrinsic attributes eg ownerAddress, tokenId, contractAddress
 
                             //Now evaluate the selection
                             boolean exclude = EvaluateSelection.evaluate(selection.head, idAttrResults);
@@ -1540,6 +1541,14 @@ public class AssetDefinitionService implements ParseResult, AttributeInterface
         });
     }
 
+    private void addIntrinsicAttributes(Map<String, TokenScriptResult.Attribute> attrs, Token token, BigInteger tokenId)
+    {
+        //add tokenId, ownerAddress & contractAddress
+        attrs.put("tokenId", new TokenScriptResult.Attribute("tokenId", "tokenId", tokenId, tokenId.toString(10)));
+        attrs.put("ownerAddress", new TokenScriptResult.Attribute("ownerAddress", "ownerAddress", BigInteger.ZERO, token.getWallet()));
+        attrs.put("contractAddress", new TokenScriptResult.Attribute("contractAddress", "contractAddress", BigInteger.ZERO, token.getAddress()));
+    }
+
     public String checkFunctionDenied(Token token, String actionName, List<BigInteger> tokenIds)
     {
         String denialMessage = null;
@@ -1564,6 +1573,8 @@ public class AssetDefinitionService implements ParseResult, AttributeInterface
                     if (attrResult != null) attrs.put(attrId, attrResult);
                 }
 
+                addIntrinsicAttributes(attrs, token, tokenId);
+
                 boolean exclude = EvaluateSelection.evaluate(selection.head, attrs);
                 if (exclude && !TextUtils.isEmpty(selection.denialMessage))
                 {
diff --git a/lib/src/main/java/com/alphawallet/token/entity/FilterType.java b/lib/src/main/java/com/alphawallet/token/entity/FilterType.java
index a875a5f42..e593172d3 100644
--- a/lib/src/main/java/com/alphawallet/token/entity/FilterType.java
+++ b/lib/src/main/java/com/alphawallet/token/entity/FilterType.java
@@ -5,5 +5,5 @@ package com.alphawallet.token.entity;
  */
 public enum FilterType
 {
-    AND, OR, NOT, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL_TO, GREATER_THAN, LESS_THAN, EQUAL, VALUE, START_BRACE, END_BRACE
+    AND, OR, NOT, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL_TO, GREATER_THAN, LESS_THAN, EQUAL, VALUE, ATTRIBUTE, START_BRACE, END_BRACE
 }
\\ No newline at end of file
diff --git a/lib/src/main/java/com/alphawallet/token/entity/TSFilterNode.java b/lib/src/main/java/com/alphawallet/token/entity/TSFilterNode.java
index 990577c91..c72abf2ce 100644
--- a/lib/src/main/java/com/alphawallet/token/entity/TSFilterNode.java
+++ b/lib/src/main/java/com/alphawallet/token/entity/TSFilterNode.java
@@ -2,6 +2,9 @@ package com.alphawallet.token.entity;
 
 import java.math.BigInteger;
 import java.util.Map;
+import java.util.regex.Matcher;
+
+import static com.alphawallet.token.entity.TSSelection.decodeParam;
 
 /**
  * Created by JB on 21/05/2020.
@@ -17,9 +20,14 @@ public class TSFilterNode
     String strValue = null;
     LogicState logic = LogicState.NONE;
 
-    public TSFilterNode(String val, TSFilterNode p)
+    public TSFilterNode(String val, TSFilterNode p, FilterType t)
     {
-        type = FilterType.VALUE;
+        if (t == FilterType.ATTRIBUTE)
+        {
+            val = extractAttribute(val);
+        }
+
+        type = t;
         try
         {
             value = new BigInteger(val);
@@ -166,7 +174,7 @@ public class TSFilterNode
         {
             returnValue = node.logic.toString();
         }
-        else if (node.strValue != null && node.strValue.length() > 0)
+        else if (node.type == FilterType.ATTRIBUTE)
         {
             TokenScriptResult.Attribute attr = attrs.get(node.strValue);
             if (attr != null)
@@ -174,10 +182,10 @@ public class TSFilterNode
                 //found an attribute
                 returnValue = attr.text;
             }
-            else
-            {
-                returnValue = node.strValue;
-            }
+        }
+        else if (node.strValue != null && node.strValue.length() > 0)
+        {
+            returnValue = node.strValue;
         }
 
         return returnValue;
@@ -210,6 +218,20 @@ public class TSFilterNode
         }
     }
 
+    private String extractAttribute(String val)
+    {
+        Matcher matcher = decodeParam.matcher(val);
+        if (matcher.find() && !matcher.group(1).isEmpty())
+        {
+            //found an attribute param
+            return matcher.group(1);
+        }
+        else
+        {
+            return val;
+        }
+    }
+
     enum LogicState
     {
         NONE, TRUE, FALSE
diff --git a/lib/src/main/java/com/alphawallet/token/entity/TSSelection.java b/lib/src/main/java/com/alphawallet/token/entity/TSSelection.java
index 997790326..f9ec64e0b 100644
--- a/lib/src/main/java/com/alphawallet/token/entity/TSSelection.java
+++ b/lib/src/main/java/com/alphawallet/token/entity/TSSelection.java
@@ -7,6 +7,8 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Created by JB on 21/05/2020.
@@ -19,6 +21,8 @@ public class TSSelection
     private boolean negate = false;
     public String name = null;
 
+    public static final Pattern decodeParam = Pattern.compile("[$][{](\\\\w*)[}]$");
+
     public TSSelection(String filterExpression) throws SAXException
     {
         //tokenise
@@ -137,14 +141,14 @@ public class TSSelection
 
         String b = tokens.next();
         FilterType typeB = getType(b);
-        if (typeB != FilterType.VALUE)
+        if (!(typeB == FilterType.VALUE || typeB == FilterType.ATTRIBUTE))
         {
             throw new SAXException("PARSE ERROR: Invalid subject after '" + a + logic);
         }
 
         TSFilterNode comparator = new TSFilterNode(typeLogic, currentNode);
-        comparator.first = new TSFilterNode(a, comparator);
-        comparator.second = new TSFilterNode(b, comparator);
+        comparator.first = new TSFilterNode(a, comparator, FilterType.ATTRIBUTE);
+        comparator.second = new TSFilterNode(b, comparator, typeB);
         if (negate)
         {
             comparator.negate = true;
@@ -190,7 +194,7 @@ public class TSSelection
                 type = FilterType.NOT;
                 break;
             default:
-                type = FilterType.VALUE;
+                type = unwrapValue(token);
                 break;
         }
 
@@ -291,6 +295,20 @@ public class TSSelection
         return attrs;
     }
 
+    private FilterType unwrapValue(String token)
+    {
+        Matcher matcher = decodeParam.matcher(token);
+        if (matcher.find() && !matcher.group(1).isEmpty())
+        {
+            //found an attribute param
+            return FilterType.ATTRIBUTE;
+        }
+        else
+        {
+            return FilterType.VALUE;
+        }
+    }
+
     public boolean checkParse()
     {
         return name != null && name.length() > 0