import BigNumber from 'bignumber.js' ; import BN from 'bn.js' ; import abi from 'ethereumjs-abi' ; import { addHexPrefix , stripHexPrefix } from 'ethereumjs-util' ; import { toChecksumAddressByChainId } from '@utils' ; import { FuncParams , FunctionOutputMappings , Input , ISuppliedArgs , ITypeMapping , Output } from './types' ; export class AbiFunction { public constant : boolean ; public outputs : Output [ ] ; public inputs : Input [ ] ; private funcParams : FuncParams ; private inputNames : string [ ] ; private inputTypes : string [ ] ; private outputNames : string [ ] ; private outputTypes : string [ ] ; private methodSelector : string ; private name : string ; constructor ( abiFunc , outputMappings ) { Object . assign ( this , abiFunc ) ; this . init ( outputMappings ) ; } public encodeInput = ( suppliedInputs = { } ) => { const args = this . processSuppliedArgs ( suppliedInputs ) ; const encodedCall = this . makeEncodedFuncCall ( args ) ; return encodedCall ; } ; public decodeInput = ( argString , chainId ) => { argString = argString . replace ( addHexPrefix ( this . methodSelector ) , '' ) ; const argBuffer = Buffer . from ( argString , 'hex' ) ; const argArr = abi . rawDecode ( this . inputTypes , argBuffer ) ; return argArr . reduce ( ( argObj , currArg , index ) => { const currName = this . inputNames [ index ] ; const currType = this . inputTypes [ index ] ; return { ... argObj , [ currName ] : this . parsePostDecodedValue ( currType , currArg , chainId ) } ; } , { } ) ; } ; public decodeOutput = ( argString , chainId ) => { argString = argString . replace ( addHexPrefix ( this . methodSelector ) , '' ) ; argString = argString . replace ( '0x' , '' ) ; const argBuffer = Buffer . from ( argString , 'hex' ) ; const argArr = abi . rawDecode ( this . outputTypes , argBuffer ) ; return argArr . reduce ( ( argObj , currArg , index ) => { const currName = this . outputNames [ index ] ; const currType = this . outputTypes [ index ] ; return { ... argObj , [ currName ] : this . parsePostDecodedValue ( currType , currArg , chainId ) } ; } , { } ) ; } ; private init ( outputMappings = [ ] ) { this . inputTypes = this . inputs . map ( ( { type } ) => type ) ; this . outputTypes = this . outputs . map ( ( { type } ) => type ) ; this . inputNames = this . inputs . map ( ( { name } , i ) => name || `${ i }` ) ; this . outputNames = this . outputs . map ( ( { name } , i ) => outputMappings [ i ] || name || `${ i }` ) ; this . funcParams = this . makeFuncParams ( ) ; this . methodSelector = abi . methodID ( this . name , this . inputTypes ) . toString ( 'hex' ) ; } private parsePostDecodedValue = ( type , value , chainId ) => { const valueMapping = { address : ( val ) => toChecksumAddressByChainId ( val . toString ( 16 ) , chainId ) , 'address[]' : ( val ) => val . map ( ( x ) => toChecksumAddressByChainId ( x . toString ( 16 ) , chainId ) ) } ; const mapppedType = valueMapping [ type ] ; return mapppedType ? mapppedType ( value ) : BN . isBN ( value ) ? value . toString ( ) : value ; } ; private parsePreEncodedValue = ( type , value ) => { if ( type === 'bytes' ) { return Buffer . from ( stripHexPrefix ( value ) , 'hex' ) ; } return BigNumber . isBigNumber ( value ) ? value . toString ( ) : value ; } ; private makeFuncParams = ( ) => this . inputs . reduce ( ( accumulator , _ , idx ) => { const name = this . inputNames [ idx ] ; const type = this . inputTypes [ idx ] ; const inputHandler = ( inputToParse ) => ( { name , type , value : this . parsePreEncodedValue ( type , inputToParse ) } ) ; return { ... accumulator , [ name ] : { processInput : inputHandler , type , name } } ; } , { } ) ; private makeEncodedFuncCall = ( args ) => { const encodedArgs = abi . rawEncode ( this . inputTypes , args ) . toString ( 'hex' ) ; return addHexPrefix ( `${ this . methodSelector }${ encodedArgs }` ) ; } ; private processSuppliedArgs = ( suppliedArgs ) => this . inputNames . map ( ( name ) => { const type = this . funcParams [ name ] . type ; if ( typeof suppliedArgs [ name ] === 'undefined' ) { throw Error ( `Expected argument "${ name }" of type "${ type }" missing, suppliedArgs: ${ JSON . stringify ( suppliedArgs , null , 2 ) }` ) ; } const value = suppliedArgs [ name ] ; const processedArg = this . funcParams [ name ] . processInput ( value ) ; return processedArg . value ; } ) ; }