@hyperlane-xyz/core
Version:
Core solidity contracts for Hyperlane
577 lines (528 loc) • 18.1 kB
text/typescript
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,
)
})
})
})
})