@unruggable/gateways
Version:
Trustless Ethereum Multichain CCIP-Read Gateway
214 lines (213 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.OPFaultRollup = exports.GAME_ABI = exports.GAME_FINDER_ABI = exports.PORTAL_ABI = void 0;
const contract_1 = require("ethers/contract");
const abi_1 = require("ethers/abi");
const chains_js_1 = require("../chains.cjs");
const utils_js_1 = require("../utils.cjs");
const AbstractOPRollup_js_1 = require("./AbstractOPRollup.cjs");
// https://docs.optimism.io/chain/differences
// https://specs.optimism.io/fault-proof/stage-one/bridge-integration.html
exports.PORTAL_ABI = new abi_1.Interface([
`function disputeGameFactory() view returns (address)`,
`function respectedGameType() view returns (uint32)`,
`function disputeGameBlacklist(address game) view returns (bool)`,
]);
exports.GAME_FINDER_ABI = new abi_1.Interface([
`error GameNotFound()`,
`error InvalidGameTypeBitMask()`,
`function findGameIndex(address portal, uint256 minAge, uint256 gameTypeBitMask, uint256 gameCount) view returns (uint256)`,
`function gameAtIndex(address portal, uint256 minAge, uint256 gameTypeBitMask, uint256 gameIndex) view returns (
uint256 gameType, uint256 created, address gameProxy, uint256 l2BlockNumber, bytes32 rootClaim
)`,
]);
exports.GAME_ABI = new abi_1.Interface([
`function rootClaim() view returns (bytes32)`,
]);
const GAME_FINDER_MAINNET = '0x61F50A76bfb2Ad8620A3E8F81aa27f3bEb1Db0D7';
const GAME_FINDER_SEPOLIA = '0x1577670E6AC3307D17A8f81464ADc2070A2EFcaa';
//const GAME_FINDER_HOLESKY = '0xedb18cd8d9D6AF54C4Ac1FbDBF2E098F413c3fe9';
class OPFaultRollup extends AbstractOPRollup_js_1.AbstractOPRollup {
minAgeSec;
// https://docs.optimism.io/chain/addresses
static mainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.OP,
OptimismPortal: '0xbEb5Fc579115071764c7423A4f12eDde41f106Ed',
GameFinder: GAME_FINDER_MAINNET,
};
static sepoliaConfig = {
chain1: chains_js_1.CHAINS.SEPOLIA,
chain2: chains_js_1.CHAINS.OP_SEPOLIA,
OptimismPortal: '0x16Fc5058F25648194471939df75CF27A2fdC48BC',
GameFinder: GAME_FINDER_SEPOLIA,
};
// https://docs.base.org/docs/base-contracts#l1-contract-addresses
static baseMainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.BASE,
OptimismPortal: '0x49048044D57e1C92A77f79988d21Fa8fAF74E97e',
GameFinder: GAME_FINDER_MAINNET,
};
// https://docs.base.org/docs/base-contracts/#ethereum-testnet-sepolia
static baseSepoliaConfig = {
chain1: chains_js_1.CHAINS.SEPOLIA,
chain2: chains_js_1.CHAINS.BASE_SEPOLIA,
OptimismPortal: '0x49f53e41452C74589E85cA1677426Ba426459e85',
GameFinder: GAME_FINDER_SEPOLIA,
};
// https://docs.inkonchain.com/useful-information/contracts#l1-contract-addresses
static inkMainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.INK,
OptimismPortal: '0x5d66c1782664115999c47c9fa5cd031f495d3e4f',
GameFinder: GAME_FINDER_MAINNET,
};
static inkSepoliaConfig = {
chain1: chains_js_1.CHAINS.SEPOLIA,
chain2: chains_js_1.CHAINS.INK_SEPOLIA,
OptimismPortal: '0x5c1d29C6c9C8b0800692acC95D700bcb4966A1d7',
GameFinder: GAME_FINDER_SEPOLIA,
};
// https://docs.unichain.org/docs/technical-information/contract-addresses
static unichainMainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.UNICHAIN,
OptimismPortal: '0x0bd48f6B86a26D3a217d0Fa6FfE2B491B956A7a2',
GameFinder: GAME_FINDER_MAINNET,
};
static unichainSepoliaConfig = {
chain1: chains_js_1.CHAINS.SEPOLIA,
chain2: chains_js_1.CHAINS.UNICHAIN_SEPOLIA,
OptimismPortal: '0x0d83dab629f0e0F9d36c0Cbc89B69a489f0751bD',
GameFinder: GAME_FINDER_SEPOLIA,
};
// https://docs.soneium.org/docs/builders/contracts
static soneiumMainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.SONEIUM,
OptimismPortal: '0x88e529a6ccd302c948689cd5156c83d4614fae92',
GameFinder: GAME_FINDER_MAINNET,
};
static soneiumMinatoConfig = {
chain1: chains_js_1.CHAINS.SEPOLIA,
chain2: chains_js_1.CHAINS.SONEIUM_SEPOLIA,
OptimismPortal: '0x65ea1489741A5D72fFdD8e6485B216bBdcC15Af3',
GameFinder: GAME_FINDER_SEPOLIA,
};
// https://build.swellnetwork.io/docs/developer-resources/contract-addresses
static swellMainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.SWELL,
OptimismPortal: '0x758E0EE66102816F5C3Ec9ECc1188860fbb87812',
GameFinder: GAME_FINDER_MAINNET,
};
static swellSepoliaConfig = {
chain1: chains_js_1.CHAINS.SEPOLIA,
chain2: chains_js_1.CHAINS.SWELL_SEPOLIA,
OptimismPortal: '0x595329c60c0b9e54a5246e98fb0fa7fcfd454f64',
GameFinder: GAME_FINDER_SEPOLIA,
};
// https://docs.worldcoin.org/world-chain/developers/world-chain-contracts
static worldMainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.WORLD,
OptimismPortal: '0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C',
GameFinder: GAME_FINDER_MAINNET,
};
// https://storage.googleapis.com/cel2-rollup-files/celo/deployment-l1.json
static celoMainnetConfig = {
chain1: chains_js_1.CHAINS.MAINNET,
chain2: chains_js_1.CHAINS.CELO,
OptimismPortal: '0xc5c5D157928BDBD2ACf6d0777626b6C75a9EAEDC',
GameFinder: GAME_FINDER_MAINNET,
};
// 20240917: delayed constructor not needed
OptimismPortal;
GameFinder;
gameTypes = []; // if empty, dynamically uses respectedGameType()
unfinalizedRootClaimTimeoutMs = 30000;
constructor(providers, config, minAgeSec = 0) {
super(providers);
this.minAgeSec = minAgeSec;
this.OptimismPortal = new contract_1.Contract(config.OptimismPortal, exports.PORTAL_ABI, providers.provider1);
this.GameFinder = new contract_1.Contract(config.GameFinder, exports.GAME_FINDER_ABI, providers.provider1);
}
get gameTypeBitMask() {
return this.gameTypes.reduce((a, x) => a | (1 << x), 0);
}
get unfinalized() {
return !!this.minAgeSec; // nonzero => unfinalized
}
async fetchRespectedGameType() {
return this.OptimismPortal.respectedGameType({
blockTag: this.latestBlockTag,
});
}
async _ensureRootClaim(index) {
// dodge canary by requiring a valid root claim
// finalized claims are assumed valid
if (this.unfinalized) {
const timeout = Date.now() + this.unfinalizedRootClaimTimeoutMs; // prevent "infinite" loop
for (;;) {
try {
await this.fetchCommit(index);
break;
}
catch (err) {
// NOTE: this could fail for a variety of reasons
// so we can't just catch "invalid root claim"
// canary often has invalid block <== likely triggers first
// canary has invalid time
// canary has invalid root claim
// 20250503: this can infinite loop when the rpc errors suck
if ((0, utils_js_1.isEthersError)(err))
throw err;
if (Date.now() > timeout)
throw new Error(`timeout _ensureRootClaim()`);
index = await this.GameFinder.findGameIndex(this.OptimismPortal.target, this.minAgeSec, this.gameTypeBitMask, index);
}
}
}
return index;
}
async fetchLatestCommitIndex() {
// the primary assumption is that the anchor root is the finalized state
// however, this is strangely conditional on the gameType
// (apparently because the anchor state registry is *not* intended for finalization)
// after a gameType switch, the finalized state "rewinds" to the latest game of the new type
// to solve this, we use the latest finalized game of *any* supported gameType
// 20240820: correctly handles the aug 16 respectedGameType change
// this should be simplified in the future once there is a better policy
// 20240822: once again uses a helper contract to reduce rpc burden
return this._ensureRootClaim(await this.GameFinder.findGameIndex(this.OptimismPortal.target, this.minAgeSec, this.gameTypeBitMask, 0, // most recent game
{ blockTag: this.latestBlockTag }));
}
async _fetchParentCommitIndex(commit) {
return this._ensureRootClaim(await this.GameFinder.findGameIndex(this.OptimismPortal.target, this.minAgeSec, this.gameTypeBitMask, commit.index));
}
async _fetchCommit(index) {
// note: GameFinder checks isCommitStillValid()
const game = (await this.GameFinder.gameAtIndex(this.OptimismPortal.target, this.minAgeSec, this.gameTypeBitMask, index)).toObject();
if (!game.l2BlockNumber)
throw new Error('invalid game');
const commit = await this.createCommit(index, game.l2BlockNumber);
if (this.unfinalized) {
const gameProxy = new contract_1.Contract(game.gameProxy, exports.GAME_ABI, this.provider1);
const expected = await gameProxy.rootClaim();
const computed = (0, AbstractOPRollup_js_1.hashOutputRootProof)(commit);
if (expected !== computed)
throw new Error(`invalid root claim`);
}
return { ...commit, game };
}
async isCommitStillValid(commit) {
return !(await this.OptimismPortal.disputeGameBlacklist(commit.game.gameProxy));
}
windowFromSec(sec) {
// finalization time is on-chain
// https://github.com/ethereum-optimism/optimism/blob/a81de910dc2fd9b2f67ee946466f2de70d62611a/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L590
return sec;
}
}
exports.OPFaultRollup = OPFaultRollup;