@ark-us/evm2wasm
Version:
This is a JS protope of a EVM to eWASM transcompiler
798 lines (715 loc) • 28.2 kB
JavaScript
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)))`,
}
}