ewasm-jsvm
Version:
"ewasm virtual machine for javascript"
161 lines (135 loc) • 4.88 kB
JavaScript
const { randomHash, randomAddress, toBN, uint8ArrayToHex, hexToUint8Array } = require('./utils.js');
const persistenceMock = (accounts = {}) => {
const emptyAccount = (address) => Object.assign({}, { address, balance: toBN(0), empty: true});
// runtimeCode null - address not set / selfdestructed
// runtimeCode.length === 0 - address set, not contract
const format = (_account) => {
const account = {..._account};
if (typeof account.address !== 'string' || account.address.slice(0, 2) !== '0x' || account.address.length !== 42) throw new Error('invalid Ethereum address');
if (typeof account.runtimeCode === 'string') account.runtimeCode = hexToUint8Array(account.runtimeCode);
account.balance = toBN(account.balance);
return account;
}
const get = address => cloneContract(accounts[address] || emptyAccount(address));
const set = (account) => {
// pathToWasm ?
account.address = account.address || randomAddress();
let { address, runtimeCode, storage, balance = 0, removed } = format(account);
// if (accounts[address]) throw new Error('Address already exists');
storage = storage || (runtimeCode ? {} : null);
runtimeCode = runtimeCode || (removed ? undefined : new Uint8Array(0));
accounts[address] = {
address,
runtimeCode,
balance: toBN(balance),
storage,
empty: false,
};
return address;
}
const remove = address => {
if (accounts[address].balance.gt(toBN(0))) throw new Error('Contract removal failed, because it still has money.');
// delete accounts[address];
// type: 'removed'
accounts[address] = emptyAccount(address);
accounts[address].empty = false;
// we need this for comparing persistence changes; do better
accounts[address].removed = true;
}
const updateBalance = (address, total) => {
if (!accounts[address]) accounts[address] = emptyAccount(address);
accounts[address].balance = toBN(total);
accounts[address].empty = false;
}
const setBulk = (accounts = {}) => {
Object.keys(accounts).forEach(addr => {
set(cloneContract(format(accounts[addr])));
})
}
return {
get,
set,
updateBalance,
remove,
setBulk,
}
}
const blocks = (blocks = []) => {
let count = 0;
const blocksByHash = {};
blocks.forEach(block => {
blocksByHash[block.hash] = block.number;
count = block.number + 1;
});
const set = () => {
const block = {
number: count,
timestamp: (new Date()).getTime(),
// mock
hash: randomHash(),
difficulty: 2307651677621404,
gasLimit: 30000000,
coinbase: '0x'.padEnd(42, '0'), // randomAddress(),
}
blocks.push(block);
blocksByHash[block.hash] = count;
count ++;
return block;
}
const get = tag => {
if (parseInt(tag) === tag) return blocks[tag];
return blocks[count - 1];
}
return {
get,
set,
}
}
const logs = (logs = []) => {
const logsByBlockNumber = {};
logs.forEach(log => {
if(!logsByBlockNumber[log.blockNumber]) logsByBlockNumber[log.blockNumber] = [];
logsByBlockNumber[log.blockNumber].push(log);
});
const set = (log) => {
logs.push(log);
if (!logsByBlockNumber[log.blockNumber]) {
logsByBlockNumber[log.blockNumber] = [];
}
logsByBlockNumber[log.blockNumber].push(log);
}
const getBlockLogs = number => logsByBlockNumber[number];
const getLogs = () => logs;
const setBulk = (logs = []) => {
logs.forEach(set);
}
return { set, getBlockLogs, getLogs, setBulk };
}
const cloneStorage = storage => {
if (!storage) return {};
const clonedStorage = {};
Object.keys(storage).forEach(key => {
clonedStorage[key] = hexToUint8Array(uint8ArrayToHex(storage[key]));
});
return clonedStorage;
}
const cloneContract = obj => {
return {
...obj,
balance: obj.balance.clone(),
storage: cloneStorage(obj.storage),
}
}
const cloneContext = (context = {}) => {
const newcontext = {};
Object.keys(context).forEach(addr => {
newcontext[addr] = cloneContract(context[addr]);
});
return newcontext;
}
const cloneLog = log => {
const {address, blockNumber, data, topics} = log;
return {address, blockNumber, data: hexToUint8Array(uint8ArrayToHex(data)), topics: [...topics]}
}
const cloneLogs = logs => logs.map(cloneLog);
module.exports = { persistence: persistenceMock, blocks, logs, cloneContext, cloneLogs, cloneStorage, cloneContract };