Skip to content

Commit

Permalink
ERC-20/ERC-721 Support (#50)
Browse files Browse the repository at this point in the history
* caching layer first pass

* wire up evm_log_client

* start of mapper

* .gitignore

* update readme

* rough draft

* address.go

* refactor filenames

* fix map init

* update gitignore

* contractInfo cache layer

* new op types

* cleanup + start of test framework

* fix tests + linting errors

* fix linter nits

* update comments + readme

* nits

* feedback

* construction api

* fix linting errors

* small bug fixes

* naming refactor

* data api endpoint

* linting fix

* edge case cleanup

* remove tylersmith lib

* start of EnableERC20 config

* nits

* update config var name

* update serviceConfig init

* data structure feedback

* bug squashing

* burn address fix

* start of different modes

* refactor modes

* standard and analytic modes implemented for data ingestion

* mapper helper.go file

* update formatting

* update service config

* update address check to toLower

* linter clean up

* linter clean up

* nit cleanup

* refactor token whitelist name

* refactor indexdefaulttokens

* refactor ConvertEVMTopicHashToAddress

* continued clean up

* increased granularity on erc721

* further cleanup

* linter clean up

* reorder operations

* final clean up

* bug fix

* linter cleanup

* code review feedback

* update read.me
  • Loading branch information
learyce authored Dec 17, 2021
1 parent 5633fa7 commit cb77ca6
Show file tree
Hide file tree
Showing 24 changed files with 950 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ rosetta-data
.avalanchego
*.swp
data
.vscode*
__debug_bin*
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Before you start running the server you need to create a configuration file:
{
"rpc_endpoint": "https://api.avax-test.network",
"mode": "online",
"listen_addr": "0.0.0.0:8080"
"listen_addr": "0.0.0.0:8080",
"genesis_block_hash" :"0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b",
}
```

Expand All @@ -55,7 +56,11 @@ Full configuration example:
"listen_addr": "0.0.0.0:8080",
"network_name": "Fuji",
"chain_id": 43113,
"log_requests": true
"log_requests": true,
"genesis_block_hash" :"0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b",
"index_unknown_tokens": false,
"ingestion_mode" : "standard",
"token_whitelist" : []
}
```

Expand All @@ -66,9 +71,14 @@ Where:
| mode | string | `online` | Mode of operations. One of: `online`, `offline`
| rpc_endpoint | string | `http://localhost:9650` | Avalanche RPC endpoint
| listen_addr | string | `http://localhost:8080` | Rosetta server listen address (host/port)
| network_name | string | - | Avalanche network name
| chain_id | integer | - | Avalanche C-Chain ID
| log_requests | bool | `false` | Enable request body logging
| network_name | string | - | Avalanche network name
| chain_id | integer | - | Avalanche C-Chain ID
| genesis_block_hash | string | - | The block hash for the genesis block
| index_unknown_tokens | bool | `false` | Enables ingesting tokens that don't have a public symbol or decimal variable=
| ingestion_mode | string | `standard`| Toggles between standard and analytics ingesting modes
| token_whitelist |[]string | [] | Enables ingesting for the provided ERC20 contract addresses in standard mode.

The token whitelist only supports tokens that emit evm transfer logs for all minting (from should be 0x000---), burning (to address should be 0x0000) and transfer events are supported. All other tokens will break cause ingestion to fail.

### RPC Endpoints

Expand Down Expand Up @@ -136,6 +146,12 @@ Run the construction check:
make check-testnet-construction
```

## Rebuild the ContractInfoToken.go autogen file.

```bash
abigen --abi contractInfo.abi --pkg main --type ContractInfoToken --out client/contractInfoToken.go
```

## License

BSD 3-Clause
Expand Down
22 changes: 20 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"math/big"

"github.com/ava-labs/coreth/core/types"
ethtypes "github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/interfaces"
ethcommon "github.com/ethereum/go-ethereum/common"
Expand All @@ -24,6 +25,7 @@ type Client interface {
TransactionByHash(context.Context, ethcommon.Hash) (*ethtypes.Transaction, bool, error)
TransactionReceipt(context.Context, ethcommon.Hash) (*ethtypes.Receipt, error)
TraceTransaction(context.Context, string) (*Call, []*FlatCall, error)
EvmTransferLogs(ctx context.Context, blockHash ethcommon.Hash, transactionHash ethcommon.Hash) ([]types.Log, error)
SendTransaction(context.Context, *ethtypes.Transaction) error
BalanceAt(context.Context, ethcommon.Address, *big.Int) (*big.Int, error)
NonceAt(context.Context, ethcommon.Address, *big.Int) (uint64, error)
Expand All @@ -34,11 +36,15 @@ type Client interface {
NetworkName(context.Context) (string, error)
Peers(context.Context) ([]Peer, error)
NodeVersion(context.Context) (string, error)
ContractInfo(contractAddress ethcommon.Address, isErc20 bool) (*ContractInfo, error)
CallContract(ctx context.Context, msg interfaces.CallMsg, blockNumber *big.Int) ([]byte, error)
}

type client struct {
*EthClient
*InfoClient
*EvmLogsClient
*ContractClient
}

// NewClient returns a new client for Avalanche APIs
Expand All @@ -53,8 +59,20 @@ func NewClient(endpoint string) (Client, error) {
return nil, err
}

evmlogs, err := NewEvmLogsClient(endpoint)
if err != nil {
return nil, err
}

contract, err := NewContractClient(endpoint)
if err != nil {
return nil, err
}

return client{
EthClient: eth,
InfoClient: info,
EthClient: eth,
InfoClient: info,
EvmLogsClient: evmlogs,
ContractClient: contract,
}, nil
}
70 changes: 70 additions & 0 deletions client/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package client

import (
"fmt"
"strings"

"github.com/ava-labs/avalanchego/cache"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)

const (
contractCacheSize = 1024
)

// ContractClient is a client for the calling contract information
type ContractClient struct {
ethClient *ethclient.Client
cache *cache.LRU
}

// NewContractClient returns a new ContractInfo client
func NewContractClient(endpoint string) (*ContractClient, error) {
endpoint = strings.TrimSuffix(endpoint, "/")

c, err := ethclient.Dial(fmt.Sprintf("%s%s", endpoint, prefixEth))
if err != nil {
return nil, err
}

cache := &cache.LRU{Size: contractCacheSize}

return &ContractClient{
ethClient: c,
cache: cache,
}, nil
}

// ContractInfo returns the ContractInfo for a specific address
func (c *ContractClient) ContractInfo(contractAddress common.Address, isErc20 bool) (*ContractInfo, error) {
cachedInfo, isCached := c.cache.Get(contractAddress)

if isCached {
castCachedInfo := cachedInfo.(*ContractInfo)
return castCachedInfo, nil
}

token, err := NewContractInfoToken(contractAddress, c.ethClient)
if err != nil {
return nil, err
}
symbol, symbolErr := token.Symbol(nil)
decimals, decimalErr := token.Decimals(nil)

// Any of these indicate a failure to get complete information from contract
if symbolErr != nil || decimalErr != nil || symbol == "" || decimals == 0 {
if isErc20 {
symbol = UnknownERC20Symbol
decimals = UnknownERC20Decimals
} else {
symbol = UnknownERC721Symbol
decimals = UnknownERC721Decimals
}
}
contractInfo := &ContractInfo{Symbol: symbol, Decimals: decimals}

// Cache defaults for contract address to avoid unnecessary lookups
c.cache.Put(contractAddress, contractInfo)
return contractInfo, nil
}
Loading

0 comments on commit cb77ca6

Please sign in to comment.