AlphaWallet / alpha-wallet-android

An advanced Ethereum mobile wallet
https://www.alphawallet.com
MIT License
584 stars 528 forks source link

Add Transaction log detection for ERC20 Tokens #2507

Open JamesSmartCell opened 2 years ago

JamesSmartCell commented 2 years ago

Enable Transaction Log pickup for ERC20 Tokens.

You can search for Approve, Transfer and common other DeFi events.

Check out the implementation in ERC721Token and ERC1155Token and do something along those lines; the reason being there will be another upgrade to find the contract constructor and use that to help sync logs, which should be able to slot easily into the methods used to pick up logs in Tokens.

JamesSmartCell commented 2 years ago

Further note to this:

For implementations for ERC721 and ERC1155 check the respective ERC721Token and ERC1155Token classes.

There is a class wrapped by the base Token class, class EventSync which provides the mechanism to synchronise event checks, taking into account maximum number of logs which can be fetched by the node.

The relevant events for ERC20 are (See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol for more details):

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);

You can add a system to check both events in the same call similar to ERC1155 checks for BatchTransfer and SingleTransfer. The reason you need to check all events in the single call is that we only have one event check range (for simplicity), if we exceed the log count then we narrow the check window until it passes then continue to crawl the blocks for events.

I have roughly annotated the corresponding updateBalance in ERC1155 with what needs to be done. There would be other wins too, like once this function returns balance for ERC20 the calling function can be simplified as there would be no need for a switch.

    @Override
    public BigDecimal updateBalance(Realm realm)
    {
        SyncDef sync = eventSync.getSyncDef(realm); // <-- this calculates the block range to check, based on previous success/failure
        if (sync == null) return balance;

         // Convert to event params. 
        DefaultBlockParameter startBlock = DefaultBlockParameter.valueOf(sync.eventReadStartBlock);
        DefaultBlockParameter endBlock = DefaultBlockParameter.valueOf(sync.eventReadEndBlock);
        if (sync.eventReadEndBlock.compareTo(BigInteger.valueOf(-1L)) == 0) endBlock = DefaultBlockParameterName.LATEST;

        //take a note of the current block#
        BigInteger currentBlock = TransactionsService.getCurrentBlock(tokenInfo.chainId);

        try
        {
            final Web3j web3j = TokenRepository.getWeb3jService(tokenInfo.chainId);

            Pair<Integer, HashSet<BigInteger>> evRead = eventSync.processTransferEvents(web3j,
                    getBalanceUpdateEvents(), startBlock, endBlock, realm);  //<-- check here for ERC20 Transfer events - may require a new function in EventSync, as the uint256 value is now amount not TokenId.

            Pair<Integer, HashSet<BigInteger>> batchRead = eventSync.processTransferEvents(web3j,
                    getBatchBalanceUpdateEvents(), startBlock, endBlock, realm);  //<-- check here for ERC20 Approve events - will require a new function in EventSync

            // <-- from here no need for remainder of this block as we don't have tokenIds, only need to updateEventReads.
            evRead.second.addAll(batchRead.second); //<-- no need for these with ERC20
            //combine the tokenIds with existing assets
            evRead.second.addAll(assets.keySet());      //<-- no need for this with ERC20

            //update balances of all
            List<Uint256> balances = fetchBalances(evRead.second);
            //update realm
            updateRealmBalance(realm, evRead.second, balances);

            //update read points
            eventSync.updateEventReads(realm, sync, currentBlock, evRead.first); //means our event read was fine
        }
        catch (LogOverflowException e)
        {
            //handle log read overflow; reduce search size
            if (eventSync.handleEthLogError(e.error, startBlock, endBlock, sync, realm))
            {
                //recurse until we find a good value
                updateBalance(realm);
            }
        }
        catch (Exception e)
        {
            Timber.e(e);
        }

        return new BigDecimal(assets.keySet().size());  //<-- would return balance of ERC20 here.
    }