UNPKG

@hyperlane-xyz/core

Version:

Core solidity contracts for Hyperlane

577 lines (528 loc) 18.1 kB
import { ethers } from 'hardhat' import { assert, expect } from 'chai' import { UpkeepTranscoder30__factory as UpkeepTranscoderFactory } from '../../../typechain/factories/UpkeepTranscoder30__factory' import { UpkeepTranscoder30 as UpkeepTranscoder } from '../../../typechain/UpkeepTranscoder30' import { KeeperRegistry2_0__factory as KeeperRegistry2_0Factory } from '../../../typechain/factories/KeeperRegistry2_0__factory' import { LinkToken__factory as LinkTokenFactory } from '../../../typechain/factories/LinkToken__factory' import { MockV3Aggregator__factory as MockV3AggregatorFactory } from '../../../typechain/factories/MockV3Aggregator__factory' import { UpkeepMock__factory as UpkeepMockFactory } from '../../../typechain/factories/UpkeepMock__factory' import { evmRevert } from '../../test-helpers/matchers' import { BigNumber, Signer } from 'ethers' import { getUsers, Personas } from '../../test-helpers/setup' import { KeeperRegistryLogic2_0__factory as KeeperRegistryLogic20Factory } from '../../../typechain/factories/KeeperRegistryLogic2_0__factory' import { KeeperRegistry1_3__factory as KeeperRegistry1_3Factory } from '../../../typechain/factories/KeeperRegistry1_3__factory' import { KeeperRegistryLogic1_3__factory as KeeperRegistryLogicFactory } from '../../../typechain/factories/KeeperRegistryLogic1_3__factory' import { toWei } from '../../test-helpers/helpers' import { LinkToken } from '../../../typechain' let upkeepMockFactory: UpkeepMockFactory let upkeepTranscoderFactory: UpkeepTranscoderFactory let transcoder: UpkeepTranscoder let linkTokenFactory: LinkTokenFactory let mockV3AggregatorFactory: MockV3AggregatorFactory let keeperRegistryFactory20: KeeperRegistry2_0Factory let keeperRegistryFactory13: KeeperRegistry1_3Factory let keeperRegistryLogicFactory20: KeeperRegistryLogic20Factory let keeperRegistryLogicFactory13: KeeperRegistryLogicFactory let personas: Personas let owner: Signer let upkeepsV1: any[] let upkeepsV2: any[] let upkeepsV3: any[] let admins: string[] let admin0: Signer let admin1: Signer const executeGas = BigNumber.from('100000') const paymentPremiumPPB = BigNumber.from('250000000') const flatFeeMicroLink = BigNumber.from(0) const blockCountPerTurn = BigNumber.from(3) const randomBytes = '0x1234abcd' const stalenessSeconds = BigNumber.from(43820) const gasCeilingMultiplier = BigNumber.from(1) const checkGasLimit = BigNumber.from(20000000) const fallbackGasPrice = BigNumber.from(200) const fallbackLinkPrice = BigNumber.from(200000000) const maxPerformGas = BigNumber.from(5000000) const minUpkeepSpend = BigNumber.from(0) const maxCheckDataSize = BigNumber.from(1000) const maxPerformDataSize = BigNumber.from(1000) const mode = BigNumber.from(0) const linkEth = BigNumber.from(300000000) const gasWei = BigNumber.from(100) const registryGasOverhead = BigNumber.from('80000') const balance = 50000000000000 const amountSpent = 200000000000000 const target0 = '0xffffffffffffffffffffffffffffffffffffffff' const target1 = '0xfffffffffffffffffffffffffffffffffffffffe' const lastKeeper0 = '0x233a95ccebf3c9f934482c637c08b4015cdd6ddd' const lastKeeper1 = '0x233a95ccebf3c9f934482c637c08b4015cdd6ddc' enum UpkeepFormat { V1, V2, V3, V4, } const idx = [123, 124] async function getUpkeepID(tx: any) { const receipt = await tx.wait() return receipt.events[0].args.id } const encodeConfig = (config: any) => { return ethers.utils.defaultAbiCoder.encode( [ 'tuple(uint32 paymentPremiumPPB,uint32 flatFeeMicroLink,uint32 checkGasLimit,uint24 stalenessSeconds\ ,uint16 gasCeilingMultiplier,uint96 minUpkeepSpend,uint32 maxPerformGas,uint32 maxCheckDataSize,\ uint32 maxPerformDataSize,uint256 fallbackGasPrice,uint256 fallbackLinkPrice,address transcoder,\ address registrar)', ], [config], ) } const encodeUpkeepV1 = (ids: number[], upkeeps: any[], checkDatas: any[]) => { return ethers.utils.defaultAbiCoder.encode( [ 'uint256[]', 'tuple(uint96,address,uint32,uint64,address,uint96,address)[]', 'bytes[]', ], [ids, upkeeps, checkDatas], ) } const encodeUpkeepV2 = (ids: number[], upkeeps: any[], checkDatas: any[]) => { return ethers.utils.defaultAbiCoder.encode( [ 'uint256[]', 'tuple(uint96,address,uint96,address,uint32,uint32,address,bool)[]', 'bytes[]', ], [ids, upkeeps, checkDatas], ) } const encodeUpkeepV3 = ( ids: number[], upkeeps: any[], checkDatas: any[], admins: string[], ) => { return ethers.utils.defaultAbiCoder.encode( [ 'uint256[]', 'tuple(uint32,uint32,bool,address,uint96,uint96,uint32)[]', 'bytes[]', 'address[]', ], [ids, upkeeps, checkDatas, admins], ) } before(async () => { // @ts-ignore bug in autogen file upkeepTranscoderFactory = await ethers.getContractFactory( 'UpkeepTranscoder3_0', ) personas = (await getUsers()).personas linkTokenFactory = await ethers.getContractFactory( 'src/v0.8/shared/test/helpers/LinkTokenTestHelper.sol:LinkTokenTestHelper', ) // need full path because there are two contracts with name MockV3Aggregator mockV3AggregatorFactory = (await ethers.getContractFactory( 'src/v0.8/tests/MockV3Aggregator.sol:MockV3Aggregator', )) as unknown as MockV3AggregatorFactory upkeepMockFactory = await ethers.getContractFactory('UpkeepMock') owner = personas.Norbert admin0 = personas.Neil admin1 = personas.Nick admins = [ (await admin0.getAddress()).toLowerCase(), (await admin1.getAddress()).toLowerCase(), ] }) async function deployLinkToken() { return await linkTokenFactory.connect(owner).deploy() } async function deployFeeds() { return [ await mockV3AggregatorFactory.connect(owner).deploy(0, gasWei), await mockV3AggregatorFactory.connect(owner).deploy(9, linkEth), ] } async function deployLegacyRegistry1_2( linkToken: LinkToken, gasPriceFeed: any, linkEthFeed: any, ) { const mock = await upkeepMockFactory.deploy() // @ts-ignore bug in autogen file const keeperRegistryFactory = await ethers.getContractFactory('KeeperRegistry1_2') transcoder = await upkeepTranscoderFactory.connect(owner).deploy() const legacyRegistry = await keeperRegistryFactory .connect(owner) .deploy(linkToken.address, linkEthFeed.address, gasPriceFeed.address, { paymentPremiumPPB, flatFeeMicroLink, blockCountPerTurn, checkGasLimit, stalenessSeconds, gasCeilingMultiplier, minUpkeepSpend, maxPerformGas, fallbackGasPrice, fallbackLinkPrice, transcoder: transcoder.address, registrar: ethers.constants.AddressZero, }) const tx = await legacyRegistry .connect(owner) .registerUpkeep( mock.address, executeGas, await admin0.getAddress(), randomBytes, ) const id = await getUpkeepID(tx) return [id, legacyRegistry] } async function deployLegacyRegistry1_3( linkToken: LinkToken, gasPriceFeed: any, linkEthFeed: any, ) { const mock = await upkeepMockFactory.deploy() // @ts-ignore bug in autogen file keeperRegistryFactory13 = await ethers.getContractFactory('KeeperRegistry1_3') // @ts-ignore bug in autogen file keeperRegistryLogicFactory13 = await ethers.getContractFactory( 'KeeperRegistryLogic1_3', ) const registryLogic13 = await keeperRegistryLogicFactory13 .connect(owner) .deploy( 0, registryGasOverhead, linkToken.address, linkEthFeed.address, gasPriceFeed.address, ) const config = { paymentPremiumPPB, flatFeeMicroLink, blockCountPerTurn, checkGasLimit, stalenessSeconds, gasCeilingMultiplier, minUpkeepSpend, maxPerformGas, fallbackGasPrice, fallbackLinkPrice, transcoder: transcoder.address, registrar: ethers.constants.AddressZero, } const Registry1_3 = await keeperRegistryFactory13 .connect(owner) .deploy(registryLogic13.address, config) const tx = await Registry1_3.connect(owner).registerUpkeep( mock.address, executeGas, await admin0.getAddress(), randomBytes, ) const id = await getUpkeepID(tx) return [id, Registry1_3] } async function deployRegistry2_0( linkToken: LinkToken, gasPriceFeed: any, linkEthFeed: any, ) { // @ts-ignore bug in autogen file keeperRegistryFactory20 = await ethers.getContractFactory('KeeperRegistry2_0') // @ts-ignore bug in autogen file keeperRegistryLogicFactory20 = await ethers.getContractFactory( 'KeeperRegistryLogic2_0', ) const config = { paymentPremiumPPB, flatFeeMicroLink, checkGasLimit, stalenessSeconds, gasCeilingMultiplier, minUpkeepSpend, maxCheckDataSize, maxPerformDataSize, maxPerformGas, fallbackGasPrice, fallbackLinkPrice, transcoder: transcoder.address, registrar: ethers.constants.AddressZero, } const registryLogic = await keeperRegistryLogicFactory20 .connect(owner) .deploy(mode, linkToken.address, linkEthFeed.address, gasPriceFeed.address) const Registry2_0 = await keeperRegistryFactory20 .connect(owner) .deploy(registryLogic.address) // deploys a registry, setups of initial configuration, registers an upkeep const keeper1 = personas.Carol const keeper2 = personas.Eddy const keeper3 = personas.Nancy const keeper4 = personas.Norbert const keeper5 = personas.Nick const payee1 = personas.Nelly const payee2 = personas.Norbert const payee3 = personas.Nick const payee4 = personas.Eddy const payee5 = personas.Carol // signers const signer1 = new ethers.Wallet( '0x7777777000000000000000000000000000000000000000000000000000000001', ) const signer2 = new ethers.Wallet( '0x7777777000000000000000000000000000000000000000000000000000000002', ) const signer3 = new ethers.Wallet( '0x7777777000000000000000000000000000000000000000000000000000000003', ) const signer4 = new ethers.Wallet( '0x7777777000000000000000000000000000000000000000000000000000000004', ) const signer5 = new ethers.Wallet( '0x7777777000000000000000000000000000000000000000000000000000000005', ) const keeperAddresses = [ await keeper1.getAddress(), await keeper2.getAddress(), await keeper3.getAddress(), await keeper4.getAddress(), await keeper5.getAddress(), ] const payees = [ await payee1.getAddress(), await payee2.getAddress(), await payee3.getAddress(), await payee4.getAddress(), await payee5.getAddress(), ] const signers = [signer1, signer2, signer3, signer4, signer5] const signerAddresses = [] for (const signer of signers) { signerAddresses.push(await signer.getAddress()) } const f = 1 const offchainVersion = 1 const offchainBytes = '0x' await Registry2_0.connect(owner).setConfig( signerAddresses, keeperAddresses, f, encodeConfig(config), offchainVersion, offchainBytes, ) await Registry2_0.connect(owner).setPayees(payees) return Registry2_0 } describe('UpkeepTranscoder3_0', () => { beforeEach(async () => { transcoder = await upkeepTranscoderFactory.connect(owner).deploy() }) describe('#typeAndVersion', () => { it('uses the correct type and version', async () => { const typeAndVersion = await transcoder.typeAndVersion() assert.equal(typeAndVersion, 'UpkeepTranscoder 3.0.0') }) }) describe('#transcodeUpkeeps', () => { const encodedData = '0xabcd' it('reverts if the from type is not V1 or V2', async () => { await evmRevert( transcoder.transcodeUpkeeps( UpkeepFormat.V3, UpkeepFormat.V1, encodedData, ), ) await evmRevert( transcoder.transcodeUpkeeps( UpkeepFormat.V4, UpkeepFormat.V1, encodedData, ), ) }) context('when from and to versions are correct', () => { upkeepsV3 = [ [executeGas, 2 ** 32 - 1, false, target0, amountSpent, balance, 0], [executeGas, 2 ** 32 - 1, false, target1, amountSpent, balance, 0], ] it('transcodes V1 upkeeps to V3 properly, regardless of toVersion value', async () => { upkeepsV1 = [ [ balance, lastKeeper0, executeGas, 2 ** 32, target0, amountSpent, await admin0.getAddress(), ], [ balance, lastKeeper1, executeGas, 2 ** 32, target1, amountSpent, await admin1.getAddress(), ], ] const data = await transcoder.transcodeUpkeeps( UpkeepFormat.V1, UpkeepFormat.V1, encodeUpkeepV1(idx, upkeepsV1, ['0xabcd', '0xffff']), ) assert.equal( encodeUpkeepV3(idx, upkeepsV3, ['0xabcd', '0xffff'], admins), data, ) }) it('transcodes V2 upkeeps to V3 properly, regardless of toVersion value', async () => { upkeepsV2 = [ [ balance, lastKeeper0, amountSpent, await admin0.getAddress(), executeGas, 2 ** 32 - 1, target0, false, ], [ balance, lastKeeper1, amountSpent, await admin1.getAddress(), executeGas, 2 ** 32 - 1, target1, false, ], ] const data = await transcoder.transcodeUpkeeps( UpkeepFormat.V2, UpkeepFormat.V2, encodeUpkeepV2(idx, upkeepsV2, ['0xabcd', '0xffff']), ) assert.equal( encodeUpkeepV3(idx, upkeepsV3, ['0xabcd', '0xffff'], admins), data, ) }) it('migrates upkeeps from 1.2 registry to 2.0', async () => { const linkToken = await deployLinkToken() const [gasPriceFeed, linkEthFeed] = await deployFeeds() const [id, legacyRegistry] = await deployLegacyRegistry1_2( linkToken, gasPriceFeed, linkEthFeed, ) const Registry2_0 = await deployRegistry2_0( linkToken, gasPriceFeed, linkEthFeed, ) await linkToken .connect(owner) .approve(legacyRegistry.address, toWei('1000')) await legacyRegistry.connect(owner).addFunds(id, toWei('1000')) // set outgoing permission to registry 2_0 and incoming permission for registry 1_2 await legacyRegistry.setPeerRegistryMigrationPermission( Registry2_0.address, 1, ) await Registry2_0.setPeerRegistryMigrationPermission( legacyRegistry.address, 2, ) expect((await legacyRegistry.getUpkeep(id)).balance).to.equal( toWei('1000'), ) expect((await legacyRegistry.getUpkeep(id)).checkData).to.equal( randomBytes, ) expect((await legacyRegistry.getState()).state.numUpkeeps).to.equal(1) await legacyRegistry .connect(admin0) .migrateUpkeeps([id], Registry2_0.address) expect((await legacyRegistry.getState()).state.numUpkeeps).to.equal(0) expect((await Registry2_0.getState()).state.numUpkeeps).to.equal(1) expect((await legacyRegistry.getUpkeep(id)).balance).to.equal(0) expect((await legacyRegistry.getUpkeep(id)).checkData).to.equal('0x') expect((await Registry2_0.getUpkeep(id)).balance).to.equal( toWei('1000'), ) expect( (await Registry2_0.getState()).state.expectedLinkBalance, ).to.equal(toWei('1000')) expect(await linkToken.balanceOf(Registry2_0.address)).to.equal( toWei('1000'), ) expect((await Registry2_0.getUpkeep(id)).checkData).to.equal( randomBytes, ) }) it('migrates upkeeps from 1.3 registry to 2.0', async () => { const linkToken = await deployLinkToken() const [gasPriceFeed, linkEthFeed] = await deployFeeds() const [id, legacyRegistry] = await deployLegacyRegistry1_3( linkToken, gasPriceFeed, linkEthFeed, ) const Registry2_0 = await deployRegistry2_0( linkToken, gasPriceFeed, linkEthFeed, ) await linkToken .connect(owner) .approve(legacyRegistry.address, toWei('1000')) await legacyRegistry.connect(owner).addFunds(id, toWei('1000')) // set outgoing permission to registry 2_0 and incoming permission for registry 1_3 await legacyRegistry.setPeerRegistryMigrationPermission( Registry2_0.address, 1, ) await Registry2_0.setPeerRegistryMigrationPermission( legacyRegistry.address, 2, ) expect((await legacyRegistry.getUpkeep(id)).balance).to.equal( toWei('1000'), ) expect((await legacyRegistry.getUpkeep(id)).checkData).to.equal( randomBytes, ) expect((await legacyRegistry.getState()).state.numUpkeeps).to.equal(1) await legacyRegistry .connect(admin0) .migrateUpkeeps([id], Registry2_0.address) expect((await legacyRegistry.getState()).state.numUpkeeps).to.equal(0) expect((await Registry2_0.getState()).state.numUpkeeps).to.equal(1) expect((await legacyRegistry.getUpkeep(id)).balance).to.equal(0) expect((await legacyRegistry.getUpkeep(id)).checkData).to.equal('0x') expect((await Registry2_0.getUpkeep(id)).balance).to.equal( toWei('1000'), ) expect( (await Registry2_0.getState()).state.expectedLinkBalance, ).to.equal(toWei('1000')) expect(await linkToken.balanceOf(Registry2_0.address)).to.equal( toWei('1000'), ) expect((await Registry2_0.getUpkeep(id)).checkData).to.equal( randomBytes, ) }) }) }) })