import { Variable , VariableLocation , VariableType } from "../variable" ; import { ValueDetail } from "../detail/value" ; import { EnumDetail } from "../detail/enum" ; import { ArrayDetail } from "../detail/array" ; import { StructDetail } from "../detail/struct" ; import { MappingDetail } from "../detail/mapping" ; import { LibSdbRuntime } from "../../../runtime" ; import { ContractDetail } from "../detail/contract" ; import { LibSdbTypes } from "../../types" ; import { BN } from "bn.js" ; export class VariableProcessor { private _runtime : LibSdbRuntime ; private _variable : Variable ; public _currentStorageSlot : BN ; public _currentStorageSlotOffset : number ; constructor ( variable , storageSlot , storageSlotOffset ) { this . _runtime = LibSdbRuntime . instance ( ) ; this . _variable = variable ; this . _currentStorageSlot = storageSlot ; this . _currentStorageSlotOffset = storageSlotOffset ; } public applyType ( storageLocation , parentName ) { const varType = this . _variable . originalType ; if ( this . _variable . isStateVariable === true ) { this . _variable . location = VariableLocation . Storage ; } else { if ( storageLocation === "default" ) { let isReferenceType = false ; isReferenceType = isReferenceType || varType . startsWith ( "struct" ) ; isReferenceType = isReferenceType || varType . startsWith ( "mapping" ) ; isReferenceType = isReferenceType || varType . includes ( "[" ) && varType . includes ( "]" ) ; if ( isReferenceType ) { if ( parentName === "ParameterList" ) { this . _variable . location = VariableLocation . Memory ; } else { this . _variable . location = VariableLocation . Storage ; } } else { this . _variable . location = VariableLocation . Stack ; } } else if ( storageLocation === "storage" ) { this . _variable . location = VariableLocation . Storage ; } else if ( storageLocation === "memory" ) { this . _variable . location = VariableLocation . Memory ; } else { this . _variable . location = VariableLocation . Stack ; } } this . _variable . detail = this . processDetails ( varType ) ; } public processDetails ( typeName , isRoot = true ) { let leaf = null ; let match = null ; let remainderTypeName = "" ; let result ; if ( ( match = /^bool/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new ValueDetail ( this . _variable ) ; leaf . type = VariableType . Boolean ; leaf . storageLength = 32 ; } else if ( ( match = /^uint/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new ValueDetail ( this . _variable ) ; leaf . type = VariableType . UnsignedInteger ; const index = match . index + match [ 0 ] . length ; const remainder = typeName . substr ( index ) ; const bitMatch = /^[0-9]+/g . exec ( remainder ) ; if ( bitMatch !== null ) { leaf . storageLength = Math . ceil ( ( parseInt ( bitMatch [ 0 ] ) || 256 ) / 8 ) ; remainderTypeName = remainder . substr ( bitMatch . index + bitMatch [ 0 ] . length ) ; } else { leaf . storageLength = 32 ; } } else if ( ( match = /^int/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new ValueDetail ( this . _variable ) ; leaf . type = VariableType . Integer ; const index = match . index + match [ 0 ] . length ; const remainder = typeName . substr ( index ) ; const bitMatch = /^[0-9]+/g . exec ( remainder ) ; if ( bitMatch !== null ) { leaf . storageLength = Math . ceil ( ( parseInt ( bitMatch [ 0 ] ) || 256 ) / 8 ) ; remainderTypeName = remainder . substr ( bitMatch . index + bitMatch [ 0 ] . length ) ; } else { leaf . storageLength = 32 ; } } else if ( ( match = /^address/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new ValueDetail ( this . _variable ) ; leaf . type = VariableType . Address ; leaf . storageLength = 20 ; } else if ( ( match = /^(bytes)([1-9]|[12][0-9]|3[0-2])\b/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new ValueDetail ( this . _variable ) ; leaf . type = VariableType . FixedByteArray ; leaf . storageLength = parseInt ( match [ 2 ] ) || 32 ; } else if ( ( match = /^bytes/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new ArrayDetail ( this . _variable ) ; this . _runtime . _variableReferenceIds . set ( leaf . id , leaf ) ; leaf . memberType = new ValueDetail ( this . _variable ) ; leaf . memberType . type = VariableType . FixedByteArray ; leaf . memberType . storageLength = 32 ; const index = match . index + match [ 0 ] . length ; const remainder = typeName . substr ( index ) ; const locationMatch = /^ (storage|memory|calldata) ?(pointer|ref)?/g . exec ( remainder ) ; if ( locationMatch !== null ) { const locationString = locationMatch [ 1 ] . trim ( ) ; switch ( locationString ) { case "storage" : leaf . location = VariableLocation . Storage ; leaf . isPointer = locationMatch [ 2 ] === "pointer" ; break ; case "memory" : leaf . location = VariableLocation . Memory ; leaf . isPointer = false ; break ; case "calldata" : leaf . location = VariableLocation . CallData ; leaf . isPointer = false ; break ; } remainderTypeName = remainder . substr ( locationMatch . index + locationMatch [ 0 ] . length ) ; } leaf . isDynamic = true ; } else if ( ( match = /^string/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new ArrayDetail ( this . _variable ) ; this . _runtime . _variableReferenceIds . set ( leaf . id , leaf ) ; leaf . memberType = new ValueDetail ( this . _variable ) ; leaf . memberType . type = VariableType . FixedByteArray ; leaf . memberType . storageLength = 32 ; const index = match . index + match [ 0 ] . length ; const remainder = typeName . substr ( index ) ; const locationMatch = /^ (storage|memory|calldata) ?(pointer|ref)?/g . exec ( remainder ) ; if ( locationMatch !== null ) { const locationString = locationMatch [ 1 ] . trim ( ) ; switch ( locationString ) { case "storage" : leaf . location = VariableLocation . Storage ; leaf . isPointer = locationMatch [ 2 ] === "pointer" ; break ; case "memory" : leaf . location = VariableLocation . Memory ; leaf . isPointer = false ; break ; case "calldata" : leaf . location = VariableLocation . CallData ; leaf . isPointer = false ; break ; } remainderTypeName = remainder . substr ( locationMatch . index + locationMatch [ 0 ] . length ) ; } leaf . isDynamic = true ; } else if ( ( match = /^struct ([\S]+)\.([^\r\n\t\f\v \[]+)/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new StructDetail ( this . _variable ) ; this . _runtime . _variableReferenceIds . set ( leaf . id , leaf ) ; const index = match . index + match [ 0 ] . length ; const remainder = typeName . substr ( index ) ; const locationMatch = /^ (storage|memory|calldata) ?(pointer|ref)?/g . exec ( remainder ) ; if ( locationMatch !== null ) { const locationString = locationMatch [ 1 ] . trim ( ) ; switch ( locationString ) { case "storage" : leaf . location = VariableLocation . Storage ; leaf . isPointer = locationMatch [ 2 ] === "pointer" ; break ; case "memory" : leaf . location = VariableLocation . Memory ; leaf . isPointer = false ; break ; case "calldata" : leaf . location = VariableLocation . CallData ; leaf . isPointer = false ; break ; } remainderTypeName = remainder . substr ( locationMatch . index + locationMatch [ 0 ] . length ) ; } const structContractName = match [ 1 ] ; const structName = match [ 2 ] ; leaf . name = structContractName + "." + structName ; const contract = this . _runtime . _contractsByName . get ( structContractName ) ; if ( contract ) { const structVariables = contract . structDefinitions . get ( structName ) ; if ( structVariables !== undefined ) { for ( const structVariable of structVariables ) { let variable = structVariable [ 1 ] . clone ( ) ; variable . location = leaf . location ; if ( variable . detail !== null ) { variable . detail . variable = leaf . variable ; } leaf . members . push ( { name : variable . name , detail : variable . detail } ) ; } } } } else if ( ( match = /^mapping\((.*?(?=(?: => )|$)) => (.*)\)/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new MappingDetail ( this . _variable ) ; this . _runtime . _variableReferenceIds . set ( leaf . id , leaf ) ; const key = this . processDetails ( match [ 1 ] , false ) ; if ( key instanceof ValueDetail || key instanceof ArrayDetail ) { leaf . key = key ; } else { throw "shouldnt happen" ; } leaf . value = this . processDetails ( match [ 2 ] , false ) ; } else if ( ( match = /^contract ([^\r\n\t\f\v \[]+)/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new ContractDetail ( this . _variable ) ; leaf . name = match [ 1 ] ; } else if ( ( match = /^enum ([\S]+)\.([^\r\n\t\f\v \[]+)/g . exec ( typeName ) ) !== null || ( match = /^enum/g . exec ( typeName ) ) !== null ) { remainderTypeName = typeName . substr ( match . index + match [ 0 ] . length ) ; leaf = new EnumDetail ( this . _variable ) ; if ( leaf instanceof EnumDetail ) { const enumContractName = match [ 1 ] ; const enumName = match [ 2 ] ; const contract = this . _runtime . _contractsByName . get ( enumContractName ) ; if ( contract ) { const enumDefinition = contract . enumDefinitions . get ( enumName ) ; if ( enumDefinition !== undefined ) { leaf . definition = enumDefinition ; const numValues = leaf . definition . values . length ; leaf . storageLength = Math . ceil ( Math . log2 ( numValues + 1 ) / 8 ) ; } } } } else { } if ( match !== null ) { let arrays = [ ] ; let arrayMatch = null ; do { arrayMatch = /^\[([0-9]*)\]/g . exec ( remainderTypeName ) ; if ( arrayMatch !== null ) { let array = new ArrayDetail ( this . _variable ) ; this . _runtime . _variableReferenceIds . set ( array . id , array ) ; array . isDynamic = arrayMatch . length === 0 ; array . length = arrayMatch . length > 0 ? parseInt ( arrayMatch [ 1 ] ) : 0 ; const index2 = arrayMatch . index + arrayMatch [ 0 ] . length ; const remainder = remainderTypeName . substr ( index2 ) ; const locationMatch = /^ (storage|memory|calldata) ?(pointer|ref)?/g . exec ( remainder ) ; if ( locationMatch !== null ) { const locationString = locationMatch [ 1 ] . trim ( ) ; switch ( locationString ) { case "storage" : { array . location = VariableLocation . Storage ; array . isPointer = locationMatch [ 2 ] === "pointer" ; break ; } case "memory" : { array . location = VariableLocation . Memory ; array . isPointer = false ; break ; } case "calldata" : { array . location = VariableLocation . CallData ; array . isPointer = false ; break ; } } remainderTypeName = remainderTypeName . substr ( locationMatch . index + locationMatch [ 0 ] . length ) ; } remainderTypeName = remainderTypeName . substr ( arrayMatch . index + arrayMatch [ 0 ] . length ) ; arrays . push ( array ) ; } } while ( remainderTypeName !== "" ) ; if ( arrays . length > 0 ) { for ( let i = 0 ; i < arrays . length ; i ++ ) { if ( i === 0 ) { arrays [ i ] . memberType = leaf ! ; } else { arrays [ i ] . memberType = arrays [ i - 1 ] ; } if ( ! arrays [ i ] . isDynamic ) { for ( let j = 0 ; j < arrays [ i ] . length ; j ++ ) { let clone = arrays [ i ] . memberType . clone ( ) ; arrays [ i ] . members . push ( clone ) ; if ( ! ( clone instanceof ValueDetail ) ) { this . _runtime . _variableReferenceIds . set ( clone . id , clone ) ; } } } } result = arrays [ arrays . length - 1 ] ; } else { result = leaf ! ; } if ( isRoot ) { this . applyPositions ( result ) ; } return result ; } else { return null ; } } private applyMemoryPositions ( detail , offsetPosition = 0 ) { detail . offset = null ; if ( detail instanceof ValueDetail ) { detail . position = new BN ( offsetPosition ) ; } else if ( detail instanceof ArrayDetail ) { if ( ! detail . isDynamic ) { if ( ! ( detail . memberType instanceof ArrayDetail ) || ! detail . memberType . isDynamic ) { for ( let i = 0 ; i < detail . members . length ; i ++ ) { this . applyMemoryPositions ( detail . members [ i ] , i * detail . memberType . memoryLength ) ; } } } } else if ( detail instanceof StructDetail ) { let currentSize = 0 ; for ( let i = 0 ; i < detail . members . length ; i ++ ) { const memberDetail = detail . members [ i ] . detail ; if ( memberDetail !== null && ( ! ( memberDetail instanceof ArrayDetail ) || ! ( memberDetail as ArrayDetail ) . isDynamic ) ) { this . applyMemoryPositions ( memberDetail , currentSize ) ; currentSize += memberDetail . memoryLength ; } } } else if ( detail instanceof MappingDetail ) { } else if ( detail instanceof ContractDetail ) { detail . position = new BN ( offsetPosition ) ; } } private storageIncrementSlot ( ) { this . _currentStorageSlot = this . _currentStorageSlot . addn ( 1 ) ; this . _currentStorageSlotOffset = 0 ; } private storageIncrementOffset ( offset ) { this . _currentStorageSlotOffset += offset ; } private storageCheckSpaceLeft ( storageLength ) { const spaceLeft = 32 - this . _currentStorageSlotOffset ; if ( spaceLeft - storageLength < 0 ) { this . storageIncrementSlot ( ) ; } } private storageCheckNewSlotRequired ( ) { if ( this . _currentStorageSlotOffset > 0 ) { this . storageIncrementSlot ( ) ; } } private storageCheckEndOfSlot ( ) { if ( this . _currentStorageSlotOffset >= 32 ) { this . storageIncrementSlot ( ) ; } } private applyStoragePosition ( detail ) { detail . position = this . _currentStorageSlot . clone ( ) ; detail . offset = this . _currentStorageSlotOffset ; } public applyStoragePositions ( detail ) { if ( detail instanceof ValueDetail ) { this . storageCheckSpaceLeft ( detail . storageLength ) ; this . applyStoragePosition ( detail ) ; this . storageIncrementOffset ( detail . storageLength ) ; this . storageCheckEndOfSlot ( ) ; } else if ( detail instanceof ArrayDetail ) { if ( detail . isPointer ) { } else { this . storageCheckNewSlotRequired ( ) ; this . applyStoragePosition ( detail ) ; if ( ! detail . isDynamic ) { for ( let i = 0 ; i < detail . members . length ; i ++ ) { this . applyStoragePositions ( detail . members [ i ] ) ; } } if ( detail . position . eq ( this . _currentStorageSlot ) || this . _currentStorageSlotOffset > 0 ) { this . storageIncrementSlot ( ) ; } } } else if ( detail instanceof StructDetail ) { if ( detail . isPointer ) { } else { this . storageCheckNewSlotRequired ( ) ; this . applyStoragePosition ( detail ) ; for ( let i = 0 ; i < detail . members . length ; i ++ ) { const memberDetail = detail . members [ i ] . detail ; if ( memberDetail !== null ) { this . applyStoragePositions ( memberDetail ) ; } } if ( detail . position . eq ( this . _currentStorageSlot ) || this . _currentStorageSlotOffset > 0 ) { this . storageIncrementSlot ( ) ; } } } else if ( detail instanceof MappingDetail ) { this . storageCheckNewSlotRequired ( ) ; this . applyStoragePosition ( detail ) ; this . storageIncrementSlot ( ) ; } else if ( detail instanceof ContractDetail ) { this . storageCheckSpaceLeft ( detail . storageLength ) ; this . applyStoragePosition ( detail ) ; this . storageIncrementOffset ( detail . storageLength ) ; this . storageCheckEndOfSlot ( ) ; } } public applyPositions ( detail ) { switch ( detail . variable . location ) { case ( VariableLocation . Stack ) : { detail . position = new BN ( 0 ) ; detail . offset = null ; break ; } case ( VariableLocation . Memory ) : { this . applyMemoryPositions ( detail ) ; break ; } case ( VariableLocation . CallData ) : { detail . offset = null ; break ; } case ( VariableLocation . Storage ) : { this . applyStoragePositions ( detail ) ; break ; } } } }