Skip to content

Commit

Permalink
feat: generate tokenlist alongside the repo (#353)
Browse files Browse the repository at this point in the history
* feat: generate tokenlist alongside the repo

* fix: package.lock

* feat: add underlying to tokenlist
  • Loading branch information
sakulstra authored Feb 8, 2024
1 parent e1faa69 commit a1a60f6
Show file tree
Hide file tree
Showing 12 changed files with 7,699 additions and 2 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
"devDependencies": {
"@bgd-labs/js-utils": "^1.1.1",
"@types/node": "^20.10.5",
"@uniswap/token-lists": "^1.0.0-beta.33",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"esbuild-plugin-file-path-extensions": "^2.0.0",
"prettier": "^3.0.3",
"prettier-plugin-solidity": "^1.1.3",
Expand Down
1 change: 1 addition & 0 deletions scripts/configs/abis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const ABI_INTERFACES = [
'IDataWarehouse',
'IExecutorWithTimelock',
'IERC20',
'IERC20Detailed',
'IAToken',
'IDefaultInterestRateStrategy',
'IAaveOracle',
Expand Down
4 changes: 4 additions & 0 deletions scripts/generateAddresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {scrollAddresses} from './configs/networks/scroll';
import {polygonZkEvmAddresses} from './configs/networks/polygonZkEvm';
import {governanceConfigScroll} from './configs/governance/scroll';
import {governanceConfigPolygonZkEvm} from './configs/governance/polygonZkEvm';
import {generateTokenList} from './generator/generateTokenList';

async function main() {
// cleanup ts artifacts
Expand Down Expand Up @@ -104,6 +105,7 @@ async function main() {
avalancheProtoV2,
].map((config) => generateProtocolV2Library(config)),
);

const v3LibraryNames = await Promise.all(
[
mainnetProtoV3Pool,
Expand All @@ -130,6 +132,8 @@ async function main() {
].map((config) => generateProtocolV3Library(config)),
);

generateTokenList([...v2LibraryNames, ...v3LibraryNames]);

const networkAddresses = [
arbitrumAddresses,
arbitrumSepoliaAddresses,
Expand Down
3 changes: 1 addition & 2 deletions scripts/generator/assetsLibraryGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {Hex, zeroAddress} from 'viem';
import {ReserveData} from '../configs/types';
import {generateSolidityConstants, wrapIntoSolidityLibrary} from './utils';
import {ChainId} from '@bgd-labs/js-utils';

/**
* As symbols are used as variable name in Solidity and Javascript there are certain characters that are not allowed and should be replaced.
Expand Down Expand Up @@ -59,7 +58,7 @@ export function fixSymbol(symbol: string, _underlying: string) {
}

export function generateAssetsLibrary(
chainId: ChainId,
chainId: number,
reservesData: ReserveData[],
libraryName: string,
) {
Expand Down
144 changes: 144 additions & 0 deletions scripts/generator/generateTokenList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {schema, TokenInfo, TokenList} from '@uniswap/token-lists';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import {ReserveData} from '../configs/types';
import {readFileSync, existsSync, writeFileSync} from 'fs';
import {cwd} from 'process';
import {join} from 'path';
import prettier from 'prettier';
import {Address, getContract, zeroAddress} from 'viem';
import {IERC20Detailed_ABI} from '../../src/ts/abis/IERC20Detailed';
import {CHAIN_ID_CLIENT_MAP} from '@bgd-labs/js-utils';
import {fixSymbol} from './assetsLibraryGenerator';

const TAGS = {
underlying: 'underlying',
aaveV2: 'aaveV2',
aTokenV2: 'aTokenV2',
aaveV3: 'aaveV3',
aTokenV3: 'aTokenV3',
stataToken: 'stataToken',
} as const;

type TokenListParams = {
name: string;
chainId: number;
reservesData: ReserveData[];
}[];

function findInList(tokens: TokenInfo[], address: Address, chainId: number) {
return tokens.find((x) => x.address === address && x.chainId === chainId);
}

export async function generateTokenList(pools: TokenListParams) {
const path = join(cwd(), 'tokenlist.json');
const cachedList: TokenList = existsSync(path)
? JSON.parse(readFileSync(path, 'utf-8'))
: {tokens: []};

const tokens: TokenInfo[] = [];
for (const {reservesData, chainId, name: poolName} of pools) {
for (const reserve of reservesData) {
async function addToken(token: Address, tags: string[], extensions?: Record<string, string>) {
const alreadyInList = findInList(tokens, token, chainId);
if (alreadyInList) return;
const cache = findInList(cachedList.tokens, token, chainId);
if (cache) return tokens.push(cache);
else {
const erc20contract = getContract({
abi: IERC20Detailed_ABI,
address: token,
client: CHAIN_ID_CLIENT_MAP[chainId],
});
const [name, symbol] =
token == '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2'
? ['Maker', 'MKR']
: await Promise.all([erc20contract.read.name(), erc20contract.read.symbol()]);
return tokens.push({
chainId: chainId,
address: token,
name: name.length > 40 ? `${name.substring(0, 37)}...` : name, // schema limits to 40 characters
decimals: reserve.decimals,
symbol: fixSymbol(symbol, token),
tags,
...(extensions ? {extensions} : {}),
});
}
}
await addToken(reserve.UNDERLYING, [TAGS.underlying]);
await addToken(
reserve.A_TOKEN,
/V2/.test(poolName) ? [TAGS.aTokenV2, TAGS.aaveV2] : [TAGS.aTokenV3, TAGS.aaveV3],
{pool: poolName, underlying: reserve.UNDERLYING},
);
if (reserve.STATA_TOKEN && reserve.STATA_TOKEN != zeroAddress)
await addToken(
reserve.STATA_TOKEN,
[/V2/.test(poolName) ? TAGS.aaveV3 : TAGS.aaveV3, TAGS.stataToken],
{
pool: poolName,
underlying: reserve.UNDERLYING,
underlyingAToken: reserve.A_TOKEN,
},
);
}
}

// if (cachedList.tokens.length === tokens.length) return;
const tokenList: TokenList = {
name: 'Aave token list',
logoURI: 'ipfs://QmWzL3TSmkMhbqGBEwyeFyWVvLmEo3F44HBMFnmTUiTfp1',
keywords: ['audited', 'verified', 'aave'],

tags: {
[TAGS.underlying]: {
name: 'underlyingAsset',
description: 'Tokens that are used as underlying assets in the Aave protocol',
},
[TAGS.aaveV2]: {
name: 'Aave V2',
description: 'Tokens related to aave v2',
},
[TAGS.aaveV3]: {
name: 'Aave V3',
description: 'Tokens related to aave v3',
},
[TAGS.aTokenV2]: {
name: 'aToken V2',
description: 'Tokens that earn interest on the Aave Protocol V2',
},
[TAGS.aTokenV3]: {
name: 'aToken V3',
description: 'Tokens that earn interest on the Aave Protocol V3',
},
[TAGS.stataToken]: {
name: 'static a token',
description: 'Tokens that are wrapped into a 4626 Vault',
},
},
timestamp: new Date().toISOString(),
version: {
major: 3,
minor: 0,
patch: cachedList.version?.patch ? cachedList.version.patch + 1 : 0,
},
tokens,
};

const ajv = new Ajv({allErrors: true, verbose: true});
addFormats(ajv);
const validator = ajv.compile(schema);
const valid = validator(tokenList);
if (valid) {
writeFileSync(
path,
await prettier.format(JSON.stringify(tokenList), {
filepath: path,
}),
);
}
if (validator.errors) {
console.log(validator.errors);
throw new Error('error creating tokenlist');
}
}
3 changes: 3 additions & 0 deletions scripts/generator/protocolV2Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ export async function generateProtocolV2Library(config: PoolConfig) {
// `export {${assetsLibraryName}} from './${assetsLibraryName}';\r\n`,
// );
return {
name,
reservesData,
chainId: config.chainId,
js: [`export * as ${name} from './${name}';`],
solidity: [`import {${name}} from './${name}.sol';`],
};
Expand Down
3 changes: 3 additions & 0 deletions scripts/generator/protocolV3Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ export async function generateProtocolV3Library(config: PoolConfig) {
appendFileSync(`./src/ts/${name}.ts`, eModesLibrary.js);

return {
name,
reservesData,
chainId: config.chainId,
js: [`export * as ${name} from './${name}';`],
solidity: [`import {${name}} from './${name}.sol';`],
};
Expand Down
2 changes: 2 additions & 0 deletions src/test/AaveV2Ethereum.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity >=0.6.0;

import 'forge-std/Test.sol';
import {AaveV2Ethereum} from '../AaveAddressBook.sol';
// import is unnecessary here, but needed somewhere in the project so we can infer the abi from build artifacts
import {IERC20Detailed} from 'aave-v3-core/contracts/dependencies/openzeppelin/contracts/IERC20Detailed.sol';

contract AaveAddressBookTest is Test {
function setUp() public {}
Expand Down
1 change: 1 addition & 0 deletions src/ts/AaveAddressBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export {IGovernancePowerStrategy_ABI} from './abis/IGovernancePowerStrategy';
export {IDataWarehouse_ABI} from './abis/IDataWarehouse';
export {IExecutorWithTimelock_ABI} from './abis/IExecutorWithTimelock';
export {IERC20_ABI} from './abis/IERC20';
export {IERC20Detailed_ABI} from './abis/IERC20Detailed';
export {IAToken_ABI} from './abis/IAToken';
export {IDefaultInterestRateStrategy_ABI} from './abis/IDefaultInterestRateStrategy';
export {IAaveOracle_ABI} from './abis/IAaveOracle';
Expand Down
Loading

0 comments on commit a1a60f6

Please sign in to comment.