import path from 'path' ; import chalk from 'chalk' ; import { promises as fs } from 'fs' ; import { compare as compareVersions } from 'compare-versions' ; import { ManifestData , ImplDeployment , ProxyDeployment } from '../manifest' ; import type { StorageItem , StorageLayout , TypeItem , TypeItemMembers , StructMember } from '../storage/layout' ; const OPEN_ZEPPELIN_FOLDER = '.openzeppelin' ; const EXPORT_FILE = 'openzeppelin-cli-export.json' ; const PROJECT_FILE = path . join ( OPEN_ZEPPELIN_FOLDER , 'project.json' ) ; const SUCCESS_CHECK = chalk . green ( '✔' ) + ' ' ; export async function migrateLegacyProject ( ) { const manifestFiles = await getManifestFiles ( ) ; const networksExportData = await migrateManifestFiles ( manifestFiles ) ; const { compiler } = await getProjectFile ( ) ; const exportData = { networks : networksExportData , compiler , } ; await writeJSONFile ( EXPORT_FILE , exportData ) ; console . log ( SUCCESS_CHECK + `Migration data exported to ${ EXPORT_FILE }` ) ; await deleteLegacyFiles ( manifestFiles ) ; console . log ( "\nThese were your project's compiler options:" ) ; console . log ( JSON . stringify ( compiler , null , 2 ) ) ; } export async function migrateManifestFiles ( manifestFiles ) { const migratableManifestFiles = manifestFiles . filter ( manifest => ! isDevelopmentNetwork ( getNetworkName ( manifest ) ) ) ; const migratableManifestsData = { } ; for ( const migratableFile of migratableManifestFiles ) { const network = getNetworkName ( migratableFile ) ; migratableManifestsData [ network ] = JSON . parse ( await fs . readFile ( migratableFile , 'utf8' ) ) ; } const { newManifestsData , networksExportData } = migrateManifestsData ( migratableManifestsData ) ; for ( const network in newManifestsData ) { const newManifestData = newManifestsData [ network ] ; const newFilename = getNewManifestLocation ( network ) ; await writeJSONFile ( newFilename , newManifestData ) ; console . log ( SUCCESS_CHECK + `Successfully migrated ${ newFilename }` ) ; } return networksExportData ; } export async function deleteLegacyFiles ( manifestFiles ) { const developmentManifests = manifestFiles . filter ( manifestFile => isDevelopmentNetwork ( getNetworkName ( manifestFile ) ) ) ; for ( const manifestFile of developmentManifests ) { console . log ( SUCCESS_CHECK + `Deleting unused development manifest ${ manifestFile }` ) ; await fs . unlink ( manifestFile ) ; } console . log ( SUCCESS_CHECK + `Deleting ${ PROJECT_FILE }` ) ; await fs . unlink ( PROJECT_FILE ) ; } export function migrateManifestsData ( manifestsData ) { const networksExportData = { } ; const newManifestsData = { } ; for ( const network of Object . keys ( manifestsData ) ) { const oldManifestData = manifestsData [ network ] ; const { manifestVersion , zosversion } = oldManifestData ; const currentVersion = manifestVersion ?? zosversion ; if ( currentVersion === undefined ) { throw new Error ( 'Migration failed: manifest version too old. Update your OpenZeppelin CLI version.' ) ; } if ( compareVersions ( currentVersion , '3.0' , '>=' ) ) { continue ; } if ( currentVersion !== '2.2' ) { throw new Error ( `Migration failed: expected manifest version 2.2, got ${ currentVersion } instead. Update your OpenZeppelin CLI version.` ) ; } newManifestsData [ network ] = updateManifestData ( oldManifestData ) ; networksExportData [ network ] = getExportData ( oldManifestData ) ; } return { newManifestsData , networksExportData , } ; } async function getManifestFiles ( ) { const files = await fs . readdir ( OPEN_ZEPPELIN_FOLDER ) ; return files . filter ( isManifestFile ) . map ( location => path . join ( OPEN_ZEPPELIN_FOLDER , location ) ) ; } async function getProjectFile ( ) { return JSON . parse ( await fs . readFile ( PROJECT_FILE , 'utf8' ) ) ; } function isManifestFile ( filename ) { const network = getNetworkName ( filename ) ; return isPublicNetwork ( network ) || isDevelopmentNetwork ( network ) || isUnknownNetwork ( network ) ; } function getNetworkName ( filename ) { return path . basename ( filename , '.json' ) ; } function isDevelopmentNetwork ( network ) { return /^dev-(31337|\d{13,})$/ . test ( network ) ; } function isUnknownNetwork ( network ) { return ! isDevelopmentNetwork ( network ) && /^dev-\d+$/ . test ( network ) ; } function isPublicNetwork ( network ) { return [ 'mainnet' , 'rinkeby' , 'ropsten' , 'kovan' , 'goerli' ] . includes ( network ) ; } function getNewManifestLocation ( oldName ) { const filename = isUnknownNetwork ( oldName ) ? oldName . replace ( 'dev' , 'unknown' ) : oldName ; return path . join ( OPEN_ZEPPELIN_FOLDER , `${ filename }.json` ) ; } async function writeJSONFile ( location , data ) { await fs . writeFile ( location , JSON . stringify ( data , null , 2 ) ) ; } function getExportData ( oldManifestData ) { const networksExportData = Object . assign ( { } , oldManifestData ) ; delete networksExportData . proxyAdmin ; delete networksExportData . contracts ; delete networksExportData . solidityLibs ; delete networksExportData . manifestVersion ; delete networksExportData . zosversion ; delete networksExportData . version ; delete networksExportData . frozen ; return networksExportData ; } function updateManifestData ( oldManifestData ) { const proxyAdmin = oldManifestData . proxyAdmin . address ; if ( proxyAdmin === undefined ) { throw new Error ( 'Legacy manifest does not have admin address' ) ; } if ( Object . keys ( oldManifestData . solidityLibs ) . length > 0 ) { throw new Error ( 'Legacy manifest links to external libraries which are not yet supported' ) ; } return { manifestVersion : '3.2' , impls : transformImplementations ( oldManifestData . contracts ) , proxies : [ ... transformProxies ( oldManifestData . proxies ) ] , admin : { address : proxyAdmin , } , } ; } function * transformProxies ( proxies ) { for ( const contractName in proxies ) { for ( const proxy of proxies [ contractName ] ) { switch ( proxy . kind ) { case 'Upgradeable' : if ( proxy . address ) { yield { address : proxy . address , kind : 'transparent' , } ; } break ; case 'Minimal' : case 'NonProxy' : break ; } } } } function transformImplementations ( contracts ) { const impls = { } ; for ( const contractName in contracts ) { const contract = contracts [ contractName ] ; if ( contract . deployedBytecodeHash === undefined ) { continue ; } else { impls [ contract . deployedBytecodeHash ] = transformImplementationItem ( contract ) ; } } return impls ; } function transformImplementationItem ( contract ) { if ( contract . address === undefined ) { throw new Error ( 'Could not find implementation address' ) ; } return { address : contract . address , layout : transformLayout ( contract ) , } ; } function transformLayout ( contract ) { const { types , storage } = contract ; if ( types === undefined || storage === undefined ) { throw new Error ( "Storage layout can't be undefined" ) ; } const astIds = Object . keys ( types ) ; const getAstId = ( typeName ) : number => { const astId = astIds . indexOf ( typeName ) ; if ( astId === - 1 ) { throw new Error ( `Could not find type ${ typeName }` ) ; } return astId ; } ; return { storage : storage . map ( ( storageItem ) => transformStorageItem ( storageItem , getAstId ) ) , types : transformTypes ( types , getAstId ) , } ; } function transformStorageItem ( storageItem , getAstId ) { return { contract : storageItem . contract , label : storageItem . label , type : transformTypeName ( storageItem . type , getAstId ) , src : storageItem . path , } ; } function transformTypes ( oldTypes , getAstId ) { const newTypes = { } ; for ( const typeName in oldTypes ) { newTypes [ transformTypeName ( typeName , getAstId ) ] = transformType ( getTypeKind ( typeName ) , oldTypes [ typeName ] , getAstId ) ; } return newTypes ; } function transformType ( typeKind , oldType , getAstId ) { switch ( typeKind ) { case 'Struct' : return { label : stripContractName ( oldType . label ) , members : ( oldType . members as StructMember [ ] ) . map ( member => ( { label : stripContractName ( member . label ) , type : transformTypeName ( member . type , getAstId ) , } ) ) , } ; case 'Enum' : return { label : stripContractName ( oldType . label ) , members : oldType . members , } ; default : return { label : stripContractName ( oldType . label ) , } ; } } function transformTypeName ( typeName , getAstId ) { switch ( getTypeKind ( typeName ) ) { case 'Struct' : return transformStructTypeName ( typeName , getAstId ) ; case 'Enum' : return transformEnumTypeName ( typeName , getAstId ) ; case 'Mapping' : return transformMappingTypeName ( typeName , getAstId ) ; case 'DynArray' : return transformDynArrayTypeName ( typeName , getAstId ) ; case 'StaticArray' : return transformStaticArrayTypeName ( typeName , getAstId ) ; case 'Elementary' : return typeName ; default : throw new Error ( `Unknown type: ${ typeName }` ) ; } } function transformStructTypeName ( typeName , getAstId ) { const name = stripContractName ( getArgument ( typeName ) ) ; const astId = getAstId ( typeName ) ; return `t_struct(${ name })${ astId }_storage` ; } function transformEnumTypeName ( typeName , getAstId ) { const name = stripContractName ( getArgument ( typeName ) ) ; const astId = getAstId ( typeName ) ; return `t_enum(${ name })${ astId }` ; } function transformMappingTypeName ( typeName , getAstId ) { const valueType = transformTypeName ( getArgument ( typeName ) , getAstId ) ; return `t_mapping(unknown,${ valueType })` ; } function transformDynArrayTypeName ( typeName , getAstId ) { const valueType = transformTypeName ( getArgument ( typeName ) , getAstId ) ; return `t_array(${ valueType })dyn_storage` ; } function transformStaticArrayTypeName ( typeName , getAstId ) { const size = typeName . match ( /:(\d+)/ ) ! [ 1 ] ; const valueType = transformTypeName ( getArgument ( typeName ) , getAstId ) ; return `t_array(${ valueType })${ size }_storage` ; } function getArgument ( typeName ) { return typeName . match ( /<(.+)>/ ) ! [ 1 ] ; } function stripContractName ( s ) { return s . match ( /(.+\.)?(.+)/ ) ! [ 2 ] ; } function getTypeKind ( typeName ) { if ( /^t_struct<.+>/ . test ( typeName ) ) { return 'Struct' ; } else if ( /^t_enum<.+>/ . test ( typeName ) ) { return 'Enum' ; } else if ( /^t_mapping<.+>/ . test ( typeName ) ) { return 'Mapping' ; } else if ( /^t_array:dyn<.+>/ . test ( typeName ) ) { return 'DynArray' ; } else if ( /^t_array:\d+<.+>/ . test ( typeName ) ) { return 'StaticArray' ; } else { return 'Elementary' ; } } type TypeKind = 'Elementary' | 'Mapping' | 'Struct' | 'Enum' | 'DynArray' | 'StaticArray' ; type LegacyContracts = Record < string , ContractInterface > ; type LegacyProxies = Record < string , ProxyInterface [ ] > ; type AstIdGetter = ( typeName : string ) => number ; type LegacyTypes = Record < string , LegacyType > ; type NetworkExportData = Pick < NetworkFileData , 'proxies' | 'proxyFactory' | 'app' | 'package' | 'provider' > ; export interface MigrationOutput { newManifestsData : Record < string , ManifestData > ; networksExportData : Record < string , NetworkExportData > ; } interface LegacyType { id : string ; kind : string ; label : string ; members ? : TypeItemMembers ; } interface LegacyStorageItem extends StorageItem { path : string ; astId : number ; } interface ContractInterface { address ? : string ; constructorCode ? : string ; localBytecodeHash ? : string ; deployedBytecodeHash ? : string ; bodyBytecodeHash ? : string ; types ? : any ; storage ? : any ; warnings ? : any ; [ id ] : any ; } interface SolidityLibInterface { address : string ; constructorCode : string ; bodyBytecodeHash : string ; localBytecodeHash : string ; deployedBytecodeHash : string ; } enum ProxyType { Upgradeable = 'Upgradeable' , Minimal = 'Minimal' , NonProxy = 'NonProxy' } interface ProxyInterface { contractName ? : string ; package ? : string ; address ? : string ; version ? : string ; implementation ? : string ; admin ? : string ; kind ? : ProxyType ; bytecodeHash ? : string ; } interface DependencyInterface { name ? : string ; package ? : string ; version ? : string ; customDeploy ? : boolean ; } interface AddressWrapper { address ? : string ; } export interface NetworkFileData { contracts : { [ name ] : ContractInterface ; } ; solidityLibs : { [ name ] : SolidityLibInterface ; } ; proxies : { [ contractName ] : ProxyInterface [ ] ; } ; manifestVersion ? : string ; zosversion ? : string ; proxyAdmin : AddressWrapper ; proxyFactory : AddressWrapper ; app : AddressWrapper ; package : AddressWrapper ; provider : AddressWrapper ; version : string ; frozen : boolean ; dependencies : { [ dependencyName ] : DependencyInterface ; } ; } interface ConfigFileCompilerOptions { manager : string ; solcVersion : string ; contractsDir : string ; artifactsDir : string ; compilerSettings : { evmVersion : string ; optimizer : { enabled : boolean ; runs ? : string ; } ; } ; typechain : { enabled : boolean ; outDir ? : string ; target ? : string ; } ; } export interface ProjectFileData { name : string ; version : string ; manifestVersion ? : string ; zosversion ? : string ; dependencies : { [ name ] : string ; } ; contracts : string [ ] ; publish : boolean ; compiler : Partial < ConfigFileCompilerOptions > ; telemetryOptIn ? : boolean ; }