Skip to content
This repository has been archived by the owner on Jan 21, 2022. It is now read-only.

Commit

Permalink
Expose a richer token decoding schema.
Browse files Browse the repository at this point in the history
From an ERC20 decoded transaction, one can now fetch the symbol of the
token contract, total supply and balances.

Resolves #42.
  • Loading branch information
Raúl Kripalani committed Jun 29, 2018
1 parent 13ee810 commit c35b096
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 51 deletions.
2 changes: 2 additions & 0 deletions .prettierignore
@@ -0,0 +1,2 @@
src/abi/
dist/
229 changes: 213 additions & 16 deletions src/__tests__/txdec/erc20.test.ts
Expand Up @@ -14,10 +14,14 @@ test('erc20: not decodable', async () => {
operation
... on ERC20Transfer {
from {
address
account {
address
}
}
to {
address
account {
address
}
}
value
}
Expand Down Expand Up @@ -48,10 +52,14 @@ test('erc20: transfer transaction', async () => {
operation
... on ERC20Transfer {
from {
address
account {
address
}
}
to {
address
account {
address
}
}
value
}
Expand All @@ -69,10 +77,14 @@ test('erc20: transfer transaction', async () => {
standard: 'ERC20',
operation: 'transfer',
from: {
address: '0xBaA705866f77Af9194A8a91B8104438b20272958',
account: {
address: '0xBaA705866f77Af9194A8a91B8104438b20272958',
},
},
to: {
address: '0xf477dc44297101ab68e7f05936d8f0810a223878',
account: {
address: '0xf477dc44297101ab68e7f05936d8f0810a223878',
},
},
value: '121299942024',
};
Expand All @@ -93,10 +105,14 @@ test('erc20: approval transaction', async () => {
operation
... on ERC20Approve {
from {
address
account {
address
}
}
spender {
address
account {
address
}
}
value
}
Expand All @@ -114,10 +130,14 @@ test('erc20: approval transaction', async () => {
standard: 'ERC20',
operation: 'approve',
from: {
address: '0xdE85906e9d436D8705aAC205cb19350C0AA9655F',
account: {
address: '0xdE85906e9d436D8705aAC205cb19350C0AA9655F',
},
},
spender: {
address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819',
account: {
address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819',
},
},
value: '1816000000000000000',
};
Expand All @@ -139,13 +159,19 @@ test('erc20: transferFrom transaction', async () => {
operation
... on ERC20TransferFrom {
from {
address
account {
address
}
}
to {
address
account {
address
}
}
spender {
address
account {
address
}
}
value
}
Expand All @@ -163,13 +189,19 @@ test('erc20: transferFrom transaction', async () => {
standard: 'ERC20',
operation: 'transferFrom',
from: {
address: '0x7d849b947da6de041fe289e307ac3132e14a6c5f',
account: {
address: '0x7d849b947da6de041fe289e307ac3132e14a6c5f',
},
},
to: {
address: '0x0d6b5a54f940bf3d52e438cab785981aaefdf40c',
account: {
address: '0x0d6b5a54f940bf3d52e438cab785981aaefdf40c',
},
},
spender: {
address: '0x0D6B5A54F940BF3D52E438CaB785981aAeFDf40C',
account: {
address: '0x0D6B5A54F940BF3D52E438CaB785981aAeFDf40C',
},
},
value: '22000000000000000000',
};
Expand All @@ -178,3 +210,168 @@ test('erc20: transferFrom transaction', async () => {
expect(tx.value).toBe(0);
expect(tx.decoded).toEqual(decoded);
});

test('erc20: fetch symbol and totalSupply', async () => {
const query = `
{
block(number: 5600000) {
transactionAt(index: 2) {
index
decoded {
standard
operation
... on ERC20Transfer {
tokenContract {
symbol
totalSupply
}
from {
account {
address
}
}
to {
account {
address
}
}
value
}
}
}
}
}`;

const result = await execQuery(query);
expect(result.errors).toBeUndefined();
expect(result.data).not.toBeUndefined();

const decoded = {
standard: 'ERC20',
operation: 'transfer',
tokenContract: {
symbol: 'NULS',
totalSupply: 4e25,
},
from: {
account: {
address: '0xbcCA7f6Fd6D7ea2755E83ac78A853793efbaA512',
},
},
to: {
account: {
address: '0x6cc5f688a315f3dc28a7781717a9a798a59fda7b',
},
},
value: '2.814408939999999999999e+21',
};

const tx = result.data.block.transactionAt;
expect(tx.decoded).toEqual(decoded);
});

test('erc20: fetch null symbol', async () => {
const query = `
{
block(number: 5600000) {
transactionAt(index: 11) {
index
decoded {
standard
operation
... on ERC20Transfer {
tokenContract {
symbol
totalSupply
}
from {
account {
address
}
}
to {
account {
address
}
}
value
}
}
}
}
}`;

const result = await execQuery(query);
expect(result.errors).toBeUndefined();
expect(result.data).not.toBeUndefined();

const decoded = {
standard: 'ERC20',
operation: 'transfer',
tokenContract: {
symbol: null,
totalSupply: 1e27,
},
from: {
account: {
address: '0x6748F50f686bfbcA6Fe8ad62b22228b87F31ff2b',
},
},
to: {
account: {
address: '0x7a7e781c707aafc6406b8d7dcdf738a49469aca2',
},
},
value: '99500000000000000000',
};

const tx = result.data.block.transactionAt;
expect(tx.decoded).toEqual(decoded);
});

test('erc20: fetch token balance of recipient (account 0x0)', async () => {
const query = `
{
block(number: 5875781) {
transactionsInvolving(participants:["0xb5a5f22694352c15b00323844ad545abb2b11028"]) {
index
decoded {
standard
operation
... on ERC20Transfer {
tokenContract {
account {
address
}
symbol
totalSupply
}
from {
account {
address
}
}
to {
account {
address
}
tokenBalance
}
value
}
}
}
}
}`;

const result = await execQuery(query);
expect(result.errors).toBeUndefined();
expect(result.data).not.toBeUndefined();

const tx = result.data.block.transactionsInvolving[0];
expect(tx.decoded.standard).toEqual('ERC20');
expect(tx.decoded.operation).toEqual('transfer');
expect(tx.decoded.tokenContract.account.address).toEqual('0xb5A5F22694352C15B00323844aD545ABb2B11028');
expect(tx.decoded.to.account.address).toEqual('0x0000000000000000000000000000000000000000');
expect(tx.decoded.to.tokenBalance).toBeGreaterThan(0);
});
8 changes: 8 additions & 0 deletions src/abi/erc20.json
Expand Up @@ -15,6 +15,14 @@
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [{ "name": "", "type": "string" }],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
Expand Down
5 changes: 3 additions & 2 deletions src/model/core/EthqlTransaction.ts
@@ -1,5 +1,6 @@
import { Transaction } from 'web3/eth/types';
import engine from '../../txdec';
import { EthqlContext } from '../EthqlContext';
import EthqlAccount from './EthqlAccount';

type Overwrite<T1, T2> = { [P in Exclude<keyof T1, keyof T2>]: T1[P] } & T2;
Expand Down Expand Up @@ -31,11 +32,11 @@ class EthqlTransaction {
return this.transactionIndex;
}

public get decoded() {
public async decoded(_, context: EthqlContext) {
if (!this.inputData || this.inputData === '0x') {
return null;
}
return engine.decodeTransaction(this);
return engine.decodeTransaction(this, context);
}
}

Expand Down
36 changes: 27 additions & 9 deletions src/schema/erc20.graphql
@@ -1,26 +1,44 @@
# import Account, DecodedTransaction from "core.graphql"

type ERC20Transfer implements DecodedTransaction {
type TokenContract {
account: Account
symbol: String
totalSupply: Long
}

interface ERC20Transaction {
tokenContract: TokenContract
}

type TokenHolder {
account: Account!
tokenBalance: Long
}

type ERC20Transfer implements DecodedTransaction & ERC20Transaction {
standard: String
operation: String
from: Account
to: Account
from: TokenHolder
to: TokenHolder
value: String
tokenContract: TokenContract
}

type ERC20TransferFrom implements DecodedTransaction {
type ERC20TransferFrom implements DecodedTransaction & ERC20Transaction {
standard: String
operation: String
from: Account
to: Account
from: TokenHolder
to: TokenHolder
value: String
spender: Account
spender: TokenHolder
tokenContract: TokenContract
}

type ERC20Approve implements DecodedTransaction {
standard: String
operation: String
from: Account
spender: Account
from: TokenHolder
spender: TokenHolder
value: String
tokenContract: TokenContract
}

0 comments on commit c35b096

Please sign in to comment.