基于浏览器以太坊前端虚拟环境构建

335 阅读3分钟

基于js语言,构建以太坊底链的evm环境,采用 @ethereumjs/vm来实现虚拟环境的构建

import VM from '@ethereumjs/vm';

const _vm = new VM({ allowUnlimitedContractSize: true });

上述完成了一个底链的构建,

如果你对于底链属性进行配置,提供了一个公共的类Common

import Common from '@ethereumjs/common';

const common = new Common()

new VM({common });

可以设置 chain、hardfork等属性

建议不要对底链属性进行配置,如果没有特殊的要求的话,这些已经足够了

事件监听

VM继承AsyncEventEmitter,提供了一些事件的监听

  • beforeBlock

在运行一个block之前触发的函数

  • afterBlock

在运行一个block之后触发的函数

  • beforeTx

在发起一个交易之前触发的函数

  • afterTx

交易发生之后出的函数

  • beforeMessage
  • afterMessage
  • step

在运行evm的时候处罚

  • newContract

部署合约的时候出发


_vm.on('newContract',(data)=>{

console.log(data,"部署新合约")

})

创建用户

StateManager模块提供了对于用户操作的相关方法

  • getAccount.

获取用户信息

  • deleteAccount

删除用户信息

  • putAccount

新增用户信息

  • touchAccount

联系

需要注意的地方是,前三个是promise返回的,需要注意异步调用

实现一个新增用户的方法

只简单的方法,也是vm在初始化的时候自己在做的事情

const address = new Address(Buffer.from(addressStr, 'hex'))

const newAccount = Account.fromAccountData({ balance: 1 })

await this.stateManager.putAccount(address, newAccount)

// 不知道用户的私钥

方法二

import { Wallet } from 'ethers';

export const insertAccount = async (vm: VM) => {

const acctData = {

nonce: 0,

balance: new BN(1e11), //账户钱包的大小

};

const wallet = Wallet.createRandom(); // 创建一个wallet

const account = Account.fromAccountData(acctData);//创建一个账户

const privateKey = wallet.privateKey.replace(/^0x/, ''); // 私钥字符串

const privateKeyBuffer = Buffer.from(privateKey, 'hex');//私钥buffer

const addressBuffer = privateToAddress(privateKeyBuffer); // 转为地址

const address = new Address(addressBuffer); //转为addres类型私钥

await vm.stateManager.putAccount(address, account); //

return {

address: wallet.address.toLowerCase(),

addressBuffer,

privateKey,

privateKeyBuffer,

account,

value: wallet.address.toLowerCase(),

text: wallet.address.toLowerCase(),

};

//返回这个用户的相关信息 地址 私钥

};

部署合约

部署合约到EVM,对于链来说,部署合约也是一次发送交易的过程,所以部署合约调用的是vm的runTx方法

async function deployContract(

vm: VM,

senderPrivateKey: Buffer, // 部署用户的私钥

deploymentBytecode: Buffer, //编译过来的Bytecode

obj: any, //abi里面type类型为constructor的对象

greeting: any, //部署的实现需要传递的参数,有时需要传递有时不需要传递,具体看sol代码

): Promise<Address> {

try {

const params = obj?.length > 0 ? {

types: [obj[0].type],

values: [greeting],

} : null;

const data = encodeDeployment(deploymentBytecode.toString('hex'), params);

const txData = {

data,

nonce: await getAccountNonce(vm, senderPrivateKey), //获取用户新增时候设置的nonce

};

const tx = Transaction.fromTxData(buildTransaction(txData)).sign(senderPrivateKey); //交易进行签名

const deploymentResult = await vm.runTx({ tx }); 进行交易

// 如果要获取交易的哈希,需要用tx里面获取 tx.hash();

if (deploymentResult.execResult.exceptionError) {

throw deploymentResult.execResult.exceptionError;

}

return deploymentResult.createdAddress!; //交易地址

} catch (e) {

}

};

// 将Bytecode和参数转化为字符串

export const encodeDeployment = (

bytecode: string,

params?: {

types: any[]

values: unknown[]

},

) => {

const deploymentData = `0x${bytecode}`;

if (params) {

const argumentsEncoded = AbiCoder.encode(params.types, params.values);

return deploymentData + argumentsEncoded.slice(2);

}

return deploymentData;

};

发送交易

发送交易,调用的是runTx方法

async function transFun(vm, contractAddress, senderPrivateKey, obj) {

try {

const funName = obj.name; //调用合约的那个方法

const param = obj.inputs.length > 0 ? {

types: [obj.inputs[0].type],

values: [obj.inputs[0]?.value],

} : null; // 调用合约方法传递的参数

const data = encodeFunction(funName, param);// 转移成字符串

const txData = {

to: contractAddress,

data,

gasPrice: 1,

nonce: await getAccountNonce(vm, senderPrivateKey),

}; //交易数据

const tx = Transaction.fromTxData(buildTransaction(txData)).sign(senderPrivateKey);//签名

const setGreetingResult = await vm.runTx({ tx });发送交易

if (setGreetingResult.execResult.exceptionError) {

throw setGreetingResult.execResult.exceptionError;

}

} catch (e) {

console.log(e);

}

}