From 470e1030f4319bd960c73a588df9eacfbb9c4e6b Mon Sep 17 00:00:00 2001 From: Chris De Leon Date: Fri, 21 Jun 2024 12:27:40 -0700 Subject: [PATCH] remove hardhat tests --- contracts/gas-snapshots/l2ep.gas-snapshot | 52 +- .../OptimismSequencerUptimeFeed.t.sol | 2 +- .../scroll/ScrollSequencerUptimeFeed.t.sol | 2 +- .../L2EP/ArbitrumCrossDomainForwarder.test.ts | 194 -------- .../L2EP/ArbitrumCrossDomainGovernor.test.ts | 365 -------------- .../L2EP/ArbitrumSequencerUptimeFeed.test.ts | 415 ---------------- .../test/v0.8/L2EP/ArbitrumValidator.test.ts | 134 ----- .../test/v0.8/L2EP/CrossDomainOwnable.test.ts | 77 --- .../L2EP/OptimismCrossDomainForwarder.test.ts | 224 --------- .../L2EP/OptimismCrossDomainGovernor.test.ts | 409 ---------------- .../L2EP/OptimismSequencerUptimeFeed.test.ts | 426 ---------------- .../test/v0.8/L2EP/OptimismValidator.test.ts | 123 ----- .../L2EP/ScrollCrossDomainForwarder.test.ts | 259 ---------- .../L2EP/ScrollCrossDomainGovernor.test.ts | 459 ------------------ .../L2EP/ScrollSequencerUptimeFeed.test.ts | 424 ---------------- .../test/v0.8/L2EP/ScrollValidator.test.ts | 127 ----- 16 files changed, 28 insertions(+), 3664 deletions(-) delete mode 100644 contracts/test/v0.8/L2EP/ArbitrumCrossDomainForwarder.test.ts delete mode 100644 contracts/test/v0.8/L2EP/ArbitrumCrossDomainGovernor.test.ts delete mode 100644 contracts/test/v0.8/L2EP/ArbitrumSequencerUptimeFeed.test.ts delete mode 100644 contracts/test/v0.8/L2EP/ArbitrumValidator.test.ts delete mode 100644 contracts/test/v0.8/L2EP/CrossDomainOwnable.test.ts delete mode 100644 contracts/test/v0.8/L2EP/OptimismCrossDomainForwarder.test.ts delete mode 100644 contracts/test/v0.8/L2EP/OptimismCrossDomainGovernor.test.ts delete mode 100644 contracts/test/v0.8/L2EP/OptimismSequencerUptimeFeed.test.ts delete mode 100644 contracts/test/v0.8/L2EP/OptimismValidator.test.ts delete mode 100644 contracts/test/v0.8/L2EP/ScrollCrossDomainForwarder.test.ts delete mode 100644 contracts/test/v0.8/L2EP/ScrollCrossDomainGovernor.test.ts delete mode 100644 contracts/test/v0.8/L2EP/ScrollSequencerUptimeFeed.test.ts delete mode 100644 contracts/test/v0.8/L2EP/ScrollValidator.test.ts diff --git a/contracts/gas-snapshots/l2ep.gas-snapshot b/contracts/gas-snapshots/l2ep.gas-snapshot index 324cacfc024..0f8ed03b74c 100644 --- a/contracts/gas-snapshots/l2ep.gas-snapshot +++ b/contracts/gas-snapshots/l2ep.gas-snapshot @@ -68,27 +68,27 @@ OptimismCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 4 OptimismCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28775) OptimismCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16482) OptimismCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11030) -OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetAnswer() (gas: 59785) -OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetRoundData() (gas: 60331) -OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetTimestamp() (gas: 59640) -OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestAnswer() (gas: 57577) -OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestRound() (gas: 57463) -OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestRoundData() (gas: 58005) -OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestTimestamp() (gas: 57430) -OptimismSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 71804) +OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetAnswer() (gas: 62285) +OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetRoundData() (gas: 62831) +OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetTimestamp() (gas: 62140) +OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestAnswer() (gas: 60077) +OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestRound() (gas: 59963) +OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestRoundData() (gas: 60505) +OptimismSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestTimestamp() (gas: 59930) +OptimismSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 74304) OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17679) OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17897) OptimismSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17603) OptimismSequencerUptimeFeed_Constructor:test_InitialState() (gas: 22110) -OptimismSequencerUptimeFeed_GasCosts:test_GasCosts() (gas: 69567) +OptimismSequencerUptimeFeed_GasCosts:test_GasCosts() (gas: 72067) OptimismSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601843) OptimismSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574481) -OptimismSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 67230) +OptimismSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 69730) OptimismSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13214) OptimismSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddrAndNotL1SenderAddr() (gas: 23632) -OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 77137) -OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 97545) -OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 97605) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 79637) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 100045) +OptimismSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 100105) OptimismValidator_SetGasLimit:test_CorrectlyUpdatesTheGasLimit() (gas: 18695) OptimismValidator_Validate:test_PostSequencerOffline() (gas: 74813) OptimismValidator_Validate:test_PostSequencerStatusWhenThereIsNotStatusChange() (gas: 74869) @@ -119,27 +119,27 @@ ScrollCrossDomainGovernor_TransferL1Ownership:test_CallableByL1Owner() (gas: 489 ScrollCrossDomainGovernor_TransferL1Ownership:test_CallableByL1OwnerOrZeroAddress() (gas: 28841) ScrollCrossDomainGovernor_TransferL1Ownership:test_NotCallableByL2Owner() (gas: 16482) ScrollCrossDomainGovernor_TransferL1Ownership:test_NotCallableByNonOwners() (gas: 11030) -ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetAnswer() (gas: 57940) -ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetRoundData() (gas: 58476) -ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetTimestamp() (gas: 57795) -ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestAnswer() (gas: 55578) -ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestRound() (gas: 55458) -ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestRoundData() (gas: 56169) -ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestTimestamp() (gas: 55448) -ScrollSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 70090) +ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetAnswer() (gas: 60440) +ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetRoundData() (gas: 60976) +ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForGetTimestamp() (gas: 60295) +ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestAnswer() (gas: 58078) +ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestRound() (gas: 57958) +ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestRoundData() (gas: 58669) +ScrollSequencerUptimeFeed_AggregatorInterfaceGasCosts:test_GasUsageForLatestTimestamp() (gas: 57948) +ScrollSequencerUptimeFeed_AggregatorV3Interface:test_AggregatorV3Interface() (gas: 72590) ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetAnswerWhenRoundDoesNotExistYet() (gas: 17675) ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetRoundDataWhenRoundDoesNotExistYet() (gas: 17893) ScrollSequencerUptimeFeed_AggregatorV3Interface:test_RevertGetTimestampWhenRoundDoesNotExistYet() (gas: 17599) ScrollSequencerUptimeFeed_Constructor:test_InitialState() (gas: 103508) -ScrollSequencerUptimeFeed_GasCosts:test_GasCosts() (gas: 67258) +ScrollSequencerUptimeFeed_GasCosts:test_GasCosts() (gas: 69758) ScrollSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceAllowReadsIfConsumingContractIsWhitelisted() (gas: 601694) ScrollSequencerUptimeFeed_ProtectReadsOnAggregatorV2V3InterfaceFunctions:test_AggregatorV2V3InterfaceDisallowReadsIfConsumingContractIsNotWhitelisted() (gas: 574481) -ScrollSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 65115) +ScrollSequencerUptimeFeed_UpdateStatus:test_IgnoreOutOfOrderUpdates() (gas: 67615) ScrollSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddr() (gas: 13214) ScrollSequencerUptimeFeed_UpdateStatus:test_RevertIfNotL2CrossDomainMessengerAddrAndNotL1SenderAddr() (gas: 23632) -ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 74720) -ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 93408) -ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 93468) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenNoChange() (gas: 77220) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndNoTimeChange() (gas: 95908) +ScrollSequencerUptimeFeed_UpdateStatus:test_UpdateStatusWhenStatusChangeAndTimeChange() (gas: 95968) ScrollValidator_SetGasLimit:test_CorrectlyUpdatesTheGasLimit() (gas: 18829) ScrollValidator_Validate:test_PostSequencerOffline() (gas: 78349) ScrollValidator_Validate:test_PostSequencerStatusWhenThereIsNotStatusChange() (gas: 78411) diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol index 60598b9f952..bf5af82fa94 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol @@ -336,7 +336,7 @@ contract OptimismSequencerUptimeFeed_GasCosts is OptimismSequencerUptimeFeedTest uint256 gasFinal; // measures gas used for no update - expectedGasUsed = 10197; // NOTE: used to be 38594 in hardhat tests + expectedGasUsed = 12689; // NOTE: used to be 38594 in hardhat tests gasStart = gasleft(); s_optimismSequencerUptimeFeed.updateStatus(false, uint64(timestamp + 1000)); gasFinal = gasleft(); diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol index 520fbf6dfdc..4c4cbb40c98 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol @@ -340,7 +340,7 @@ contract ScrollSequencerUptimeFeed_GasCosts is ScrollSequencerUptimeFeedTest { uint256 gasFinal; // measures gas used for no update - expectedGasUsed = 10197; // NOTE: used to be 38594 in hardhat tests + expectedGasUsed = 12691; // NOTE: used to be 38594 in hardhat tests gasStart = gasleft(); s_scrollSequencerUptimeFeed.updateStatus(false, uint64(timestamp + 1000)); gasFinal = gasleft(); diff --git a/contracts/test/v0.8/L2EP/ArbitrumCrossDomainForwarder.test.ts b/contracts/test/v0.8/L2EP/ArbitrumCrossDomainForwarder.test.ts deleted file mode 100644 index 6b6d8abad1b..00000000000 --- a/contracts/test/v0.8/L2EP/ArbitrumCrossDomainForwarder.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { - impersonateAs, - publicAbi, - toArbitrumL2AliasAddress, -} from '../../test-helpers/helpers' - -let owner: SignerWithAddress -let stranger: SignerWithAddress -let l1OwnerAddress: string -let crossdomainMessenger: SignerWithAddress -let newL1OwnerAddress: string -let newOwnerCrossdomainMessenger: SignerWithAddress -let forwarderFactory: ContractFactory -let greeterFactory: ContractFactory -let forwarder: Contract -let greeter: Contract - -before(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - stranger = accounts[1] - l1OwnerAddress = owner.address - newL1OwnerAddress = stranger.address - - // Contract factories - forwarderFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainForwarder.sol:ArbitrumCrossDomainForwarder', - owner, - ) - greeterFactory = await ethers.getContractFactory( - 'src/v0.8/tests/Greeter.sol:Greeter', - owner, - ) -}) - -describe('ArbitrumCrossDomainForwarder', () => { - beforeEach(async () => { - // governor config - crossdomainMessenger = await impersonateAs( - toArbitrumL2AliasAddress(l1OwnerAddress), - ) - await owner.sendTransaction({ - to: crossdomainMessenger.address, - value: ethers.utils.parseEther('1.0'), - }) - newOwnerCrossdomainMessenger = await impersonateAs( - toArbitrumL2AliasAddress(newL1OwnerAddress), - ) - await owner.sendTransaction({ - to: newOwnerCrossdomainMessenger.address, - value: ethers.utils.parseEther('1.0'), - }) - - forwarder = await forwarderFactory.deploy(l1OwnerAddress) - greeter = await greeterFactory.deploy(forwarder.address) - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(forwarder, [ - 'typeAndVersion', - 'crossDomainMessenger', - 'forward', - 'l1Owner', - 'transferL1Ownership', - 'acceptL1Ownership', - // ConfirmedOwner methods: - 'owner', - 'transferOwnership', - 'acceptOwnership', - ]) - }) - - describe('#constructor', () => { - it('should set the owner correctly', async () => { - const response = await forwarder.owner() - assert.equal(response, owner.address) - }) - - it('should set the l1Owner correctly', async () => { - const response = await forwarder.l1Owner() - assert.equal(response, l1OwnerAddress) - }) - - it('should set the crossdomain messenger correctly', async () => { - const response = await forwarder.crossDomainMessenger() - assert.equal(response, crossdomainMessenger.address) - }) - - it('should set the typeAndVersion correctly', async () => { - const response = await forwarder.typeAndVersion() - assert.equal(response, 'ArbitrumCrossDomainForwarder 1.0.0') - }) - }) - - describe('#forward', () => { - it('should not be callable by unknown address', async () => { - await expect( - forwarder.connect(stranger).forward(greeter.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - await forwarder - .connect(crossdomainMessenger) - .forward(greeter.address, setGreetingData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should revert when contract call reverts', async () => { - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [''], - ) - await expect( - forwarder - .connect(crossdomainMessenger) - .forward(greeter.address, setGreetingData), - ).to.be.revertedWith('Invalid greeting length') - }) - }) - - describe('#transferL1Ownership', () => { - it('should not be callable by non-owners', async () => { - await expect( - forwarder.connect(stranger).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should not be callable by L2 owner', async () => { - const forwarderOwner = await forwarder.owner() - assert.equal(forwarderOwner, owner.address) - - await expect( - forwarder.connect(owner).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by current L1 owner', async () => { - const currentL1Owner = await forwarder.l1Owner() - await expect( - forwarder - .connect(crossdomainMessenger) - .transferL1Ownership(newL1OwnerAddress), - ) - .to.emit(forwarder, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, newL1OwnerAddress) - }) - - it('should be callable by current L1 owner to zero address', async () => { - const currentL1Owner = await forwarder.l1Owner() - await expect( - forwarder - .connect(crossdomainMessenger) - .transferL1Ownership(ethers.constants.AddressZero), - ) - .to.emit(forwarder, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, ethers.constants.AddressZero) - }) - }) - - describe('#acceptL1Ownership', () => { - it('should not be callable by non pending-owners', async () => { - await expect( - forwarder.connect(crossdomainMessenger).acceptL1Ownership(), - ).to.be.revertedWith('Must be proposed L1 owner') - }) - - it('should be callable by pending L1 owner', async () => { - const currentL1Owner = await forwarder.l1Owner() - await forwarder - .connect(crossdomainMessenger) - .transferL1Ownership(newL1OwnerAddress) - await expect( - forwarder.connect(newOwnerCrossdomainMessenger).acceptL1Ownership(), - ) - .to.emit(forwarder, 'L1OwnershipTransferred') - .withArgs(currentL1Owner, newL1OwnerAddress) - - const updatedL1Owner = await forwarder.l1Owner() - assert.equal(updatedL1Owner, newL1OwnerAddress) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/ArbitrumCrossDomainGovernor.test.ts b/contracts/test/v0.8/L2EP/ArbitrumCrossDomainGovernor.test.ts deleted file mode 100644 index 96e93e833ec..00000000000 --- a/contracts/test/v0.8/L2EP/ArbitrumCrossDomainGovernor.test.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import etherslib, { Contract, ContractFactory } from 'ethers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { - impersonateAs, - publicAbi, - stripHexPrefix, - toArbitrumL2AliasAddress, -} from '../../test-helpers/helpers' - -let owner: SignerWithAddress -let stranger: SignerWithAddress -let l1OwnerAddress: string -let crossdomainMessenger: SignerWithAddress -let newL1OwnerAddress: string -let newOwnerCrossdomainMessenger: SignerWithAddress -let governorFactory: ContractFactory -let greeterFactory: ContractFactory -let multisendFactory: ContractFactory -let governor: Contract -let greeter: Contract -let multisend: Contract - -before(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - stranger = accounts[1] - l1OwnerAddress = owner.address - newL1OwnerAddress = stranger.address - - // Contract factories - governorFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainGovernor.sol:ArbitrumCrossDomainGovernor', - owner, - ) - greeterFactory = await ethers.getContractFactory( - 'src/v0.8/tests/Greeter.sol:Greeter', - owner, - ) - multisendFactory = await ethers.getContractFactory( - 'src/v0.8/vendor/MultiSend.sol:MultiSend', - owner, - ) -}) - -describe('ArbitrumCrossDomainGovernor', () => { - beforeEach(async () => { - // governor config - crossdomainMessenger = await impersonateAs( - toArbitrumL2AliasAddress(l1OwnerAddress), - ) - await owner.sendTransaction({ - to: crossdomainMessenger.address, - value: ethers.utils.parseEther('1.0'), - }) - newOwnerCrossdomainMessenger = await impersonateAs( - toArbitrumL2AliasAddress(newL1OwnerAddress), - ) - await owner.sendTransaction({ - to: newOwnerCrossdomainMessenger.address, - value: ethers.utils.parseEther('1.0'), - }) - - governor = await governorFactory.deploy(l1OwnerAddress) - greeter = await greeterFactory.deploy(governor.address) - multisend = await multisendFactory.deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(governor, [ - 'typeAndVersion', - 'crossDomainMessenger', - 'forward', - 'forwardDelegate', - 'l1Owner', - 'transferL1Ownership', - 'acceptL1Ownership', - // ConfirmedOwner methods: - 'owner', - 'transferOwnership', - 'acceptOwnership', - ]) - }) - - describe('#constructor', () => { - it('should set the owner correctly', async () => { - const response = await governor.owner() - assert.equal(response, owner.address) - }) - - it('should set the l1Owner correctly', async () => { - const response = await governor.l1Owner() - assert.equal(response, l1OwnerAddress) - }) - - it('should set the crossdomain messenger correctly', async () => { - const response = await governor.crossDomainMessenger() - assert.equal(response, crossdomainMessenger.address) - }) - - it('should set the typeAndVersion correctly', async () => { - const response = await governor.typeAndVersion() - assert.equal(response, 'ArbitrumCrossDomainGovernor 1.0.0') - }) - }) - - describe('#forward', () => { - it('should not be callable by unknown address', async () => { - await expect( - governor.connect(stranger).forward(greeter.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger or owner') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - await governor - .connect(crossdomainMessenger) - .forward(greeter.address, setGreetingData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should be callable by L2 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - await governor.connect(owner).forward(greeter.address, setGreetingData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should revert when contract call reverts', async () => { - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [''], - ) - await expect( - governor - .connect(crossdomainMessenger) - .forward(greeter.address, setGreetingData), - ).to.be.revertedWith('Invalid greeting length') - }) - }) - - describe('#forwardDelegate', () => { - it('should not be callable by unknown address', async () => { - await expect( - governor.connect(stranger).forwardDelegate(multisend.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger or owner') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'bar', - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - await governor - .connect(crossdomainMessenger) - .forwardDelegate(multisend.address, multisendData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, 'bar') - }) - - it('should be callable by L2 owner', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'bar', - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - await governor - .connect(owner) - .forwardDelegate(multisend.address, multisendData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, 'bar') - }) - - it('should revert batch when one call fails', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - '', // should revert - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - await expect( - governor - .connect(crossdomainMessenger) - .forwardDelegate(multisend.address, multisendData), - ).to.be.revertedWith('Governor delegatecall reverted') - - const greeting = await greeter.greeting() - assert.equal(greeting, '') // Unchanged - }) - - it('should bubble up revert when contract call reverts', async () => { - const triggerRevertData = - greeterFactory.interface.encodeFunctionData('triggerRevert') - await expect( - governor - .connect(crossdomainMessenger) - .forwardDelegate(greeter.address, triggerRevertData), - ).to.be.revertedWith('Greeter: revert triggered') - }) - }) - - describe('#transferL1Ownership', () => { - it('should not be callable by non-owners', async () => { - await expect( - governor.connect(stranger).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should not be callable by L2 owner', async () => { - const governorOwner = await governor.owner() - assert.equal(governorOwner, owner.address) - - await expect( - governor.connect(owner).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by current L1 owner', async () => { - const currentL1Owner = await governor.l1Owner() - await expect( - governor - .connect(crossdomainMessenger) - .transferL1Ownership(newL1OwnerAddress), - ) - .to.emit(governor, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, newL1OwnerAddress) - }) - - it('should be callable by current L1 owner to zero address', async () => { - const currentL1Owner = await governor.l1Owner() - await expect( - governor - .connect(crossdomainMessenger) - .transferL1Ownership(ethers.constants.AddressZero), - ) - .to.emit(governor, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, ethers.constants.AddressZero) - }) - }) - - describe('#acceptL1Ownership', () => { - it('should not be callable by non pending-owners', async () => { - await expect( - governor.connect(crossdomainMessenger).acceptL1Ownership(), - ).to.be.revertedWith('Must be proposed L1 owner') - }) - - it('should be callable by pending L1 owner', async () => { - const currentL1Owner = await governor.l1Owner() - await governor - .connect(crossdomainMessenger) - .transferL1Ownership(newL1OwnerAddress) - await expect( - governor.connect(newOwnerCrossdomainMessenger).acceptL1Ownership(), - ) - .to.emit(governor, 'L1OwnershipTransferred') - .withArgs(currentL1Owner, newL1OwnerAddress) - - const updatedL1Owner = await governor.l1Owner() - assert.equal(updatedL1Owner, newL1OwnerAddress) - }) - }) -}) - -// Multisend contract helpers - -/** - * Encodes an underlying transaction for the Multisend contract - * - * @param operation 0 for CALL, 1 for DELEGATECALL - * @param to tx target address - * @param value tx value - * @param data tx data - */ -export function encodeTxData( - operation: number, - to: string, - value: number, - data: string, -): string { - let dataBuffer = Buffer.from(stripHexPrefix(data), 'hex') - const types = ['uint8', 'address', 'uint256', 'uint256', 'bytes'] - const values = [operation, to, value, dataBuffer.length, dataBuffer] - let encoded = ethers.utils.solidityPack(types, values) - return stripHexPrefix(encoded) -} - -/** - * Encodes a Multisend call - * - * @param MultisendInterface Ethers Interface object of the Multisend contract - * @param transactions one or more transactions to include in the Multisend call - * @param to tx target address - * @param value tx value - * @param data tx data - */ -export function encodeMultisendData( - MultisendInterface: etherslib.utils.Interface, - transactions: { to: string; value: number; data: string }[], -): string { - let nestedTransactionData = '0x' - for (let transaction of transactions) { - nestedTransactionData += encodeTxData( - 0, - transaction.to, - transaction.value, - transaction.data, - ) - } - const encodedMultisendFnData = MultisendInterface.encodeFunctionData( - 'multiSend', - [nestedTransactionData], - ) - return encodedMultisendFnData -} diff --git a/contracts/test/v0.8/L2EP/ArbitrumSequencerUptimeFeed.test.ts b/contracts/test/v0.8/L2EP/ArbitrumSequencerUptimeFeed.test.ts deleted file mode 100644 index 5b101a34a31..00000000000 --- a/contracts/test/v0.8/L2EP/ArbitrumSequencerUptimeFeed.test.ts +++ /dev/null @@ -1,415 +0,0 @@ -import { ethers, network } from 'hardhat' -import { BigNumber, Contract } from 'ethers' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' - -describe('ArbitrumSequencerUptimeFeed', () => { - let flags: Contract - let arbitrumSequencerUptimeFeed: Contract - let accessController: Contract - let uptimeFeedConsumer: Contract - let deployer: SignerWithAddress - let l1Owner: SignerWithAddress - let l2Messenger: SignerWithAddress - const gasUsedDeviation = 100 - - before(async () => { - const accounts = await ethers.getSigners() - deployer = accounts[0] - l1Owner = accounts[1] - const dummy = accounts[2] - const l2MessengerAddress = ethers.utils.getAddress( - BigNumber.from(l1Owner.address) - .add('0x1111000000000000000000000000000000001111') - .toHexString(), - ) - // Pretend we're on L2 - await network.provider.request({ - method: 'hardhat_impersonateAccount', - params: [l2MessengerAddress], - }) - l2Messenger = await ethers.getSigner(l2MessengerAddress) - // Credit the L2 messenger with some ETH - await dummy.sendTransaction({ - to: l2Messenger.address, - value: (await dummy.getBalance()).sub(ethers.utils.parseEther('0.1')), - }) - }) - - beforeEach(async () => { - const accessControllerFactory = await ethers.getContractFactory( - 'src/v0.8/shared/access/SimpleWriteAccessController.sol:SimpleWriteAccessController', - deployer, - ) - accessController = await accessControllerFactory.deploy() - - const flagsHistoryFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/Flags.sol:Flags', - deployer, - ) - flags = await flagsHistoryFactory.deploy( - accessController.address, - accessController.address, - ) - await accessController.addAccess(flags.address) - - const arbitrumSequencerStatusRecorderFactory = - await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol:ArbitrumSequencerUptimeFeed', - deployer, - ) - arbitrumSequencerUptimeFeed = - await arbitrumSequencerStatusRecorderFactory.deploy( - flags.address, - l1Owner.address, - ) - // Required for ArbitrumSequencerUptimeFeed to raise/lower flags - await accessController.addAccess(arbitrumSequencerUptimeFeed.address) - // Required for ArbitrumSequencerUptimeFeed to read flags - await flags.addAccess(arbitrumSequencerUptimeFeed.address) - - // Deployer requires access to invoke initialize - await accessController.addAccess(deployer.address) - // Once ArbitrumSequencerUptimeFeed has access, we can initialise the 0th aggregator round - const initTx = await arbitrumSequencerUptimeFeed - .connect(deployer) - .initialize() - await expect(initTx).to.emit(arbitrumSequencerUptimeFeed, 'Initialized') - - // Mock consumer - const statusFeedConsumerFactory = await ethers.getContractFactory( - 'src/v0.8/tests/FeedConsumer.sol:FeedConsumer', - deployer, - ) - uptimeFeedConsumer = await statusFeedConsumerFactory.deploy( - arbitrumSequencerUptimeFeed.address, - ) - }) - - describe('constants', () => { - it('should have the correct value for FLAG_L2_SEQ_OFFLINE', async () => { - const flag: string = - await arbitrumSequencerUptimeFeed.FLAG_L2_SEQ_OFFLINE() - expect(flag.toLowerCase()).to.equal( - '0xa438451d6458044c3c8cd2f6f31c91ac882a6d91', - ) - }) - }) - - describe('#updateStatus', () => { - it(`should update status when status has changed and incoming timestamp is newer than the latest`, async () => { - let timestamp = await arbitrumSequencerUptimeFeed.latestTimestamp() - let tx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(arbitrumSequencerUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(1) - - // Submit another status update, same status, newer timestamp, should ignore - tx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp.add(1000)) - await expect(tx).not.to.emit(arbitrumSequencerUptimeFeed, 'AnswerUpdated') - await expect(tx).to.emit(arbitrumSequencerUptimeFeed, 'UpdateIgnored') - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal('1') - expect(await arbitrumSequencerUptimeFeed.latestTimestamp()).to.equal( - timestamp, - ) - - // Submit another status update, different status, newer timestamp should update - timestamp = timestamp.add(2000) - tx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - await expect(tx) - .to.emit(arbitrumSequencerUptimeFeed, 'AnswerUpdated') - .withArgs(0, 3 /** roundId */, timestamp) - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(0) - expect(await arbitrumSequencerUptimeFeed.latestTimestamp()).to.equal( - timestamp, - ) - }) - - it(`should update status when status has changed and incoming timestamp is the same as latest`, async () => { - const timestamp = await arbitrumSequencerUptimeFeed.latestTimestamp() - let tx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(arbitrumSequencerUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(1) - - // Submit another status update, same status, same timestamp, should ignore - tx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx).not.to.emit(arbitrumSequencerUptimeFeed, 'AnswerUpdated') - await expect(tx).to.emit(arbitrumSequencerUptimeFeed, 'UpdateIgnored') - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal('1') - expect(await arbitrumSequencerUptimeFeed.latestTimestamp()).to.equal( - timestamp, - ) - - // Submit another status update, different status, same timestamp should update - tx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - await expect(tx) - .to.emit(arbitrumSequencerUptimeFeed, 'AnswerUpdated') - .withArgs(0, 3 /** roundId */, timestamp) - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(0) - expect(await arbitrumSequencerUptimeFeed.latestTimestamp()).to.equal( - timestamp, - ) - }) - - it('should ignore out-of-order updates', async () => { - const timestamp = ( - await arbitrumSequencerUptimeFeed.latestTimestamp() - ).add(10_000) - // Update status - let tx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(arbitrumSequencerUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(1) - - // Update with different status, but stale timestamp, should be ignored - const staleTimestamp = timestamp.sub(1000) - tx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(false, staleTimestamp) - await expect(tx).to.not.emit(arbitrumSequencerUptimeFeed, 'AnswerUpdated') - await expect(tx).to.emit(arbitrumSequencerUptimeFeed, 'UpdateIgnored') - }) - }) - - describe('AggregatorV3Interface', () => { - it('should return valid answer from getRoundData and latestRoundData', async () => { - let [roundId, answer, startedAt, updatedAt, answeredInRound] = - await arbitrumSequencerUptimeFeed.latestRoundData() - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - expect(answeredInRound).to.equal(roundId) - expect(startedAt).to.equal(updatedAt) // startedAt = updatedAt = timestamp - - // Submit status update with different status and newer timestamp, should update - const timestamp = (startedAt as BigNumber).add(1000) - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - ;[roundId, answer, startedAt, updatedAt, answeredInRound] = - await arbitrumSequencerUptimeFeed.getRoundData(2) - expect(roundId).to.equal(2) - expect(answer).to.equal(1) - expect(answeredInRound).to.equal(roundId) - expect(startedAt).to.equal(timestamp) - expect(updatedAt).to.equal(startedAt) - - // Check that last round is still returning the correct data - ;[roundId, answer, startedAt, updatedAt, answeredInRound] = - await arbitrumSequencerUptimeFeed.getRoundData(1) - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - expect(answeredInRound).to.equal(roundId) - expect(startedAt).to.equal(updatedAt) - - // Assert latestRoundData corresponds to latest round id - expect(await arbitrumSequencerUptimeFeed.getRoundData(2)).to.deep.equal( - await arbitrumSequencerUptimeFeed.latestRoundData(), - ) - }) - - it('should return 0 from #getRoundData when round does not yet exist (future roundId)', async () => { - const [roundId, answer, startedAt, updatedAt, answeredInRound] = - await arbitrumSequencerUptimeFeed.getRoundData(2) - expect(roundId).to.equal(2) - expect(answer).to.equal(0) - expect(startedAt).to.equal(0) - expect(updatedAt).to.equal(0) - expect(answeredInRound).to.equal(2) - }) - }) - - describe('Protect reads on AggregatorV2V3Interface functions', () => { - it('should disallow reads on AggregatorV2V3Interface functions when consuming contract is not whitelisted', async () => { - // Sanity - consumer is not whitelisted - expect(await arbitrumSequencerUptimeFeed.checkEnabled()).to.be.true - expect( - await arbitrumSequencerUptimeFeed.hasAccess( - uptimeFeedConsumer.address, - '0x00', - ), - ).to.be.false - - // Assert reads are not possible from consuming contract - await expect(uptimeFeedConsumer.latestAnswer()).to.be.revertedWith( - 'No access', - ) - await expect(uptimeFeedConsumer.latestRoundData()).to.be.revertedWith( - 'No access', - ) - }) - - it('should allow reads on AggregatorV2V3Interface functions when consuming contract is whitelisted', async () => { - // Whitelist consumer - await arbitrumSequencerUptimeFeed.addAccess(uptimeFeedConsumer.address) - // Sanity - consumer is whitelisted - expect(await arbitrumSequencerUptimeFeed.checkEnabled()).to.be.true - expect( - await arbitrumSequencerUptimeFeed.hasAccess( - uptimeFeedConsumer.address, - '0x00', - ), - ).to.be.true - - // Assert reads are possible from consuming contract - expect(await uptimeFeedConsumer.latestAnswer()).to.be.equal('0') - const [roundId, answer] = await uptimeFeedConsumer.latestRoundData() - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - }) - }) - - describe('Gas costs', () => { - it('should consume a known amount of gas for updates @skip-coverage', async () => { - // Sanity - start at flag = 0 (`false`) - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(0) - let timestamp = await arbitrumSequencerUptimeFeed.latestTimestamp() - - // Gas for no update - timestamp = timestamp.add(1000) - const _noUpdateTx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - const noUpdateTx = await _noUpdateTx.wait(1) - // Assert no update - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(0) - expect(noUpdateTx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28300, - gasUsedDeviation, - ) - - // Gas for update - timestamp = timestamp.add(1000) - const _updateTx = await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - const updateTx = await _updateTx.wait(1) - // Assert update - expect(await arbitrumSequencerUptimeFeed.latestAnswer()).to.equal(1) - expect(updateTx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 93015, - gasUsedDeviation, - ) - }) - - describe('Aggregator interface', () => { - beforeEach(async () => { - const timestamp = ( - await arbitrumSequencerUptimeFeed.latestTimestamp() - ).add(1000) - // Initialise a round - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - }) - - it('should consume a known amount of gas for getRoundData(uint80) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .populateTransaction.getRoundData(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 31157, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestRoundData() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestRoundData(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28523, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestAnswer() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestAnswer(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28329, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestTimestamp() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestTimestamp(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28229, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestRound() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestRound(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28245, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for getAnswer(roundId) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .populateTransaction.getAnswer(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 30799, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for getTimestamp(roundId) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await arbitrumSequencerUptimeFeed - .connect(l2Messenger) - .populateTransaction.getTimestamp(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 30753, - gasUsedDeviation, - ) - }) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/ArbitrumValidator.test.ts b/contracts/test/v0.8/L2EP/ArbitrumValidator.test.ts deleted file mode 100644 index 232eea95839..00000000000 --- a/contracts/test/v0.8/L2EP/ArbitrumValidator.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { ethers } from 'hardhat' -import { BigNumber, BigNumberish, Contract, ContractFactory } from 'ethers' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { - deployMockContract, - MockContract, -} from '@ethereum-waffle/mock-contract' -/// Pick ABIs from compilation -// @ts-ignore -import { abi as arbitrumSequencerStatusRecorderAbi } from '../../../artifacts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol/ArbitrumSequencerUptimeFeed.json' -// @ts-ignore -import { abi as arbitrumInboxAbi } from '../../../artifacts/src/v0.8/vendor/arb-bridge-eth/v0.8.0-custom/contracts/bridge/interfaces/IInbox.sol/IInbox.json' -// @ts-ignore -import { abi as aggregatorAbi } from '../../../artifacts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol/AggregatorV2V3Interface.json' - -const truncateBigNumToAddress = (num: BigNumberish) => { - // Pad, then slice off '0x' prefix - const hexWithoutPrefix = BigNumber.from(num).toHexString().slice(2) - // Ethereum address is 20B -> 40 hex chars w/o 0x prefix - const truncated = hexWithoutPrefix - .split('') - .reverse() - .slice(0, 40) - .reverse() - .join('') - return '0x' + truncated -} - -describe('ArbitrumValidator', () => { - const MAX_GAS = BigNumber.from(1_000_000) - const GAS_PRICE_BID = BigNumber.from(1_000_000) - const BASE_FEE = BigNumber.from(14_000_000_000) - /** Fake L2 target */ - const L2_SEQ_STATUS_RECORDER_ADDRESS = - '0x491B1dDA0A8fa069bbC1125133A975BF4e85a91b' - let arbitrumValidator: Contract - let accessController: Contract - let arbitrumSequencerStatusRecorderFactory: ContractFactory - let mockArbitrumInbox: Contract - let l1GasFeed: MockContract - let deployer: SignerWithAddress - let eoaValidator: SignerWithAddress - let arbitrumValidatorL2Address: string - before(async () => { - const accounts = await ethers.getSigners() - deployer = accounts[0] - eoaValidator = accounts[1] - }) - - beforeEach(async () => { - const accessControllerFactory = await ethers.getContractFactory( - 'src/v0.8/shared/access/SimpleWriteAccessController.sol:SimpleWriteAccessController', - deployer, - ) - accessController = await accessControllerFactory.deploy() - - // Required for building the calldata - arbitrumSequencerStatusRecorderFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol:ArbitrumSequencerUptimeFeed', - deployer, - ) - l1GasFeed = await deployMockContract(deployer as any, aggregatorAbi) - await l1GasFeed.mock.latestRoundData.returns( - '73786976294838220258' /** roundId */, - '96800000000' /** answer */, - '163826896' /** startedAt */, - '1638268960' /** updatedAt */, - '73786976294838220258' /** answeredInRound */, - ) - // Arbitrum Inbox contract on L1 - const mockArbitrumInboxFactory = await ethers.getContractFactory( - 'src/v0.8/tests/MockArbitrumInbox.sol:MockArbitrumInbox', - ) - mockArbitrumInbox = await mockArbitrumInboxFactory.deploy() - - // Contract under test - const arbitrumValidatorFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol:ArbitrumValidator', - deployer, - ) - arbitrumValidator = await arbitrumValidatorFactory.deploy( - mockArbitrumInbox.address, - L2_SEQ_STATUS_RECORDER_ADDRESS, - accessController.address, - MAX_GAS /** L1 gas bid */, - GAS_PRICE_BID /** L2 gas bid */, - BASE_FEE, - l1GasFeed.address, - 0, - ) - // Transfer some ETH to the ArbitrumValidator contract - await deployer.sendTransaction({ - to: arbitrumValidator.address, - value: ethers.utils.parseEther('1.0'), - }) - arbitrumValidatorL2Address = ethers.utils.getAddress( - truncateBigNumToAddress( - BigNumber.from(arbitrumValidator.address).add( - '0x1111000000000000000000000000000000001111', - ), - ), - ) - }) - - describe('#validate', () => { - it('post sequencer offline', async () => { - await arbitrumValidator.addAccess(eoaValidator.address) - - const now = Math.ceil(Date.now() / 1000) + 1000 - await ethers.provider.send('evm_setNextBlockTimestamp', [now]) - const arbitrumSequencerStatusRecorderCallData = - arbitrumSequencerStatusRecorderFactory.interface.encodeFunctionData( - 'updateStatus', - [true, now], - ) - await expect(arbitrumValidator.connect(eoaValidator).validate(0, 0, 1, 1)) - .to.emit( - mockArbitrumInbox, - 'RetryableTicketNoRefundAliasRewriteCreated', - ) - .withArgs( - L2_SEQ_STATUS_RECORDER_ADDRESS, - 0, - '25312000000000', - arbitrumValidatorL2Address, - arbitrumValidatorL2Address, - MAX_GAS, - GAS_PRICE_BID, - arbitrumSequencerStatusRecorderCallData, - ) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/CrossDomainOwnable.test.ts b/contracts/test/v0.8/L2EP/CrossDomainOwnable.test.ts deleted file mode 100644 index 7d9d58cfbaa..00000000000 --- a/contracts/test/v0.8/L2EP/CrossDomainOwnable.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' - -let owner: SignerWithAddress -let stranger: SignerWithAddress -let l1OwnerAddress: string -let ownableFactory: ContractFactory -let ownable: Contract - -before(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - stranger = accounts[1] - l1OwnerAddress = owner.address - - // Contract factories - ownableFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/CrossDomainOwnable.sol:CrossDomainOwnable', - owner, - ) -}) - -describe('CrossDomainOwnable', () => { - beforeEach(async () => { - ownable = await ownableFactory.deploy(l1OwnerAddress) - }) - - describe('#constructor', () => { - it('should set the l1Owner correctly', async () => { - const response = await ownable.l1Owner() - assert.equal(response, l1OwnerAddress) - }) - }) - - describe('#transferL1Ownership', () => { - it('should not be callable by non-owners', async () => { - await expect( - ownable.connect(stranger).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Only callable by L1 owner') - }) - - it('should be callable by current L1 owner', async () => { - const currentL1Owner = await ownable.l1Owner() - await expect(ownable.transferL1Ownership(stranger.address)) - .to.emit(ownable, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, stranger.address) - }) - - it('should be callable by current L1 owner to zero address', async () => { - const currentL1Owner = await ownable.l1Owner() - await expect(ownable.transferL1Ownership(ethers.constants.AddressZero)) - .to.emit(ownable, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, ethers.constants.AddressZero) - }) - }) - - describe('#acceptL1Ownership', () => { - it('should not be callable by non pending-owners', async () => { - await expect( - ownable.connect(stranger).acceptL1Ownership(), - ).to.be.revertedWith('Only callable by proposed L1 owner') - }) - - it('should be callable by pending L1 owner', async () => { - const currentL1Owner = await ownable.l1Owner() - await ownable.transferL1Ownership(stranger.address) - await expect(ownable.connect(stranger).acceptL1Ownership()) - .to.emit(ownable, 'L1OwnershipTransferred') - .withArgs(currentL1Owner, stranger.address) - - const updatedL1Owner = await ownable.l1Owner() - assert.equal(updatedL1Owner, stranger.address) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/OptimismCrossDomainForwarder.test.ts b/contracts/test/v0.8/L2EP/OptimismCrossDomainForwarder.test.ts deleted file mode 100644 index 3b75b412bfd..00000000000 --- a/contracts/test/v0.8/L2EP/OptimismCrossDomainForwarder.test.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { publicAbi } from '../../test-helpers/helpers' - -let owner: SignerWithAddress -let stranger: SignerWithAddress -let l1OwnerAddress: string -let newL1OwnerAddress: string -let forwarderFactory: ContractFactory -let greeterFactory: ContractFactory -let crossDomainMessengerFactory: ContractFactory -let crossDomainMessenger: Contract -let forwarder: Contract -let greeter: Contract - -before(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - stranger = accounts[1] - - // forwarder config - l1OwnerAddress = owner.address - newL1OwnerAddress = stranger.address - - // Contract factories - forwarderFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/optimism/OptimismCrossDomainForwarder.sol:OptimismCrossDomainForwarder', - owner, - ) - greeterFactory = await ethers.getContractFactory( - 'src/v0.8/tests/Greeter.sol:Greeter', - owner, - ) - crossDomainMessengerFactory = await ethers.getContractFactory( - 'src/v0.8/vendor/MockOVMCrossDomainMessenger.sol:MockOVMCrossDomainMessenger', - ) -}) - -describe('OptimismCrossDomainForwarder', () => { - beforeEach(async () => { - crossDomainMessenger = - await crossDomainMessengerFactory.deploy(l1OwnerAddress) - forwarder = await forwarderFactory.deploy( - crossDomainMessenger.address, - l1OwnerAddress, - ) - greeter = await greeterFactory.deploy(forwarder.address) - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(forwarder, [ - 'typeAndVersion', - 'crossDomainMessenger', - 'forward', - 'l1Owner', - 'transferL1Ownership', - 'acceptL1Ownership', - // ConfirmedOwner methods: - 'owner', - 'transferOwnership', - 'acceptOwnership', - ]) - }) - - describe('#constructor', () => { - it('should set the owner correctly', async () => { - const response = await forwarder.owner() - assert.equal(response, owner.address) - }) - - it('should set the l1Owner correctly', async () => { - const response = await forwarder.l1Owner() - assert.equal(response, l1OwnerAddress) - }) - - it('should set the crossdomain messenger correctly', async () => { - const response = await forwarder.crossDomainMessenger() - assert.equal(response, crossDomainMessenger.address) - }) - - it('should set the typeAndVersion correctly', async () => { - const response = await forwarder.typeAndVersion() - assert.equal(response, 'OptimismCrossDomainForwarder 1.0.0') - }) - }) - - describe('#forward', () => { - it('should not be callable by unknown address', async () => { - await expect( - forwarder.connect(stranger).forward(greeter.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'forward', - [greeter.address, setGreetingData], - ) - await crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(forwarder.address, forwardData, 0) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should revert when contract call reverts', async () => { - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [''], - ) - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'forward', - [greeter.address, setGreetingData], - ) - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(forwarder.address, forwardData, 0), - ).to.be.revertedWith('Invalid greeting length') - }) - }) - - describe('#transferL1Ownership', () => { - it('should not be callable by non-owners', async () => { - await expect( - forwarder.connect(stranger).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should not be callable by L2 owner', async () => { - const forwarderOwner = await forwarder.owner() - assert.equal(forwarderOwner, owner.address) - - await expect( - forwarder.connect(owner).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by current L1 owner', async () => { - const currentL1Owner = await forwarder.l1Owner() - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [newL1OwnerAddress], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(forwarder.address, forwardData, 0), - ) - .to.emit(forwarder, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, newL1OwnerAddress) - }) - - it('should be callable by current L1 owner to zero address', async () => { - const currentL1Owner = await forwarder.l1Owner() - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [ethers.constants.AddressZero], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(forwarder.address, forwardData, 0), - ) - .to.emit(forwarder, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, ethers.constants.AddressZero) - }) - }) - - describe('#acceptL1Ownership', () => { - it('should not be callable by non pending-owners', async () => { - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'acceptL1Ownership', - [], - ) - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(forwarder.address, forwardData, 0), - ).to.be.revertedWith('Must be proposed L1 owner') - }) - - it('should be callable by pending L1 owner', async () => { - const currentL1Owner = await forwarder.l1Owner() - - // Transfer ownership - const forwardTransferData = forwarderFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [newL1OwnerAddress], - ) - await crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(forwarder.address, forwardTransferData, 0) - - const forwardAcceptData = forwarderFactory.interface.encodeFunctionData( - 'acceptL1Ownership', - [], - ) - // Simulate cross-chain message from another sender - await crossDomainMessenger._setMockMessageSender(newL1OwnerAddress) - - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(forwarder.address, forwardAcceptData, 0), - ) - .to.emit(forwarder, 'L1OwnershipTransferred') - .withArgs(currentL1Owner, newL1OwnerAddress) - - const updatedL1Owner = await forwarder.l1Owner() - assert.equal(updatedL1Owner, newL1OwnerAddress) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/OptimismCrossDomainGovernor.test.ts b/contracts/test/v0.8/L2EP/OptimismCrossDomainGovernor.test.ts deleted file mode 100644 index 7fbd0f9aa23..00000000000 --- a/contracts/test/v0.8/L2EP/OptimismCrossDomainGovernor.test.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import etherslib, { Contract, ContractFactory } from 'ethers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { publicAbi, stripHexPrefix } from '../../test-helpers/helpers' - -let owner: SignerWithAddress -let stranger: SignerWithAddress -let l1OwnerAddress: string -let newL1OwnerAddress: string -let governorFactory: ContractFactory -let greeterFactory: ContractFactory -let multisendFactory: ContractFactory -let crossDomainMessengerFactory: ContractFactory -let crossDomainMessenger: Contract -let governor: Contract -let greeter: Contract -let multisend: Contract - -before(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - stranger = accounts[1] - - // governor config - l1OwnerAddress = owner.address - newL1OwnerAddress = stranger.address - - // Contract factories - governorFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/optimism/OptimismCrossDomainGovernor.sol:OptimismCrossDomainGovernor', - owner, - ) - greeterFactory = await ethers.getContractFactory( - 'src/v0.8/tests/Greeter.sol:Greeter', - owner, - ) - multisendFactory = await ethers.getContractFactory( - 'src/v0.8/vendor/MultiSend.sol:MultiSend', - owner, - ) - crossDomainMessengerFactory = await ethers.getContractFactory( - 'src/v0.8/vendor/MockOVMCrossDomainMessenger.sol:MockOVMCrossDomainMessenger', - ) -}) - -describe('OptimismCrossDomainGovernor', () => { - beforeEach(async () => { - crossDomainMessenger = - await crossDomainMessengerFactory.deploy(l1OwnerAddress) - governor = await governorFactory.deploy( - crossDomainMessenger.address, - l1OwnerAddress, - ) - greeter = await greeterFactory.deploy(governor.address) - multisend = await multisendFactory.deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(governor, [ - 'typeAndVersion', - 'crossDomainMessenger', - 'forward', - 'forwardDelegate', - 'l1Owner', - 'transferL1Ownership', - 'acceptL1Ownership', - // ConfirmedOwner methods: - 'owner', - 'transferOwnership', - 'acceptOwnership', - ]) - }) - - describe('#constructor', () => { - it('should set the owner correctly', async () => { - const response = await governor.owner() - assert.equal(response, owner.address) - }) - - it('should set the l1Owner correctly', async () => { - const response = await governor.l1Owner() - assert.equal(response, l1OwnerAddress) - }) - - it('should set the crossdomain messenger correctly', async () => { - const response = await governor.crossDomainMessenger() - assert.equal(response, crossDomainMessenger.address) - }) - - it('should set the typeAndVersion correctly', async () => { - const response = await governor.typeAndVersion() - assert.equal(response, 'OptimismCrossDomainGovernor 1.0.0') - }) - }) - - describe('#forward', () => { - it('should not be callable by unknown address', async () => { - await expect( - governor.connect(stranger).forward(greeter.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger or owner') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - const forwardData = governorFactory.interface.encodeFunctionData( - 'forward', - [greeter.address, setGreetingData], - ) - await crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardData, 0) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should be callable by L2 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - await governor.connect(owner).forward(greeter.address, setGreetingData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should revert when contract call reverts', async () => { - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [''], - ) - const forwardData = governorFactory.interface.encodeFunctionData( - 'forward', - [greeter.address, setGreetingData], - ) - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardData, 0), - ).to.be.revertedWith('Invalid greeting length') - }) - }) - - describe('#forwardDelegate', () => { - it('should not be callable by unknown address', async () => { - await expect( - governor.connect(stranger).forwardDelegate(multisend.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger or owner') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'bar', - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - const forwardData = governorFactory.interface.encodeFunctionData( - 'forwardDelegate', - [multisend.address, multisendData], - ) - - await crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardData, 0) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, 'bar') - }) - - it('should be callable by L2 owner', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'bar', - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - await governor - .connect(owner) - .forwardDelegate(multisend.address, multisendData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, 'bar') - }) - - it('should revert batch when one call fails', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - '', // should revert - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - const forwardData = governorFactory.interface.encodeFunctionData( - 'forwardDelegate', - [multisend.address, multisendData], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardData, 0), - ).to.be.revertedWith('Governor delegatecall reverted') - - const greeting = await greeter.greeting() - assert.equal(greeting, '') // Unchanged - }) - - it('should bubble up revert when contract call reverts', async () => { - const triggerRevertData = - greeterFactory.interface.encodeFunctionData('triggerRevert') - const forwardData = governorFactory.interface.encodeFunctionData( - 'forwardDelegate', - [greeter.address, triggerRevertData], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardData, 0), - ).to.be.revertedWith('Greeter: revert triggered') - }) - }) - - describe('#transferL1Ownership', () => { - it('should not be callable by non-owners', async () => { - await expect( - governor.connect(stranger).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should not be callable by L2 owner', async () => { - const governorOwner = await governor.owner() - assert.equal(governorOwner, owner.address) - - await expect( - governor.connect(owner).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by current L1 owner', async () => { - const currentL1Owner = await governor.l1Owner() - const forwardData = governorFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [newL1OwnerAddress], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardData, 0), - ) - .to.emit(governor, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, newL1OwnerAddress) - }) - - it('should be callable by current L1 owner to zero address', async () => { - const currentL1Owner = await governor.l1Owner() - const forwardData = governorFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [ethers.constants.AddressZero], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardData, 0), - ) - .to.emit(governor, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, ethers.constants.AddressZero) - }) - }) - - describe('#acceptL1Ownership', () => { - it('should not be callable by non pending-owners', async () => { - const forwardData = governorFactory.interface.encodeFunctionData( - 'acceptL1Ownership', - [], - ) - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardData, 0), - ).to.be.revertedWith('Must be proposed L1 owner') - }) - - it('should be callable by pending L1 owner', async () => { - const currentL1Owner = await governor.l1Owner() - - // Transfer ownership - const forwardTransferData = governorFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [newL1OwnerAddress], - ) - await crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardTransferData, 0) - - const forwardAcceptData = governorFactory.interface.encodeFunctionData( - 'acceptL1Ownership', - [], - ) - // Simulate cross-chain message from another sender - await crossDomainMessenger._setMockMessageSender(newL1OwnerAddress) - - await expect( - crossDomainMessenger // Simulate cross-chain OVM message - .connect(stranger) - .sendMessage(governor.address, forwardAcceptData, 0), - ) - .to.emit(governor, 'L1OwnershipTransferred') - .withArgs(currentL1Owner, newL1OwnerAddress) - - const updatedL1Owner = await governor.l1Owner() - assert.equal(updatedL1Owner, newL1OwnerAddress) - }) - }) -}) - -// Multisend contract helpers - -/** - * Encodes an underlying transaction for the Multisend contract - * - * @param operation 0 for CALL, 1 for DELEGATECALL - * @param to tx target address - * @param value tx value - * @param data tx data - */ -export function encodeTxData( - operation: number, - to: string, - value: number, - data: string, -): string { - let dataBuffer = Buffer.from(stripHexPrefix(data), 'hex') - const types = ['uint8', 'address', 'uint256', 'uint256', 'bytes'] - const values = [operation, to, value, dataBuffer.length, dataBuffer] - let encoded = ethers.utils.solidityPack(types, values) - return stripHexPrefix(encoded) -} - -/** - * Encodes a Multisend call - * - * @param MultisendInterface Ethers Interface object of the Multisend contract - * @param transactions one or more transactions to include in the Multisend call - * @param to tx target address - * @param value tx value - * @param data tx data - */ -export function encodeMultisendData( - MultisendInterface: etherslib.utils.Interface, - transactions: { to: string; value: number; data: string }[], -): string { - let nestedTransactionData = '0x' - for (let transaction of transactions) { - nestedTransactionData += encodeTxData( - 0, - transaction.to, - transaction.value, - transaction.data, - ) - } - const encodedMultisendFnData = MultisendInterface.encodeFunctionData( - 'multiSend', - [nestedTransactionData], - ) - return encodedMultisendFnData -} diff --git a/contracts/test/v0.8/L2EP/OptimismSequencerUptimeFeed.test.ts b/contracts/test/v0.8/L2EP/OptimismSequencerUptimeFeed.test.ts deleted file mode 100644 index 32e17b10770..00000000000 --- a/contracts/test/v0.8/L2EP/OptimismSequencerUptimeFeed.test.ts +++ /dev/null @@ -1,426 +0,0 @@ -import { ethers, network } from 'hardhat' -import { BigNumber, Contract } from 'ethers' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' - -describe('OptimismSequencerUptimeFeed', () => { - let l2CrossDomainMessenger: Contract - let optimismUptimeFeed: Contract - let uptimeFeedConsumer: Contract - let deployer: SignerWithAddress - let l1Owner: SignerWithAddress - let l2Messenger: SignerWithAddress - let dummy: SignerWithAddress - const gasUsedDeviation = 100 - const initialStatus = 0 - - before(async () => { - const accounts = await ethers.getSigners() - deployer = accounts[0] - l1Owner = accounts[1] - dummy = accounts[3] - - const l2CrossDomainMessengerFactory = await ethers.getContractFactory( - 'src/v0.8/tests/MockOptimismL2CrossDomainMessenger.sol:MockOptimismL2CrossDomainMessenger', - deployer, - ) - - l2CrossDomainMessenger = await l2CrossDomainMessengerFactory.deploy() - - // Pretend we're on L2 - await network.provider.request({ - method: 'hardhat_impersonateAccount', - params: [l2CrossDomainMessenger.address], - }) - l2Messenger = await ethers.getSigner(l2CrossDomainMessenger.address) - // Credit the L2 messenger with some ETH - await dummy.sendTransaction({ - to: l2Messenger.address, - value: ethers.utils.parseEther('10'), - }) - }) - - beforeEach(async () => { - const optimismSequencerStatusRecorderFactory = - await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol:OptimismSequencerUptimeFeed', - deployer, - ) - optimismUptimeFeed = await optimismSequencerStatusRecorderFactory.deploy( - l1Owner.address, - l2CrossDomainMessenger.address, - initialStatus, - ) - - // Set mock sender in mock L2 messenger contract - await l2CrossDomainMessenger.setSender(l1Owner.address) - - // Mock consumer - const statusFeedConsumerFactory = await ethers.getContractFactory( - 'src/v0.8/tests/FeedConsumer.sol:FeedConsumer', - deployer, - ) - uptimeFeedConsumer = await statusFeedConsumerFactory.deploy( - optimismUptimeFeed.address, - ) - }) - - describe('constructor', () => { - it('should have been deployed with the correct initial state', async () => { - const l1Sender = await optimismUptimeFeed.l1Sender() - expect(l1Sender).to.equal(l1Owner.address) - const { roundId, answer } = await optimismUptimeFeed.latestRoundData() - expect(roundId).to.equal(1) - expect(answer).to.equal(initialStatus) - }) - }) - - describe('#updateStatus', () => { - it('should revert if called by an address that is not the L2 Cross Domain Messenger', async () => { - let timestamp = await optimismUptimeFeed.latestTimestamp() - expect( - optimismUptimeFeed.connect(dummy).updateStatus(true, timestamp), - ).to.be.revertedWith('InvalidSender') - }) - - it('should revert if called by an address that is not the L2 Cross Domain Messenger and is not the L1 sender', async () => { - let timestamp = await optimismUptimeFeed.latestTimestamp() - await l2CrossDomainMessenger.setSender(dummy.address) - expect( - optimismUptimeFeed.connect(dummy).updateStatus(true, timestamp), - ).to.be.revertedWith('InvalidSender') - }) - - it(`should update status when status has not changed and incoming timestamp is the same as latest`, async () => { - const timestamp = await optimismUptimeFeed.latestTimestamp() - let tx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(optimismUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await optimismUptimeFeed.latestAnswer()).to.equal(1) - - const latestRoundBeforeUpdate = await optimismUptimeFeed.latestRoundData() - - tx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp.add(200)) - - // Submit another status update with the same status - const latestBlock = await ethers.provider.getBlock('latest') - - await expect(tx) - .to.emit(optimismUptimeFeed, 'RoundUpdated') - .withArgs(1, latestBlock.timestamp) - expect(await optimismUptimeFeed.latestAnswer()).to.equal(1) - expect(await optimismUptimeFeed.latestTimestamp()).to.equal(timestamp) - - // Verify that latest round has been properly updated - const latestRoundDataAfterUpdate = - await optimismUptimeFeed.latestRoundData() - expect(latestRoundDataAfterUpdate.roundId).to.equal( - latestRoundBeforeUpdate.roundId, - ) - expect(latestRoundDataAfterUpdate.answer).to.equal( - latestRoundBeforeUpdate.answer, - ) - expect(latestRoundDataAfterUpdate.startedAt).to.equal( - latestRoundBeforeUpdate.startedAt, - ) - expect(latestRoundDataAfterUpdate.answeredInRound).to.equal( - latestRoundBeforeUpdate.answeredInRound, - ) - expect(latestRoundDataAfterUpdate.updatedAt).to.equal( - latestBlock.timestamp, - ) - }) - - it(`should update status when status has changed and incoming timestamp is newer than the latest`, async () => { - let timestamp = await optimismUptimeFeed.latestTimestamp() - let tx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(optimismUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await optimismUptimeFeed.latestAnswer()).to.equal(1) - - // Submit another status update, different status, newer timestamp should update - timestamp = timestamp.add(2000) - tx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - await expect(tx) - .to.emit(optimismUptimeFeed, 'AnswerUpdated') - .withArgs(0, 3 /** roundId */, timestamp) - expect(await optimismUptimeFeed.latestAnswer()).to.equal(0) - expect(await optimismUptimeFeed.latestTimestamp()).to.equal(timestamp) - }) - - it(`should update status when status has changed and incoming timestamp is the same as latest`, async () => { - const timestamp = await optimismUptimeFeed.latestTimestamp() - let tx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(optimismUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await optimismUptimeFeed.latestAnswer()).to.equal(1) - - // Submit another status update, different status, same timestamp should update - tx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - await expect(tx) - .to.emit(optimismUptimeFeed, 'AnswerUpdated') - .withArgs(0, 3 /** roundId */, timestamp) - expect(await optimismUptimeFeed.latestAnswer()).to.equal(0) - expect(await optimismUptimeFeed.latestTimestamp()).to.equal(timestamp) - }) - - it('should ignore out-of-order updates', async () => { - const timestamp = (await optimismUptimeFeed.latestTimestamp()).add(10_000) - // Update status - let tx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(optimismUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await optimismUptimeFeed.latestAnswer()).to.equal(1) - - // Update with different status, but stale timestamp, should be ignored - const staleTimestamp = timestamp.sub(1000) - tx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(false, staleTimestamp) - await expect(tx).to.not.emit(optimismUptimeFeed, 'AnswerUpdated') - await expect(tx).to.emit(optimismUptimeFeed, 'UpdateIgnored') - }) - }) - - describe('AggregatorV3Interface', () => { - it('should return valid answer from getRoundData and latestRoundData', async () => { - let [roundId, answer, startedAt, updatedAt, answeredInRound] = - await optimismUptimeFeed.latestRoundData() - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - expect(answeredInRound).to.equal(roundId) - - // Submit status update with different status and newer timestamp, should update - const timestamp = (startedAt as BigNumber).add(1000) - await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - ;[roundId, answer, startedAt, updatedAt, answeredInRound] = - await optimismUptimeFeed.getRoundData(2) - expect(roundId).to.equal(2) - expect(answer).to.equal(1) - expect(answeredInRound).to.equal(roundId) - expect(startedAt).to.equal(timestamp) - expect(updatedAt).to.equal(updatedAt) - - // Check that last round is still returning the correct data - ;[roundId, answer, startedAt, updatedAt, answeredInRound] = - await optimismUptimeFeed.getRoundData(1) - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - expect(answeredInRound).to.equal(roundId) - expect(startedAt).to.equal(startedAt) - expect(updatedAt).to.equal(updatedAt) - - // Assert latestRoundData corresponds to latest round id - expect(await optimismUptimeFeed.getRoundData(2)).to.deep.equal( - await optimismUptimeFeed.latestRoundData(), - ) - }) - - it('should revert from #getRoundData when round does not yet exist (future roundId)', async () => { - expect(optimismUptimeFeed.getRoundData(2)).to.be.revertedWith( - 'NoDataPresent()', - ) - }) - - it('should revert from #getAnswer when round does not yet exist (future roundId)', async () => { - expect(optimismUptimeFeed.getAnswer(2)).to.be.revertedWith( - 'NoDataPresent()', - ) - }) - - it('should revert from #getTimestamp when round does not yet exist (future roundId)', async () => { - expect(optimismUptimeFeed.getTimestamp(2)).to.be.revertedWith( - 'NoDataPresent()', - ) - }) - }) - - describe('Protect reads on AggregatorV2V3Interface functions', () => { - it('should disallow reads on AggregatorV2V3Interface functions when consuming contract is not whitelisted', async () => { - // Sanity - consumer is not whitelisted - expect(await optimismUptimeFeed.checkEnabled()).to.be.true - expect( - await optimismUptimeFeed.hasAccess(uptimeFeedConsumer.address, '0x00'), - ).to.be.false - - // Assert reads are not possible from consuming contract - await expect(uptimeFeedConsumer.latestAnswer()).to.be.revertedWith( - 'No access', - ) - await expect(uptimeFeedConsumer.latestRoundData()).to.be.revertedWith( - 'No access', - ) - }) - - it('should allow reads on AggregatorV2V3Interface functions when consuming contract is whitelisted', async () => { - // Whitelist consumer - await optimismUptimeFeed.addAccess(uptimeFeedConsumer.address) - // Sanity - consumer is whitelisted - expect(await optimismUptimeFeed.checkEnabled()).to.be.true - expect( - await optimismUptimeFeed.hasAccess(uptimeFeedConsumer.address, '0x00'), - ).to.be.true - - // Assert reads are possible from consuming contract - expect(await uptimeFeedConsumer.latestAnswer()).to.be.equal('0') - const [roundId, answer] = await uptimeFeedConsumer.latestRoundData() - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - }) - }) - - describe('Gas costs', () => { - it('should consume a known amount of gas for updates @skip-coverage', async () => { - // Sanity - start at flag = 0 (`false`) - expect(await optimismUptimeFeed.latestAnswer()).to.equal(0) - let timestamp = await optimismUptimeFeed.latestTimestamp() - - // Gas for no update - timestamp = timestamp.add(1000) - const _noUpdateTx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - const noUpdateTx = await _noUpdateTx.wait(1) - // Assert no update - expect(await optimismUptimeFeed.latestAnswer()).to.equal(0) - expect(noUpdateTx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 38594, - gasUsedDeviation, - ) - - // Gas for update - timestamp = timestamp.add(1000) - const _updateTx = await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - const updateTx = await _updateTx.wait(1) - // Assert update - expect(await optimismUptimeFeed.latestAnswer()).to.equal(1) - expect(updateTx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 60170, - gasUsedDeviation, - ) - }) - - describe('Aggregator interface', () => { - beforeEach(async () => { - const timestamp = (await optimismUptimeFeed.latestTimestamp()).add(1000) - // Initialise a round - await optimismUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - }) - - it('should consume a known amount of gas for getRoundData(uint80) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await optimismUptimeFeed - .connect(l2Messenger) - .populateTransaction.getRoundData(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 30952, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestRoundData() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await optimismUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestRoundData(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28523, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestAnswer() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await optimismUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestAnswer(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28329, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestTimestamp() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await optimismUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestTimestamp(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28229, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestRound() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await optimismUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestRound(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28245, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for getAnswer(roundId) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await optimismUptimeFeed - .connect(l2Messenger) - .populateTransaction.getAnswer(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 30682, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for getTimestamp(roundId) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await optimismUptimeFeed - .connect(l2Messenger) - .populateTransaction.getTimestamp(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 30570, - gasUsedDeviation, - ) - }) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/OptimismValidator.test.ts b/contracts/test/v0.8/L2EP/OptimismValidator.test.ts deleted file mode 100644 index ee69211f56d..00000000000 --- a/contracts/test/v0.8/L2EP/OptimismValidator.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { ethers } from 'hardhat' -import { BigNumber, Contract, ContractFactory } from 'ethers' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -/// Pick ABIs from compilation -// @ts-ignore -import { abi as optimismSequencerStatusRecorderAbi } from '../../../artifacts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol/OptimismSequencerUptimeFeed.json' -// @ts-ignore -import { abi as optimismL1CrossDomainMessengerAbi } from '@eth-optimism/contracts/artifacts/contracts/L1/messaging/L1CrossDomainMessenger.sol' -// @ts-ignore -import { abi as aggregatorAbi } from '../../../artifacts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol/AggregatorV2V3Interface.json' - -describe('OptimismValidator', () => { - const GAS_LIMIT = BigNumber.from(1_900_000) - /** Fake L2 target */ - const L2_SEQ_STATUS_RECORDER_ADDRESS = - '0x491B1dDA0A8fa069bbC1125133A975BF4e85a91b' - let optimismValidator: Contract - let optimismUptimeFeedFactory: ContractFactory - let mockOptimismL1CrossDomainMessenger: Contract - let deployer: SignerWithAddress - let eoaValidator: SignerWithAddress - - before(async () => { - const accounts = await ethers.getSigners() - deployer = accounts[0] - eoaValidator = accounts[1] - }) - - beforeEach(async () => { - // Required for building the calldata - optimismUptimeFeedFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol:OptimismSequencerUptimeFeed', - deployer, - ) - - // Optimism Messenger contract on L1 - const mockOptimismL1CrossDomainMessengerFactory = - await ethers.getContractFactory( - 'src/v0.8/tests/MockOptimismL1CrossDomainMessenger.sol:MockOptimismL1CrossDomainMessenger', - ) - mockOptimismL1CrossDomainMessenger = - await mockOptimismL1CrossDomainMessengerFactory.deploy() - - // Contract under test - const optimismValidatorFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/optimism/OptimismValidator.sol:OptimismValidator', - deployer, - ) - - optimismValidator = await optimismValidatorFactory.deploy( - mockOptimismL1CrossDomainMessenger.address, - L2_SEQ_STATUS_RECORDER_ADDRESS, - GAS_LIMIT, - ) - }) - - describe('#setGasLimit', () => { - it('correctly updates the gas limit', async () => { - const newGasLimit = BigNumber.from(2_000_000) - const tx = await optimismValidator.setGasLimit(newGasLimit) - await tx.wait() - const currentGasLimit = await optimismValidator.getGasLimit() - expect(currentGasLimit).to.equal(newGasLimit) - }) - }) - - describe('#validate', () => { - it('reverts if called by account with no access', async () => { - await expect( - optimismValidator.connect(eoaValidator).validate(0, 0, 1, 1), - ).to.be.revertedWith('No access') - }) - - it('posts sequencer status when there is not status change', async () => { - await optimismValidator.addAccess(eoaValidator.address) - - const currentBlock = await ethers.provider.getBlock('latest') - const futureTimestamp = currentBlock.timestamp + 5000 - - await ethers.provider.send('evm_setNextBlockTimestamp', [futureTimestamp]) - const sequencerStatusRecorderCallData = - optimismUptimeFeedFactory.interface.encodeFunctionData('updateStatus', [ - false, - futureTimestamp, - ]) - - await expect(optimismValidator.connect(eoaValidator).validate(0, 0, 0, 0)) - .to.emit(mockOptimismL1CrossDomainMessenger, 'SentMessage') - .withArgs( - L2_SEQ_STATUS_RECORDER_ADDRESS, - optimismValidator.address, - sequencerStatusRecorderCallData, - 0, - GAS_LIMIT, - ) - }) - - it('post sequencer offline', async () => { - await optimismValidator.addAccess(eoaValidator.address) - - const currentBlock = await ethers.provider.getBlock('latest') - const futureTimestamp = currentBlock.timestamp + 10000 - - await ethers.provider.send('evm_setNextBlockTimestamp', [futureTimestamp]) - const sequencerStatusRecorderCallData = - optimismUptimeFeedFactory.interface.encodeFunctionData('updateStatus', [ - true, - futureTimestamp, - ]) - - await expect(optimismValidator.connect(eoaValidator).validate(0, 0, 1, 1)) - .to.emit(mockOptimismL1CrossDomainMessenger, 'SentMessage') - .withArgs( - L2_SEQ_STATUS_RECORDER_ADDRESS, - optimismValidator.address, - sequencerStatusRecorderCallData, - 0, - GAS_LIMIT, - ) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/ScrollCrossDomainForwarder.test.ts b/contracts/test/v0.8/L2EP/ScrollCrossDomainForwarder.test.ts deleted file mode 100644 index 923d41326ae..00000000000 --- a/contracts/test/v0.8/L2EP/ScrollCrossDomainForwarder.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import { Contract, ContractFactory } from 'ethers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { publicAbi } from '../../test-helpers/helpers' - -let owner: SignerWithAddress -let stranger: SignerWithAddress -let l1OwnerAddress: string -let newL1OwnerAddress: string -let forwarderFactory: ContractFactory -let greeterFactory: ContractFactory -let crossDomainMessengerFactory: ContractFactory -let crossDomainMessenger: Contract -let forwarder: Contract -let greeter: Contract - -before(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - stranger = accounts[1] - - // forwarder config - l1OwnerAddress = owner.address - newL1OwnerAddress = stranger.address - - // Contract factories - forwarderFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/scroll/ScrollCrossDomainForwarder.sol:ScrollCrossDomainForwarder', - owner, - ) - greeterFactory = await ethers.getContractFactory( - 'src/v0.8/tests/Greeter.sol:Greeter', - owner, - ) - crossDomainMessengerFactory = await ethers.getContractFactory( - 'src/v0.8/vendor/MockScrollCrossDomainMessenger.sol:MockScrollCrossDomainMessenger', - ) -}) - -describe('ScrollCrossDomainForwarder', () => { - beforeEach(async () => { - crossDomainMessenger = - await crossDomainMessengerFactory.deploy(l1OwnerAddress) - forwarder = await forwarderFactory.deploy( - crossDomainMessenger.address, - l1OwnerAddress, - ) - greeter = await greeterFactory.deploy(forwarder.address) - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(forwarder, [ - 'typeAndVersion', - 'crossDomainMessenger', - 'forward', - 'l1Owner', - 'transferL1Ownership', - 'acceptL1Ownership', - // ConfirmedOwner methods: - 'owner', - 'transferOwnership', - 'acceptOwnership', - ]) - }) - - describe('#constructor', () => { - it('should set the owner correctly', async () => { - const response = await forwarder.owner() - assert.equal(response, owner.address) - }) - - it('should set the l1Owner correctly', async () => { - const response = await forwarder.l1Owner() - assert.equal(response, l1OwnerAddress) - }) - - it('should set the crossdomain messenger correctly', async () => { - const response = await forwarder.crossDomainMessenger() - assert.equal(response, crossDomainMessenger.address) - }) - - it('should set the typeAndVersion correctly', async () => { - const response = await forwarder.typeAndVersion() - assert.equal(response, 'ScrollCrossDomainForwarder 1.0.0') - }) - }) - - describe('#forward', () => { - it('should not be callable by unknown address', async () => { - await expect( - forwarder.connect(stranger).forward(greeter.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'forward', - [greeter.address, setGreetingData], - ) - await crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - forwarder.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should revert when contract call reverts', async () => { - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [''], - ) - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'forward', - [greeter.address, setGreetingData], - ) - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - forwarder.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ).to.be.revertedWith('Invalid greeting length') - }) - }) - - describe('#transferL1Ownership', () => { - it('should not be callable by non-owners', async () => { - await expect( - forwarder.connect(stranger).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should not be callable by L2 owner', async () => { - const forwarderOwner = await forwarder.owner() - assert.equal(forwarderOwner, owner.address) - - await expect( - forwarder.connect(owner).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by current L1 owner', async () => { - const currentL1Owner = await forwarder.l1Owner() - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [newL1OwnerAddress], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - forwarder.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ) - .to.emit(forwarder, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, newL1OwnerAddress) - }) - - it('should be callable by current L1 owner to zero address', async () => { - const currentL1Owner = await forwarder.l1Owner() - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [ethers.constants.AddressZero], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - forwarder.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ) - .to.emit(forwarder, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, ethers.constants.AddressZero) - }) - }) - - describe('#acceptL1Ownership', () => { - it('should not be callable by non pending-owners', async () => { - const forwardData = forwarderFactory.interface.encodeFunctionData( - 'acceptL1Ownership', - [], - ) - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - forwarder.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ).to.be.revertedWith('Must be proposed L1 owner') - }) - - it('should be callable by pending L1 owner', async () => { - const currentL1Owner = await forwarder.l1Owner() - - // Transfer ownership - const forwardTransferData = forwarderFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [newL1OwnerAddress], - ) - await crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - forwarder.address, // target - 0, // value - forwardTransferData, // message - 0, // gasLimit - ) - - const forwardAcceptData = forwarderFactory.interface.encodeFunctionData( - 'acceptL1Ownership', - [], - ) - // Simulate cross-chain message from another sender - await crossDomainMessenger._setMockMessageSender(newL1OwnerAddress) - - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - forwarder.address, // target - 0, // value - forwardAcceptData, // message - 0, // gasLimit - ), - ) - .to.emit(forwarder, 'L1OwnershipTransferred') - .withArgs(currentL1Owner, newL1OwnerAddress) - - const updatedL1Owner = await forwarder.l1Owner() - assert.equal(updatedL1Owner, newL1OwnerAddress) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/ScrollCrossDomainGovernor.test.ts b/contracts/test/v0.8/L2EP/ScrollCrossDomainGovernor.test.ts deleted file mode 100644 index d2211145bb6..00000000000 --- a/contracts/test/v0.8/L2EP/ScrollCrossDomainGovernor.test.ts +++ /dev/null @@ -1,459 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import etherslib, { Contract, ContractFactory } from 'ethers' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { publicAbi, stripHexPrefix } from '../../test-helpers/helpers' - -let owner: SignerWithAddress -let stranger: SignerWithAddress -let l1OwnerAddress: string -let newL1OwnerAddress: string -let governorFactory: ContractFactory -let greeterFactory: ContractFactory -let multisendFactory: ContractFactory -let crossDomainMessengerFactory: ContractFactory -let crossDomainMessenger: Contract -let governor: Contract -let greeter: Contract -let multisend: Contract - -before(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - stranger = accounts[1] - - // governor config - l1OwnerAddress = owner.address - newL1OwnerAddress = stranger.address - - // Contract factories - governorFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/scroll/ScrollCrossDomainGovernor.sol:ScrollCrossDomainGovernor', - owner, - ) - greeterFactory = await ethers.getContractFactory( - 'src/v0.8/tests/Greeter.sol:Greeter', - owner, - ) - multisendFactory = await ethers.getContractFactory( - 'src/v0.8/vendor/MultiSend.sol:MultiSend', - owner, - ) - crossDomainMessengerFactory = await ethers.getContractFactory( - 'src/v0.8/vendor/MockScrollCrossDomainMessenger.sol:MockScrollCrossDomainMessenger', - ) -}) - -describe('ScrollCrossDomainGovernor', () => { - beforeEach(async () => { - crossDomainMessenger = - await crossDomainMessengerFactory.deploy(l1OwnerAddress) - governor = await governorFactory.deploy( - crossDomainMessenger.address, - l1OwnerAddress, - ) - greeter = await greeterFactory.deploy(governor.address) - multisend = await multisendFactory.deploy() - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(governor, [ - 'typeAndVersion', - 'crossDomainMessenger', - 'forward', - 'forwardDelegate', - 'l1Owner', - 'transferL1Ownership', - 'acceptL1Ownership', - // ConfirmedOwner methods: - 'owner', - 'transferOwnership', - 'acceptOwnership', - ]) - }) - - describe('#constructor', () => { - it('should set the owner correctly', async () => { - const response = await governor.owner() - assert.equal(response, owner.address) - }) - - it('should set the l1Owner correctly', async () => { - const response = await governor.l1Owner() - assert.equal(response, l1OwnerAddress) - }) - - it('should set the crossdomain messenger correctly', async () => { - const response = await governor.crossDomainMessenger() - assert.equal(response, crossDomainMessenger.address) - }) - - it('should set the typeAndVersion correctly', async () => { - const response = await governor.typeAndVersion() - assert.equal(response, 'ScrollCrossDomainGovernor 1.0.0') - }) - }) - - describe('#forward', () => { - it('should not be callable by unknown address', async () => { - await expect( - governor.connect(stranger).forward(greeter.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger or owner') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - const forwardData = governorFactory.interface.encodeFunctionData( - 'forward', - [greeter.address, setGreetingData], - ) - await crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should be callable by L2 owner', async () => { - const newGreeting = 'hello' - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [newGreeting], - ) - await governor.connect(owner).forward(greeter.address, setGreetingData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, newGreeting) - }) - - it('should revert when contract call reverts', async () => { - const setGreetingData = greeterFactory.interface.encodeFunctionData( - 'setGreeting', - [''], - ) - const forwardData = governorFactory.interface.encodeFunctionData( - 'forward', - [greeter.address, setGreetingData], - ) - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ).to.be.revertedWith('Invalid greeting length') - }) - }) - - describe('#forwardDelegate', () => { - it('should not be callable by unknown address', async () => { - await expect( - governor.connect(stranger).forwardDelegate(multisend.address, '0x'), - ).to.be.revertedWith('Sender is not the L2 messenger or owner') - }) - - it('should be callable by crossdomain messenger address / L1 owner', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'bar', - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - const forwardData = governorFactory.interface.encodeFunctionData( - 'forwardDelegate', - [multisend.address, multisendData], - ) - - await crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, 'bar') - }) - - it('should be callable by L2 owner', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'bar', - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - await governor - .connect(owner) - .forwardDelegate(multisend.address, multisendData) - - const updatedGreeting = await greeter.greeting() - assert.equal(updatedGreeting, 'bar') - }) - - it('should revert batch when one call fails', async () => { - const calls = [ - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - 'foo', - ]), - value: 0, - }, - { - to: greeter.address, - data: greeterFactory.interface.encodeFunctionData('setGreeting', [ - '', // should revert - ]), - value: 0, - }, - ] - const multisendData = encodeMultisendData(multisend.interface, calls) - const forwardData = governorFactory.interface.encodeFunctionData( - 'forwardDelegate', - [multisend.address, multisendData], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ).to.be.revertedWith('Governor delegatecall reverted') - - const greeting = await greeter.greeting() - assert.equal(greeting, '') // Unchanged - }) - - it('should bubble up revert when contract call reverts', async () => { - const triggerRevertData = - greeterFactory.interface.encodeFunctionData('triggerRevert') - const forwardData = governorFactory.interface.encodeFunctionData( - 'forwardDelegate', - [greeter.address, triggerRevertData], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ).to.be.revertedWith('Greeter: revert triggered') - }) - }) - - describe('#transferL1Ownership', () => { - it('should not be callable by non-owners', async () => { - await expect( - governor.connect(stranger).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should not be callable by L2 owner', async () => { - const governorOwner = await governor.owner() - assert.equal(governorOwner, owner.address) - - await expect( - governor.connect(owner).transferL1Ownership(stranger.address), - ).to.be.revertedWith('Sender is not the L2 messenger') - }) - - it('should be callable by current L1 owner', async () => { - const currentL1Owner = await governor.l1Owner() - const forwardData = governorFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [newL1OwnerAddress], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ) - .to.emit(governor, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, newL1OwnerAddress) - }) - - it('should be callable by current L1 owner to zero address', async () => { - const currentL1Owner = await governor.l1Owner() - const forwardData = governorFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [ethers.constants.AddressZero], - ) - - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ) - .to.emit(governor, 'L1OwnershipTransferRequested') - .withArgs(currentL1Owner, ethers.constants.AddressZero) - }) - }) - - describe('#acceptL1Ownership', () => { - it('should not be callable by non pending-owners', async () => { - const forwardData = governorFactory.interface.encodeFunctionData( - 'acceptL1Ownership', - [], - ) - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardData, // message - 0, // gasLimit - ), - ).to.be.revertedWith('Must be proposed L1 owner') - }) - - it('should be callable by pending L1 owner', async () => { - const currentL1Owner = await governor.l1Owner() - - // Transfer ownership - const forwardTransferData = governorFactory.interface.encodeFunctionData( - 'transferL1Ownership', - [newL1OwnerAddress], - ) - await crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardTransferData, // message - 0, // gasLimit - ) - - const forwardAcceptData = governorFactory.interface.encodeFunctionData( - 'acceptL1Ownership', - [], - ) - // Simulate cross-chain message from another sender - await crossDomainMessenger._setMockMessageSender(newL1OwnerAddress) - - await expect( - crossDomainMessenger // Simulate cross-chain message - .connect(stranger) - ['sendMessage(address,uint256,bytes,uint256)']( - governor.address, // target - 0, // value - forwardAcceptData, // message - 0, // gasLimit - ), - ) - .to.emit(governor, 'L1OwnershipTransferred') - .withArgs(currentL1Owner, newL1OwnerAddress) - - const updatedL1Owner = await governor.l1Owner() - assert.equal(updatedL1Owner, newL1OwnerAddress) - }) - }) -}) - -// Multisend contract helpers - -/** - * Encodes an underlying transaction for the Multisend contract - * - * @param operation 0 for CALL, 1 for DELEGATECALL - * @param to tx target address - * @param value tx value - * @param data tx data - */ -export function encodeTxData( - operation: number, - to: string, - value: number, - data: string, -): string { - const dataBuffer = Buffer.from(stripHexPrefix(data), 'hex') - const types = ['uint8', 'address', 'uint256', 'uint256', 'bytes'] - const values = [operation, to, value, dataBuffer.length, dataBuffer] - const encoded = ethers.utils.solidityPack(types, values) - return stripHexPrefix(encoded) -} - -/** - * Encodes a Multisend call - * - * @param MultisendInterface Ethers Interface object of the Multisend contract - * @param transactions one or more transactions to include in the Multisend call - * @param to tx target address - * @param value tx value - * @param data tx data - */ -export function encodeMultisendData( - MultisendInterface: etherslib.utils.Interface, - transactions: { to: string; value: number; data: string }[], -): string { - let nestedTransactionData = '0x' - for (const transaction of transactions) { - nestedTransactionData += encodeTxData( - 0, - transaction.to, - transaction.value, - transaction.data, - ) - } - const encodedMultisendFnData = MultisendInterface.encodeFunctionData( - 'multiSend', - [nestedTransactionData], - ) - return encodedMultisendFnData -} diff --git a/contracts/test/v0.8/L2EP/ScrollSequencerUptimeFeed.test.ts b/contracts/test/v0.8/L2EP/ScrollSequencerUptimeFeed.test.ts deleted file mode 100644 index d0fecf3b189..00000000000 --- a/contracts/test/v0.8/L2EP/ScrollSequencerUptimeFeed.test.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { ethers, network } from 'hardhat' -import { BigNumber, Contract } from 'ethers' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' - -describe('ScrollSequencerUptimeFeed', () => { - let l2CrossDomainMessenger: Contract - let scrollUptimeFeed: Contract - let uptimeFeedConsumer: Contract - let deployer: SignerWithAddress - let l1Owner: SignerWithAddress - let l2Messenger: SignerWithAddress - let dummy: SignerWithAddress - const gasUsedDeviation = 100 - const initialStatus = 0 - - before(async () => { - const accounts = await ethers.getSigners() - deployer = accounts[0] - l1Owner = accounts[1] - dummy = accounts[3] - - const l2CrossDomainMessengerFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/test/mocks/scroll/MockScrollL2CrossDomainMessenger.sol:MockScrollL2CrossDomainMessenger', - deployer, - ) - - l2CrossDomainMessenger = await l2CrossDomainMessengerFactory.deploy() - - // Pretend we're on L2 - await network.provider.request({ - method: 'hardhat_impersonateAccount', - params: [l2CrossDomainMessenger.address], - }) - l2Messenger = await ethers.getSigner(l2CrossDomainMessenger.address) - // Credit the L2 messenger with some ETH - await dummy.sendTransaction({ - to: l2Messenger.address, - value: ethers.utils.parseEther('10'), - }) - }) - - beforeEach(async () => { - const scrollSequencerStatusRecorderFactory = - await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/scroll/ScrollSequencerUptimeFeed.sol:ScrollSequencerUptimeFeed', - deployer, - ) - scrollUptimeFeed = await scrollSequencerStatusRecorderFactory.deploy( - l1Owner.address, - l2CrossDomainMessenger.address, - initialStatus, - ) - - // Set mock sender in mock L2 messenger contract - await l2CrossDomainMessenger.setSender(l1Owner.address) - - // Mock consumer - const statusFeedConsumerFactory = await ethers.getContractFactory( - 'src/v0.8/tests/FeedConsumer.sol:FeedConsumer', - deployer, - ) - uptimeFeedConsumer = await statusFeedConsumerFactory.deploy( - scrollUptimeFeed.address, - ) - }) - - describe('constructor', () => { - it('should have been deployed with the correct initial state', async () => { - const l1Sender = await scrollUptimeFeed.l1Sender() - expect(l1Sender).to.equal(l1Owner.address) - const { roundId, answer } = await scrollUptimeFeed.latestRoundData() - expect(roundId).to.equal(1) - expect(answer).to.equal(initialStatus) - }) - }) - - describe('#updateStatus', () => { - it('should revert if called by an address that is not the L2 Cross Domain Messenger', async () => { - const timestamp = await scrollUptimeFeed.latestTimestamp() - expect( - scrollUptimeFeed.connect(dummy).updateStatus(true, timestamp), - ).to.be.revertedWith('InvalidSender') - }) - - it('should revert if called by an address that is not the L2 Cross Domain Messenger and is not the L1 sender', async () => { - const timestamp = await scrollUptimeFeed.latestTimestamp() - await l2CrossDomainMessenger.setSender(dummy.address) - expect( - scrollUptimeFeed.connect(dummy).updateStatus(true, timestamp), - ).to.be.revertedWith('InvalidSender') - }) - - it(`should update status when status has not changed and incoming timestamp is the same as latest`, async () => { - const timestamp = await scrollUptimeFeed.latestTimestamp() - let tx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(scrollUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await scrollUptimeFeed.latestAnswer()).to.equal(1) - - const latestRoundBeforeUpdate = await scrollUptimeFeed.latestRoundData() - - tx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp.add(200)) - - // Submit another status update with the same status - const latestBlock = await ethers.provider.getBlock('latest') - - await expect(tx) - .to.emit(scrollUptimeFeed, 'RoundUpdated') - .withArgs(1, latestBlock.timestamp) - expect(await scrollUptimeFeed.latestAnswer()).to.equal(1) - expect(await scrollUptimeFeed.latestTimestamp()).to.equal(timestamp) - - // Verify that latest round has been properly updated - const latestRoundDataAfterUpdate = - await scrollUptimeFeed.latestRoundData() - expect(latestRoundDataAfterUpdate.roundId).to.equal( - latestRoundBeforeUpdate.roundId, - ) - expect(latestRoundDataAfterUpdate.answer).to.equal( - latestRoundBeforeUpdate.answer, - ) - expect(latestRoundDataAfterUpdate.startedAt).to.equal( - latestRoundBeforeUpdate.startedAt, - ) - expect(latestRoundDataAfterUpdate.answeredInRound).to.equal( - latestRoundBeforeUpdate.answeredInRound, - ) - expect(latestRoundDataAfterUpdate.updatedAt).to.equal( - latestBlock.timestamp, - ) - }) - - it(`should update status when status has changed and incoming timestamp is newer than the latest`, async () => { - let timestamp = await scrollUptimeFeed.latestTimestamp() - let tx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(scrollUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await scrollUptimeFeed.latestAnswer()).to.equal(1) - - // Submit another status update, different status, newer timestamp should update - timestamp = timestamp.add(2000) - tx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - await expect(tx) - .to.emit(scrollUptimeFeed, 'AnswerUpdated') - .withArgs(0, 3 /** roundId */, timestamp) - expect(await scrollUptimeFeed.latestAnswer()).to.equal(0) - expect(await scrollUptimeFeed.latestTimestamp()).to.equal(timestamp) - }) - - it(`should update status when status has changed and incoming timestamp is the same as latest`, async () => { - const timestamp = await scrollUptimeFeed.latestTimestamp() - let tx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(scrollUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await scrollUptimeFeed.latestAnswer()).to.equal(1) - - // Submit another status update, different status, same timestamp should update - tx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - await expect(tx) - .to.emit(scrollUptimeFeed, 'AnswerUpdated') - .withArgs(0, 3 /** roundId */, timestamp) - expect(await scrollUptimeFeed.latestAnswer()).to.equal(0) - expect(await scrollUptimeFeed.latestTimestamp()).to.equal(timestamp) - }) - - it('should ignore out-of-order updates', async () => { - const timestamp = (await scrollUptimeFeed.latestTimestamp()).add(10_000) - // Update status - let tx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - await expect(tx) - .to.emit(scrollUptimeFeed, 'AnswerUpdated') - .withArgs(1, 2 /** roundId */, timestamp) - expect(await scrollUptimeFeed.latestAnswer()).to.equal(1) - - // Update with different status, but stale timestamp, should be ignored - const staleTimestamp = timestamp.sub(1000) - tx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(false, staleTimestamp) - await expect(tx).to.not.emit(scrollUptimeFeed, 'AnswerUpdated') - await expect(tx).to.emit(scrollUptimeFeed, 'UpdateIgnored') - }) - }) - - describe('AggregatorV3Interface', () => { - it('should return valid answer from getRoundData and latestRoundData', async () => { - let [roundId, answer, startedAt, updatedAt, answeredInRound] = - await scrollUptimeFeed.latestRoundData() - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - expect(answeredInRound).to.equal(roundId) - expect(startedAt).to.equal(updatedAt) - - // Submit status update with different status and newer timestamp, should update - const timestamp = (startedAt as BigNumber).add(1000) - await scrollUptimeFeed.connect(l2Messenger).updateStatus(true, timestamp) - ;[roundId, answer, startedAt, updatedAt, answeredInRound] = - await scrollUptimeFeed.getRoundData(2) - expect(roundId).to.equal(2) - expect(answer).to.equal(1) - expect(answeredInRound).to.equal(roundId) - expect(startedAt).to.equal(timestamp) - expect(updatedAt.lte(startedAt)).to.be.true - - // Check that last round is still returning the correct data - ;[roundId, answer, startedAt, updatedAt, answeredInRound] = - await scrollUptimeFeed.getRoundData(1) - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - expect(answeredInRound).to.equal(roundId) - expect(startedAt).to.equal(updatedAt) - - // Assert latestRoundData corresponds to latest round id - expect(await scrollUptimeFeed.getRoundData(2)).to.deep.equal( - await scrollUptimeFeed.latestRoundData(), - ) - }) - - it('should revert from #getRoundData when round does not yet exist (future roundId)', async () => { - expect(scrollUptimeFeed.getRoundData(2)).to.be.revertedWith( - 'NoDataPresent()', - ) - }) - - it('should revert from #getAnswer when round does not yet exist (future roundId)', async () => { - expect(scrollUptimeFeed.getAnswer(2)).to.be.revertedWith( - 'NoDataPresent()', - ) - }) - - it('should revert from #getTimestamp when round does not yet exist (future roundId)', async () => { - expect(scrollUptimeFeed.getTimestamp(2)).to.be.revertedWith( - 'NoDataPresent()', - ) - }) - }) - - describe('Protect reads on AggregatorV2V3Interface functions', () => { - it('should disallow reads on AggregatorV2V3Interface functions when consuming contract is not whitelisted', async () => { - // Sanity - consumer is not whitelisted - expect(await scrollUptimeFeed.checkEnabled()).to.be.true - expect( - await scrollUptimeFeed.hasAccess(uptimeFeedConsumer.address, '0x00'), - ).to.be.false - - // Assert reads are not possible from consuming contract - await expect(uptimeFeedConsumer.latestAnswer()).to.be.revertedWith( - 'No access', - ) - await expect(uptimeFeedConsumer.latestRoundData()).to.be.revertedWith( - 'No access', - ) - }) - - it('should allow reads on AggregatorV2V3Interface functions when consuming contract is whitelisted', async () => { - // Whitelist consumer - await scrollUptimeFeed.addAccess(uptimeFeedConsumer.address) - // Sanity - consumer is whitelisted - expect(await scrollUptimeFeed.checkEnabled()).to.be.true - expect( - await scrollUptimeFeed.hasAccess(uptimeFeedConsumer.address, '0x00'), - ).to.be.true - - // Assert reads are possible from consuming contract - expect(await uptimeFeedConsumer.latestAnswer()).to.be.equal('0') - const [roundId, answer] = await uptimeFeedConsumer.latestRoundData() - expect(roundId).to.equal(1) - expect(answer).to.equal(0) - }) - }) - - describe('Gas costs', () => { - it('should consume a known amount of gas for updates @skip-coverage', async () => { - // Sanity - start at flag = 0 (`false`) - expect(await scrollUptimeFeed.latestAnswer()).to.equal(0) - let timestamp = await scrollUptimeFeed.latestTimestamp() - - // Gas for no update - timestamp = timestamp.add(1000) - const _noUpdateTx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(false, timestamp) - const noUpdateTx = await _noUpdateTx.wait(1) - // Assert no update - expect(await scrollUptimeFeed.latestAnswer()).to.equal(0) - expect(noUpdateTx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 38594, - gasUsedDeviation, - ) - - // Gas for update - timestamp = timestamp.add(1000) - const _updateTx = await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - const updateTx = await _updateTx.wait(1) - // Assert update - expect(await scrollUptimeFeed.latestAnswer()).to.equal(1) - expect(updateTx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 58458, - gasUsedDeviation, - ) - }) - - describe('Aggregator interface', () => { - beforeEach(async () => { - const timestamp = (await scrollUptimeFeed.latestTimestamp()).add(1000) - // Initialise a round - await scrollUptimeFeed - .connect(l2Messenger) - .updateStatus(true, timestamp) - }) - - it('should consume a known amount of gas for getRoundData(uint80) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await scrollUptimeFeed - .connect(l2Messenger) - .populateTransaction.getRoundData(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 30952, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestRoundData() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await scrollUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestRoundData(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28523, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestAnswer() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await scrollUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestAnswer(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28229, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestTimestamp() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await scrollUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestTimestamp(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28129, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for latestRound() @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await scrollUptimeFeed - .connect(l2Messenger) - .populateTransaction.latestRound(), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 28145, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for getAnswer(roundId) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await scrollUptimeFeed - .connect(l2Messenger) - .populateTransaction.getAnswer(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 30682, - gasUsedDeviation, - ) - }) - - it('should consume a known amount of gas for getTimestamp(roundId) @skip-coverage', async () => { - const _tx = await l2Messenger.sendTransaction( - await scrollUptimeFeed - .connect(l2Messenger) - .populateTransaction.getTimestamp(1), - ) - const tx = await _tx.wait(1) - expect(tx.cumulativeGasUsed.toNumber()).to.be.closeTo( - 30570, - gasUsedDeviation, - ) - }) - }) - }) -}) diff --git a/contracts/test/v0.8/L2EP/ScrollValidator.test.ts b/contracts/test/v0.8/L2EP/ScrollValidator.test.ts deleted file mode 100644 index 94205a03112..00000000000 --- a/contracts/test/v0.8/L2EP/ScrollValidator.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { ethers } from 'hardhat' -import { BigNumber, Contract, ContractFactory } from 'ethers' -import { expect } from 'chai' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' - -describe('ScrollValidator', () => { - const GAS_LIMIT = BigNumber.from(1_900_000) - /** Fake L2 target */ - const L2_SEQ_STATUS_RECORDER_ADDRESS = - '0x491B1dDA0A8fa069bbC1125133A975BF4e85a91b' - let scrollValidator: Contract - let l1MessageQueue: Contract - let scrollUptimeFeedFactory: ContractFactory - let mockScrollL1CrossDomainMessenger: Contract - let deployer: SignerWithAddress - let eoaValidator: SignerWithAddress - - before(async () => { - const accounts = await ethers.getSigners() - deployer = accounts[0] - eoaValidator = accounts[1] - }) - - beforeEach(async () => { - // Required for building the calldata - scrollUptimeFeedFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/scroll/ScrollSequencerUptimeFeed.sol:ScrollSequencerUptimeFeed', - deployer, - ) - - // Scroll Messenger contract on L1 - const mockScrollL1CrossDomainMessengerFactory = - await ethers.getContractFactory( - 'src/v0.8/l2ep/test/mocks/scroll/MockScrollL1CrossDomainMessenger.sol:MockScrollL1CrossDomainMessenger', - ) - mockScrollL1CrossDomainMessenger = - await mockScrollL1CrossDomainMessengerFactory.deploy() - - // Scroll Message Queue contract on L1 - const l1MessageQueueFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/test/mocks/scroll/MockScrollL1MessageQueue.sol:MockScrollL1MessageQueue', - deployer, - ) - l1MessageQueue = await l1MessageQueueFactory.deploy() - - // Contract under test - const scrollValidatorFactory = await ethers.getContractFactory( - 'src/v0.8/l2ep/dev/scroll/ScrollValidator.sol:ScrollValidator', - deployer, - ) - - scrollValidator = await scrollValidatorFactory.deploy( - mockScrollL1CrossDomainMessenger.address, - L2_SEQ_STATUS_RECORDER_ADDRESS, - l1MessageQueue.address, - GAS_LIMIT, - ) - }) - - describe('#setGasLimit', () => { - it('correctly updates the gas limit', async () => { - const newGasLimit = BigNumber.from(2_000_000) - const tx = await scrollValidator.setGasLimit(newGasLimit) - await tx.wait() - const currentGasLimit = await scrollValidator.getGasLimit() - expect(currentGasLimit).to.equal(newGasLimit) - }) - }) - - describe('#validate', () => { - it('reverts if called by account with no access', async () => { - await expect( - scrollValidator.connect(eoaValidator).validate(0, 0, 1, 1), - ).to.be.revertedWith('No access') - }) - - it('posts sequencer status when there is not status change', async () => { - await scrollValidator.addAccess(eoaValidator.address) - - const currentBlock = await ethers.provider.getBlock('latest') - const futureTimestamp = currentBlock.timestamp + 5000 - - await ethers.provider.send('evm_setNextBlockTimestamp', [futureTimestamp]) - const sequencerStatusRecorderCallData = - scrollUptimeFeedFactory.interface.encodeFunctionData('updateStatus', [ - false, - futureTimestamp, - ]) - - await expect(scrollValidator.connect(eoaValidator).validate(0, 0, 0, 0)) - .to.emit(mockScrollL1CrossDomainMessenger, 'SentMessage') - .withArgs( - scrollValidator.address, // sender - L2_SEQ_STATUS_RECORDER_ADDRESS, // target - 0, // value - 0, // nonce - GAS_LIMIT, // gas limit - sequencerStatusRecorderCallData, // message - ) - }) - - it('post sequencer offline', async () => { - await scrollValidator.addAccess(eoaValidator.address) - - const currentBlock = await ethers.provider.getBlock('latest') - const futureTimestamp = currentBlock.timestamp + 10000 - - await ethers.provider.send('evm_setNextBlockTimestamp', [futureTimestamp]) - const sequencerStatusRecorderCallData = - scrollUptimeFeedFactory.interface.encodeFunctionData('updateStatus', [ - true, - futureTimestamp, - ]) - - await expect(scrollValidator.connect(eoaValidator).validate(0, 0, 1, 1)) - .to.emit(mockScrollL1CrossDomainMessenger, 'SentMessage') - .withArgs( - scrollValidator.address, // sender - L2_SEQ_STATUS_RECORDER_ADDRESS, // target - 0, // value - 0, // nonce - GAS_LIMIT, // gas limit - sequencerStatusRecorderCallData, // message - ) - }) - }) -})