@hyperlane-xyz/core
Version:
Core solidity contracts for Hyperlane
1,052 lines (947 loc) • 36.5 kB
text/typescript
import { ethers } from 'hardhat'
import {
BigNumber,
BigNumberish,
BytesLike,
Contract,
ContractFactory,
Signer,
} from 'ethers'
import { assert, expect } from 'chai'
import { evmRevert, evmRevertCustomError } from '../../test-helpers/matchers'
import { getUsers, Personas } from '../../test-helpers/setup'
import { MockV3Aggregator__factory as MockV3AggregatorFactory } from '../../../typechain/factories/MockV3Aggregator__factory'
import { UpkeepMock__factory as UpkeepMockFactory } from '../../../typechain/factories/UpkeepMock__factory'
import { ChainModuleBase__factory as ChainModuleBaseFactory } from '../../../typechain/factories/ChainModuleBase__factory'
import { MockV3Aggregator } from '../../../typechain/MockV3Aggregator'
import { UpkeepMock } from '../../../typechain/UpkeepMock'
import { randomAddress, toWei } from '../../test-helpers/helpers'
import { ChainModuleBase } from '../../../typechain/ChainModuleBase'
import { AutomationRegistrar2_3 as Registrar } from '../../../typechain/AutomationRegistrar2_3'
import { deployRegistry23 } from './helpers'
import { IAutomationRegistryMaster2_3 as IAutomationRegistry } from '../../../typechain'
// copied from AutomationRegistryBase2_3.sol
enum Trigger {
CONDITION,
LOG,
}
const zeroAddress = ethers.constants.AddressZero
const wrappedNativeTokenAddress = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
type OnChainConfig = Parameters<IAutomationRegistry['setConfigTypeSafe']>[3]
let linkTokenFactory: ContractFactory
let mockV3AggregatorFactory: MockV3AggregatorFactory
let upkeepMockFactory: UpkeepMockFactory
let personas: Personas
before(async () => {
personas = (await getUsers()).personas
linkTokenFactory = await ethers.getContractFactory(
'src/v0.8/shared/test/helpers/LinkTokenTestHelper.sol:LinkTokenTestHelper',
)
mockV3AggregatorFactory = (await ethers.getContractFactory(
'src/v0.8/tests/MockV3Aggregator.sol:MockV3Aggregator',
)) as unknown as MockV3AggregatorFactory
upkeepMockFactory = await ethers.getContractFactory('UpkeepMock')
})
const errorMsgs = {
onlyOwner: 'revert Only callable by owner',
onlyAdmin: 'OnlyAdminOrOwner',
hashPayload: 'HashMismatch',
requestNotFound: 'RequestNotFound',
}
describe('AutomationRegistrar2_3', () => {
const upkeepName = 'SampleUpkeep'
const linkUSD = BigNumber.from('2000000000') // 1 LINK = $20
const nativeUSD = BigNumber.from('400000000000') // 1 ETH = $4000
const gasWei = BigNumber.from(100)
const performGas = BigNumber.from(100000)
const paymentPremiumPPB = BigNumber.from(250000000)
const flatFeeMilliCents = BigNumber.from(0)
const maxAllowedAutoApprove = 5
const trigger = '0xdeadbeef'
const offchainConfig = '0x01234567'
const keepers = [
randomAddress(),
randomAddress(),
randomAddress(),
randomAddress(),
]
const emptyBytes = '0x00'
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 fallbackNativePrice = BigNumber.from(200000000)
const maxCheckDataSize = BigNumber.from(10000)
const maxPerformDataSize = BigNumber.from(10000)
const maxRevertDataSize = BigNumber.from(1000)
const maxPerformGas = BigNumber.from(5000000)
const minimumRegistrationAmount = BigNumber.from('1000000000000000000')
const amount = BigNumber.from('5000000000000000000')
const transcoder = ethers.constants.AddressZero
const upkeepManager = ethers.Wallet.createRandom().address
// Enum values are not auto exported in ABI so have to manually declare
const autoApproveType_DISABLED = 0
const autoApproveType_ENABLED_SENDER_ALLOWLIST = 1
const autoApproveType_ENABLED_ALL = 2
let owner: Signer
let admin: Signer
let someAddress: Signer
let registrarOwner: Signer
let stranger: Signer
let requestSender: Signer
let linkToken: Contract
let linkUSDFeed: MockV3Aggregator
let nativeUSDFeed: MockV3Aggregator
let gasPriceFeed: MockV3Aggregator
let mock: UpkeepMock
let registry: IAutomationRegistry
let registrar: Registrar
let chainModuleBase: ChainModuleBase
let chainModuleBaseFactory: ChainModuleBaseFactory
let onchainConfig: OnChainConfig
type RegistrationParams = {
upkeepContract: string
amount: BigNumberish
adminAddress: string
gasLimit: BigNumberish
triggerType: BigNumberish
billingToken: string
name: string
encryptedEmail: BytesLike
checkData: BytesLike
triggerConfig: BytesLike
offchainConfig: BytesLike
}
function encodeRegistrationParams(params: RegistrationParams) {
return (
'0x' +
registrar.interface
.encodeFunctionData('registerUpkeep', [params])
.slice(10)
)
}
beforeEach(async () => {
owner = personas.Default
admin = personas.Neil
someAddress = personas.Ned
registrarOwner = personas.Nelly
stranger = personas.Nancy
requestSender = personas.Norbert
linkToken = await linkTokenFactory.connect(owner).deploy()
gasPriceFeed = await mockV3AggregatorFactory
.connect(owner)
.deploy(0, gasWei)
linkUSDFeed = await mockV3AggregatorFactory
.connect(owner)
.deploy(8, linkUSD)
nativeUSDFeed = await mockV3AggregatorFactory
.connect(owner)
.deploy(8, nativeUSD)
chainModuleBaseFactory = await ethers.getContractFactory('ChainModuleBase')
chainModuleBase = await chainModuleBaseFactory.connect(owner).deploy()
registry = await deployRegistry23(
owner,
linkToken.address,
linkUSDFeed.address,
nativeUSDFeed.address,
gasPriceFeed.address,
zeroAddress,
0, // onchain payout mode
wrappedNativeTokenAddress,
)
mock = await upkeepMockFactory.deploy()
const registrarFactory = await ethers.getContractFactory(
'AutomationRegistrar2_3',
)
registrar = await registrarFactory.connect(registrarOwner).deploy(
linkToken.address,
registry.address,
[
{
triggerType: Trigger.CONDITION,
autoApproveType: autoApproveType_DISABLED,
autoApproveMaxAllowed: 0,
},
{
triggerType: Trigger.LOG,
autoApproveType: autoApproveType_DISABLED,
autoApproveMaxAllowed: 0,
},
],
[linkToken.address],
[minimumRegistrationAmount],
wrappedNativeTokenAddress,
)
await linkToken
.connect(owner)
.transfer(await requestSender.getAddress(), toWei('1000'))
onchainConfig = {
checkGasLimit,
stalenessSeconds,
gasCeilingMultiplier,
maxCheckDataSize,
maxPerformDataSize,
maxRevertDataSize,
maxPerformGas,
fallbackGasPrice,
fallbackLinkPrice,
fallbackNativePrice,
transcoder,
registrars: [registrar.address],
upkeepPrivilegeManager: upkeepManager,
chainModule: chainModuleBase.address,
reorgProtectionEnabled: true,
financeAdmin: await admin.getAddress(),
}
await registry.connect(owner).setConfigTypeSafe(
keepers,
keepers,
1,
onchainConfig,
1,
'0x',
[linkToken.address],
[
{
gasFeePPB: paymentPremiumPPB,
flatFeeMilliCents,
priceFeed: await registry.getLinkUSDFeedAddress(),
fallbackPrice: 200,
minSpend: minimumRegistrationAmount,
decimals: 18,
},
],
)
})
describe('#typeAndVersion', () => {
it('uses the correct type and version', async () => {
const typeAndVersion = await registrar.typeAndVersion()
assert.equal(typeAndVersion, 'AutomationRegistrar 2.3.0')
})
})
describe('#onTokenTransfer', () => {
it('reverts if not called by the LINK token', async () => {
await evmRevertCustomError(
registrar
.connect(someAddress)
.onTokenTransfer(await someAddress.getAddress(), 0, '0x'),
registrar,
'OnlyLink',
)
})
it('reverts if the admin address is 0x0000...', async () => {
const abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: '0x0000000000000000000000000000000000000000',
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
await evmRevertCustomError(
linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes),
registrar,
'InvalidAdminAddress',
)
})
it('Auto Approve ON - registers an upkeep on KeeperRegistry instantly and emits both RegistrationRequested and RegistrationApproved events', async () => {
//set auto approve ON with high threshold limits
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_ENABLED_ALL,
maxAllowedAutoApprove,
)
//register with auto approve ON
const abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
const tx = await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
const [id] = await registry.getActiveUpkeepIDs(0, 1)
//confirm if a new upkeep has been registered and the details are the same as the one just registered
const newupkeep = await registry.getUpkeep(id)
assert.equal(newupkeep.target, mock.address)
assert.equal(newupkeep.admin, await admin.getAddress())
assert.equal(newupkeep.checkData, emptyBytes)
assert.equal(newupkeep.balance.toString(), amount.toString())
assert.equal(newupkeep.performGas, performGas.toNumber())
assert.equal(newupkeep.offchainConfig, offchainConfig)
await expect(tx).to.emit(registrar, 'RegistrationRequested')
await expect(tx).to.emit(registrar, 'RegistrationApproved')
})
it('Auto Approve ON - ignores the amount passed in and uses the actual amount sent', async () => {
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_ENABLED_ALL,
maxAllowedAutoApprove,
)
const abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount: amount.mul(10), // muhahahaha 😈
billingToken: linkToken.address,
})
await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
const [id] = await registry.getActiveUpkeepIDs(0, 1)
expect(await registry.getBalance(id)).to.equal(amount)
})
it('Auto Approve OFF - does not registers an upkeep on KeeperRegistry, emits only RegistrationRequested event', async () => {
//get upkeep count before attempting registration
const beforeCount = (await registry.getState()).state.numUpkeeps
//set auto approve OFF, threshold limits dont matter in this case
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_DISABLED,
maxAllowedAutoApprove,
)
//register with auto approve OFF
const abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
const tx = await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
const receipt = await tx.wait()
//get upkeep count after attempting registration
const afterCount = (await registry.getState()).state.numUpkeeps
//confirm that a new upkeep has NOT been registered and upkeep count is still the same
assert.deepEqual(beforeCount, afterCount)
//confirm that only RegistrationRequested event is emitted and RegistrationApproved event is not
await expect(tx).to.emit(registrar, 'RegistrationRequested')
await expect(tx).not.to.emit(registrar, 'RegistrationApproved')
const hash = receipt.logs[2].topics[1]
const pendingRequest = await registrar.getPendingRequest(hash)
assert.equal(await admin.getAddress(), pendingRequest[0])
assert.ok(amount.eq(pendingRequest[1]))
})
it('Auto Approve ON - Throttle max approvals - does not register an upkeep on KeeperRegistry beyond the max limit, emits only RegistrationRequested event after limit is hit', async () => {
assert.equal((await registry.getState()).state.numUpkeeps.toNumber(), 0)
//set auto approve on, with max 1 allowed
await registrar
.connect(registrarOwner)
.setTriggerConfig(Trigger.CONDITION, autoApproveType_ENABLED_ALL, 1)
//set auto approve on, with max 1 allowed
await registrar
.connect(registrarOwner)
.setTriggerConfig(Trigger.LOG, autoApproveType_ENABLED_ALL, 1)
// register within threshold, new upkeep should be registered
let abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
assert.equal((await registry.getState()).state.numUpkeeps.toNumber(), 1) // 0 -> 1
// try registering another one, new upkeep should not be registered
abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas.toNumber() + 1, // make unique hash
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
assert.equal((await registry.getState()).state.numUpkeeps.toNumber(), 1) // Still 1
// register a second type of upkeep, different limit
abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas, // make unique hash
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.LOG,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
assert.equal((await registry.getState()).state.numUpkeeps.toNumber(), 2) // 1 -> 2
// Now set new max limit to 2. One more upkeep should get auto approved
await registrar
.connect(registrarOwner)
.setTriggerConfig(Trigger.CONDITION, autoApproveType_ENABLED_ALL, 2)
abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas.toNumber() + 2, // make unique hash
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
assert.equal((await registry.getState()).state.numUpkeeps.toNumber(), 3) // 2 -> 3
// One more upkeep should not get registered
abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas.toNumber() + 3, // make unique hash
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
assert.equal((await registry.getState()).state.numUpkeeps.toNumber(), 3) // Still 3
})
it('Auto Approve Sender Allowlist - sender in allowlist - registers an upkeep on KeeperRegistry instantly and emits both RegistrationRequested and RegistrationApproved events', async () => {
const senderAddress = await requestSender.getAddress()
//set auto approve to ENABLED_SENDER_ALLOWLIST type with high threshold limits
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_ENABLED_SENDER_ALLOWLIST,
maxAllowedAutoApprove,
)
// Add sender to allowlist
await registrar
.connect(registrarOwner)
.setAutoApproveAllowedSender(senderAddress, true)
//register with auto approve ON
const abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
const tx = await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
const [id] = await registry.getActiveUpkeepIDs(0, 1)
//confirm if a new upkeep has been registered and the details are the same as the one just registered
const newupkeep = await registry.getUpkeep(id)
assert.equal(newupkeep.target, mock.address)
assert.equal(newupkeep.admin, await admin.getAddress())
assert.equal(newupkeep.checkData, emptyBytes)
assert.equal(newupkeep.balance.toString(), amount.toString())
assert.equal(newupkeep.performGas, performGas.toNumber())
await expect(tx).to.emit(registrar, 'RegistrationRequested')
await expect(tx).to.emit(registrar, 'RegistrationApproved')
})
it('Auto Approve Sender Allowlist - sender NOT in allowlist - does not registers an upkeep on KeeperRegistry, emits only RegistrationRequested event', async () => {
const beforeCount = (await registry.getState()).state.numUpkeeps
const senderAddress = await requestSender.getAddress()
//set auto approve to ENABLED_SENDER_ALLOWLIST type with high threshold limits
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_ENABLED_SENDER_ALLOWLIST,
maxAllowedAutoApprove,
)
// Explicitly remove sender from allowlist
await registrar
.connect(registrarOwner)
.setAutoApproveAllowedSender(senderAddress, false)
//register. auto approve shouldn't happen
const abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
const tx = await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
const receipt = await tx.wait()
//get upkeep count after attempting registration
const afterCount = (await registry.getState()).state.numUpkeeps
//confirm that a new upkeep has NOT been registered and upkeep count is still the same
assert.deepEqual(beforeCount, afterCount)
//confirm that only RegistrationRequested event is emitted and RegistrationApproved event is not
await expect(tx).to.emit(registrar, 'RegistrationRequested')
await expect(tx).not.to.emit(registrar, 'RegistrationApproved')
const hash = receipt.logs[2].topics[1]
const pendingRequest = await registrar.getPendingRequest(hash)
assert.equal(await admin.getAddress(), pendingRequest[0])
assert.ok(amount.eq(pendingRequest[1]))
})
})
describe('#registerUpkeep', () => {
it('reverts with empty message if amount sent is not available in LINK allowance', async () => {
await evmRevert(
registrar.connect(someAddress).registerUpkeep({
name: upkeepName,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
triggerType: Trigger.CONDITION,
checkData: emptyBytes,
triggerConfig: trigger,
offchainConfig: emptyBytes,
amount,
encryptedEmail: emptyBytes,
billingToken: linkToken.address,
}),
'',
)
})
it('reverts if the amount passed in data is less than configured minimum', async () => {
const amt = minimumRegistrationAmount.sub(1)
await linkToken.connect(requestSender).approve(registrar.address, amt)
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_ENABLED_ALL,
maxAllowedAutoApprove,
)
await evmRevertCustomError(
registrar.connect(requestSender).registerUpkeep({
name: upkeepName,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
triggerType: Trigger.CONDITION,
checkData: emptyBytes,
triggerConfig: trigger,
offchainConfig: emptyBytes,
amount: amt,
encryptedEmail: emptyBytes,
billingToken: linkToken.address,
}),
registrar,
'InsufficientPayment',
)
})
it('reverts if the billing token is not supported', async () => {
await linkToken
.connect(requestSender)
.approve(registrar.address, minimumRegistrationAmount)
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_ENABLED_ALL,
maxAllowedAutoApprove,
)
await registry
.connect(owner)
.setConfigTypeSafe(keepers, keepers, 1, onchainConfig, 1, '0x', [], [])
await evmRevertCustomError(
registrar.connect(requestSender).registerUpkeep({
name: upkeepName,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
triggerType: Trigger.CONDITION,
checkData: emptyBytes,
triggerConfig: trigger,
offchainConfig: emptyBytes,
amount: minimumRegistrationAmount,
encryptedEmail: emptyBytes,
billingToken: linkToken.address,
}),
registrar,
'InvalidBillingToken',
)
})
it('Auto Approve ON - registers an upkeep on KeeperRegistry instantly and emits both RegistrationRequested and RegistrationApproved events', async () => {
//set auto approve ON with high threshold limits
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_ENABLED_ALL,
maxAllowedAutoApprove,
)
await linkToken.connect(requestSender).approve(registrar.address, amount)
const tx = await registrar.connect(requestSender).registerUpkeep({
name: upkeepName,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
triggerType: Trigger.CONDITION,
checkData: emptyBytes,
triggerConfig: trigger,
offchainConfig,
amount,
encryptedEmail: emptyBytes,
billingToken: linkToken.address,
})
assert.equal((await registry.getState()).state.numUpkeeps.toNumber(), 1) // 0 -> 1
//confirm if a new upkeep has been registered and the details are the same as the one just registered
const [id] = await registry.getActiveUpkeepIDs(0, 1)
const newupkeep = await registry.getUpkeep(id)
assert.equal(newupkeep.target, mock.address)
assert.equal(newupkeep.admin, await admin.getAddress())
assert.equal(newupkeep.checkData, emptyBytes)
assert.equal(newupkeep.balance.toString(), amount.toString())
assert.equal(newupkeep.performGas, performGas.toNumber())
assert.equal(newupkeep.offchainConfig, offchainConfig)
await expect(tx).to.emit(registrar, 'RegistrationRequested')
await expect(tx).to.emit(registrar, 'RegistrationApproved')
})
})
describe('#setAutoApproveAllowedSender', () => {
it('reverts if not called by the owner', async () => {
const tx = registrar
.connect(stranger)
.setAutoApproveAllowedSender(await admin.getAddress(), false)
await evmRevert(tx, 'Only callable by owner')
})
it('sets the allowed status correctly and emits log', async () => {
const senderAddress = await stranger.getAddress()
let tx = await registrar
.connect(registrarOwner)
.setAutoApproveAllowedSender(senderAddress, true)
await expect(tx)
.to.emit(registrar, 'AutoApproveAllowedSenderSet')
.withArgs(senderAddress, true)
let senderAllowedStatus = await registrar
.connect(owner)
.getAutoApproveAllowedSender(senderAddress)
assert.isTrue(senderAllowedStatus)
tx = await registrar
.connect(registrarOwner)
.setAutoApproveAllowedSender(senderAddress, false)
await expect(tx)
.to.emit(registrar, 'AutoApproveAllowedSenderSet')
.withArgs(senderAddress, false)
senderAllowedStatus = await registrar
.connect(owner)
.getAutoApproveAllowedSender(senderAddress)
assert.isFalse(senderAllowedStatus)
})
})
describe('#setTriggerConfig', () => {
it('reverts if not called by the owner', async () => {
const tx = registrar
.connect(stranger)
.setTriggerConfig(Trigger.LOG, autoApproveType_ENABLED_ALL, 100)
await evmRevert(tx, 'Only callable by owner')
})
it('changes the config', async () => {
const tx = await registrar
.connect(registrarOwner)
.setTriggerConfig(Trigger.LOG, autoApproveType_ENABLED_ALL, 100)
await registrar.getTriggerRegistrationDetails(Trigger.LOG)
await expect(tx)
.to.emit(registrar, 'TriggerConfigSet')
.withArgs(Trigger.LOG, autoApproveType_ENABLED_ALL, 100)
})
})
describe('#approve', () => {
let params: RegistrationParams
beforeEach(async () => {
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_DISABLED,
maxAllowedAutoApprove,
)
params = {
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
}
//register with auto approve OFF
const abiEncodedBytes = encodeRegistrationParams(params)
const tx = await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
await tx.wait()
})
it('reverts if not called by the owner', async () => {
const tx = registrar.connect(stranger).approve({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig: emptyBytes,
amount,
billingToken: linkToken.address,
})
await evmRevert(tx, 'Only callable by owner')
})
it('reverts if the hash does not exist', async () => {
const tx = registrar.connect(registrarOwner).approve({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig: emptyBytes,
amount,
billingToken: linkToken.address,
})
await evmRevertCustomError(tx, registrar, errorMsgs.requestNotFound)
})
it('reverts if any member of the payload changes', async () => {
const invalidFields: any[] = [
{
name: 'fake',
},
{
encryptedEmail: '0xdeadbeef',
},
{
upkeepContract: ethers.Wallet.createRandom().address,
},
{
gasLimit: performGas.add(1),
},
{
adminAddress: randomAddress(),
},
{
checkData: '0xdeadbeef',
},
{
triggerType: Trigger.LOG,
},
{
triggerConfig: '0x1234',
},
{
offchainConfig: '0xdeadbeef',
},
{
amount: amount.add(1),
},
{
billingToken: randomAddress(),
},
]
for (let i = 0; i < invalidFields.length; i++) {
const field = invalidFields[i]
const badParams = Object.assign({}, params, field) as RegistrationParams
const tx = registrar.connect(registrarOwner).approve(badParams)
await expect(
tx,
`expected ${JSON.stringify(field)} to cause failure, but succeeded`,
).to.be.revertedWithCustomError(registrar, errorMsgs.requestNotFound)
}
})
it('approves an existing registration request', async () => {
const tx = await registrar.connect(registrarOwner).approve({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
await expect(tx).to.emit(registrar, 'RegistrationApproved')
})
it('deletes the request afterwards / reverts if the request DNE', async () => {
await registrar.connect(registrarOwner).approve({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
const tx = registrar.connect(registrarOwner).approve({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
await evmRevertCustomError(tx, registrar, errorMsgs.requestNotFound)
})
})
describe('#cancel', () => {
let hash: string
beforeEach(async () => {
await registrar
.connect(registrarOwner)
.setTriggerConfig(
Trigger.CONDITION,
autoApproveType_DISABLED,
maxAllowedAutoApprove,
)
//register with auto approve OFF
const abiEncodedBytes = encodeRegistrationParams({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig,
amount,
billingToken: linkToken.address,
})
const tx = await linkToken
.connect(requestSender)
.transferAndCall(registrar.address, amount, abiEncodedBytes)
const receipt = await tx.wait()
hash = receipt.logs[2].topics[1]
})
it('reverts if not called by the admin / owner', async () => {
const tx = registrar.connect(stranger).cancel(hash)
await evmRevertCustomError(tx, registrar, errorMsgs.onlyAdmin)
})
it('reverts if the hash does not exist', async () => {
const tx = registrar
.connect(registrarOwner)
.cancel(
'0x000000000000000000000000322813fd9a801c5507c9de605d63cea4f2ce6c44',
)
await evmRevertCustomError(tx, registrar, errorMsgs.requestNotFound)
})
it('refunds the total request balance to the admin address if owner cancels', async () => {
const before = await linkToken.balanceOf(await admin.getAddress())
const tx = await registrar.connect(registrarOwner).cancel(hash)
const after = await linkToken.balanceOf(await admin.getAddress())
assert.isTrue(after.sub(before).eq(amount.mul(BigNumber.from(1))))
await expect(tx).to.emit(registrar, 'RegistrationRejected')
})
it('refunds the total request balance to the admin address if admin cancels', async () => {
const before = await linkToken.balanceOf(await admin.getAddress())
const tx = await registrar.connect(admin).cancel(hash)
const after = await linkToken.balanceOf(await admin.getAddress())
assert.isTrue(after.sub(before).eq(amount.mul(BigNumber.from(1))))
await expect(tx).to.emit(registrar, 'RegistrationRejected')
})
it('deletes the request hash', async () => {
await registrar.connect(registrarOwner).cancel(hash)
let tx = registrar.connect(registrarOwner).cancel(hash)
await evmRevertCustomError(tx, registrar, errorMsgs.requestNotFound)
tx = registrar.connect(registrarOwner).approve({
name: upkeepName,
encryptedEmail: emptyBytes,
upkeepContract: mock.address,
gasLimit: performGas,
adminAddress: await admin.getAddress(),
checkData: emptyBytes,
triggerType: Trigger.CONDITION,
triggerConfig: trigger,
offchainConfig: emptyBytes,
amount,
billingToken: linkToken.address,
})
await evmRevertCustomError(tx, registrar, errorMsgs.requestNotFound)
})
})
})