基于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);
}
}