@ -5,8 +5,9 @@ import {
ERC20__factory ,
ERC20__factory ,
HypERC20Collateral ,
HypERC20Collateral ,
IXERC20Lockbox__factory ,
IXERC20Lockbox__factory ,
TokenRouter ,
} from '@hyperlane-xyz/core' ;
} from '@hyperlane-xyz/core' ;
import { eqAddress } from '@hyperlane-xyz/utils' ;
import { eqAddress , objMap } from '@hyperlane-xyz/utils' ;
import { TokenMismatchViolation } from '../deploy/types.js' ;
import { TokenMismatchViolation } from '../deploy/types.js' ;
import { ProxiedRouterChecker } from '../router/ProxiedRouterChecker.js' ;
import { ProxiedRouterChecker } from '../router/ProxiedRouterChecker.js' ;
@ -66,6 +67,30 @@ export class HypERC20Checker extends ProxiedRouterChecker<
const expectedConfig = this . configMap [ chain ] ;
const expectedConfig = this . configMap [ chain ] ;
const hypToken = this . app . router ( this . app . getContracts ( chain ) ) ;
const hypToken = this . app . router ( this . app . getContracts ( chain ) ) ;
// Check all actual decimals are consistent
const actualChainDecimals = await this . getEvmActualDecimals ( ) ;
this . checkDecimalConsistency (
chain ,
hypToken ,
actualChainDecimals ,
'actual' ,
true ,
) ;
// Check all config decimals are consistent as well
const configDecimals = objMap (
this . configMap ,
( _chain , config ) = > config . decimals ,
) ;
this . checkDecimalConsistency (
chain ,
hypToken ,
configDecimals ,
'config' ,
false ,
) ;
if ( isNativeConfig ( expectedConfig ) ) {
if ( isNativeConfig ( expectedConfig ) ) {
try {
try {
await this . multiProvider . estimateGas ( chain , {
await this . multiProvider . estimateGas ( chain , {
@ -86,37 +111,119 @@ export class HypERC20Checker extends ProxiedRouterChecker<
} else if ( isSyntheticConfig ( expectedConfig ) ) {
} else if ( isSyntheticConfig ( expectedConfig ) ) {
await checkERC20 ( hypToken as unknown as ERC20 , expectedConfig ) ;
await checkERC20 ( hypToken as unknown as ERC20 , expectedConfig ) ;
} else if ( isCollateralConfig ( expectedConfig ) ) {
} else if ( isCollateralConfig ( expectedConfig ) ) {
const collateralToken = await this . getCollateralToken ( chain ) ;
const actualToken = await (
hypToken as unknown as HypERC20Collateral
) . wrappedToken ( ) ;
if ( ! eqAddress ( collateralToken . address , actualToken ) ) {
const violation : TokenMismatchViolation = {
type : 'CollateralTokenMismatch' ,
chain ,
expected : collateralToken.address ,
actual : actualToken ,
tokenAddress : hypToken.address ,
} ;
this . addViolation ( violation ) ;
}
}
}
private cachedAllActualDecimals : Record < ChainName , number > | undefined =
undefined ;
async getEvmActualDecimals ( ) : Promise < Record < ChainName , number > > {
if ( this . cachedAllActualDecimals ) {
return this . cachedAllActualDecimals ;
}
const entries = await Promise . all (
this . getEvmChains ( ) . map ( async ( chain ) = > {
const token = this . app . router ( this . app . getContracts ( chain ) ) ;
return [ chain , await this . getActualDecimals ( chain , token ) ] ;
} ) ,
) ;
this . cachedAllActualDecimals = Object . fromEntries ( entries ) ;
return this . cachedAllActualDecimals ! ;
}
async getActualDecimals (
chain : ChainName ,
hypToken : TokenRouter ,
) : Promise < number > {
const expectedConfig = this . configMap [ chain ] ;
let decimals : number | undefined = undefined ;
if ( isNativeConfig ( expectedConfig ) ) {
decimals =
this . multiProvider . getChainMetadata ( chain ) . nativeToken ? . decimals ;
} else if ( isSyntheticConfig ( expectedConfig ) ) {
decimals = await ( hypToken as unknown as ERC20 ) . decimals ( ) ;
} else if ( isCollateralConfig ( expectedConfig ) ) {
const collateralToken = await this . getCollateralToken ( chain ) ;
decimals = await collateralToken . decimals ( ) ;
}
if ( decimals === undefined ) {
throw new Error ( 'Actual decimals not found' ) ;
}
return decimals ;
}
async getCollateralToken ( chain : ChainName ) : Promise < ERC20 > {
const expectedConfig = this . configMap [ chain ] ;
let collateralToken : ERC20 | undefined = undefined ;
if ( isCollateralConfig ( expectedConfig ) ) {
const provider = this . multiProvider . getProvider ( chain ) ;
const provider = this . multiProvider . getProvider ( chain ) ;
let collateralToken : ERC20 ;
if ( expectedConfig . type === TokenType . XERC20Lockbox ) {
if ( expectedConfig . type === TokenType . XERC20Lockbox ) {
const collateralTokenAddress = await IXERC20Lockbox__factory . connect (
const collateralTokenAddress = await IXERC20Lockbox__factory . connect (
expectedConfig . token ,
expectedConfig . token ,
provider ,
provider ,
) . callStatic . ERC20 ( ) ;
) . callStatic . ERC20 ( ) ;
collateralToken = await ERC20__factory . connect (
collateralToken = ERC20__factory . connect (
collateralTokenAddress ,
collateralTokenAddress ,
provider ,
provider ,
) ;
) ;
} else {
} else {
collateralToken = await ERC20__factory . connect (
collateralToken = ERC20__factory . connect (
expectedConfig . token ,
expectedConfig . token ,
provider ,
provider ,
) ;
) ;
}
}
const actualToken = await (
}
hypToken as unknown as HypERC20Collateral
if ( ! collateralToken ) {
) . wrappedToken ( ) ;
throw new Error ( 'Collateral token not found' ) ;
if ( ! eqAddress ( collateralToken . address , actualToken ) ) {
}
return collateralToken ;
}
checkDecimalConsistency (
chain : ChainName ,
hypToken : TokenRouter ,
chainDecimals : Record < ChainName , number | undefined > ,
decimalType : string ,
nonEmpty : boolean ,
) {
const uniqueChainDecimals = new Set (
Object . values ( chainDecimals ) . filter ( ( decimals ) = > ! ! decimals ) ,
) ;
if (
uniqueChainDecimals . size > 1 ||
( nonEmpty && uniqueChainDecimals . size === 0 )
) {
const violation : TokenMismatchViolation = {
const violation : TokenMismatchViolation = {
type : 'CollateralTokenMismatch' ,
type : 'TokenDecimals Mismatch' ,
chain ,
chain ,
expected : collateralToken.address ,
expected : ` ${
actual : actualToken ,
nonEmpty ? 'non-empty and ' : ''
} consistent $ { decimalType } decimals ` ,
actual : JSON.stringify ( chainDecimals ) ,
tokenAddress : hypToken.address ,
tokenAddress : hypToken.address ,
} ;
} ;
this . addViolation ( violation ) ;
this . addViolation ( violation ) ;
}
}
}
}
}
}
}