Skip to content
This repository was archived by the owner on Apr 4, 2022. It is now read-only.

Commit

Permalink
[Explorer] Support for Bytes32 Token name/symbol methods (#800)
Browse files Browse the repository at this point in the history
# Summary

Closes #728 

Added bytes32 symbol and name methods using the Bytes32 contract interface.

![image](https://user-images.githubusercontent.com/11525018/138787052-b5561429-f24f-4eda-880e-05151fca1931.png)

# To Test

1. Go to `orders/<order id>` page (i.e: `0xa2e6c6fe3a83080e0c911e3e75ef4fce8bddb2929f25685c9631255f88dcb6a704a66cbba0485d7b21af836f52b711401300fddb615f8e69`)
* You'll see token name and symbol for tokens on `Amount` row.
  • Loading branch information
matextrem authored Oct 27, 2021
1 parent 55bae20 commit 0b05538
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 7 deletions.
30 changes: 30 additions & 0 deletions src/abis/erc20_bytes32.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
29 changes: 27 additions & 2 deletions src/api/erc20/Erc20Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BN from 'bn.js'
import { AbiItem } from 'web3-utils'
import { Erc20Contract, toBN } from '@gnosis.pm/dex-js'
import erc20Abi from '@gnosis.pm/dex-js/build/contracts/abi/Erc20.json'
import erc20_bytes32Abi from 'abis/erc20_bytes32.json'

import { TxOptionalParams, Receipt } from 'types'
import { ZERO } from 'const'
Expand Down Expand Up @@ -63,7 +64,11 @@ export interface TransferFromParams extends TransferParams {
*/
export interface Erc20Api {
name(params: NameParams): Promise<string>
name32Bytes(params: NameParams): Promise<string>

symbol(params: SymbolParams): Promise<string>
symbol32Bytes(params: SymbolParams): Promise<string>

decimals(params: DecimalsParams): Promise<number>
totalSupply(params: TotalSupplyParams): Promise<BN>

Expand All @@ -87,6 +92,7 @@ export interface Erc20ApiDependencies {
*/
export class Erc20ApiImpl implements Erc20Api {
private _contractPrototype: Erc20Contract
private _contract32BytesPrototype: Erc20Contract
private web3: Web3
private readonly localErc20Details: Erc20Details

Expand All @@ -101,12 +107,25 @@ export class Erc20ApiImpl implements Erc20Api {
this.localErc20Details = ERC20_DETAILS

this._contractPrototype = new this.web3.eth.Contract(erc20Abi as AbiItem[]) as unknown as Erc20Contract
this._contract32BytesPrototype = new this.web3.eth.Contract(
erc20_bytes32Abi as AbiItem[],
) as unknown as Erc20Contract

// TODO remove later
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(window as any).erc20 = this._contractPrototype
}

public async name32Bytes({ tokenAddress }: NameParams): Promise<string> {
this._contract32BytesPrototype.options.address = tokenAddress
return this._contract32BytesPrototype.methods.name().call()
}

public async symbol32Bytes({ tokenAddress }: NameParams): Promise<string> {
this._contract32BytesPrototype.options.address = tokenAddress
return this._contract32BytesPrototype.methods.symbol().call()
}

public async balanceOf({ networkId, tokenAddress, userAddress }: BalanceOfParams): Promise<BN> {
if (!userAddress || !tokenAddress) return ZERO

Expand All @@ -125,7 +144,10 @@ export class Erc20ApiImpl implements Erc20Api {

const erc20 = this._getERC20AtAddress(networkId, tokenAddress)

return erc20.methods.name().call()
return erc20.methods
.name()
.call()
.catch(() => this.name32Bytes({ tokenAddress, networkId }))
}

public async symbol({ tokenAddress, networkId }: SymbolParams): Promise<string> {
Expand All @@ -136,7 +158,10 @@ export class Erc20ApiImpl implements Erc20Api {

const erc20 = this._getERC20AtAddress(networkId, tokenAddress)

return erc20.methods.symbol().call()
return erc20.methods
.symbol()
.call()
.catch(() => this.symbol32Bytes({ tokenAddress, networkId }))
}

public async decimals({ tokenAddress, networkId }: DecimalsParams): Promise<number> {
Expand Down
17 changes: 17 additions & 0 deletions src/api/erc20/Erc20ApiMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ interface Allowances {

interface Erc20Info {
name?: string
name32Bytes?: string
symbol?: string
symbol32Bytes?: string
decimals?: number
}

Expand All @@ -52,6 +54,21 @@ export class Erc20ApiMock implements Erc20Api {
this._totalSupply = totalSupply
this._tokens = tokens
}
public async name32Bytes({ tokenAddress }: NameParams): Promise<string> {
const erc20Info = this._initTokens(tokenAddress)
// Throws when token without `name32Bytes` to mock contract behavior
assert(erc20Info.name32Bytes, "token does not implement 'name32Bytes'")

return erc20Info.name32Bytes
}
public async symbol32Bytes({ tokenAddress }: NameParams): Promise<string> {
const erc20Info = this._initTokens(tokenAddress)

// Throws when token without `symbol` to mock contract behavior
assert(erc20Info.symbol32Bytes, "token does not implement 'symbol32Bytes'")

return erc20Info.symbol32Bytes
}

public async balanceOf({ tokenAddress, userAddress }: BalanceOfParams): Promise<BN> {
const userBalances = this._balances[userAddress]
Expand Down
9 changes: 7 additions & 2 deletions src/services/helpers/getErc20Info.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Web3 from 'web3'

import { logDebug, silentPromise } from 'utils'
import { logDebug, silentPromise, parseStringOrBytes32 } from 'utils'
import { DEFAULT_PRECISION } from 'const'
import { Erc20Api } from 'api/erc20/Erc20Api'
import { TokenErc20 } from '@gnosis.pm/dex-js'
Expand Down Expand Up @@ -40,5 +40,10 @@ export async function getErc20Info({ tokenAddress, networkId, erc20Api, web3 }:
silentPromise(erc20Api.name({ tokenAddress, networkId }), errorMsg),
silentPromise(erc20Api.decimals({ tokenAddress, networkId }), errorMsg),
])
return { address: tokenAddress, symbol, name, decimals: decimals || DEFAULT_PRECISION }
return {
address: tokenAddress,
symbol: parseStringOrBytes32(symbol, 'UNKNOWN'),
name: parseStringOrBytes32(name, 'Unknown Token'),
decimals: decimals || DEFAULT_PRECISION,
}
}
13 changes: 13 additions & 0 deletions src/utils/format.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import BigNumber from 'bignumber.js'
import { parseBytes32String } from '@ethersproject/strings'
import { arrayify } from 'ethers/lib/utils'

import { TokenErc20, formatSmart, safeTokenName } from '@gnosis.pm/dex-js'

Expand Down Expand Up @@ -322,3 +324,14 @@ export function formattingAmountPrecision(
smallLimit: getMinimumRepresentableValue(typeFormatPrecision[typePrecision]),
})
}

// parse a name or symbol from a token response
const BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/

export function parseStringOrBytes32(value: string | undefined, defaultValue: string): string {
return value && BYTES32_REGEX.test(value) && arrayify(value)[31] === 0
? parseBytes32String(value)
: value && value.length > 0
? value
: defaultValue
}
43 changes: 40 additions & 3 deletions test/api/ExchangeApi/Erc20ApiMock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,24 @@ describe('Basic view functions', () => {
it('returns name', async () => {
expect(await instance.name({ tokenAddress: FEE_TOKEN, networkId: NETWORK_ID })).toBe('Fee token')
})

it('returns name32bytes', async () => {
expect(
await instance.name32Bytes({
tokenAddress: '0xF1290473E210b2108A85237fbCd7b6eb42Cc654F',
networkId: NETWORK_ID,
}),
).toBe('0x4865646765547261646500000000000000000000000000000000000000000000')
})

it("throws when there's no name 32bytes", async () => {
try {
await instance.name32Bytes({ tokenAddress: TOKEN_1, networkId: NETWORK_ID })
fail('Should not reach')
} catch (e) {
expect(e.message).toMatch(/token does not implement 'name32Bytes'/)
}
})
it("throws when there's no name", async () => {
try {
await instance.name({ tokenAddress: TOKEN_1, networkId: NETWORK_ID })
Expand All @@ -109,6 +127,25 @@ describe('Basic view functions', () => {
it('returns symbol', async () => {
expect(await instance.symbol({ tokenAddress: FEE_TOKEN, networkId: NETWORK_ID })).toBe('FEET')
})

it('returns symbol32bytes', async () => {
expect(
await instance.symbol32Bytes({
tokenAddress: '0xF1290473E210b2108A85237fbCd7b6eb42Cc654F',
networkId: NETWORK_ID,
}),
).toBe('0x4845444700000000000000000000000000000000000000000000000000000000')
})

it("throws when there's no symbol 32bytes", async () => {
try {
await instance.symbol32Bytes({ tokenAddress: TOKEN_1, networkId: NETWORK_ID })
fail('Should not reach')
} catch (e) {
expect(e.message).toMatch(/token does not implement 'symbol32Bytes'/)
}
})

it("throws when there's no symbol", async () => {
try {
await instance.symbol({ tokenAddress: TOKEN_1, networkId: NETWORK_ID })
Expand Down Expand Up @@ -218,7 +255,7 @@ describe('Write functions', () => {
await instance
.transfer({ userAddress: USER_2, tokenAddress: TOKEN_1, toAddress: CONTRACT, amount, networkId: NETWORK_ID })
.then(() => fail('Should not succeed'))
.catch(e => {
.catch((e) => {
expect(e.message).toMatch(/^The user doesn't have enough balance$/)
})
})
Expand Down Expand Up @@ -303,7 +340,7 @@ describe('Write functions', () => {
.then(() => {
fail('Should not succeed')
})
.catch(e => {
.catch((e) => {
expect(e.message).toMatch(/^The user doesn't have enough balance$/)
})
})
Expand All @@ -321,7 +358,7 @@ describe('Write functions', () => {
.then(() => {
fail('Should not succeed')
})
.catch(e => {
.catch((e) => {
expect(e.message).toMatch(/^Not allowed to perform this transfer$/)
})
})
Expand Down
4 changes: 4 additions & 0 deletions test/data/unregisteredTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ export default {
symbol: 'OWL',
decimals: 18,
},
'0xF1290473E210b2108A85237fbCd7b6eb42Cc654F': {
name32Bytes: '0x4865646765547261646500000000000000000000000000000000000000000000',
symbol32Bytes: '0x4845444700000000000000000000000000000000000000000000000000000000',
},
[FEE_TOKEN]: { name: 'Fee token', symbol: 'FEET', decimals: 18 },
}
13 changes: 13 additions & 0 deletions test/utils/format.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { parseStringOrBytes32 } from 'utils'

describe('parse string or bytes32', () => {
test('parseStringOrBytes32 parse string', () => {
const symbol = 'HEDG'
expect(parseStringOrBytes32(symbol, 'UNKNOWN')).toMatch(symbol)
})

test('parseStringOrBytes32 parse bytes32', () => {
const name = '0x4865646765547261646500000000000000000000000000000000000000000000'
expect(parseStringOrBytes32(name, 'Unknown Token')).toMatch(/HedgeTrade/)
})
})

0 comments on commit 0b05538

Please sign in to comment.