UNPKG

movement-sdk

Version:
371 lines (333 loc) 12.9 kB
import fs from "fs"; import {MvInfo, MvTable} from "./comm_interface"; import {logger} from "./logger"; interface Input { internalType: string; name: string; type: string; } interface Output { internalType: string; name: string; type: string; } interface FunctionDefinition { inputs: Input[]; name: string; outputs: Output[]; stateMutability: string; type: string; opcodes: string[]; } interface ContractDefinition { owner: string; name: string; functions: FunctionDefinition[]; } const EVM_TO_MOVE_TYPE = new Map() EVM_TO_MOVE_TYPE.set("uint64", 3) // u64 const EVM_TO_MOVE_VISIBILITY = new Map() EVM_TO_MOVE_VISIBILITY.set("view", 1) // public EVM_TO_MOVE_VISIBILITY.set("pure", 1)// public const SUPPORT_CODE = ["ADD", "RETURN", "SUB"] function getMoveSignature(evmType: string): string { const t = EVM_TO_MOVE_TYPE.get(evmType) if (!t) throw new Error(`parse signature error, now not support ${evmType}`) return t } function getFunVisibility(evmType: string): string { const t = EVM_TO_MOVE_VISIBILITY.get(evmType) if (!t) throw new Error(`parse visibility error, now not support ${evmType}`) return t } function normalizeAddress(address: string): string { let ret = address if (address.startsWith("0x")) ret = address.slice(2) return ret.toLowerCase(); } function numToHex(num: number): string { let str = num.toString(16) if (str.length % 2 !== 0) { str = `0${str}` } return str } function strToHex(str: string): string { let hexStr = Buffer.from(str, "utf8").toString("hex") if (hexStr.length % 2 !== 0) { hexStr = `0${hexStr}` } return hexStr } export class BuildCode { private schema: ContractDefinition; private info: MvInfo private code: string = "" constructor(schema: ContractDefinition) { this.schema = schema; this.info = {magicValue: "", selfModule: 0, tableCount: 0, tables: [], version: 0} } private static getBaseTable(): MvTable { return { kind: "00", offset: 0, size: 0, content: "", name: "", index: [], payload: [] } } build() { // We must maintain the following function call order this.header(); this.module_handles() this.function_handles() this.signature() this.identifiers() this.address_identifiers() this.metadata() this.function_definitions() this.parse_fun() this.inner_build(); } private inner_build() { logger.info("movement build bytecode processing") const buf = Buffer.alloc(4) buf.writeUint32LE(this.info.version, 0) this.code = this.info.magicValue.concat( buf.toString("hex"), numToHex(this.info.tableCount)) const base = this.info.tables[0] base.offset = 0 Object.values<number>(base.index).forEach((it) => { base.content += numToHex(it) base.size += 1 }) this.code += base.kind.concat(numToHex(base.offset), numToHex(base.size)) for (let i = 1; i < this.info.tables.length; i += 1) { const table = this.info.tables[i] const preTable = this.info.tables[i - 1] if (table.kind === "03") { table.payload.forEach((it: any) => { table.content += numToHex(it.module).concat( numToHex(it.name), numToHex(it.parameters), numToHex(it.return), numToHex(it.type_parameters.length), ) // TODO add type_parameters content }) } if (table.kind === "05") { table.payload.forEach((it: any) => { table.content += numToHex(it.length) it.forEach((el: any) => { table.content += numToHex(el) }) }) } if (table.kind === "07") { table.payload.forEach((it: any) => { const content = strToHex(it) table.content += numToHex(content.length / 2).concat(content) }) } if (table.kind === "08") { table.content = table.payload.join("") } if (table.kind === "10") { const key = strToHex(table.payload.key) const {value} = table.payload table.content += numToHex(key.length / 2).concat(key) table.content += numToHex(value.length / 2).concat(value) } if (table.kind === "0c") { const doubleBytes = ["0b"] table.payload.forEach((it: any) => { table.content += numToHex(it.function_handle).concat( numToHex(it.visibility), numToHex(it.is_entry), numToHex(it.acquires_global_resources.length), // TODO:add the array content numToHex(it.locals), numToHex(it.bytecode.filter((x: string) => !doubleBytes.includes(x)).length), it.bytecode.join("") ) }) } table.offset = preTable.offset + preTable.size table.size = table.content.length / 2; this.code += table.kind.concat(numToHex(table.offset), numToHex(table.size)) } for (let i = 0; i < this.info.tables.length; i += 1) { this.code += this.info.tables[i].content } // Todo why? const selfModuleIndex = "00" this.code = this.code.concat(selfModuleIndex) logger.success("movement build bytecode successfully") } save(p: string) { fs.writeFileSync(p, Buffer.from(this.code, "hex")); logger.success(`movement save bytecode to ${p} successfully`) } private header() { logger.info("movement start build bytecode header") // now this is fixed value this.info.magicValue = "a11ceb0b" this.info.version = 6 this.info.tableCount = 7 } private module_handles() { logger.info("movement start build bytecode module_handles") // now we think this table is fixed const table = BuildCode.getBaseTable(); table.kind = "01" table.name = "MODULE_HANDLES" table.offset = 0 table.index = {address: 0, name: 0} this.info.tables.push(table) } private function_handles() { logger.info("movement start build bytecode function_handles") const table = BuildCode.getBaseTable() table.kind = "03" table.name = "FUNCTION_HANDLES" table.offset = 0 this.info.tables.push(table) } private signature() { logger.info("movement start build bytecode signature") const table = BuildCode.getBaseTable() table.kind = "05" table.name = "SIGNATURES" // this always exist empty parameters or return values, so add to this table at the first table.payload.push([]) this.info.tables.push(table) } private identifiers() { logger.info("movement start build bytecode identifiers") const table = BuildCode.getBaseTable() table.kind = "07" table.name = "IDENTIFIERS" table.payload.push(this.schema.name) this.info.tables.push(table) } private address_identifiers() { logger.info("movement start build bytecode address_identifiers") // now we think this table is fixed const table = BuildCode.getBaseTable() table.kind = "08" table.name = "ADDRESS_IDENTIFIERS" table.payload.push(normalizeAddress(this.schema.owner)) this.info.tables.push(table) } private metadata() { logger.info("movement start build bytecode metadata") const table = BuildCode.getBaseTable() table.kind = "10" table.name = "METADATA" // now we think this table is fixed table.payload = { key: "aptos::metadata_v1", value: "" } this.info.tables.push(table) } private function_definitions() { logger.info("movement start build bytecode function_definitions") const table = BuildCode.getBaseTable() table.kind = "0c" table.name = "FUNCTION_DEFINITIONS" this.info.tables.push(table) } private parse_fun() { logger.info("movement start parse bytecode function") const tableIdentifiers = this.info.tables.find(it => it.kind === "07")! const tableMetadata = this.info.tables.find(it => it.kind === "10")! const tableSignatures = this.info.tables.find(it => it.kind === "05")! const tableFunHandler = this.info.tables.find(it => it.kind === "03")! const tableFunDefinitions = this.info.tables.find(it => it.kind === "0c")! function signatureIndex(t: string[]) { return tableSignatures.payload.findIndex((it: any) => JSON.stringify(it) === JSON.stringify(t)) } const functions = this.schema.functions.filter(it => it.type === "function") for (let i = 0; i < functions.length; i += 1) { const fun = functions[i] tableIdentifiers.payload.push(fun.name) const funHex = strToHex(fun.name) tableMetadata.payload.value += `000001${numToHex(funHex.length / 2)}${funHex}010100` let parametersIndex = 0; let returnIndex = 0; const funInputType = fun.inputs.map(it => getMoveSignature(it.type)) if (funInputType.length > 0) { const index = signatureIndex(funInputType) if (index > -1) { parametersIndex = index } else { parametersIndex = tableSignatures.payload.push(funInputType) - 1 } } const funOutputType = fun.outputs.map(it => getMoveSignature(it.type)) if (funOutputType.length > 0) { const index = signatureIndex(funOutputType) if (index > -1) { returnIndex = index } else { returnIndex = tableSignatures.payload.push(funOutputType) - 1 } } tableFunHandler.payload.push({ module: 0, // now we think it is fixed value, name: i + 1, // as the module name is first, so add 1, type_parameters: [], // TODO decode more type parameters, parameters: parametersIndex, return: returnIndex, }) const def: { function_handle: any, visibility: string, is_entry: number, acquires_global_resources: [], locals: number, bytecode: string[] } = { function_handle: tableFunHandler.payload.length - 1, visibility: getFunVisibility(fun.stateMutability), is_entry: 0,// TODO parse why ? acquires_global_resources: [], // TODO parse why ?, // ULEB128 index into the SIGNATURES table for the types of the locals of the function, // this should parse according to code ,but now we just set it as fixed value locals: 0, bytecode: [] } const codes = fun.opcodes; for (let i1 = 0; i1 < codes.length; i1 += 1) { const code = codes[i1]; if (!SUPPORT_CODE.includes(code)) { throw new Error(`current not support ${code}`) } if (code === "ADD") { for (let j = 0; j < funInputType.length; j += 1) { def.bytecode.push("0b") def.bytecode.push(`0${j}`) } def.bytecode.push("16") } if (code === "SUB") { for (let j = 0; j < funInputType.length; j += 1) { def.bytecode.push("0b") def.bytecode.push(`0${j}`) } def.bytecode.push("17") } if (code === "RETURN") { def.bytecode.push("02") } } tableFunDefinitions.payload.push(def) logger.success("movement start parse bytecode function successfully") } } }