@tokamak-network/thanos-sdk
Version:
Tools for working with Thanos
945 lines • 58.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CrossChainMessenger = void 0;
const abstract_provider_1 = require("@ethersproject/abstract-provider");
const ethers_1 = require("ethers");
const core_utils_1 = require("@tokamak-network/core-utils");
const rlp = __importStar(require("rlp"));
const L1CrossDomainMessenger_json_1 = __importDefault(require("@tokamak-network/thanos-contracts/forge-artifacts/L1CrossDomainMessenger.sol/L1CrossDomainMessenger.json"));
const semver_1 = __importDefault(require("semver"));
const interfaces_1 = require("./interfaces");
const utils_1 = require("./utils");
class CrossChainMessenger {
constructor(opts) {
var _a;
this._outputCache = [];
this.populateTransaction = {
sendMessage: async (message, opts) => {
if (message.direction === interfaces_1.MessageDirection.L1_TO_L2) {
return this.contracts.l1.L1CrossDomainMessenger.populateTransaction.sendMessage(message.target, message.message, (opts === null || opts === void 0 ? void 0 : opts.l2GasLimit) || (await this.estimateL2MessageGasLimit(message)), (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
else {
return this.contracts.l2.L2CrossDomainMessenger.populateTransaction.sendMessage(message.target, message.message, 0, (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
},
resendMessage: async (message, messageGasLimit, opts, messageIndex = 0) => {
const resolved = await this.toCrossChainMessage(message, messageIndex);
if (resolved.direction === interfaces_1.MessageDirection.L2_TO_L1) {
throw new Error(`cannot resend L2 to L1 message`);
}
if (this.bedrock) {
return this.populateTransaction.finalizeMessage(resolved, Object.assign(Object.assign({}, (opts || {})), { overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.overrides), { gasLimit: messageGasLimit }) }), messageIndex);
}
else {
const legacyL1XDM = new ethers_1.ethers.Contract(this.contracts.l1.L1CrossDomainMessenger.address, L1CrossDomainMessenger_json_1.default.abi, this.l1SignerOrProvider);
return legacyL1XDM.populateTransaction.replayMessage(resolved.target, resolved.sender, resolved.message, resolved.messageNonce, resolved.minGasLimit, messageGasLimit, (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
},
proveMessage: async (message, opts, messageIndex = 0) => {
const resolved = await this.toCrossChainMessage(message, messageIndex);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error('cannot finalize L1 to L2 message');
}
if (!this.bedrock) {
throw new Error('message proving only applies after the bedrock upgrade');
}
const withdrawal = await this.toLowLevelMessage(resolved, messageIndex);
const proof = await this.getBedrockMessageProof(resolved, messageIndex);
const args = [
[
withdrawal.messageNonce,
withdrawal.sender,
withdrawal.target,
withdrawal.value,
withdrawal.minGasLimit,
withdrawal.message,
],
proof.l2OutputIndex,
[
proof.outputRootProof.version,
proof.outputRootProof.stateRoot,
proof.outputRootProof.messagePasserStorageRoot,
proof.outputRootProof.latestBlockhash,
],
proof.withdrawalProof,
(opts === null || opts === void 0 ? void 0 : opts.overrides) || {},
];
return this.contracts.l1.OptimismPortal.populateTransaction.proveWithdrawalTransaction(...args);
},
finalizeMessage: async (message, opts, messageIndex = 0) => {
const resolved = await this.toCrossChainMessage(message, messageIndex);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`cannot finalize L1 to L2 message`);
}
if (this.bedrock) {
const messageHashV1 = (0, core_utils_1.hashCrossDomainMessagev1)(resolved.messageNonce, resolved.sender, resolved.target, resolved.value, resolved.minGasLimit, resolved.message);
const [isFailed, withdrawal] = await Promise.allSettled([
this.contracts.l1.L1CrossDomainMessenger.failedMessages(messageHashV1),
this.toLowLevelMessage(resolved, messageIndex),
]);
if (isFailed.status === 'rejected' ||
withdrawal.status === 'rejected') {
const rejections = [isFailed, withdrawal]
.filter((p) => p.status === 'rejected')
.map((p) => p.reason);
throw rejections.length > 1
? new AggregateError(rejections)
: rejections[0];
}
if (isFailed.value === true) {
const xdmWithdrawal = this.contracts.l1.L1CrossDomainMessenger.interface.decodeFunctionData('relayMessage', withdrawal.value.message);
return this.contracts.l1.L1CrossDomainMessenger.populateTransaction.relayMessage(xdmWithdrawal._nonce, xdmWithdrawal._sender, xdmWithdrawal._target, xdmWithdrawal._value, xdmWithdrawal._minGasLimit, xdmWithdrawal._message, (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
return this.contracts.l1.OptimismPortal.populateTransaction.finalizeWithdrawalTransaction([
withdrawal.value.messageNonce,
withdrawal.value.sender,
withdrawal.value.target,
withdrawal.value.value,
withdrawal.value.minGasLimit,
withdrawal.value.message,
], (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
else {
const proof = await this.getMessageProof(resolved, messageIndex);
const legacyL1XDM = new ethers_1.ethers.Contract(this.contracts.l1.L1CrossDomainMessenger.address, L1CrossDomainMessenger_json_1.default.abi, this.l1SignerOrProvider);
return legacyL1XDM.populateTransaction.relayMessage(resolved.target, resolved.sender, resolved.message, resolved.messageNonce, proof, (opts === null || opts === void 0 ? void 0 : opts.overrides) || {});
}
},
bridgeETH: async (amount, opts, isEstimatingGas = false) => {
if (!ethers_1.ethers.Signer.isSigner(this.l1SignerOrProvider)) {
throw new Error('unable to deposit without an l1 signer');
}
const from = this.l1SignerOrProvider.getAddress();
const getOpts = async () => {
var _a, _b, _c, _d;
if (isEstimatingGas) {
return opts;
}
const gasEstimation = await this.estimateGas.bridgeETH(amount, Object.assign(Object.assign({}, opts), { overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.overrides), { from: (_b = (_a = opts === null || opts === void 0 ? void 0 : opts.overrides) === null || _a === void 0 ? void 0 : _a.from) !== null && _b !== void 0 ? _b : from }) }));
return Object.assign(Object.assign({}, opts), { overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.overrides), { gasLimit: gasEstimation.add(gasEstimation.div(2)), from: (_d = (_c = opts === null || opts === void 0 ? void 0 : opts.overrides) === null || _c === void 0 ? void 0 : _c.from) !== null && _d !== void 0 ? _d : from }) });
};
return this.bridges.ETH.populateTransaction.deposit(ethers_1.ethers.constants.AddressZero, core_utils_1.predeploys.ETH, amount, await getOpts());
},
withdrawETH: async (amount, opts) => {
return this.bridges.ETH.populateTransaction.withdraw(ethers_1.ethers.constants.AddressZero, core_utils_1.predeploys.ETH, amount, opts);
},
bridgeNativeToken: async (amount, opts, isEstimatingGas = false) => {
const getOpts = async () => {
var _a, _b, _c, _d;
if (isEstimatingGas) {
return opts;
}
if (!ethers_1.ethers.Signer.isSigner(this.l1SignerOrProvider)) {
throw new Error('unable to deposit without an l1 signer');
}
const from = this.l1SignerOrProvider.getAddress();
const gasEstimation = await this.estimateGas.bridgeNativeToken(amount, Object.assign(Object.assign({}, opts), { overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.overrides), { from: (_b = (_a = opts === null || opts === void 0 ? void 0 : opts.overrides) === null || _a === void 0 ? void 0 : _a.from) !== null && _b !== void 0 ? _b : from }) }));
return Object.assign(Object.assign({}, opts), { overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.overrides), { gasLimit: gasEstimation.add(gasEstimation.div(2)), from: (_d = (_c = opts === null || opts === void 0 ? void 0 : opts.overrides) === null || _c === void 0 ? void 0 : _c.from) !== null && _d !== void 0 ? _d : from }) });
};
return this.bridges.NativeToken.populateTransaction.deposit(this.nativeTokenAddress, core_utils_1.predeploys.LegacyERC20NativeToken, amount, await getOpts());
},
withdrawNativeToken: async (amount, opts) => {
return this.bridges.NativeToken.populateTransaction.withdraw(this.nativeTokenAddress, core_utils_1.predeploys.LegacyERC20NativeToken, amount, opts);
},
approveNativeToken: async (amount, opts) => {
return this.bridges.NativeToken.populateTransaction.approve(this.nativeTokenAddress, core_utils_1.predeploys.LegacyERC20NativeToken, amount, opts);
},
approveERC20: async (l1Token, l2Token, amount, opts) => {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
return bridge.populateTransaction.approve(l1Token, l2Token, amount, opts);
},
bridgeERC20: async (l1Token, l2Token, amount, opts, isEstimatingGas = false) => {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
const getOpts = async () => {
var _a, _b, _c, _d;
if (isEstimatingGas) {
return opts;
}
if (!ethers_1.ethers.Signer.isSigner(this.l1SignerOrProvider)) {
throw new Error('unable to deposit without an l1 signer');
}
const from = this.l1SignerOrProvider.getAddress();
const gasEstimation = await this.estimateGas.bridgeERC20(l1Token, l2Token, amount, Object.assign(Object.assign({}, opts), { overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.overrides), { from: (_b = (_a = opts === null || opts === void 0 ? void 0 : opts.overrides) === null || _a === void 0 ? void 0 : _a.from) !== null && _b !== void 0 ? _b : from }) }));
return Object.assign(Object.assign({}, opts), { overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts.overrides), { gasLimit: gasEstimation.add(gasEstimation.div(2)), from: (_d = (_c = opts === null || opts === void 0 ? void 0 : opts.overrides) === null || _c === void 0 ? void 0 : _c.from) !== null && _d !== void 0 ? _d : from }) });
};
return bridge.populateTransaction.deposit(l1Token, l2Token, amount, await getOpts());
},
withdrawERC20: async (l1Token, l2Token, amount, opts) => {
const bridge = await this.getBridgeForTokenPair(l1Token, l2Token);
return bridge.populateTransaction.withdraw(l1Token, l2Token, amount, opts);
},
};
this.estimateGas = {
sendMessage: async (message, opts) => {
const tx = await this.populateTransaction.sendMessage(message, opts);
if (message.direction === interfaces_1.MessageDirection.L1_TO_L2) {
return this.l1Provider.estimateGas(tx);
}
else {
return this.l2Provider.estimateGas(tx);
}
},
resendMessage: async (message, messageGasLimit, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.resendMessage(message, messageGasLimit, opts));
},
proveMessage: async (message, opts, messageIndex = 0) => {
return this.l1Provider.estimateGas(await this.populateTransaction.proveMessage(message, opts, messageIndex));
},
finalizeMessage: async (message, opts, messageIndex = 0) => {
return this.l1Provider.estimateGas(await this.populateTransaction.finalizeMessage(message, opts, messageIndex));
},
bridgeETH: async (amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.bridgeETH(amount, opts, true));
},
withdrawETH: async (amount, opts) => {
return this.l2Provider.estimateGas(await this.populateTransaction.withdrawETH(amount, opts));
},
bridgeNativeToken: async (amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.bridgeNativeToken(amount, opts, true));
},
withdrawNativeToken: async (amount, opts) => {
return this.l2Provider.estimateGas(await this.populateTransaction.withdrawNativeToken(amount, opts));
},
approveNativeToken: async (amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.approveNativeToken(amount, opts));
},
approveERC20: async (l1Token, l2Token, amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.approveERC20(l1Token, l2Token, amount, opts));
},
bridgeERC20: async (l1Token, l2Token, amount, opts) => {
return this.l1Provider.estimateGas(await this.populateTransaction.bridgeERC20(l1Token, l2Token, amount, opts, true));
},
withdrawERC20: async (l1Token, l2Token, amount, opts) => {
return this.l2Provider.estimateGas(await this.populateTransaction.withdrawERC20(l1Token, l2Token, amount, opts));
},
};
this.bedrock = (_a = opts.bedrock) !== null && _a !== void 0 ? _a : true;
this.l1SignerOrProvider = (0, utils_1.toSignerOrProvider)(opts.l1SignerOrProvider);
this.l2SignerOrProvider = (0, utils_1.toSignerOrProvider)(opts.l2SignerOrProvider);
try {
this.l1ChainId = (0, utils_1.toNumber)(opts.l1ChainId);
}
catch (err) {
throw new Error(`L1 chain ID is missing or invalid: ${opts.l1ChainId}`);
}
try {
this.l2ChainId = (0, utils_1.toNumber)(opts.l2ChainId);
}
catch (err) {
throw new Error(`L2 chain ID is missing or invalid: ${opts.l2ChainId}`);
}
if (opts.nativeTokenAddress) {
this.nativeTokenAddress = (0, utils_1.toAddress)(opts.nativeTokenAddress);
}
this.depositConfirmationBlocks =
(opts === null || opts === void 0 ? void 0 : opts.depositConfirmationBlocks) !== undefined
? (0, utils_1.toNumber)(opts.depositConfirmationBlocks)
: utils_1.DEPOSIT_CONFIRMATION_BLOCKS[this.l2ChainId] || 0;
this.l1BlockTimeSeconds =
(opts === null || opts === void 0 ? void 0 : opts.l1BlockTimeSeconds) !== undefined
? (0, utils_1.toNumber)(opts.l1BlockTimeSeconds)
: utils_1.CHAIN_BLOCK_TIMES[this.l1ChainId] || 1;
this.contracts = (0, utils_1.getAllOEContracts)(this.l2ChainId, {
l1SignerOrProvider: this.l1SignerOrProvider,
l2SignerOrProvider: this.l2SignerOrProvider,
overrides: opts.contracts,
});
this.bridges = (0, utils_1.getBridgeAdapters)(this.l2ChainId, this, {
overrides: opts.bridges,
contracts: opts.contracts,
});
}
get l1Provider() {
if (abstract_provider_1.Provider.isProvider(this.l1SignerOrProvider)) {
return this.l1SignerOrProvider;
}
else {
return this.l1SignerOrProvider.provider;
}
}
get l2Provider() {
if (abstract_provider_1.Provider.isProvider(this.l2SignerOrProvider)) {
return this.l2SignerOrProvider;
}
else {
return this.l2SignerOrProvider.provider;
}
}
get l1Signer() {
if (abstract_provider_1.Provider.isProvider(this.l1SignerOrProvider)) {
throw new Error(`messenger has no L1 signer`);
}
else {
return this.l1SignerOrProvider;
}
}
get l2Signer() {
if (abstract_provider_1.Provider.isProvider(this.l2SignerOrProvider)) {
throw new Error(`messenger has no L2 signer`);
}
else {
return this.l2SignerOrProvider;
}
}
async fpac() {
if (this.contracts.l1.OptimismPortal.address === ethers_1.ethers.constants.AddressZero) {
return false;
}
else {
return semver_1.default.gte(await this.contracts.l1.OptimismPortal.version(), '3.0.0');
}
}
async getMessagesByTransaction(transaction, opts = {}) {
var _a, _b;
await ((_b = (_a = transaction).wait) === null || _b === void 0 ? void 0 : _b.call(_a));
const txHash = (0, utils_1.toTransactionHash)(transaction);
let receipt;
if (opts.direction !== undefined) {
if (opts.direction === interfaces_1.MessageDirection.L1_TO_L2) {
receipt = await this.l1Provider.getTransactionReceipt(txHash);
}
else {
receipt = await this.l2Provider.getTransactionReceipt(txHash);
}
}
else {
receipt = await this.l1Provider.getTransactionReceipt(txHash);
if (receipt) {
opts.direction = interfaces_1.MessageDirection.L1_TO_L2;
}
else {
receipt = await this.l2Provider.getTransactionReceipt(txHash);
opts.direction = interfaces_1.MessageDirection.L2_TO_L1;
}
}
if (!receipt) {
throw new Error(`unable to find transaction receipt for ${txHash}`);
}
const messenger = opts.direction === interfaces_1.MessageDirection.L1_TO_L2
? this.contracts.l1.L1CrossDomainMessenger
: this.contracts.l2.L2CrossDomainMessenger;
return receipt.logs
.filter((log) => {
return log.address === messenger.address;
})
.filter((log) => {
const parsed = messenger.interface.parseLog(log);
return parsed.name === 'SentMessage';
})
.map((log) => {
let value = ethers_1.ethers.BigNumber.from(0);
const next = receipt.logs.find((l) => {
return (l.logIndex === log.logIndex + 1 && l.address === messenger.address);
});
if (next) {
const nextParsed = messenger.interface.parseLog(next);
if (nextParsed.name === 'SentMessageExtension1') {
value = nextParsed.args.value;
}
}
const parsed = messenger.interface.parseLog(log);
return {
direction: opts.direction,
target: parsed.args.target,
sender: parsed.args.sender,
message: parsed.args.message,
messageNonce: parsed.args.messageNonce,
value,
minGasLimit: parsed.args.gasLimit,
logIndex: log.logIndex,
blockNumber: log.blockNumber,
transactionHash: log.transactionHash,
};
});
}
async toBedrockCrossChainMessage(message, messageIndex = 0) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
const { version } = (0, core_utils_1.decodeVersionedNonce)(resolved.messageNonce);
if (version.eq(1)) {
return resolved;
}
let value = ethers_1.BigNumber.from(0);
if (resolved.direction === interfaces_1.MessageDirection.L2_TO_L1 &&
resolved.sender === this.contracts.l2.L2StandardBridge.address &&
resolved.target === this.contracts.l1.L1StandardBridge.address) {
try {
;
[, , value] =
this.contracts.l1.L1StandardBridge.interface.decodeFunctionData('finalizeETHWithdrawal', resolved.message);
}
catch (err) {
}
}
return Object.assign(Object.assign({}, resolved), { value, minGasLimit: ethers_1.BigNumber.from(0), messageNonce: (0, core_utils_1.encodeVersionedNonce)(ethers_1.BigNumber.from(0), resolved.messageNonce) });
}
async toLowLevelMessage(message, messageIndex = 0) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`can only convert L2 to L1 messages to low level`);
}
const { version } = (0, core_utils_1.decodeVersionedNonce)(resolved.messageNonce);
let updated;
if (version.eq(0)) {
updated = await this.toBedrockCrossChainMessage(resolved, messageIndex);
}
else {
updated = resolved;
}
const encoded = (0, core_utils_1.encodeCrossDomainMessageV1)(updated.messageNonce, updated.sender, updated.target, updated.value, updated.minGasLimit, updated.message);
let gasLimit;
let messageNonce;
if (version.eq(0)) {
const chainID = await (0, core_utils_1.getChainId)(this.l2Provider);
gasLimit = (0, utils_1.migratedWithdrawalGasLimit)(encoded, chainID);
messageNonce = resolved.messageNonce;
}
else {
const receipt = await this.l2Provider.getTransactionReceipt((await this.toCrossChainMessage(message)).transactionHash);
const withdrawals = [];
for (const log of receipt.logs) {
if (log.address === this.contracts.l2.BedrockMessagePasser.address) {
const decoded = this.contracts.l2.L2ToL1MessagePasser.interface.parseLog(log);
if (decoded.name === 'MessagePassed') {
withdrawals.push(decoded.args);
}
}
}
if (withdrawals.length === 0) {
throw new Error(`no withdrawals found in receipt`);
}
const withdrawal = withdrawals[messageIndex];
if (!withdrawal) {
throw new Error(`withdrawal index ${messageIndex} out of bounds there are ${withdrawals.length} withdrawals`);
}
messageNonce = withdrawal.nonce;
gasLimit = withdrawal.gasLimit;
}
return {
messageNonce,
sender: this.contracts.l2.L2CrossDomainMessenger.address,
target: this.contracts.l1.L1CrossDomainMessenger.address,
value: updated.value,
minGasLimit: gasLimit,
message: encoded,
};
}
async getBridgeForTokenPair(l1Token, l2Token) {
var _a, _b;
const bridges = [];
for (const bridge of Object.values(this.bridges)) {
try {
if (await bridge.supportsTokenPair(l1Token, l2Token)) {
bridges.push(bridge);
}
}
catch (err) {
if (!((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.toString().includes('CALL_EXCEPTION')) &&
!((_b = err === null || err === void 0 ? void 0 : err.stack) === null || _b === void 0 ? void 0 : _b.toString().includes('execution reverted'))) {
console.error('Unexpected error when checking bridge', err);
}
}
}
if (bridges.length === 0) {
throw new Error(`no supported bridge for token pair`);
}
if (bridges.length > 1) {
throw new Error(`found more than one bridge for token pair`);
}
return bridges[0];
}
async getDepositsByAddress(address, opts = {}) {
return (await Promise.all(Object.values(this.bridges).map(async (bridge) => {
return bridge.getDepositsByAddress(address, opts);
})))
.reduce((acc, val) => {
return acc.concat(val);
}, [])
.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
}
async getWithdrawalsByAddress(address, opts = {}) {
return (await Promise.all(Object.values(this.bridges).map(async (bridge) => {
return bridge.getWithdrawalsByAddress(address, opts);
})))
.reduce((acc, val) => {
return acc.concat(val);
}, [])
.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
}
async toCrossChainMessage(message, messageIndex = 0) {
if (!message) {
throw new Error('message is undefined');
}
if (message.message) {
return message;
}
else if (message.l1Token &&
message.l2Token &&
message.transactionHash) {
const messages = await this.getMessagesByTransaction(message.transactionHash);
const found = messages
.sort((a, b) => {
return a.logIndex - b.logIndex;
})
.find((m) => {
return m.logIndex > message.logIndex;
});
if (!found) {
throw new Error(`could not find SentMessage event for message`);
}
return found;
}
else {
const messages = await this.getMessagesByTransaction(message);
const out = messages[messageIndex];
if (!out) {
throw new Error(`withdrawal index ${messageIndex} out of bounds. There are ${messages.length} withdrawals`);
}
return out;
}
}
async getMessageStatus(message, messageIndex = 0, fromBlockOrBlockHash, toBlockOrBlockHash) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
const messageHashV0 = (0, core_utils_1.hashCrossDomainMessagev0)(resolved.target, resolved.sender, resolved.message, resolved.messageNonce);
const messageHashV1 = (0, core_utils_1.hashCrossDomainMessagev1)(resolved.messageNonce, resolved.sender, resolved.target, resolved.value, resolved.minGasLimit, resolved.message);
const messenger = resolved.direction === interfaces_1.MessageDirection.L1_TO_L2
? this.contracts.l2.L2CrossDomainMessenger
: this.contracts.l1.L1CrossDomainMessenger;
const success = (await messenger.successfulMessages(messageHashV0)) ||
(await messenger.successfulMessages(messageHashV1));
if (success) {
return interfaces_1.MessageStatus.RELAYED;
}
const failure = (await messenger.failedMessages(messageHashV0)) ||
(await messenger.failedMessages(messageHashV1));
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
if (failure) {
return interfaces_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE;
}
else {
return interfaces_1.MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE;
}
}
else {
if (failure) {
return interfaces_1.MessageStatus.READY_FOR_RELAY;
}
else {
let timestamp;
if (this.bedrock) {
const output = await this.getMessageBedrockOutput(resolved, messageIndex);
if (output === null) {
return interfaces_1.MessageStatus.STATE_ROOT_NOT_PUBLISHED;
}
const withdrawal = await this.toLowLevelMessage(resolved, messageIndex);
const provenWithdrawal = await this.getProvenWithdrawal((0, utils_1.hashLowLevelMessage)(withdrawal));
if (provenWithdrawal === null) {
return interfaces_1.MessageStatus.READY_TO_PROVE;
}
timestamp = provenWithdrawal.timestamp.toNumber();
}
else {
const stateRoot = await this.getMessageStateRoot(resolved, messageIndex);
if (stateRoot === null) {
return interfaces_1.MessageStatus.STATE_ROOT_NOT_PUBLISHED;
}
const bn = stateRoot.batch.blockNumber;
const block = await this.l1Provider.getBlock(bn);
timestamp = block.timestamp;
}
if (await this.fpac()) {
const withdrawal = await this.toLowLevelMessage(resolved, messageIndex);
const withdrawalHash = (0, utils_1.hashLowLevelMessage)(withdrawal);
const provenWithdrawal = await this.getProvenWithdrawal(withdrawalHash);
if (provenWithdrawal === null) {
console.warn('Unexpected code path reached in getMessageStatus, returning READY_TO_PROVE');
return interfaces_1.MessageStatus.READY_TO_PROVE;
}
if (!('proofSubmitter' in provenWithdrawal)) {
throw new Error(`expected to get FPAC withdrawal but got legacy withdrawal`);
}
try {
await this.contracts.l1.OptimismPortal2.checkWithdrawal((0, utils_1.hashLowLevelMessage)(withdrawal), provenWithdrawal.proofSubmitter);
return interfaces_1.MessageStatus.READY_FOR_RELAY;
}
catch (err) {
return interfaces_1.MessageStatus.IN_CHALLENGE_PERIOD;
}
}
else {
const challengePeriod = await this.getChallengePeriodSeconds();
const latestBlock = await this.l1Provider.getBlock('latest');
if (timestamp + challengePeriod > latestBlock.timestamp) {
return interfaces_1.MessageStatus.IN_CHALLENGE_PERIOD;
}
else {
return interfaces_1.MessageStatus.READY_FOR_RELAY;
}
}
}
}
}
async getMessageReceipt(message, messageIndex = 0, fromBlockOrBlockHash, toBlockOrHash) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
const messageHashV0 = (0, core_utils_1.hashCrossDomainMessagev0)(resolved.target, resolved.sender, resolved.message, resolved.messageNonce);
const messageHashV1 = (0, core_utils_1.hashCrossDomainMessagev1)(resolved.messageNonce, resolved.sender, resolved.target, resolved.value, resolved.minGasLimit, resolved.message);
const messenger = resolved.direction === interfaces_1.MessageDirection.L1_TO_L2
? this.contracts.l2.L2CrossDomainMessenger
: this.contracts.l1.L1CrossDomainMessenger;
const relayedMessageEvents = [
...(await messenger.queryFilter(messenger.filters.RelayedMessage(messageHashV0), fromBlockOrBlockHash, toBlockOrHash)),
...(await messenger.queryFilter(messenger.filters.RelayedMessage(messageHashV1), fromBlockOrBlockHash, toBlockOrHash)),
];
if (relayedMessageEvents.length === 1) {
return {
receiptStatus: interfaces_1.MessageReceiptStatus.RELAYED_SUCCEEDED,
transactionReceipt: await relayedMessageEvents[0].getTransactionReceipt(),
};
}
else if (relayedMessageEvents.length > 1) {
throw new Error(`multiple successful relays for message`);
}
const failedRelayedMessageEvents = [
...(await messenger.queryFilter(messenger.filters.FailedRelayedMessage(messageHashV0), fromBlockOrBlockHash, toBlockOrHash)),
...(await messenger.queryFilter(messenger.filters.FailedRelayedMessage(messageHashV1), fromBlockOrBlockHash, toBlockOrHash)),
];
if (failedRelayedMessageEvents.length > 0) {
return {
receiptStatus: interfaces_1.MessageReceiptStatus.RELAYED_FAILED,
transactionReceipt: await failedRelayedMessageEvents[failedRelayedMessageEvents.length - 1].getTransactionReceipt(),
};
}
return null;
}
async waitForMessageReceipt(message, opts = {}, messageIndex = 0) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
let totalTimeMs = 0;
while (totalTimeMs < (opts.timeoutMs || Infinity)) {
const tick = Date.now();
const receipt = await this.getMessageReceipt(resolved, messageIndex, opts.fromBlockOrBlockHash, opts.toBlockOrHash);
if (receipt !== null) {
return receipt;
}
else {
await (0, core_utils_1.sleep)(opts.pollIntervalMs || 4000);
totalTimeMs += Date.now() - tick;
}
}
throw new Error(`timed out waiting for message receipt`);
}
async waitForMessageStatus(message, status, opts = {}, messageIndex = 0) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
let totalTimeMs = 0;
while (totalTimeMs < (opts.timeoutMs || Infinity)) {
const tick = Date.now();
const currentStatus = await this.getMessageStatus(resolved, messageIndex, opts.fromBlockOrBlockHash, opts.toBlockOrBlockHash);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
if (currentStatus === status) {
return;
}
if (status === interfaces_1.MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE &&
currentStatus > status) {
return;
}
if (status === interfaces_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE &&
currentStatus === interfaces_1.MessageStatus.RELAYED) {
throw new Error(`incompatible message status, expected FAILED_L1_TO_L2_MESSAGE got RELAYED`);
}
if (status === interfaces_1.MessageStatus.RELAYED &&
currentStatus === interfaces_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE) {
throw new Error(`incompatible message status, expected RELAYED got FAILED_L1_TO_L2_MESSAGE`);
}
}
if (resolved.direction === interfaces_1.MessageDirection.L2_TO_L1) {
if (currentStatus >= status) {
return;
}
}
await (0, core_utils_1.sleep)(opts.pollIntervalMs || 4000);
totalTimeMs += Date.now() - tick;
}
throw new Error(`timed out waiting for message status change`);
}
async estimateL2MessageGasLimit(message, opts, messageIndex = 0) {
let resolved;
let from;
if (message.messageNonce === undefined) {
resolved = message;
from = opts === null || opts === void 0 ? void 0 : opts.from;
}
else {
resolved = await this.toCrossChainMessage(message, messageIndex);
from = (opts === null || opts === void 0 ? void 0 : opts.from) || resolved.sender;
}
if (resolved.direction === interfaces_1.MessageDirection.L2_TO_L1) {
throw new Error(`cannot estimate gas limit for L2 => L1 message`);
}
const estimate = await this.l2Provider.estimateGas({
from,
to: resolved.target,
data: resolved.message,
});
const bufferPercent = (opts === null || opts === void 0 ? void 0 : opts.bufferPercent) || 20;
return estimate.mul(100 + bufferPercent).div(100);
}
async estimateMessageWaitTimeSeconds(message, messageIndex = 0, fromBlockOrBlockHash, toBlockOrBlockHash) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
const status = await this.getMessageStatus(resolved, messageIndex, fromBlockOrBlockHash, toBlockOrBlockHash);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
if (status === interfaces_1.MessageStatus.RELAYED ||
status === interfaces_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE) {
return 0;
}
else {
const receipt = await this.l1Provider.getTransactionReceipt(resolved.transactionHash);
const blocksLeft = Math.max(this.depositConfirmationBlocks - receipt.confirmations, 0);
return blocksLeft * this.l1BlockTimeSeconds;
}
}
else {
if (status === interfaces_1.MessageStatus.RELAYED ||
status === interfaces_1.MessageStatus.READY_FOR_RELAY) {
return 0;
}
else if (status === interfaces_1.MessageStatus.STATE_ROOT_NOT_PUBLISHED) {
return this.getChallengePeriodSeconds();
}
else if (status === interfaces_1.MessageStatus.IN_CHALLENGE_PERIOD) {
const stateRoot = await this.getMessageStateRoot(resolved, messageIndex);
const challengePeriod = await this.getChallengePeriodSeconds();
const targetBlock = await this.l1Provider.getBlock(stateRoot.batch.blockNumber);
const latestBlock = await this.l1Provider.getBlock('latest');
return Math.max(challengePeriod - (latestBlock.timestamp - targetBlock.timestamp), 0);
}
else {
throw new Error(`unexpected message status`);
}
}
}
async getChallengePeriodSeconds() {
if (!this.bedrock) {
return (await this.contracts.l1.StateCommitmentChain.FRAUD_PROOF_WINDOW()).toNumber();
}
const oracleVersion = await this.contracts.l1.L2OutputOracle.version();
const challengePeriod = oracleVersion === '1.0.0'
?
ethers_1.BigNumber.from(await this.contracts.l1.OptimismPortal.provider.call({
to: this.contracts.l1.OptimismPortal.address,
data: '0xf4daa291',
}))
: await this.contracts.l1.L2OutputOracle.FINALIZATION_PERIOD_SECONDS();
return challengePeriod.toNumber();
}
async getProvenWithdrawal(withdrawalHash) {
if (!this.bedrock) {
throw new Error('message proving only applies after the bedrock upgrade');
}
if (!(await this.fpac())) {
const provenWithdrawal = await this.contracts.l1.OptimismPortal.provenWithdrawals(withdrawalHash);
if (provenWithdrawal.timestamp.eq(0)) {
return null;
}
else {
return provenWithdrawal;
}
}
const numProofSubmitters = ethers_1.BigNumber.from(await this.contracts.l1.OptimismPortal2.numProofSubmitters(withdrawalHash)).toNumber();
for (let i = 0; i < numProofSubmitters; i++) {
const proofSubmitter = await this.contracts.l1.OptimismPortal2.proofSubmitters(withdrawalHash, i);
const provenWithdrawal = await this.contracts.l1.OptimismPortal2.provenWithdrawals(withdrawalHash, proofSubmitter);
const game = new ethers_1.ethers.Contract(provenWithdrawal.disputeGameProxy, (0, utils_1.getContractInterfaceBedrock)('FaultDisputeGame'), this.l1SignerOrProvider);
const status = await game.status();
if (status === 1) {
continue;
}
else if (status === 2) {
return Object.assign(Object.assign({}, provenWithdrawal), { proofSubmitter });
}
else if (status > 2) {
throw new Error('got invalid game status');
}
const extraData = await game.extraData();
let l2BlockNumber;
try {
;
[l2BlockNumber] = ethers_1.ethers.utils.defaultAbiCoder.decode(['uint256'], extraData);
}
catch (err) {
continue;
}
if (await this.isValidOutputRoot(await game.rootClaim(), l2BlockNumber)) {
return Object.assign(Object.assign({}, provenWithdrawal), { proofSubmitter });
}
}
return null;
}
async isValidOutputRoot(outputRoot, l2BlockNumber) {
const cached = this._outputCache.find((other) => {
return other.root === outputRoot;
});
if (cached) {
return cached.valid;
}
if (this._outputCache.length > 10000) {
this._outputCache = this._outputCache.slice(5000);
}
try {
const provider = (0, utils_1.toJsonRpcProvider)(this.l2Provider);
const [block, proof] = await Promise.all([
provider.send('eth_getBlockByNumber', [
(0, core_utils_1.toRpcHexString)(l2BlockNumber),
false,
]),
(0, utils_1.makeStateTrieProof)(provider, l2BlockNumber, this.contracts.l2.OVM_L2ToL1MessagePasser.address, ethers_1.ethers.constants.HashZero),
]);
const output = ethers_1.ethers.utils.solidityKeccak256(['bytes32', 'bytes32', 'bytes32', 'bytes32'], [
ethers_1.ethers.constants.HashZero,
block.stateRoot,
proof.storageRoot,
block.hash,
]);
const valid = output === outputRoot;
this._outputCache.push({ root: outputRoot, valid });
return valid;
}
catch (err) {
return false;
}
}
async getMessageBedrockOutput(message, messageIndex = 0) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`cannot get a state root for an L1 to L2 message`);
}
let proposal;
let l2OutputIndex;
if (await this.fpac()) {
const gameType = await this.contracts.l1.OptimismPortal2.respectedGameType();
const gameCount = await this.contracts.l1.DisputeGameFactory.gameCount();
const latestGames = await this.contracts.l1.DisputeGameFactory.findLatestGames(gameType, Math.max(0, gameCount.sub(1).toNumber()), Math.min(100, gameCount.toNumber()));
const matches = [];
for (const game of latestGames) {
try {
const [blockNumber] = ethers_1.ethers.utils.defaultAbiCoder.decode(['uint256'], game.extraData);
if (blockNumber.gte(resolved.blockNumber)) {
matches.push(Object.assign(Object.assign({}, game), { l2BlockNumber: blockNumber }));
}
}
catch (err) {
continue;
}
}
for (let i = matches.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[matches[i], matches[j]] = [matches[j], matches[i]];
}
let match;
for (const option of matches) {
if (await this.isValidOutputRoot(option.rootClaim, option.l2BlockNumber)) {
match = option;
break;
}
}
if (!match) {
return null;
}
l2OutputIndex = match.index;
proposal = {
outputRoot: match.rootClaim,
timestamp: match.timestamp,
l2BlockNumber: match.l2BlockNumber,
};
}
else {
try {
l2OutputIndex =
await this.contracts.l1.L2OutputOracle.getL2OutputIndexAfter(resolved.blockNumber);
}
catch (err) {
if (err.message.includes('L2OutputOracle: cannot get output')) {
return null;
}
else {
throw err;
}
}
proposal = await this.contracts.l1.L2OutputOracle.getL2Output(l2OutputIndex);
}
return {
outputRoot: proposal.outputRoot,
l1Timestamp: proposal.timestamp.toNumber(),
l2BlockNumber: proposal.l2BlockNumber.toNumber(),
l2OutputIndex: l2OutputIndex.toNumber(),
};
}
async getMessageStateRoot(message, messageIndex = 0) {
const resolved = await this.toCrossChainMessage(message, messageIndex);
if (resolved.direction === interfaces_1.MessageDirection.L1_TO_L2) {
throw new Error(`cannot get a state root for an L1 to L2 message`);
}
const messageTxReceipt = await this.l2Provider.getTransactionReceipt(resolved.transactionHash);
const messageTxIndex = messageTxReceipt.blockNumber - 1;
const stateRootBatch = await this.getStateRootBatchByTransactionIndex(messageTxIndex);
if (stateRootBatch === null) {
return null;
}
const indexInBatch = messageTxIndex - stateRootBatch.header.prevTotalElements.toNumber();
if (stateRootBatch.stateRoots.length <= indexInBatch) {
throw new Error(`state root does not exist in batch`);
}
return {
stateRoot: stateRootBatch.stateRoots[indexInBatch],
stateRootIndexInBatch: indexInBatch,
batch: stateRootBatch,
};
}
async getStateBatchAppendedEventByBatchIndex(batchIndex) {
const events = await this.contracts.l1.StateCommitmentChain.queryFilter(this.contracts.l1.StateCommitmentChain.filters.StateBatchAppended(batchIndex));
if (events.length === 0) {
return null;
}
else if (events.length > 1) {
throw new Error(`found more than one StateBatchAppended event`);
}
else {
return events[0];
}
}
async getStateBatchAppendedEventByTransactionIndex(transactionIndex) {
const isEventHi = (event, index) => {
const prevTotalElements = event.args._prevTotalElements.toNumber();
return index < prevTotalElements;
};
const isEventLo = (event, index) => {
const prevTotalElements = event.args._prevTotalElements.toNumber();
const batchSize = event.args._batchSize.toNumber();
return inde