@arbitrum/sdk
Version:
Typescript library client-side interactions with Arbitrum
251 lines (250 loc) • 12.3 kB
JavaScript
/*
* Copyright 2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-env node */
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChildToParentMessageWriterClassic = exports.ChildToParentMessageReaderClassic = exports.ChildToParentMessageClassic = void 0;
const constants_1 = require("../dataEntities/constants");
const ArbSys__factory_1 = require("../abi/factories/ArbSys__factory");
const Outbox__factory_1 = require("../abi/classic/factories/Outbox__factory");
const NodeInterface__factory_1 = require("../abi/factories/NodeInterface__factory");
const eventFetcher_1 = require("../utils/eventFetcher");
const signerOrProvider_1 = require("../dataEntities/signerOrProvider");
const lib_1 = require("../utils/lib");
const errors_1 = require("../dataEntities/errors");
const message_1 = require("../dataEntities/message");
const networks_1 = require("../dataEntities/networks");
class ChildToParentMessageClassic {
constructor(batchNumber, indexInBatch) {
this.batchNumber = batchNumber;
this.indexInBatch = indexInBatch;
}
static fromBatchNumber(parentSignerOrProvider, batchNumber, indexInBatch, parentProvider) {
return signerOrProvider_1.SignerProviderUtils.isSigner(parentSignerOrProvider)
? new ChildToParentMessageWriterClassic(parentSignerOrProvider, batchNumber, indexInBatch, parentProvider)
: new ChildToParentMessageReaderClassic(parentSignerOrProvider, batchNumber, indexInBatch);
}
static async getChildToParentEvents(childProvider, filter, batchNumber, destination, uniqueId, indexInBatch) {
const eventFetcher = new eventFetcher_1.EventFetcher(childProvider);
const events = (await eventFetcher.getEvents(ArbSys__factory_1.ArbSys__factory, t => t.filters.L2ToL1Transaction(null, destination, uniqueId, batchNumber), Object.assign(Object.assign({}, filter), { address: constants_1.ARB_SYS_ADDRESS }))).map(l => (Object.assign(Object.assign({}, l.event), { transactionHash: l.transactionHash })));
if (indexInBatch) {
const indexItems = events.filter(b => b.indexInBatch.eq(indexInBatch));
if (indexItems.length === 1) {
return indexItems;
}
else if (indexItems.length > 1) {
throw new errors_1.ArbSdkError('More than one indexed item found in batch.');
}
else
return [];
}
else
return events;
}
}
exports.ChildToParentMessageClassic = ChildToParentMessageClassic;
/**
* Provides read-only access for classic Child-to-Parent-messages
*/
class ChildToParentMessageReaderClassic extends ChildToParentMessageClassic {
constructor(parentProvider, batchNumber, indexInBatch) {
super(batchNumber, indexInBatch);
this.parentProvider = parentProvider;
/**
* Contains the classic outbox address, or set to zero address if this network
* did not have a classic outbox deployed
*/
this.outboxAddress = null;
this.proof = null;
}
/**
* Classic had 2 outboxes, we need to find the correct one for the provided batch number
* @param childProvider
* @param batchNumber
* @returns
*/
async getOutboxAddress(childProvider, batchNumber) {
if (!(0, lib_1.isDefined)(this.outboxAddress)) {
const childChain = await (0, networks_1.getArbitrumNetwork)(childProvider);
// find the outbox where the activation batch number of the next outbox
// is greater than the supplied batch
const outboxes = (0, lib_1.isDefined)(childChain.ethBridge.classicOutboxes)
? Object.entries(childChain.ethBridge.classicOutboxes)
: [];
const res = outboxes
.sort((a, b) => {
if (a[1] < b[1])
return -1;
else if (a[1] === b[1])
return 0;
else
return 1;
})
.find((_, index, array) => array[index + 1] === undefined || array[index + 1][1] > batchNumber);
if (!res) {
this.outboxAddress = '0x0000000000000000000000000000000000000000';
}
else {
this.outboxAddress = res[0];
}
}
return this.outboxAddress;
}
async outboxEntryExists(childProvider) {
const outboxAddress = await this.getOutboxAddress(childProvider, this.batchNumber.toNumber());
const outbox = Outbox__factory_1.Outbox__factory.connect(outboxAddress, this.parentProvider);
return await outbox.outboxEntryExists(this.batchNumber);
}
static async tryGetProof(childProvider, batchNumber, indexInBatch) {
const nodeInterface = NodeInterface__factory_1.NodeInterface__factory.connect(constants_1.NODE_INTERFACE_ADDRESS, childProvider);
try {
return await nodeInterface.legacyLookupMessageBatchProof(batchNumber, indexInBatch);
}
catch (e) {
const expectedError = "batch doesn't exist";
const err = e;
const actualError = err && (err.message || (err.error && err.error.message));
if (actualError.includes(expectedError))
return null;
else
throw e;
}
}
/**
* Get the execution proof for this message. Returns null if the batch does not exist yet.
* @param childProvider
* @returns
*/
async tryGetProof(childProvider) {
if (!(0, lib_1.isDefined)(this.proof)) {
this.proof = await ChildToParentMessageReaderClassic.tryGetProof(childProvider, this.batchNumber, this.indexInBatch);
}
return this.proof;
}
/**
* Check if given outbox message has already been executed
*/
async hasExecuted(childProvider) {
var _a, _b;
const proofInfo = await this.tryGetProof(childProvider);
if (!(0, lib_1.isDefined)(proofInfo))
return false;
const outboxAddress = await this.getOutboxAddress(childProvider, this.batchNumber.toNumber());
const outbox = Outbox__factory_1.Outbox__factory.connect(outboxAddress, this.parentProvider);
try {
await outbox.callStatic.executeTransaction(this.batchNumber, proofInfo.proof, proofInfo.path, proofInfo.l2Sender, proofInfo.l1Dest, proofInfo.l2Block, proofInfo.l1Block, proofInfo.timestamp, proofInfo.amount, proofInfo.calldataForL1);
return false;
}
catch (err) {
const e = err;
if ((_a = e === null || e === void 0 ? void 0 : e.message) === null || _a === void 0 ? void 0 : _a.toString().includes('ALREADY_SPENT'))
return true;
if ((_b = e === null || e === void 0 ? void 0 : e.message) === null || _b === void 0 ? void 0 : _b.toString().includes('NO_OUTBOX_ENTRY'))
return false;
throw e;
}
}
/**
* Get the status of this message
* In order to check if the message has been executed proof info must be provided.
* @param childProvider
* @returns
*/
async status(childProvider) {
try {
const messageExecuted = await this.hasExecuted(childProvider);
if (messageExecuted) {
return message_1.ChildToParentMessageStatus.EXECUTED;
}
const outboxEntryExists = await this.outboxEntryExists(childProvider);
return outboxEntryExists
? message_1.ChildToParentMessageStatus.CONFIRMED
: message_1.ChildToParentMessageStatus.UNCONFIRMED;
}
catch (e) {
return message_1.ChildToParentMessageStatus.UNCONFIRMED;
}
}
/**
* Waits until the outbox entry has been created, and will not return until it has been.
* WARNING: Outbox entries are only created when the corresponding node is confirmed. Which
* can take 1 week+, so waiting here could be a very long operation.
* @param retryDelay
* @returns outbox entry status (either executed or confirmed but not pending)
*/
async waitUntilOutboxEntryCreated(childProvider, retryDelay = 500) {
const exists = await this.outboxEntryExists(childProvider);
if (exists) {
return (await this.hasExecuted(childProvider))
? message_1.ChildToParentMessageStatus.EXECUTED
: message_1.ChildToParentMessageStatus.CONFIRMED;
}
else {
await (0, lib_1.wait)(retryDelay);
return await this.waitUntilOutboxEntryCreated(childProvider, retryDelay);
}
}
/**
* Estimates the Parent Chain block number in which this Child-to-Parent tx will be available for execution
* @param childProvider
* @returns Always returns null for classic chainToParentChain messages since they can be executed in any block now.
*/
async getFirstExecutableBlock(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
childProvider) {
return null;
}
}
exports.ChildToParentMessageReaderClassic = ChildToParentMessageReaderClassic;
/**
* Provides read and write access for classic Child-to-Parent-messages
*/
class ChildToParentMessageWriterClassic extends ChildToParentMessageReaderClassic {
/**
* Instantiates a new `ChildToParentMessageWriterClassic` object.
*
* @param {Signer} parentSigner The signer to be used for executing the Child-to-Parent message.
* @param {BigNumber} batchNumber The number of the batch containing the Child-to-Parent message.
* @param {BigNumber} indexInBatch The index of the Child-to-Parent message within the batch.
* @param {Provider} [parentProvider] Optional. Used to override the Provider which is attached to `parentSigner` in case you need more control. This will be a required parameter in a future major version update.
*/
constructor(parentSigner, batchNumber, indexInBatch, parentProvider) {
super(parentProvider !== null && parentProvider !== void 0 ? parentProvider : parentSigner.provider, batchNumber, indexInBatch);
this.parentSigner = parentSigner;
}
/**
* Executes the ChildToParentMessage on Parent Chain.
* Will throw an error if the outbox entry has not been created, which happens when the
* corresponding assertion is confirmed.
* @returns
*/
async execute(childProvider, overrides) {
const status = await this.status(childProvider);
if (status !== message_1.ChildToParentMessageStatus.CONFIRMED) {
throw new errors_1.ArbSdkError(`Cannot execute message. Status is: ${status} but must be ${message_1.ChildToParentMessageStatus.CONFIRMED}.`);
}
const proofInfo = await this.tryGetProof(childProvider);
if (!(0, lib_1.isDefined)(proofInfo)) {
throw new errors_1.ArbSdkError(`Unexpected missing proof: ${this.batchNumber.toString()} ${this.indexInBatch.toString()}}`);
}
const outboxAddress = await this.getOutboxAddress(childProvider, this.batchNumber.toNumber());
const outbox = Outbox__factory_1.Outbox__factory.connect(outboxAddress, this.parentSigner);
// We can predict and print number of missing blocks
// if not challenged
return await outbox.functions.executeTransaction(this.batchNumber, proofInfo.proof, proofInfo.path, proofInfo.l2Sender, proofInfo.l1Dest, proofInfo.l2Block, proofInfo.l1Block, proofInfo.timestamp, proofInfo.amount, proofInfo.calldataForL1, overrides || {});
}
}
exports.ChildToParentMessageWriterClassic = ChildToParentMessageWriterClassic;