UNPKG

@ark-us/evm2wasm

Version:

This is a JS protope of a EVM to eWASM transcompiler

798 lines (715 loc) 28.2 kB
const BN = require('bn.js') const ethUtil = require('ethereumjs-util') const opcodes = require('./opcodes.js') const wastSyncInterface = require('./wasm/wast.json') const wastAsyncInterface = require('./wasm/wast-async.json') // const wabt = require('wabt') // map to track dependent WASM functions const depMap = new Map([ ['callback_256', ['bswap_m256']], ['callback_160', ['bswap_m160']], ['callback_128', ['bswap_m128']], ['bswap_m256', ['bswap_i64']], ['bswap_m128', ['bswap_i64']], ['bswap_m160', ['bswap_i64', 'bswap_i32']], ['keccak', ['memcpy', 'memset']], ['mod_320', ['iszero_320', 'gte_320']], ['mod_512', ['iszero_512', 'gte_512']], ['MOD', ['iszero_256', 'gte_256']], ['ADDMOD', ['mod_320']], ['MULMOD', ['mod_512']], ['SDIV', ['iszero_256', 'gte_256']], ['SMOD', ['iszero_256', 'gte_256']], ['DIV', ['iszero_256', 'gte_256']], ['EXP', ['iszero_256', 'mul_256']], ['MUL', ['mul_256']], ['ISZERO', ['iszero_256']], ['MSTORE', ['memusegas', 'bswap_m256', 'check_overflow']], ['MLOAD', ['memusegas', 'bswap_m256', 'check_overflow']], ['MSTORE8', ['memusegas', 'check_overflow']], ['CODECOPY', ['callback', 'memusegas', 'check_overflow', 'memset']], ['CALLDATALOAD', ['bswap_m256', 'bswap_i64', 'check_overflow']], ['CALLDATACOPY', ['memusegas', 'check_overflow', 'memset']], ['CALLVALUE', ['bswap_m256']], ['EXTCODECOPY', ['bswap_m256', 'callback', 'memusegas', 'check_overflow', 'memset']], ['EXTCODESIZE', ['bswap_m256', 'callback_32']], ['EXTCODEHASH', ['bswap_m256', 'callback_256']], ['RETURNDATACOPY', ['memusegas', 'check_overflow', 'memset']], ['LOG', ['memusegas', 'bswap_m256', 'check_overflow']], ['BLOCKHASH', ['check_overflow_i64', 'callback_256']], ['SHA3', ['memusegas', 'bswap_m256', 'check_overflow', 'keccak']], ['CALL', ['bswap_m256', 'bswap_m256', 'callback', 'memusegas', 'check_overflow_i64', 'check_overflow', 'check_overflow_i64', 'memset', 'callback_32']], ['DELEGATECALL', ['bswap_m256', 'callback', 'memusegas', 'check_overflow_i64', 'check_overflow', 'memset', 'callback_32']], ['STATICCALL', ['bswap_m256', 'callback', 'memusegas', 'check_overflow_i64', 'check_overflow', 'memset', 'callback_32']], ['CALLCODE', ['bswap_m256', 'bswap_m256', 'callback', 'memusegas', 'check_overflow_i64', 'check_overflow', 'check_overflow_i64', 'memset', 'callback_32']], ['CREATE', ['bswap_m256', 'bswap_m160', 'callback_256', 'memusegas', 'check_overflow']], ['CREATE2', ['bswap_m256', 'bswap_m160', 'callback_256', 'memusegas', 'check_overflow']], ['RETURN', ['memusegas', 'check_overflow']], ['REVERT', ['memusegas', 'check_overflow']], ['BALANCE', ['bswap_m256', 'callback_256']], ['SELFBALANCE', ['callback_256']], ['SELFDESTRUCT', ['bswap_m256']], ['SSTORE', ['bswap_m256', 'callback']], ['SLOAD', ['bswap_m256', 'callback_256']], ['CODESIZE', ['callback_32']], ['DIFFICULTY', ['bswap_m256']], ['CHAINID', ['bswap_m256']], ['BASEFEE', ['bswap_m160']], ['COINBASE', ['bswap_m256']], ['ORIGIN', ['bswap_m256']], ['ADDRESS', ['bswap_m256']], ['CALLER', ['bswap_m256']] ]) // maps the async ops to their call back function const callbackFuncs = new Map([ ['SSTORE', '$callback'], ['SLOAD', '$callback_256'], ['CREATE', '$callback_256'], ['CREATE2', '$callback_256'], ['CALL', '$callback_32'], ['DELEGATECALL', '$callback'], ['CALLCODE', '$callback_32'], ['EXTCODECOPY', '$callback'], ['EXTCODESIZE', '$callback_32'], ['EXTCODEHASH', '$callback_256'], ['CODECOPY', '$callback'], ['CODESIZE', '$callback_32'], ['BALANCE', '$callback_256'], ['SELFBALANCE', '$callback_256'], ['CHAINID', '$callback_256'], ['BLOCKHASH', '$callback_256'] ]) const INTERFACE_CLASSIC = "ewasm_env_1"; const INTERFACE_INTERPRETER = "ewasm_ewasm_1"; // /** // * compiles evmCode to wasm in the binary format // * @param {Array} evmCode // * @param {Object} opts // * @param {boolean} opts.stackTrace if `true` generates an runtime EVM stack trace (default: false) // * @param {boolean} opts.inlineOps if `true` inlines the EVM1 operations (default: true) // * @param {String} opts.testName is the name used for the wast file (default: 'temp') // * @param {boolean} opts.chargePerOp if `true` adds metering statements for the wasm code section corresponding to each EVM opcode as opposed to metering once per branch segment (default: false). // * @return {string} // */ // exports.evm2wasm = function (evmCode, opts = { // 'stackTrace': false, // 'useAsyncAPI': false, // 'inlineOps': true, // 'testName': 'temp', // 'chargePerOp': false, // 'isConstructor':false, // }) { // const wast = exports.evm2wast(evmCode, opts) // const mod = wabt.parseWat('arbitraryModuleName', wast) // mod.resolveNames() // mod.validate() // const bin = mod.toBinary({log: false, write_debug_names: false}).buffer // mod.destroy() // return Promise.resolve(bin) // } /** * Transcompiles EVM code to ewasm in the sexpression text format. The EVM code * is broken into segments and each instruction in those segments is replaced * with a `call` to wasm function that does the equivalent operation. Each * opcode function takes in and returns the stack pointer. * * Segments are sections of EVM code in between flow control * opcodes (JUMPI. JUMP). * All segments start at * * the beginning for EVM code * * a GAS opcode * * a JUMPDEST opcode * * After a JUMPI opcode * @param {Integer} evmCode the evm byte code * @param {Object} opts * @param {boolean} opts.stackTrace if `true` generates a stack trace (default: false) * @param {boolean} opts.inlineOps if `true` inlines the EVM1 operations (default: true) * @param {boolean} opts.chargePerOp if `true` adds metering statements for the wasm code section corresponding to each EVM opcode as opposed to metering once per branch segment (default: false). * @return {string} */ exports.evm2wast = function (deploymentEvmCode, opts = { 'stackTrace': false, 'useAsyncAPI': false, 'inlineOps': true, 'chargePerOp': false, 'isConstructor':false, 'nogas': true, }) { // sol: 39 60 00 f3 fe // yul: 39 610196 60 00 f3 fe // codecopy + (push2?) + push1 0 + return + invalid function splitConstructor(code) { if (code.length < 5) return [new Buffer(0), code]; for (let i = 3 ; i < code.length; i++) { // "6000f3fe" "6000f300" if (code[i-3] == 0x60 && code[i-2] == 0x00 && code[i-1] == 0xf3 && (code[i] == 0xfe || code[i] == 0x00)) { return [ new Buffer.from(Uint8Array.prototype.slice.call(code).slice(0,i+1)), new Buffer.from(Uint8Array.prototype.slice.call(code).slice(i+1)), ]; } } return [new Buffer(0), code]; } // adds stack height checks to the beginning of a segment function addStackCheck () { let check = '' if (segmentStackHigh !== 0) { check = `(if (i32.gt_s (global.get $sp) (i32.const ${(1023 - segmentStackHigh) * 32})) (then (unreachable)))` } if (segmentStackLow !== 0) { check += `(if (i32.lt_s (global.get $sp) (i32.const ${-segmentStackLow * 32 - 32})) (then (unreachable)))` } segment = check + segment segmentStackHigh = 0 segmentStackLow = 0 segmentStackDelta = 0 } // add a metering statment at the beginning of a segment // TODO fixme - this needs to be integrated in the interpreter opcodes function addMetering () { if (!opts.chargePerOp && !opts.nogas) { if (gasCount !== 0) { wast += `(call $useGas (i64.const ${gasCount})) ` } } wast += segment segment = '' gasCount = 0 } // finishes off a segment function endSegment () { segment += ')' addStackCheck() addMetering() } let interfaceVersion = INTERFACE_CLASSIC; if (!opts.inlineOps) { interfaceVersion = INTERFACE_INTERPRETER; } // this keep track of the opcode we have found so far. This will be used to // to figure out what .wast files to include const opcodesUsed = new Set() const ignoredOps = new Set(['JUMP', 'JUMPI', 'JUMPDEST', 'POP', 'STOP', 'INVALID']) let callbackTable = [] // an array of found segments const jumpSegments = [] // the transcompiled EVM code let wast = '' let segment = '' // keeps track of the gas that each section uses let gasCount = 0 // used for pruning dead code let jumpFound = false // the accumlitive stack difference for the current segmnet let segmentStackDelta = 0 let segmentStackHigh = 0 let segmentStackLow = 0 let evmCode = deploymentEvmCode; let evmConstructorCode = new Buffer(0) let addFuncs = []; if (!opts.isConstructor) { const [__evmConstructor, __evmCode] = splitConstructor(deploymentEvmCode); console.log('constructor', ethUtil.bufferToHex(__evmConstructor)); console.log('runtime', ethUtil.bufferToHex(__evmCode)); if (__evmConstructor.length > 5) { evmConstructorCode = __evmConstructor; const [constructorCode, constructorOpcodes] = exports.evm2wast(__evmConstructor, {...opts, isConstructor: true}); addFuncs = [constructorCode]; // add the sources for the constructor opcodes for (const value of constructorOpcodes.values()) { opcodesUsed.add(value); } } evmCode = __evmCode; } // pre-analysis for custom messages let cosmosImports = new Set(); const COSMOS_MSG_MARKER = "0x000000000000000000000000000000000000e2b891e911223344556677889900"; const COSMOS_QUERY_MARKER = "0x000000000000000000000000000000000000fe143b3911223344556677889900"; function replaceAllOccurrences(mainArray, subArray, replacementArray) { const sublen = subArray.length; let copyMainArray = new Buffer.from([...mainArray]); const indexes = [...mainArray].map((_, i) => i); const filteredIndexes = indexes.filter(i => mainArray.slice(i, i + sublen).every((elem, j) => elem === subArray[j])); filteredIndexes.reverse().map(i => { copyMainArray = new Buffer.from([ ...Uint8Array.prototype.slice.call(copyMainArray).slice(0, i), ...replacementArray, ...Uint8Array.prototype.slice.call(copyMainArray).slice(i + sublen), ]); }); return copyMainArray; } // for cosmos msg 0xe2b891e9, replace jumpdest -> jump that contains PUSH14 0xe2b891e911223344556677889900 // with // JUMPDEST PUSHX 0xe2b891e911223344556677889900000.. 0x40 mload returndatasize dup1 dup3 add 0x40 mstore 0x00 dup3 returndatacopy swap1 swap2 JUMP evmCode = replaceAllOccurrences(evmCode, ethUtil.toBuffer("0x6000606060006de2b891e91122334455667788990090506001925050915091"), ethUtil.toBuffer("0x6de2b891e9112233445566778899006040513d8082016040526000823e9091")) // replace for cosmos queries 0xfe143b39 evmCode = replaceAllOccurrences(evmCode, ethUtil.toBuffer("0x6000606060006dfe143b391122334455667788990090506001925050915091"), ethUtil.toBuffer("0x6dfe143b39112233445566778899006040513d8082016040526000823e9091")) // https://docs.soliditylang.org/en/latest/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode // TODO find metadata hash & keep it in the wasmcode for (let pc = 0; pc < evmCode.length; pc++) { const opint = evmCode[pc] const op = opcodes(opint) // creates a stack trace if (opts.stackTrace) { segment += `(call $stackTrace (i32.const ${pc}) (i32.const ${opint}) (i32.const ${op.fee}) (global.get $sp) (i32.const ${op.off}) (i32.const ${op.on}))\n` } let bytes if (opts.chargePerOp && !opts.nogas) { if (op.fee !== 0) { segment += `(call $useGas (i64.const ${op.fee})) ` } } // do not charge gas for interface methods // TODO: implement proper gas charging and enable this here if (opint < 0x30 || (opint > 0x45 && opint < 0xa0)) { gasCount += op.fee } segmentStackDelta += op.on if (segmentStackDelta > segmentStackHigh) { segmentStackHigh = segmentStackDelta } segmentStackDelta -= op.off if (segmentStackDelta < segmentStackLow) { segmentStackLow = segmentStackDelta } let canIncreaseSp = true; switch (op.name) { case 'JUMP': jumpFound = true segment += `;; jump (local.set $jump_dest (call $check_overflow (i64.load (global.get $sp)) (i64.load (i32.add (global.get $sp) (i32.const 8))) (i64.load (i32.add (global.get $sp) (i32.const 16))) (i64.load (i32.add (global.get $sp) (i32.const 24))))) (global.set $sp (i32.sub (global.get $sp) (i32.const 32))) (br $loop)` opcodesUsed.add('check_overflow') pc = findNextJumpDest(evmCode, pc) break case 'JUMPI': jumpFound = true segment += `(local.set $jump_dest (call $check_overflow (i64.load (global.get $sp)) (i64.load (i32.add (global.get $sp) (i32.const 8))) (i64.load (i32.add (global.get $sp) (i32.const 16))) (i64.load (i32.add (global.get $sp) (i32.const 24))))) (global.set $sp (i32.sub (global.get $sp) (i32.const 64))) (br_if $loop (i32.eqz (i64.eqz (i64.or (i64.load (i32.add (global.get $sp) (i32.const 32))) (i64.or (i64.load (i32.add (global.get $sp) (i32.const 40))) (i64.or (i64.load (i32.add (global.get $sp) (i32.const 48))) (i64.load (i32.add (global.get $sp) (i32.const 56))) ) ) ))))\n` opcodesUsed.add('check_overflow') addStackCheck() addMetering() break case 'JUMPDEST': endSegment() jumpSegments.push({ number: pc, type: 'jump_dest' }) gasCount = 1 break case 'GAS': segment += `(call $GAS)\n` // addMetering() // this causes an unreachable error in stackOverflowM1 -d 14 break case 'LOG': segment += `(call $LOG (i32.const ${op.number}))\n` break case 'DUP': case 'SWAP': // adds the number on the stack to SWAP segment += `(call $${op.name} (i32.const ${op.number - 1}))\n` break case 'PC': segment += `(call $PC (i32.const ${pc}))\n` break case 'PUSH': pc++ bytes = ethUtil.setLength(evmCode.slice(pc, pc += op.number), 32) const bytesRounded = Math.ceil(op.number / 8) let push = '' let q = 0 // pad the remaining of the word with 0 for (; q < 4 - bytesRounded; q++) { push = '(i64.const 0)' + push } for (; q < 4; q++) { const int64 = bytes2int64(bytes.slice(q * 8, q * 8 + 8)) push = push + `(i64.const ${int64})` } if (ethUtil.bufferToHex(bytes) === COSMOS_MSG_MARKER) { canIncreaseSp = false; segment += `(call $SENDCOSMOSMSG)` cosmosImports.add("SENDCOSMOSMSG"); } else if (ethUtil.bufferToHex(bytes) === COSMOS_QUERY_MARKER) { canIncreaseSp = false; segment += `(call $SENDCOSMOSQUERY)` cosmosImports.add("SENDCOSMOSQUERY"); } else { segment += `(call $PUSH ${push})` } pc-- break case 'POP': // do nothing break case 'STOP': segment += '(br $done)' if (jumpFound) { pc = findNextJumpDest(evmCode, pc) } else { // the rest is dead code pc = evmCode.length } break case 'SELFDESTRUCT': case 'RETURN': case 'REVERT': segment += `(call $${op.name}) (br $done)\n` if (jumpFound) { pc = findNextJumpDest(evmCode, pc) } else { // the rest is dead code pc = evmCode.length } break case 'INVALID': segment = '(unreachable)' pc = findNextJumpDest(evmCode, pc) break default: if (opts.useAsyncAPI && callbackFuncs.has(op.name)) { const cbFunc = callbackFuncs.get(op.name) let index = callbackTable.indexOf(cbFunc) if (index === -1) { index = callbackTable.push(cbFunc) - 1 } segment += `(call $${op.name} (i32.const ${index}))\n` } else { // use synchronous API segment += `(call $${op.name})\n` } } if (!ignoredOps.has(op.name)) { opcodesUsed.add(op.name) } const stackDelta = op.on - op.off // update the stack pointer if (stackDelta !== 0 && canIncreaseSp) { segment += `(global.set $sp (i32.add (global.get $sp) (i32.const ${stackDelta * 32})))\n` } // adds the logic to save the stack pointer before exiting to wiat to for a callback // note, this must be done before the sp is updated above^ if (opts.useAsyncAPI && callbackFuncs.has(op.name)) { segment += `(global.set $cb_dest (i32.const ${jumpSegments.length + 1})) (br $done))` jumpSegments.push({ type: 'cb_dest' }) } } endSegment() if (opts.isConstructor) { wast = assembleInstantiate(jumpSegments) + wast + '))' return [wast, opcodesUsed]; } else { wast = assembleSegments(jumpSegments, evmConstructorCode.length > 0) + wast + '))' } let wastFiles = wastSyncInterface // default to synchronous interface if (opts.useAsyncAPI) { wastFiles = wastAsyncInterface } let imports = [] let funcs = [] // inline EVM opcode implemention if (opts.inlineOps) { [funcs, imports] = exports.resolveFunctions(opcodesUsed, wastFiles) } else { [,, inline_imports] = exports.resolveFunctions(opcodesUsed, wastFiles) imports = inline_imports; imports.push('(import "ewasm" "GLOBAL_GET_SP" (func $GLOBAL_GET_SP (result i32)))') imports.push('(import "ewasm" "GLOBAL_SET_SP" (func $GLOBAL_SET_SP (param i32)))') imports.push('(import "ewasm" "ethereum_useGas" (func $useGas (param i64)))') } for (let cosmosImport of cosmosImports.values()) { imports.push(cosmosFunctions[cosmosImport].import); funcs.push(cosmosFunctions[cosmosImport].def); } funcs = addFuncs.concat(funcs); // import stack trace function if (opts.stackTrace) { imports.push('(import "env" "ethereum_debugPrintMemHex" (func $printMem (param i32 i32)))') imports.push('(import "env" "ethereum_debugPrinti32" (func $printI32 (param i32)))') imports.push('(import "env" "ethereum_debugPrinti64" (func $printI64 (param i64)))') // pc, opcode, fee, sp, input_count, output_count imports.push('(import "env" "ethereum_debug_evmTrace" (func $stackTrace (param i32 i32 i32 i32 i32 i32)))') } if (opts.inlineOps) { imports.push('(import "env" "ethereum_useGas" (func $useGas (param i64)))') } funcs.push(wast) wast = exports.buildModule(funcs, imports, callbackTable, evmConstructorCode, evmCode, opts.isConstructor, interfaceVersion) if (interfaceVersion === INTERFACE_INTERPRETER) { wast = wast.replace(/\(global\.get \$sp\)/g, "(call $GLOBAL_GET_SP)") wast = wast.replace(/\(global\.set \$sp /g, "(call $GLOBAL_SET_SP ") } return wast } // given an array for segments builds a wasm module from those segments // @param {Array} segments // @return {String} function assembleSegments (segments, hasConstructor) { let wasm = buildJumpMap(segments) segments.forEach((seg, index) => { wasm = `(block $${index + 1} ${wasm}` }) return ` ${hasConstructor ? "" : `(func $instantiate (export "instantiate"))`} (func $main (export "main") (local $jump_dest i32) (local $jump_map_switch i32) (local.set $jump_dest (i32.const -1)) (block $done (loop $loop ${wasm}` } function assembleInstantiate (segments) { let wasm = buildJumpMap(segments) segments.forEach((seg, index) => { wasm = `(block $${index + 1} ${wasm}` }) return ` (func $instantiate (export "instantiate") (local $jump_dest i32) (local $jump_map_switch i32) (local.set $jump_dest (i32.const -1)) (block $done (loop $loop ${wasm}` } // Builds the Jump map, which maps EVM jump location to a block label // @param {Array} segments // @return {String} function buildJumpMap (segments) { let wasm = '(unreachable)' let brTable = '' segments.forEach((seg, index) => { brTable += ' $' + (index + 1) if (seg.type === 'jump_dest') { wasm = `(if (i32.eq (local.get $jump_dest) (i32.const ${seg.number})) (then (br $${index + 1})) (else ${wasm}))` } }) wasm = ` (block $0 (if (i32.eqz (global.get $init)) (then (global.set $init (i32.const 1)) (br $0)) (else ;; the callback dest can never be in the first block (if (i32.eq (global.get $cb_dest) (i32.const 0)) (then ${wasm} ) (else ;; return callback destination and zero out $cb_dest (local.set $jump_map_switch (global.get $cb_dest)) (global.set $cb_dest (i32.const 0)) (br_table $0 ${brTable} (local.get $jump_map_switch)) )))))` return wasm } // returns the index of the next jump destination opcode in given EVM code in an // array and a starting index // @param {Array} evmCode // @param {Integer} index // @return {Integer} function findNextJumpDest (evmCode, i) { for (; i < evmCode.length; i++) { const opint = evmCode[i] const op = opcodes(opint) switch (op.name) { case 'PUSH': // skip add how many bytes where pushed i += op.number break case 'JUMPDEST': return --i } } return --i } // converts 8 bytes into a int 64 // @param {Integer} // @return {String} function bytes2int64 (bytes) { return new BN(bytes).fromTwos(64).toString() } // Ensure that dependencies are only imported once (use the Set) // @param {Set} funcSet a set of wasm function that need to be linked to their dependencies // @return {Set} function resolveFunctionDeps (funcSet, depArray) { for (let funcName of depArray) { funcSet.add(funcName); const deps = depMap.get(funcName) if (deps) { funcSet = resolveFunctionDeps(funcSet, deps); } } return funcSet } /** * given a Set of wasm function this return an array for wasm equivalents * @param {Set} funcSet * @return {Array} */ exports.resolveFunctions = function (funcSet, wastFiles) { let funcs = [] let imports = [] let inline_imports = [] let deps = resolveFunctionDeps(funcSet, [...funcSet.values()]) for (let func of deps) { funcs.push(wastFiles[func].wast) if (func !== 'CALLDATALOAD' || !funcSet.has("CALLDATACOPY")) { imports.push(wastFiles[func].imports) inline_imports.push(wastFiles[func].inline_imports) } } return [funcs, imports, inline_imports] } /** * builds a wasm module * @param {Array} funcs the function to include in the module * @param {Array} imports the imports for the module's import table * @return {string} */ exports.buildModule = function (funcs, imports = [], callbacks = [], evmConstructorCode, evmCode, isConstructor, interfaceVersion) { let funcStr = '' for (let func of funcs) { funcStr += func } let callbackTableStr = '' if (callbacks.length) { callbackTableStr = ` (table (export "callback") ;; name of table anyfunc (elem ${callbacks.join(' ')}) ;; elements will have indexes in order )` } let evmCodeStr = '' if (evmCode && !isConstructor) { let evmbytecodestart = 33832 let evmruntimestart = evmbytecodestart + evmConstructorCode.length let constructorCode = '' for (const val of evmConstructorCode.values()) { constructorCode += "\\"+val.toString(16).padStart(2, "0"); } let runtimeCode = '' for (const val of evmCode.values()) { runtimeCode += "\\"+val.toString(16).padStart(2, "0"); } evmCodeStr = ` (data (i32.const ${evmbytecodestart}) "${constructorCode}") (data (i32.const ${evmruntimestart}) "${runtimeCode}") (func $evm_bytecode (export "evm_bytecode") (result i32 i32 i32) i32.const ${evmbytecodestart} i32.const ${evmConstructorCode.length} i32.const ${evmCode.length}) ` } let opcodeHelpers; if (interfaceVersion === INTERFACE_CLASSIC) { opcodeHelpers = ` (global $cb_dest (mut i32) (i32.const 0)) (global $sp (mut i32) (i32.const -32)) (global $init (mut i32) (i32.const 0)) ;; memory related global ;; (global $evmbytecodestart i32 (i32.const 33832)) (global $memstart i32 (i32.const 62632)) ;; the number of 256 words stored in memory (global $wordCount (mut i64) (i64.const 0)) ;; what was charged for the last memory allocation (global $prevMemCost (mut i64) (i64.const 0)) ;; for SHL, SHR, SAR (global $global_ (mut i64) (i64.const 0)) (global $global__1 (mut i64) (i64.const 0)) (global $global__2 (mut i64) (i64.const 0)) ` } else { opcodeHelpers = ` (global $cb_dest (mut i32) (i32.const 0)) (global $init (mut i32) (i32.const 0)) ` } return ` (module ${imports.join('\n')} (type $et12 (func)) (func $${interfaceVersion} (export "${interfaceVersion}") (type $et12) (nop)) ${opcodeHelpers} ;; TODO: memory should only be 1, but can't resize right now (memory 500) (export "memory" (memory 0)) ${callbackTableStr} ${funcStr} ${evmCodeStr} )` } const cosmosFunctions = { "SENDCOSMOSMSG":{ import: `(import "env" "ethereum_sendCosmosMsg" (func $sendCosmosMsg (param i32 i32) (result i32) ))`, def: `(func $SENDCOSMOSMSG (local $offset0 i32)(local $length0 i32)(local.set $offset0 (call $check_overflow (i64.load (global.get $sp)) (i64.load (i32.add (global.get $sp) (i32.const 8))) (i64.load (i32.add (global.get $sp) (i32.const 16))) (i64.load (i32.add (global.get $sp) (i32.const 24))))) (local.set $offset0 (i32.add (global.get $memstart) (local.get $offset0))) ;; todo fix length - read from memory and increment offset with 32 (local.set $length0 (i32.const 0)) (call $memusegas (local.get $offset0) (i32.const 32)) ;; (local.set $offset0 (i32.add (local.get $offset0) (i32.const 32))) (i64.store (i32.add (global.get $sp) (i32.const 0)) (i64.extend_i32_u (i32.eqz (call $sendCosmosMsg(local.get $offset0)(local.get $length0))))) ;; zero out mem (i64.store (i32.add (global.get $sp) (i32.const 8)) (i64.const 0)) (i64.store (i32.add (global.get $sp) (i32.const 16)) (i64.const 0)) (i64.store (i32.add (global.get $sp) (i32.const 24)) (i64.const 0)))` }, "SENDCOSMOSQUERY":{ import: `(import "env" "ethereum_sendCosmosQuery" (func $sendCosmosQuery (param i32 i32) (result i32) ))`, def: `(func $SENDCOSMOSQUERY (local $offset0 i32)(local $length0 i32)(local.set $offset0 (call $check_overflow (i64.load (global.get $sp)) (i64.load (i32.add (global.get $sp) (i32.const 8))) (i64.load (i32.add (global.get $sp) (i32.const 16))) (i64.load (i32.add (global.get $sp) (i32.const 24))))) (local.set $offset0 (i32.add (global.get $memstart) (local.get $offset0))) ;; todo fix length - read from memory and increment offset with 32 (local.set $length0 (i32.const 0)) (call $memusegas (local.get $offset0) (i32.const 32)) ;; (local.set $offset0 (i32.add (local.get $offset0) (i32.const 32))) (i64.store (i32.add (global.get $sp) (i32.const 0)) (i64.extend_i32_u (i32.eqz (call $sendCosmosQuery(local.get $offset0)(local.get $length0))))) ;; zero out mem (i64.store (i32.add (global.get $sp) (i32.const 8)) (i64.const 0)) (i64.store (i32.add (global.get $sp) (i32.const 16)) (i64.const 0)) (i64.store (i32.add (global.get $sp) (i32.const 24)) (i64.const 0)))`, } }