import { Contract } from 'web3-eth-contract' import Web3 from 'web3' import { Account } from 'web3-core' import { ZERO_BYTES32 } from 'common/Constants' import { generateRandomBytes32 } from 'utils/wallet.util' const abi = require('../../config/abis/BEMultiSigWallet.json').abi /** * ["address", "uint256", "bytes", "uint256", "bytes32"], * [target, value, data, predecessor, salt] * data: method.encodeABI * salt: generateRandomBytes32(); */ export interface IOperationData { scheduleId?: string targets: string[] values: string[] datas: string[] predecessor: string salt: string transactionHash?: string } export class WalletReactor { private web3: Web3 private contract: Contract private account: Account constructor({ web3, address }: { web3: Web3; address: string }) { this.web3 = web3 this.account = this.web3.eth.accounts.wallet[0] this.contract = new this.web3.eth.Contract(abi, address, { from: this.account.address }) } /** * 开启执行一个timelock的任务, 该方法需要有proposer角色 * @param {*} seconds delay的秒数 * @returns */ async beginSchedule(operation: IOperationData, seconds: number) { // let operation: any = this.genOperation(contractAddress, 0, data, ZERO_BYTES32, salt) let gas = await this.contract.methods .schedule(operation.targets, operation.values, operation.datas, operation.predecessor, operation.salt, seconds) .estimateGas({ from: this.account.address }) let res = await this.contract.methods .schedule(operation.targets, operation.values, operation.datas, operation.predecessor, operation.salt, seconds) .send({ gas: gas | 0 }) operation.transactionHash = res.transactionHash return operation } /** * 取消定时, 该方法需要有proposer角色 * @param {bytes32} id beginSchedule返回的id * @returns */ async cancelSchedule(id) { let gas = await this.contract.methods.cancel(id).estimateGas({ from: this.account.address }) let res = await this.contract.methods.cancel(id).send({ gas: gas | 0 }) return res } /** * 查询定时 * @param {bytes32} scheduleId beginSchedule返回的id * @returns */ async querySchedule(scheduleId: string) { let instance = this.contract return this.makeBatchRequest([ // 查询的scheduleid是否在合约中, 包含所有状态 instance.methods.isOperation(scheduleId).call, // 查询的scheduleid是否在pending状态 instance.methods.isOperationPending(scheduleId).call, // 查询的scheduleid是否已经到了执行时间 instance.methods.isOperationReady(scheduleId).call, // 查询的scheduleid是否已经执行完成 instance.methods.isOperationDone(scheduleId).call, // 查询的scheduleid是否已经满足可执行的confirm数量 instance.methods.isConfirmed(scheduleId).call, // 查询的scheduleid的可执行时间 instance.methods.getTimestamp(scheduleId).call, ]) } /** * 执行schedule的任务, 该方法需要有executor的role * @returns */ async executeSchedule(operation: IOperationData) { let gas = await this.contract.methods .execute(operation.targets, operation.values, operation.datas, operation.predecessor, operation.salt) .estimateGas({ from: this.account.address }) gas = gas | 0 let res = await this.contract.methods .execute(operation.targets, operation.values, operation.datas, operation.predecessor, operation.salt) .send({ gas }) return res } /** * 组装schedule的请求数据 * @param {address} targets 方法所在contract的address * @param {uint256} values 如果调用的方法涉及到转账, 这里表示转账金额 * @param {bytes} datas 通过contract.methods.methodName(params).encodeABI()获取 * @param {bytes32} predecessor that specifies a dependency between operations, 如果没有, 则传bytes32(0) * @param {bytes32} salt 随机串, 用于区分不同的执行任务 * @returns */ genOperation({ targets, values, datas, predecessor, salt }: IOperationData): IOperationData { const scheduleId = this.web3.utils.keccak256( this.web3.eth.abi.encodeParameters( ['address[]', 'uint256[]', 'bytes[]', 'uint256', 'bytes32'], [targets, values, datas, predecessor, salt], ), ) return { scheduleId, targets, values, datas, predecessor, salt } } makeBatchRequest(calls: any[], callFrom?: any) { let batch = new this.web3.BatchRequest() let promises = calls.map(call => { return new Promise((resolve, reject) => { let request = call.request({ from: callFrom }, (error, data) => { if (error) { reject(error) } else { resolve(data) } }) batch.add(request) }) }) batch.execute() return Promise.all(promises) } async updateRequired(num: number) { let contractAddress = [process.env.CHAIN_WALLET_ADDRESS] let values = ['0'] let abi = await this.contract.methods.changeRequirement(num + '').encodeABI() let salt = generateRandomBytes32() let operation: any = this.genOperation({ targets: contractAddress, values, datas: [abi], predecessor: ZERO_BYTES32, salt, }) operation = await this.beginSchedule(operation, 60) return operation } }