From 7472812dc49d3565e14794863ab2b82dbdd08383 Mon Sep 17 00:00:00 2001 From: Iva Brajer Date: Mon, 24 Jun 2024 14:02:05 +0200 Subject: [PATCH] Removed obsolete Hardhat VRFv2 tests (#13667) --- .../test/v0.8/vrf/BatchBlockhashStore.test.ts | 324 ----- .../test/v0.8/vrf/VRFCoordinatorV2.test.ts | 1173 ----------------- .../v0.8/vrf/VRFCoordinatorV2Mock.test.ts | 349 ----- .../vrf/VRFSubscriptionBalanceMonitor.test.ts | 638 --------- contracts/test/v0.8/vrf/VRFV2Wrapper.test.ts | 656 --------- 5 files changed, 3140 deletions(-) delete mode 100644 contracts/test/v0.8/vrf/BatchBlockhashStore.test.ts delete mode 100644 contracts/test/v0.8/vrf/VRFCoordinatorV2.test.ts delete mode 100644 contracts/test/v0.8/vrf/VRFCoordinatorV2Mock.test.ts delete mode 100644 contracts/test/v0.8/vrf/VRFSubscriptionBalanceMonitor.test.ts delete mode 100644 contracts/test/v0.8/vrf/VRFV2Wrapper.test.ts diff --git a/contracts/test/v0.8/vrf/BatchBlockhashStore.test.ts b/contracts/test/v0.8/vrf/BatchBlockhashStore.test.ts deleted file mode 100644 index ebdc17037ce..00000000000 --- a/contracts/test/v0.8/vrf/BatchBlockhashStore.test.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { assert, expect } from 'chai' -import { Contract, Signer } from 'ethers' -import { ethers } from 'hardhat' -import * as rlp from 'rlp' - -function range(size: number, startAt = 0) { - return [...Array(size).keys()].map((i) => i + startAt) -} - -describe('BatchBlockhashStore', () => { - let blockhashStore: Contract - let batchBHS: Contract - let owner: Signer - - beforeEach(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - - const bhFactory = await ethers.getContractFactory( - 'src/v0.8/vrf/dev/BlockhashStore.sol:BlockhashStore', - accounts[0], - ) - - blockhashStore = await bhFactory.deploy() - - const batchBHSFactory = await ethers.getContractFactory( - 'src/v0.8/vrf/BatchBlockhashStore.sol:BatchBlockhashStore', - accounts[0], - ) - - batchBHS = await batchBHSFactory.deploy(blockhashStore.address) - - // Mine some blocks so that we have some blockhashes to store. - for (let i = 0; i < 10; i++) { - await ethers.provider.send('evm_mine', []) - } - }) - - describe('#store', () => { - it('stores batches of blocknumbers', async () => { - const latestBlock = await ethers.provider.send('eth_blockNumber', []) - const bottomBlock = latestBlock - 5 - const numBlocks = 3 - await batchBHS.connect(owner).store(range(numBlocks, bottomBlock)) - - // Mine some blocks to confirm the store batch tx above. - for (let i = 0; i < 2; i++) { - await ethers.provider.send('evm_mine', []) - } - - // check the bhs if it was stored - for (let i = bottomBlock; i < bottomBlock + numBlocks; i++) { - const actualBh = await blockhashStore.connect(owner).getBlockhash(i) - const expectedBh = (await ethers.provider.getBlock(i)).hash - expect(expectedBh).to.equal(actualBh) - } - }) - - it('skips block numbers that are too far back', async () => { - // blockhash(n) fails if n is more than 256 blocks behind the current block in which - // the instruction is executing. - for (let i = 0; i < 256; i++) { - await ethers.provider.send('evm_mine', []) - } - - const gettableBlock = - (await ethers.provider.send('eth_blockNumber', [])) - 1 - - // Store 3 block numbers that are too far back, and one that is close enough. - await batchBHS.connect(owner).store([1, 2, 3, gettableBlock]) - - await ethers.provider.send('evm_mine', []) - - // Only block "250" should be stored - const actualBh = await blockhashStore - .connect(owner) - .getBlockhash(gettableBlock) - const expectedBh = (await ethers.provider.getBlock(gettableBlock)).hash - expect(expectedBh).to.equal(actualBh) - - // others were not stored - for (let i of [1, 2, 3]) { - expect( - blockhashStore.connect(owner).getBlockhash(i), - ).to.be.revertedWith('blockhash not found in store') - } - }) - }) - - describe('#getBlockhashes', () => { - it('fetches blockhashes of a batch of block numbers', async () => { - // Store a bunch of block hashes - const latestBlock = await ethers.provider.send('eth_blockNumber', []) - const bottomBlock = latestBlock - 5 - const numBlocks = 3 - await batchBHS.connect(owner).store(range(numBlocks, bottomBlock)) - - // Mine some blocks to confirm the store batch tx above. - for (let i = 0; i < 2; i++) { - await ethers.provider.send('evm_mine', []) - } - - // fetch the blocks in a batch - const actualBlockhashes = await batchBHS - .connect(owner) - .getBlockhashes(range(numBlocks, bottomBlock)) - let expectedBlockhashes = [] - for (let i = bottomBlock; i < bottomBlock + numBlocks; i++) { - const block = await ethers.provider.send('eth_getBlockByNumber', [ - '0x' + i.toString(16), - false, - ]) - expectedBlockhashes.push(block.hash) - } - assert.deepEqual(actualBlockhashes, expectedBlockhashes) - }) - - it('returns 0x0 for block numbers without an associated blockhash', async () => { - const latestBlock = await ethers.provider.send('eth_blockNumber', []) - const bottomBlock = latestBlock - 5 - const numBlocks = 3 - const blockhashes = await batchBHS - .connect(owner) - .getBlockhashes(range(numBlocks, bottomBlock)) - const expected = [ - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - '0x0000000000000000000000000000000000000000000000000000000000000000', - ] - assert.deepEqual(blockhashes, expected) - }) - }) - - describe('#storeVerifyHeader', () => { - it('stores batches of blocknumbers using storeVerifyHeader [ @skip-coverage ]', async () => { - // Store a single blockhash and go backwards from there using storeVerifyHeader - const latestBlock = await ethers.provider.send('eth_blockNumber', []) - await batchBHS.connect(owner).store([latestBlock]) - await ethers.provider.send('evm_mine', []) - - const numBlocks = 3 - const startBlock = latestBlock - 1 - const blockNumbers = range( - numBlocks + 1, - startBlock - numBlocks, - ).reverse() - let blockHeaders = [] - let expectedBlockhashes = [] - for (let i of blockNumbers) { - const block = await ethers.provider.send('eth_getBlockByNumber', [ - '0x' + (i + 1).toString(16), - false, - ]) - // eip 1559 header - switch to this if we upgrade hardhat - // and use post-london forks of ethereum. - const encodedHeader = rlp.encode([ - block.parentHash, - block.sha3Uncles, - ethers.utils.arrayify(block.miner), - block.stateRoot, - block.transactionsRoot, - block.receiptsRoot, - block.logsBloom, - block.difficulty == '0x0' ? '0x' : block.difficulty, - block.number, - block.gasLimit, - block.gasUsed == '0x0' ? '0x' : block.gasUsed, - block.timestamp, - block.extraData, - block.mixHash, - block.nonce, - block.baseFeePerGas, - ]) - // // pre-london block header serialization - kept for prosperity - // const encodedHeader = rlp.encode([ - // block.parentHash, - // block.sha3Uncles, - // ethers.utils.arrayify(block.miner), - // block.stateRoot, - // block.transactionsRoot, - // block.receiptsRoot, - // block.logsBloom, - // block.difficulty, - // block.number, - // block.gasLimit, - // block.gasUsed == '0x0' ? '0x' : block.gasUsed, - // block.timestamp, - // block.extraData, - // block.mixHash, - // block.nonce, - // ]) - blockHeaders.push('0x' + encodedHeader.toString('hex')) - expectedBlockhashes.push( - ( - await ethers.provider.send('eth_getBlockByNumber', [ - '0x' + i.toString(16), - false, - ]) - ).hash, - ) - } - await batchBHS - .connect(owner) - .storeVerifyHeader(blockNumbers, blockHeaders) - - // fetch blocks that were just stored and assert correctness - const actualBlockhashes = await batchBHS - .connect(owner) - .getBlockhashes(blockNumbers) - - assert.deepEqual(actualBlockhashes, expectedBlockhashes) - }) - - describe('bad input', () => { - it('reverts on mismatched input array sizes', async () => { - // Store a single blockhash and go backwards from there using storeVerifyHeader - const latestBlock = await ethers.provider.send('eth_blockNumber', []) - await batchBHS.connect(owner).store([latestBlock]) - - await ethers.provider.send('evm_mine', []) - - const numBlocks = 3 - const startBlock = latestBlock - 1 - const blockNumbers = range( - numBlocks + 1, - startBlock - numBlocks, - ).reverse() - let blockHeaders = [] - let expectedBlockhashes = [] - for (let i of blockNumbers) { - const block = await ethers.provider.send('eth_getBlockByNumber', [ - '0x' + (i + 1).toString(16), - false, - ]) - const encodedHeader = rlp.encode([ - block.parentHash, - block.sha3Uncles, - ethers.utils.arrayify(block.miner), - block.stateRoot, - block.transactionsRoot, - block.receiptsRoot, - block.logsBloom, - block.difficulty == '0x0' ? '0x' : block.difficulty, - block.number, - block.gasLimit, - block.gasUsed == '0x0' ? '0x' : block.gasUsed, - block.timestamp, - block.extraData, - block.mixHash, - block.nonce, - block.baseFeePerGas, - ]) - blockHeaders.push('0x' + encodedHeader.toString('hex')) - expectedBlockhashes.push( - ( - await ethers.provider.send('eth_getBlockByNumber', [ - '0x' + i.toString(16), - false, - ]) - ).hash, - ) - } - // remove last element to simulate different input array sizes - blockHeaders.pop() - expect( - batchBHS.connect(owner).storeVerifyHeader(blockNumbers, blockHeaders), - ).to.be.revertedWith('input array arg lengths mismatch') - }) - - it('reverts on bad block header input', async () => { - // Store a single blockhash and go backwards from there using storeVerifyHeader - const latestBlock = await ethers.provider.send('eth_blockNumber', []) - await batchBHS.connect(owner).store([latestBlock]) - - await ethers.provider.send('evm_mine', []) - - const numBlocks = 3 - const startBlock = latestBlock - 1 - const blockNumbers = range( - numBlocks + 1, - startBlock - numBlocks, - ).reverse() - let blockHeaders = [] - let expectedBlockhashes = [] - for (let i of blockNumbers) { - const block = await ethers.provider.send('eth_getBlockByNumber', [ - '0x' + (i + 1).toString(16), - false, - ]) - const encodedHeader = rlp.encode([ - block.parentHash, - block.sha3Uncles, - ethers.utils.arrayify(block.miner), - block.stateRoot, - block.transactionsRoot, - block.receiptsRoot, - block.logsBloom, - block.difficulty == '0x0' ? '0x' : block.difficulty, - block.number, - block.gasLimit, - block.gasUsed, // incorrect: in cases where it's 0x0 it should be 0x instead. - block.timestamp, - block.extraData, - block.mixHash, - block.nonce, - block.baseFeePerGas, - ]) - blockHeaders.push('0x' + encodedHeader.toString('hex')) - expectedBlockhashes.push( - ( - await ethers.provider.send('eth_getBlockByNumber', [ - '0x' + i.toString(16), - false, - ]) - ).hash, - ) - } - expect( - batchBHS.connect(owner).storeVerifyHeader(blockNumbers, blockHeaders), - ).to.be.revertedWith('header has unknown blockhash') - }) - }) - }) -}) diff --git a/contracts/test/v0.8/vrf/VRFCoordinatorV2.test.ts b/contracts/test/v0.8/vrf/VRFCoordinatorV2.test.ts deleted file mode 100644 index 59f3811eedb..00000000000 --- a/contracts/test/v0.8/vrf/VRFCoordinatorV2.test.ts +++ /dev/null @@ -1,1173 +0,0 @@ -import { ethers } from 'hardhat' -import { BigNumber, Contract, Signer } from 'ethers' -import { assert, expect } from 'chai' -import { publicAbi } from '../../test-helpers/helpers' -import { randomAddressString } from 'hardhat/internal/hardhat-network/provider/utils/random' - -describe('VRFCoordinatorV2', () => { - let vrfCoordinatorV2: Contract - let vrfCoordinatorV2TestHelper: Contract - let linkToken: Contract - let blockHashStore: Contract - let mockLinkEth: Contract - let owner: Signer - let subOwner: Signer - let subOwnerAddress: string - let consumer: Signer - let random: Signer - let randomAddress: string - let oracle: Signer - const linkEth = BigNumber.from(300000000) - type config = { - minimumRequestBlockConfirmations: number - maxGasLimit: number - stalenessSeconds: number - gasAfterPaymentCalculation: number - weiPerUnitLink: BigNumber - } - let c: config - - beforeEach(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - subOwner = accounts[1] - subOwnerAddress = await subOwner.getAddress() - consumer = accounts[2] - random = accounts[3] - randomAddress = await random.getAddress() - oracle = accounts[4] - const ltFactory = await ethers.getContractFactory( - 'src/v0.8/shared/test/helpers/LinkTokenTestHelper.sol:LinkTokenTestHelper', - accounts[0], - ) - linkToken = await ltFactory.deploy() - const bhFactory = await ethers.getContractFactory( - 'src/v0.8/vrf/dev/BlockhashStore.sol:BlockhashStore', - accounts[0], - ) - blockHashStore = await bhFactory.deploy() - const mockAggregatorV3Factory = await ethers.getContractFactory( - 'src/v0.8/tests/MockV3Aggregator.sol:MockV3Aggregator', - accounts[0], - ) - mockLinkEth = await mockAggregatorV3Factory.deploy(0, linkEth) - const vrfCoordinatorV2Factory = await ethers.getContractFactory( - 'src/v0.8/vrf/VRFCoordinatorV2.sol:VRFCoordinatorV2', - accounts[0], - ) - vrfCoordinatorV2 = await vrfCoordinatorV2Factory.deploy( - linkToken.address, - blockHashStore.address, - mockLinkEth.address, - ) - const vrfCoordinatorV2TestHelperFactory = await ethers.getContractFactory( - 'src/v0.8/vrf/testhelpers/VRFCoordinatorV2TestHelper.sol:VRFCoordinatorV2TestHelper', - accounts[0], - ) - vrfCoordinatorV2TestHelper = await vrfCoordinatorV2TestHelperFactory.deploy( - mockLinkEth.address, - ) - await linkToken.transfer( - subOwnerAddress, - BigNumber.from('1000000000000000000'), - ) // 1 link - await linkToken.transfer( - randomAddress, - BigNumber.from('1000000000000000000'), - ) // 1 link - c = { - minimumRequestBlockConfirmations: 1, - maxGasLimit: 1000000, - stalenessSeconds: 86400, - gasAfterPaymentCalculation: - 21000 + 5000 + 2100 + 20000 + 2 * 2100 - 15000 + 7315, - weiPerUnitLink: BigNumber.from('10000000000000000'), - } - // Note if you try and use an object, ethers - // confuses that with an override object and will error. - // It appears that only arrays work for struct args. - const fc = [0, 0, 0, 0, 0, 0, 0, 0, 0] - await vrfCoordinatorV2 - .connect(owner) - .setConfig( - c.minimumRequestBlockConfirmations, - c.maxGasLimit, - c.stalenessSeconds, - c.gasAfterPaymentCalculation, - c.weiPerUnitLink, - fc, - ) - }) - - it('has a limited public interface [ @skip-coverage ]', async () => { - publicAbi(vrfCoordinatorV2, [ - // Public constants - 'MAX_CONSUMERS', - 'MAX_NUM_WORDS', - 'MAX_REQUEST_CONFIRMATIONS', - // Owner - 'acceptOwnership', - 'transferOwnership', - 'owner', - 'getConfig', - 'getFeeConfig', - 'getFallbackWeiPerUnitLink', - 'getCurrentSubId', - 'setConfig', - 'getRequestConfig', - 'recoverFunds', - 'ownerCancelSubscription', - 'getFeeTier', - 'pendingRequestExists', - 'getTotalBalance', - // Oracle - 'requestRandomWords', - 'getCommitment', // Note we use this to check if a request is already fulfilled. - 'hashOfKey', - 'fulfillRandomWords', - 'registerProvingKey', - 'deregisterProvingKey', - 'oracleWithdraw', - // Subscription management - 'createSubscription', - 'addConsumer', - 'removeConsumer', - 'getSubscription', - 'onTokenTransfer', // Effectively the fundSubscription. - 'cancelSubscription', - 'requestSubscriptionOwnerTransfer', - 'acceptSubscriptionOwnerTransfer', - // Misc - 'typeAndVersion', - 'BLOCKHASH_STORE', - 'LINK', - 'LINK_ETH_FEED', - ]) - }) - - describe('#setConfig', async function () { - it('only owner can set', async function () { - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .setConfig( - c.minimumRequestBlockConfirmations, - c.maxGasLimit, - c.stalenessSeconds, - c.gasAfterPaymentCalculation, - c.weiPerUnitLink, - [0, 0, 0, 0, 0, 0, 0, 0, 0], - ), - ).to.be.revertedWith('Only callable by owner') - // Anyone can read the config. - const resp = await vrfCoordinatorV2.connect(random).getConfig() - assert(resp[0] == c.minimumRequestBlockConfirmations) - assert(resp[1] == c.maxGasLimit) - assert(resp[2] == c.stalenessSeconds) - assert(resp[3].toString() == c.gasAfterPaymentCalculation.toString()) - }) - - it('max req confs', async function () { - await expect( - vrfCoordinatorV2 - .connect(owner) - .setConfig( - 201, - c.maxGasLimit, - c.stalenessSeconds, - c.gasAfterPaymentCalculation, - c.weiPerUnitLink, - [0, 0, 0, 0, 0, 0, 0, 0, 0], - ), - ) - .to.be.revertedWithCustomError( - vrfCoordinatorV2, - 'InvalidRequestConfirmations', - ) - .withArgs(201, 201, 200) - }) - - it('positive fallback price', async function () { - await expect( - vrfCoordinatorV2 - .connect(owner) - .setConfig( - c.minimumRequestBlockConfirmations, - c.maxGasLimit, - c.stalenessSeconds, - c.gasAfterPaymentCalculation, - 0, - [0, 0, 0, 0, 0, 0, 0, 0, 0], - ), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, 'InvalidLinkWeiPrice') - .withArgs(0) - await expect( - vrfCoordinatorV2 - .connect(owner) - .setConfig( - c.minimumRequestBlockConfirmations, - c.maxGasLimit, - c.stalenessSeconds, - c.gasAfterPaymentCalculation, - -1, - [0, 0, 0, 0, 0, 0, 0, 0, 0], - ), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, 'InvalidLinkWeiPrice') - .withArgs(-1) - }) - }) - - async function createSubscription(): Promise { - // let consumers: string[] = [await consumer.getAddress()]; - const tx = await vrfCoordinatorV2.connect(subOwner).createSubscription() - const receipt = await tx.wait() - const subId = receipt.events[0].args['subId'] - await vrfCoordinatorV2 - .connect(subOwner) - .addConsumer(subId, await consumer.getAddress()) - return subId - } - - async function createSubscriptionWithConsumers( - consumers: string[], - ): Promise { - const tx = await vrfCoordinatorV2.connect(subOwner).createSubscription() - const receipt = await tx.wait() - const subId = receipt.events[0].args['subId'] - for (let i = 0; i < consumers.length; i++) { - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, consumers[i]) - } - return subId - } - - describe('#createSubscription', async function () { - it('can create a subscription', async function () { - await expect(vrfCoordinatorV2.connect(subOwner).createSubscription()) - .to.emit(vrfCoordinatorV2, 'SubscriptionCreated') - .withArgs(1, subOwnerAddress) - const s = await vrfCoordinatorV2.getSubscription(1) - assert(s.balance.toString() == '0', 'invalid balance') - assert(s.owner == subOwnerAddress, 'invalid address') - }) - it('subscription id increments', async function () { - await expect(vrfCoordinatorV2.connect(subOwner).createSubscription()) - .to.emit(vrfCoordinatorV2, 'SubscriptionCreated') - .withArgs(1, subOwnerAddress) - await expect(vrfCoordinatorV2.connect(subOwner).createSubscription()) - .to.emit(vrfCoordinatorV2, 'SubscriptionCreated') - .withArgs(2, subOwnerAddress) - }) - it('cannot create more than the max', async function () { - const subId = createSubscriptionWithConsumers([]) - for (let i = 0; i < 100; i++) { - await vrfCoordinatorV2 - .connect(subOwner) - .addConsumer(subId, randomAddressString()) - } - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .addConsumer(subId, randomAddressString()), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `TooManyConsumers`) - }) - }) - - describe('#requestSubscriptionOwnerTransfer', async function () { - let subId: number - beforeEach(async () => { - subId = await createSubscription() - }) - it('rejects non-owner', async function () { - await expect( - vrfCoordinatorV2 - .connect(random) - .requestSubscriptionOwnerTransfer(subId, randomAddress), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeSubOwner`) - .withArgs(subOwnerAddress) - }) - it('owner can request transfer', async function () { - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .requestSubscriptionOwnerTransfer(subId, randomAddress), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionOwnerTransferRequested') - .withArgs(subId, subOwnerAddress, randomAddress) - // Same request is a noop - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .requestSubscriptionOwnerTransfer(subId, randomAddress), - ).to.not.emit(vrfCoordinatorV2, 'SubscriptionOwnerTransferRequested') - }) - }) - - describe('#acceptSubscriptionOwnerTransfer', async function () { - let subId: number - beforeEach(async () => { - subId = await createSubscription() - }) - it('subscription must exist', async function () { - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .acceptSubscriptionOwnerTransfer(1203123123), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidSubscription`) - }) - it('must be requested owner to accept', async function () { - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .requestSubscriptionOwnerTransfer(subId, randomAddress), - ) - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .acceptSubscriptionOwnerTransfer(subId), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeRequestedOwner`) - .withArgs(randomAddress) - }) - it('requested owner can accept', async function () { - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .requestSubscriptionOwnerTransfer(subId, randomAddress), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionOwnerTransferRequested') - .withArgs(subId, subOwnerAddress, randomAddress) - await expect( - vrfCoordinatorV2.connect(random).acceptSubscriptionOwnerTransfer(subId), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionOwnerTransferred') - .withArgs(subId, subOwnerAddress, randomAddress) - }) - }) - - describe('#addConsumer', async function () { - let subId: number - beforeEach(async () => { - subId = await createSubscription() - }) - it('subscription must exist', async function () { - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .addConsumer(1203123123, randomAddress), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidSubscription`) - }) - it('must be owner', async function () { - await expect( - vrfCoordinatorV2.connect(random).addConsumer(subId, randomAddress), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeSubOwner`) - .withArgs(subOwnerAddress) - }) - it('add is idempotent', async function () { - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress) - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress) - }) - it('cannot add more than maximum', async function () { - // There is one consumer, add another 99 to hit the max - for (let i = 0; i < 99; i++) { - await vrfCoordinatorV2 - .connect(subOwner) - .addConsumer(subId, randomAddressString()) - } - // Adding one more should fail - // await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress); - await expect( - vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `TooManyConsumers`) - // Same is true if we first create with the maximum - const consumers: string[] = [] - for (let i = 0; i < 100; i++) { - consumers.push(randomAddressString()) - } - subId = await createSubscriptionWithConsumers(consumers) - await expect( - vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `TooManyConsumers`) - }) - it('owner can update', async function () { - await expect( - vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionConsumerAdded') - .withArgs(subId, randomAddress) - }) - }) - - describe('#removeConsumer', async function () { - let subId: number - beforeEach(async () => { - subId = await createSubscription() - }) - it('subscription must exist', async function () { - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .removeConsumer(1203123123, randomAddress), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidSubscription`) - }) - it('must be owner', async function () { - await expect( - vrfCoordinatorV2.connect(random).removeConsumer(subId, randomAddress), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeSubOwner`) - .withArgs(subOwnerAddress) - }) - it('owner can update', async function () { - const subBefore = await vrfCoordinatorV2.getSubscription(subId) - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress) - await expect( - vrfCoordinatorV2.connect(subOwner).removeConsumer(subId, randomAddress), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionConsumerRemoved') - .withArgs(subId, randomAddress) - const subAfter = await vrfCoordinatorV2.getSubscription(subId) - // Subscription should NOT contain the removed consumer - assert.deepEqual(subBefore.consumers, subAfter.consumers) - }) - it('can remove all consumers', async function () { - // Testing the handling of zero. - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress) - await vrfCoordinatorV2 - .connect(subOwner) - .removeConsumer(subId, randomAddress) - await vrfCoordinatorV2 - .connect(subOwner) - .removeConsumer(subId, await consumer.getAddress()) - // Should be empty - const subAfter = await vrfCoordinatorV2.getSubscription(subId) - assert.deepEqual(subAfter.consumers, []) - }) - }) - - describe('#cancelSubscription', async function () { - let subId: number - beforeEach(async () => { - subId = await createSubscription() - }) - it('subscription must exist', async function () { - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .cancelSubscription(1203123123, subOwnerAddress), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidSubscription`) - }) - it('must be owner', async function () { - await expect( - vrfCoordinatorV2 - .connect(random) - .cancelSubscription(subId, subOwnerAddress), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeSubOwner`) - .withArgs(subOwnerAddress) - }) - it('can cancel', async function () { - await linkToken - .connect(subOwner) - .transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000'), - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .cancelSubscription(subId, randomAddress), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionCanceled') - .withArgs(subId, randomAddress, BigNumber.from('1000')) - const randomBalance = await linkToken.balanceOf(randomAddress) - assert.equal(randomBalance.toString(), '1000000000000001000') - await expect( - vrfCoordinatorV2.connect(subOwner).getSubscription(subId), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, 'InvalidSubscription') - }) - it('can add same consumer after canceling', async function () { - await linkToken - .connect(subOwner) - .transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000'), - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress) - await vrfCoordinatorV2 - .connect(subOwner) - .cancelSubscription(subId, randomAddress) - subId = await createSubscription() - // The cancel should have removed this consumer, so we can add it again. - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress) - }) - it('cannot cancel with pending req', async function () { - await linkToken - .connect(subOwner) - .transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000'), - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress) - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - await vrfCoordinatorV2.registerProvingKey(subOwnerAddress, testKey) - await vrfCoordinatorV2.connect(owner).reg - const kh = await vrfCoordinatorV2.hashOfKey(testKey) - await vrfCoordinatorV2.connect(consumer).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000000, // callbackGasLimit - 1, // numWords - ) - // Should revert with outstanding requests - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .cancelSubscription(subId, randomAddress), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, 'PendingRequestExists') - // However the owner is able to cancel - // funds go to the sub owner. - await expect( - vrfCoordinatorV2.connect(owner).ownerCancelSubscription(subId), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionCanceled') - .withArgs(subId, subOwnerAddress, BigNumber.from('1000')) - }) - }) - - describe('#recoverFunds', async function () { - let subId: number - beforeEach(async () => { - subId = await createSubscription() - }) - - // Note we can't test the oracleWithdraw without fulfilling a request, so leave - // that coverage to the go tests. - it('function that should change internal balance do', async function () { - type bf = [() => Promise, BigNumber] - const balanceChangingFns: Array = [ - [ - async function () { - const s = ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]) - await linkToken - .connect(subOwner) - .transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000'), - s, - ) - }, - BigNumber.from('1000'), - ], - [ - async function () { - await vrfCoordinatorV2 - .connect(subOwner) - .cancelSubscription(subId, randomAddress) - }, - BigNumber.from('-1000'), - ], - ] - for (const [fn, expectedBalanceChange] of balanceChangingFns) { - const startingBalance = await vrfCoordinatorV2.getTotalBalance() - await fn() - const endingBalance = await vrfCoordinatorV2.getTotalBalance() - assert( - endingBalance.sub(startingBalance).toString() == - expectedBalanceChange.toString(), - ) - } - }) - it('only owner can recover', async function () { - await expect( - vrfCoordinatorV2.connect(subOwner).recoverFunds(randomAddress), - ).to.be.revertedWith(`Only callable by owner`) - }) - - it('owner can recover link transferred', async function () { - // Set the internal balance - assert(BigNumber.from('0'), linkToken.balanceOf(randomAddress)) - const s = ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]) - await linkToken - .connect(subOwner) - .transferAndCall(vrfCoordinatorV2.address, BigNumber.from('1000'), s) - // Circumvent internal balance - await linkToken - .connect(subOwner) - .transfer(vrfCoordinatorV2.address, BigNumber.from('1000')) - // Should recover this 1000 - await expect(vrfCoordinatorV2.connect(owner).recoverFunds(randomAddress)) - .to.emit(vrfCoordinatorV2, 'FundsRecovered') - .withArgs(randomAddress, BigNumber.from('1000')) - assert(BigNumber.from('1000'), linkToken.balanceOf(randomAddress)) - }) - }) - - it('subscription lifecycle', async function () { - // Create subscription. - const tx = await vrfCoordinatorV2.connect(subOwner).createSubscription() - const receipt = await tx.wait() - assert(receipt.events[0].event == 'SubscriptionCreated') - assert(receipt.events[0].args['owner'] == subOwnerAddress, 'sub owner') - const subId = receipt.events[0].args['subId'] - await vrfCoordinatorV2 - .connect(subOwner) - .addConsumer(subId, await consumer.getAddress()) - - // Fund the subscription - await expect( - linkToken - .connect(subOwner) - .transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000000000000000000'), - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionFunded') - .withArgs(subId, BigNumber.from(0), BigNumber.from('1000000000000000000')) - - // Non-owners cannot change the consumers - await expect( - vrfCoordinatorV2.connect(random).addConsumer(subId, randomAddress), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeSubOwner`) - .withArgs(subOwnerAddress) - await expect( - vrfCoordinatorV2.connect(random).removeConsumer(subId, randomAddress), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeSubOwner`) - .withArgs(subOwnerAddress) - - // Non-owners cannot ask to transfer ownership - await expect( - vrfCoordinatorV2 - .connect(random) - .requestSubscriptionOwnerTransfer(subId, randomAddress), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeSubOwner`) - .withArgs(subOwnerAddress) - - // Owners can request ownership transfership - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .requestSubscriptionOwnerTransfer(subId, randomAddress), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionOwnerTransferRequested') - .withArgs(subId, subOwnerAddress, randomAddress) - - // Non-requested owners cannot accept - await expect( - vrfCoordinatorV2.connect(subOwner).acceptSubscriptionOwnerTransfer(subId), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeRequestedOwner`) - .withArgs(randomAddress) - - // Requested owners can accept - await expect( - vrfCoordinatorV2.connect(random).acceptSubscriptionOwnerTransfer(subId), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionOwnerTransferred') - .withArgs(subId, subOwnerAddress, randomAddress) - - // Transfer it back to subOwner - vrfCoordinatorV2 - .connect(random) - .requestSubscriptionOwnerTransfer(subId, subOwnerAddress) - vrfCoordinatorV2.connect(subOwner).acceptSubscriptionOwnerTransfer(subId) - - // Non-owners cannot cancel - await expect( - vrfCoordinatorV2.connect(random).cancelSubscription(subId, randomAddress), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `MustBeSubOwner`) - .withArgs(subOwnerAddress) - - await expect( - vrfCoordinatorV2 - .connect(subOwner) - .cancelSubscription(subId, randomAddress), - ) - .to.emit(vrfCoordinatorV2, 'SubscriptionCanceled') - .withArgs(subId, randomAddress, BigNumber.from('1000000000000000000')) - const random2Balance = await linkToken.balanceOf(randomAddress) - assert.equal(random2Balance.toString(), '2000000000000000000') - }) - - describe('#requestRandomWords', async function () { - let subId: number - let kh: string - beforeEach(async () => { - subId = await createSubscription() - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - kh = await vrfCoordinatorV2.hashOfKey(testKey) - }) - it('invalid subId', async function () { - await expect( - vrfCoordinatorV2.connect(random).requestRandomWords( - kh, // keyhash - 12301928312, // subId - 1, // minReqConf - 1000, // callbackGasLimit - 1, // numWords - ), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidSubscription`) - }) - it('invalid consumer', async function () { - await expect( - vrfCoordinatorV2.connect(random).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000, // callbackGasLimit - 1, // numWords - ), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidConsumer`) - .withArgs(subId, randomAddress) - }) - it('invalid req confs', async function () { - await expect( - vrfCoordinatorV2.connect(consumer).requestRandomWords( - kh, // keyhash - subId, // subId - 0, // minReqConf - 1000, // callbackGasLimit - 1, // numWords - ), - ) - .to.be.revertedWithCustomError( - vrfCoordinatorV2, - `InvalidRequestConfirmations`, - ) - .withArgs(0, 1, 200) - }) - it('gas limit too high', async function () { - await linkToken.connect(subOwner).transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000000000000000000'), // 1 link > 0.1 min. - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - await expect( - vrfCoordinatorV2.connect(consumer).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000001, // callbackGasLimit - 1, // numWords - ), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `GasLimitTooBig`) - .withArgs(1000001, 1000000) - }) - - it('nonce increments', async function () { - await linkToken.connect(subOwner).transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000000000000000000'), // 1 link > 0.1 min. - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - const r1 = await vrfCoordinatorV2.connect(consumer).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000000, // callbackGasLimit - 1, // numWords - ) - const r1Receipt = await r1.wait() - const seed1 = r1Receipt.events[0].args['requestId'] - const r2 = await vrfCoordinatorV2.connect(consumer).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000000, // callbackGasLimit - 1, // numWords - ) - const r2Receipt = await r2.wait() - const seed2 = r2Receipt.events[0].args['requestId'] - assert(seed2 != seed1) - }) - - it('emits correct log', async function () { - await linkToken.connect(subOwner).transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000000000000000000'), // 1 link > 0.1 min. - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - const reqTx = await vrfCoordinatorV2.connect(consumer).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000, // callbackGasLimit - 1, // numWords - ) - const reqReceipt = await reqTx.wait() - assert(reqReceipt.events.length == 1) - const reqEvent = reqReceipt.events[0] - assert(reqEvent.event == 'RandomWordsRequested', 'wrong event name') - assert( - reqEvent.args['keyHash'] == kh, - `wrong kh ${reqEvent.args['keyHash']} ${kh}`, - ) - assert( - reqEvent.args['subId'].toString() == subId.toString(), - 'wrong subId', - ) - assert( - reqEvent.args['minimumRequestConfirmations'].toString() == - BigNumber.from(1).toString(), - 'wrong minRequestConf', - ) - assert( - reqEvent.args['callbackGasLimit'] == 1000, - 'wrong callbackGasLimit', - ) - assert(reqEvent.args['numWords'] == 1, 'wrong numWords') - assert( - reqEvent.args['sender'] == (await consumer.getAddress()), - 'wrong sender address', - ) - }) - it('add/remove consumer invariant', async function () { - await linkToken.connect(subOwner).transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000000000000000000'), // 1 link > 0.1 min. - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - await vrfCoordinatorV2.connect(subOwner).addConsumer(subId, randomAddress) - await vrfCoordinatorV2 - .connect(subOwner) - .removeConsumer(subId, randomAddress) - await expect( - vrfCoordinatorV2.connect(random).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000, // callbackGasLimit - 1, // numWords - ), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidConsumer`) - .withArgs(subId, randomAddress) - }) - it('cancel/add subscription invariant', async function () { - await linkToken.connect(subOwner).transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000000000000000000'), // 1 link > 0.1 min. - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - await vrfCoordinatorV2 - .connect(subOwner) - .cancelSubscription(subId, randomAddress) - subId = await createSubscriptionWithConsumers([]) - // Should not succeed because consumer was previously registered - // i.e. cancel should be cleaning up correctly. - await expect( - vrfCoordinatorV2.connect(random).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000, // callbackGasLimit - 1, // numWords - ), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidConsumer`) - .withArgs(subId, randomAddress) - }) - }) - - describe('#oracleWithdraw', async function () { - it('cannot withdraw with no balance', async function () { - await expect( - vrfCoordinatorV2 - .connect(oracle) - .oracleWithdraw(randomAddressString(), BigNumber.from('100')), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `InsufficientBalance`) - }) - }) - - describe('#calculatePaymentAmount [ @skip-coverage ]', async function () { - it('output within sensible range', async function () { - // By default, hardhat sends txes with the block limit as their gas limit. - await vrfCoordinatorV2TestHelper - .connect(oracle) - .calculatePaymentAmountTest( - BigNumber.from('0'), // Gas after payment - 0, // Fee PPM - BigNumber.from('1000000000'), // Wei per unit gas (gas price) - ) - const paymentAmount = await vrfCoordinatorV2TestHelper.getPaymentAmount() - // The gas price is 1gwei and the eth/link price is set to 300000000 wei per unit link. - // paymentAmount = 1e18*weiPerUnitGas*(gasAfterPaymentCalculation + startGas - gasleft()) / uint256(weiPerUnitLink); - // So we expect x to be in the range (few thousand gas for the call) - // 1e18*1e9*(1000 gas)/30000000 < x < 1e18*1e9*(5000 gas)/30000000 - // 3.333333333E22 < x < 1.666666667E23 - //const gss = await vrfCoordinatorV2TestHelper.getGasStart(); - assert( - paymentAmount.gt(BigNumber.from('33333333330000000000000')), - 'payment too small', - ) - assert( - paymentAmount.lt(BigNumber.from('166666666600000000000000')), - 'payment too large', - ) - }) - it('payment too large', async function () { - // Set this gas price to be astronomical 1ETH/gas - // That means the payment will be (even for 1gas) - // 1e18*1e18/30000000 - // 3.333333333E28 > 1e27 (all link in existence) - await expect( - vrfCoordinatorV2TestHelper.connect(oracle).calculatePaymentAmountTest( - BigNumber.from('0'), // Gas after payment - 0, // Fee PPM - BigNumber.from('1000000000000000000'), - ), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `PaymentTooLarge`) - }) - - it('non-positive link wei price should revert', async function () { - const mockAggregatorV3Factory = await ethers.getContractFactory( - 'src/v0.8/tests/MockV3Aggregator.sol:MockV3Aggregator', - owner, - ) - const vrfCoordinatorV2TestHelperFactory = await ethers.getContractFactory( - 'VRFCoordinatorV2TestHelper', - owner, - ) - const mockLinkEthZero = await mockAggregatorV3Factory.deploy(0, 0) - const vrfCoordinatorV2TestHelperZero = - await vrfCoordinatorV2TestHelperFactory.deploy(mockLinkEthZero.address) - await expect( - vrfCoordinatorV2TestHelperZero - .connect(oracle) - .calculatePaymentAmountTest( - BigNumber.from('0'), // Gas after payment - 0, // Fee PPM - BigNumber.from('1000000000000000000'), - ), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidLinkWeiPrice`) - .withArgs(0) - const mockLinkEthNegative = await mockAggregatorV3Factory.deploy(0, -1) - const vrfCoordinatorV2TestHelperNegative = - await vrfCoordinatorV2TestHelperFactory.deploy( - mockLinkEthNegative.address, - ) - await expect( - vrfCoordinatorV2TestHelperNegative - .connect(owner) - .calculatePaymentAmountTest( - BigNumber.from('0'), // Gas after payment - 0, // Fee PPM - BigNumber.from('1000000000000000000'), - ), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `InvalidLinkWeiPrice`) - .withArgs(-1) - }) - }) - - describe('#keyRegistration', async function () { - it('register key emits log', async function () { - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - const kh = await vrfCoordinatorV2.hashOfKey(testKey) - await expect( - vrfCoordinatorV2.registerProvingKey(subOwnerAddress, testKey), - ) - .to.emit(vrfCoordinatorV2, 'ProvingKeyRegistered') - .withArgs(kh, subOwnerAddress) - const reqConfig = await vrfCoordinatorV2.getRequestConfig() - assert(reqConfig[2].length == 1) // 1 keyhash registered - }) - it('cannot re-register key', async function () { - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - const kh = await vrfCoordinatorV2.hashOfKey(testKey) - await vrfCoordinatorV2.registerProvingKey(subOwnerAddress, testKey) - await expect( - vrfCoordinatorV2.registerProvingKey(subOwnerAddress, testKey), - ) - .to.be.revertedWithCustomError( - vrfCoordinatorV2, - `ProvingKeyAlreadyRegistered`, - ) - .withArgs(kh) - }) - it('deregister key emits log', async function () { - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - const kh = await vrfCoordinatorV2.hashOfKey(testKey) - await vrfCoordinatorV2.registerProvingKey(subOwnerAddress, testKey) - await expect(vrfCoordinatorV2.deregisterProvingKey(testKey)) - .to.emit(vrfCoordinatorV2, 'ProvingKeyDeregistered') - .withArgs(kh, subOwnerAddress) - const reqConfig = await vrfCoordinatorV2.getRequestConfig() - assert(reqConfig[2].length == 0) // 0 keyhash registered - }) - it('cannot deregister unregistered key', async function () { - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - const kh = await vrfCoordinatorV2.hashOfKey(testKey) - await expect(vrfCoordinatorV2.deregisterProvingKey(testKey)) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `NoSuchProvingKey`) - .withArgs(kh) - }) - it('can register after deregister', async function () { - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - await vrfCoordinatorV2.registerProvingKey(subOwnerAddress, testKey) - await vrfCoordinatorV2.deregisterProvingKey(testKey) - await vrfCoordinatorV2.registerProvingKey(randomAddress, testKey) - }) - }) - - describe('#fulfillRandomWords', async function () { - beforeEach(async () => { - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - await vrfCoordinatorV2.registerProvingKey(subOwnerAddress, testKey) - }) - it('unregistered key should fail', async function () { - const proof = [ - [BigNumber.from('1'), BigNumber.from('3')], // pk NOT registered - [BigNumber.from('1'), BigNumber.from('2')], // gamma - BigNumber.from('1'), // c - BigNumber.from('1'), // s - BigNumber.from('1'), // seed - randomAddress, // uWitness - [BigNumber.from('1'), BigNumber.from('2')], // cGammaWitness - [BigNumber.from('1'), BigNumber.from('2')], // sHashWitness - BigNumber.from('1'), - ] // 13 words in proof - const rc = [ - 1, // blockNum - 2, // subId - 3, // callbackGasLimit - 4, // numWords - randomAddress, // sender - ] - await expect( - vrfCoordinatorV2.connect(oracle).fulfillRandomWords(proof, rc), - ) - .to.be.revertedWithCustomError(vrfCoordinatorV2, `NoSuchProvingKey`) - .withArgs( - '0xa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c', - ) - }) - it('no corresponding request', async function () { - const proof = [ - [BigNumber.from('1'), BigNumber.from('2')], // pk - [BigNumber.from('1'), BigNumber.from('2')], // gamma - BigNumber.from('1'), // c - BigNumber.from('1'), // s - BigNumber.from('1'), // seed - randomAddress, // uWitness - [BigNumber.from('1'), BigNumber.from('2')], // cGammaWitness - [BigNumber.from('1'), BigNumber.from('2')], // sHashWitness - BigNumber.from('1'), - ] // 13 words in proof - const rc = [ - 1, // blockNum - 2, // subId - 3, // callbackGasLimit - 4, // numWords - randomAddress, // sender - ] - await expect( - vrfCoordinatorV2.connect(oracle).fulfillRandomWords(proof, rc), - ).to.be.revertedWithCustomError( - vrfCoordinatorV2, - `NoCorrespondingRequest`, - ) - }) - it('incorrect commitment wrong blocknum', async function () { - const subId = await createSubscription() - await linkToken.connect(subOwner).transferAndCall( - vrfCoordinatorV2.address, - BigNumber.from('1000000000000000000'), // 1 link > 0.1 min. - ethers.utils.defaultAbiCoder.encode(['uint64'], [subId]), - ) - const testKey = [BigNumber.from('1'), BigNumber.from('2')] - const kh = await vrfCoordinatorV2.hashOfKey(testKey) - const tx = await vrfCoordinatorV2.connect(consumer).requestRandomWords( - kh, // keyhash - subId, // subId - 1, // minReqConf - 1000, // callbackGasLimit - 1, // numWords - ) - const reqReceipt = await tx.wait() - // We give it the right proof length and a valid preSeed - // but an invalid commitment - const preSeed = reqReceipt.events[0].args['preSeed'] - const proof = [ - [BigNumber.from('1'), BigNumber.from('2')], - [BigNumber.from('1'), BigNumber.from('2')], - BigNumber.from('1'), - BigNumber.from('1'), - preSeed, - randomAddress, - [BigNumber.from('1'), BigNumber.from('2')], - [BigNumber.from('1'), BigNumber.from('2')], - BigNumber.from('1'), - ] - const rc = [ - reqReceipt.blockNumber + 1, // Wrong blocknumber - subId, - 1000, - 1, - await consumer.getAddress(), - ] - await expect( - vrfCoordinatorV2.connect(oracle).fulfillRandomWords(proof, rc), - ).to.be.revertedWithCustomError(vrfCoordinatorV2, `IncorrectCommitment`) - }) - }) - - describe('#getFeeTier', async function () { - beforeEach(async () => { - await expect( - vrfCoordinatorV2 - .connect(owner) - .setConfig( - c.minimumRequestBlockConfirmations, - c.maxGasLimit, - c.stalenessSeconds, - c.gasAfterPaymentCalculation, - c.weiPerUnitLink, - [10000, 1000, 100, 10, 1, 10, 20, 30, 40], - ), - ) - }) - it('tier1', async function () { - assert((await vrfCoordinatorV2.connect(random).getFeeTier(0)) == 10000) - assert((await vrfCoordinatorV2.connect(random).getFeeTier(5)) == 10000) - assert((await vrfCoordinatorV2.connect(random).getFeeTier(10)) == 10000) - }) - it('tier2', async function () { - assert((await vrfCoordinatorV2.connect(random).getFeeTier(11)) == 1000) - assert((await vrfCoordinatorV2.connect(random).getFeeTier(12)) == 1000) - assert((await vrfCoordinatorV2.connect(random).getFeeTier(20)) == 1000) - }) - it('tier3', async function () { - assert((await vrfCoordinatorV2.connect(random).getFeeTier(21)) == 100) - assert((await vrfCoordinatorV2.connect(random).getFeeTier(30)) == 100) - }) - it('tier4', async function () { - assert((await vrfCoordinatorV2.connect(random).getFeeTier(31)) == 10) - assert((await vrfCoordinatorV2.connect(random).getFeeTier(40)) == 10) - }) - it('tier5', async function () { - assert((await vrfCoordinatorV2.connect(random).getFeeTier(41)) == 1) - assert((await vrfCoordinatorV2.connect(random).getFeeTier(123102)) == 1) - }) - }) - - /* - Note that all the fulfillment happy path testing is done in Go, to make use of the existing go code to produce - proofs offchain. - */ -}) diff --git a/contracts/test/v0.8/vrf/VRFCoordinatorV2Mock.test.ts b/contracts/test/v0.8/vrf/VRFCoordinatorV2Mock.test.ts deleted file mode 100644 index 5bcb2cd5fa0..00000000000 --- a/contracts/test/v0.8/vrf/VRFCoordinatorV2Mock.test.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { assert, expect } from 'chai' -import { BigNumber, Contract, Signer } from 'ethers' -import { ethers } from 'hardhat' - -describe('VRFCoordinatorV2Mock', () => { - let vrfCoordinatorV2Mock: Contract - let vrfConsumerV2: Contract - let linkToken: Contract - let subOwner: Signer - let random: Signer - let subOwnerAddress: string - let pointOneLink = BigNumber.from('100000000000000000') - let oneLink = BigNumber.from('1000000000000000000') - let keyhash = - '0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0' - let testConsumerAddress = '0x1111000000000000000000000000000000001111' - let testConsumerAddress2 = '0x1111000000000000000000000000000000001110' - - beforeEach(async () => { - const accounts = await ethers.getSigners() - subOwner = accounts[1] - subOwnerAddress = await subOwner.getAddress() - random = accounts[2] - - const vrfCoordinatorV2MockFactory = await ethers.getContractFactory( - 'src/v0.8/vrf/mocks/VRFCoordinatorV2Mock.sol:VRFCoordinatorV2Mock', - accounts[0], - ) - vrfCoordinatorV2Mock = await vrfCoordinatorV2MockFactory.deploy( - pointOneLink, - 1e9, // 0.000000001 LINK per gas - ) - - const ltFactory = await ethers.getContractFactory( - 'src/v0.8/shared/test/helpers/LinkTokenTestHelper.sol:LinkTokenTestHelper', - accounts[0], - ) - linkToken = await ltFactory.deploy() - - const vrfConsumerV2Factory = await ethers.getContractFactory( - 'src/v0.8/vrf/testhelpers/VRFConsumerV2.sol:VRFConsumerV2', - accounts[0], - ) - vrfConsumerV2 = await vrfConsumerV2Factory.deploy( - vrfCoordinatorV2Mock.address, - linkToken.address, - ) - }) - - async function createSubscription(): Promise { - const tx = await vrfCoordinatorV2Mock.connect(subOwner).createSubscription() - const receipt = await tx.wait() - return receipt.events[0].args['subId'] - } - - describe('#createSubscription', async function () { - it('can create a subscription', async function () { - await expect(vrfCoordinatorV2Mock.connect(subOwner).createSubscription()) - .to.emit(vrfCoordinatorV2Mock, 'SubscriptionCreated') - .withArgs(1, subOwnerAddress) - const s = await vrfCoordinatorV2Mock.getSubscription(1) - assert(s.balance.toString() == '0', 'invalid balance') - assert(s.owner == subOwnerAddress, 'invalid address') - }) - it('subscription id increments', async function () { - await expect(vrfCoordinatorV2Mock.connect(subOwner).createSubscription()) - .to.emit(vrfCoordinatorV2Mock, 'SubscriptionCreated') - .withArgs(1, subOwnerAddress) - await expect(vrfCoordinatorV2Mock.connect(subOwner).createSubscription()) - .to.emit(vrfCoordinatorV2Mock, 'SubscriptionCreated') - .withArgs(2, subOwnerAddress) - }) - }) - describe('#addConsumer', async function () { - it('can add a consumer to a subscription', async function () { - let subId = await createSubscription() - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .addConsumer(subId, testConsumerAddress), - ) - .to.emit(vrfCoordinatorV2Mock, 'ConsumerAdded') - .withArgs(subId, testConsumerAddress) - let sub = await vrfCoordinatorV2Mock - .connect(subOwner) - .getSubscription(subId) - expect(sub.consumers).to.eql([testConsumerAddress]) - }) - it('cannot add a consumer to a nonexistent subscription', async function () { - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .addConsumer(4, testConsumerAddress), - ).to.be.revertedWithCustomError( - vrfCoordinatorV2Mock, - 'InvalidSubscription', - ) - }) - it('cannot add more than the consumer maximum', async function () { - let subId = await createSubscription() - for (let i = 0; i < 100; i++) { - const testIncrementingAddress = BigNumber.from(i) - .add('0x1000000000000000000000000000000000000000') - .toHexString() - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .addConsumer(subId, testIncrementingAddress), - ).to.emit(vrfCoordinatorV2Mock, 'ConsumerAdded') - } - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .addConsumer(subId, testConsumerAddress), - ).to.be.revertedWithCustomError(vrfCoordinatorV2Mock, 'TooManyConsumers') - }) - }) - describe('#removeConsumer', async function () { - it('can remove a consumer from a subscription', async function () { - let subId = await createSubscription() - for (const addr of [testConsumerAddress, testConsumerAddress2]) { - await expect( - vrfCoordinatorV2Mock.connect(subOwner).addConsumer(subId, addr), - ).to.emit(vrfCoordinatorV2Mock, 'ConsumerAdded') - } - - let sub = await vrfCoordinatorV2Mock - .connect(subOwner) - .getSubscription(subId) - expect(sub.consumers).to.eql([testConsumerAddress, testConsumerAddress2]) - - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .removeConsumer(subId, testConsumerAddress), - ) - .to.emit(vrfCoordinatorV2Mock, 'ConsumerRemoved') - .withArgs(subId, testConsumerAddress) - - sub = await vrfCoordinatorV2Mock.connect(subOwner).getSubscription(subId) - expect(sub.consumers).to.eql([testConsumerAddress2]) - }) - it('cannot remove a consumer from a nonexistent subscription', async function () { - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .removeConsumer(4, testConsumerAddress), - ).to.be.revertedWithCustomError( - vrfCoordinatorV2Mock, - 'InvalidSubscription', - ) - }) - it('cannot remove a consumer after it is already removed', async function () { - let subId = await createSubscription() - - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .addConsumer(subId, testConsumerAddress), - ).to.emit(vrfCoordinatorV2Mock, 'ConsumerAdded') - - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .removeConsumer(subId, testConsumerAddress), - ).to.emit(vrfCoordinatorV2Mock, 'ConsumerRemoved') - - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .removeConsumer(subId, testConsumerAddress), - ).to.be.revertedWithCustomError(vrfCoordinatorV2Mock, 'InvalidConsumer') - }) - }) - describe('#fundSubscription', async function () { - it('can fund a subscription', async function () { - let subId = await createSubscription() - await expect( - vrfCoordinatorV2Mock.connect(subOwner).fundSubscription(subId, oneLink), - ) - .to.emit(vrfCoordinatorV2Mock, 'SubscriptionFunded') - .withArgs(subId, 0, oneLink) - let sub = await vrfCoordinatorV2Mock - .connect(subOwner) - .getSubscription(subId) - expect(sub.balance).to.equal(oneLink) - }) - it('cannot fund a nonexistent subscription', async function () { - await expect( - vrfCoordinatorV2Mock.connect(subOwner).fundSubscription(4, oneLink), - ).to.be.revertedWithCustomError( - vrfCoordinatorV2Mock, - 'InvalidSubscription', - ) - }) - }) - describe('#cancelSubscription', async function () { - it('can cancel a subscription', async function () { - let subId = await createSubscription() - await expect( - vrfCoordinatorV2Mock.connect(subOwner).getSubscription(subId), - ).to.not.be.reverted - - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .cancelSubscription(subId, subOwner.getAddress()), - ).to.emit(vrfCoordinatorV2Mock, 'SubscriptionCanceled') - - await expect( - vrfCoordinatorV2Mock.connect(subOwner).getSubscription(subId), - ).to.be.revertedWithCustomError( - vrfCoordinatorV2Mock, - 'InvalidSubscription', - ) - }) - }) - describe('#fulfillRandomWords', async function () { - it('fails to fulfill without being a valid consumer', async function () { - let subId = await createSubscription() - - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .requestRandomWords(keyhash, subId, 3, 500_000, 2), - ).to.be.revertedWithCustomError(vrfCoordinatorV2Mock, 'InvalidConsumer') - }) - it('fails to fulfill with insufficient funds', async function () { - let subId = await createSubscription() - await vrfCoordinatorV2Mock - .connect(subOwner) - .addConsumer(subId, await subOwner.getAddress()) - - await expect( - vrfCoordinatorV2Mock - .connect(subOwner) - .requestRandomWords(keyhash, subId, 3, 500_000, 2), - ) - .to.emit(vrfCoordinatorV2Mock, 'RandomWordsRequested') - .withArgs(keyhash, 1, 100, subId, 3, 500_000, 2, subOwnerAddress) - - await expect( - vrfCoordinatorV2Mock - .connect(random) - .fulfillRandomWords(1, vrfConsumerV2.address), - ).to.be.revertedWithCustomError( - vrfCoordinatorV2Mock, - 'InsufficientBalance', - ) - }) - it('can request and fulfill [ @skip-coverage ]', async function () { - let subId = await createSubscription() - await vrfCoordinatorV2Mock - .connect(subOwner) - .addConsumer(subId, vrfConsumerV2.address) - await expect( - vrfCoordinatorV2Mock.connect(subOwner).fundSubscription(subId, oneLink), - ).to.not.be.reverted - - // Call requestRandomWords from the consumer contract so that the requestId - // member variable on the consumer is appropriately set. - expect( - await vrfConsumerV2 - .connect(subOwner) - .requestRandomness(keyhash, subId, 3, 500_000, 2), - ) - .to.emit(vrfCoordinatorV2Mock, 'RandomWordsRequested') - .withArgs(keyhash, 1, 100, subId, 3, 500_000, 2, vrfConsumerV2.address) - - let tx = await vrfCoordinatorV2Mock - .connect(random) - .fulfillRandomWords(1, vrfConsumerV2.address) - let receipt = await tx.wait() - expect(receipt.events[0].event).to.equal('RandomWordsFulfilled') - expect(receipt.events[0].args['requestId']).to.equal(1) - expect(receipt.events[0].args['outputSeed']).to.equal(1) - expect(receipt.events[0].args['success']).to.equal(true) - assert( - receipt.events[0].args['payment'] - .sub(BigNumber.from('100119403000000000')) - .lt(BigNumber.from('10000000000')), - ) - - // Check that balance was subtracted - let sub = await vrfCoordinatorV2Mock - .connect(random) - .getSubscription(subId) - expect(sub.balance).to.equal( - oneLink.sub(receipt.events[0].args['payment']), - ) - }) - it('Correctly allows for user override of fulfillRandomWords [ @skip-coverage ]', async function () { - let subId = await createSubscription() - await vrfCoordinatorV2Mock - .connect(subOwner) - .addConsumer(subId, vrfConsumerV2.address) - await expect( - vrfCoordinatorV2Mock.connect(subOwner).fundSubscription(subId, oneLink), - ).to.not.be.reverted - - // Call requestRandomWords from the consumer contract so that the requestId - // member variable on the consumer is appropriately set. - expect( - await vrfConsumerV2 - .connect(subOwner) - .requestRandomness(keyhash, subId, 3, 500_000, 2), - ) - .to.emit(vrfCoordinatorV2Mock, 'RandomWordsRequested') - .withArgs(keyhash, 1, 100, subId, 3, 500_000, 2, vrfConsumerV2.address) - - // Call override with incorrect word count. - await expect( - vrfCoordinatorV2Mock - .connect(random) - .fulfillRandomWordsWithOverride( - 1, - vrfConsumerV2.address, - [1, 2, 3, 4, 5], - ), - ).to.be.revertedWithCustomError( - vrfCoordinatorV2Mock, - 'InvalidRandomWords', - ) - - // Call override correctly. - let tx = await vrfCoordinatorV2Mock - .connect(random) - .fulfillRandomWordsWithOverride(1, vrfConsumerV2.address, [2533, 1768]) - let receipt = await tx.wait() - expect(receipt.events[0].event).to.equal('RandomWordsFulfilled') - expect(receipt.events[0].args['requestId']).to.equal(1) - expect(receipt.events[0].args['outputSeed']).to.equal(1) - expect(receipt.events[0].args['success']).to.equal(true) - assert( - receipt.events[0].args['payment'] - .sub(BigNumber.from('100120516000000000')) - .lt(BigNumber.from('10000000000')), - ) - - // Check that balance was subtracted - let sub = await vrfCoordinatorV2Mock - .connect(random) - .getSubscription(subId) - expect(sub.balance).to.equal( - oneLink.sub(receipt.events[0].args['payment']), - ) - }) - }) -}) diff --git a/contracts/test/v0.8/vrf/VRFSubscriptionBalanceMonitor.test.ts b/contracts/test/v0.8/vrf/VRFSubscriptionBalanceMonitor.test.ts deleted file mode 100644 index ea5cdb5f395..00000000000 --- a/contracts/test/v0.8/vrf/VRFSubscriptionBalanceMonitor.test.ts +++ /dev/null @@ -1,638 +0,0 @@ -import { ethers } from 'hardhat' -import { assert, expect } from 'chai' -import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { - LinkToken, - VRFSubscriptionBalanceMonitorExposed, -} from '../../../typechain' -import * as h from '../../test-helpers/helpers' -import { BigNumber, Contract } from 'ethers' - -const OWNABLE_ERR = 'Only callable by owner' -const INVALID_WATCHLIST_ERR = `InvalidWatchList` -const PAUSED_ERR = 'Pausable: paused' -const ONLY_KEEPER_ERR = `OnlyKeeperRegistry` - -const zeroLINK = ethers.utils.parseEther('0') -const oneLINK = ethers.utils.parseEther('1') -const twoLINK = ethers.utils.parseEther('2') -const threeLINK = ethers.utils.parseEther('3') -const fiveLINK = ethers.utils.parseEther('5') -const sixLINK = ethers.utils.parseEther('6') -const tenLINK = ethers.utils.parseEther('10') -const oneHundredLINK = ethers.utils.parseEther('100') - -let lt: LinkToken -let coordinator: Contract -let bm: VRFSubscriptionBalanceMonitorExposed -let owner: SignerWithAddress -let stranger: SignerWithAddress -let keeperRegistry: SignerWithAddress - -const sub1 = BigNumber.from(1) -const sub2 = BigNumber.from(2) -const sub3 = BigNumber.from(3) -const sub4 = BigNumber.from(4) -const sub5 = BigNumber.from(5) -const sub6 = BigNumber.from(6) - -const toNums = (bigNums: BigNumber[]) => bigNums.map((n) => n.toNumber()) - -async function assertWatchlistBalances( - balance1: BigNumber, - balance2: BigNumber, - balance3: BigNumber, - balance4: BigNumber, - balance5: BigNumber, - balance6: BigNumber, -) { - await h.assertSubscriptionBalance(coordinator, sub1, balance1, 'sub 1') - await h.assertSubscriptionBalance(coordinator, sub2, balance2, 'sub 2') - await h.assertSubscriptionBalance(coordinator, sub3, balance3, 'sub 3') - await h.assertSubscriptionBalance(coordinator, sub4, balance4, 'sub 4') - await h.assertSubscriptionBalance(coordinator, sub5, balance5, 'sub 5') - await h.assertSubscriptionBalance(coordinator, sub6, balance6, 'sub 6') -} - -describe('VRFSubscriptionBalanceMonitor', () => { - beforeEach(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - stranger = accounts[1] - keeperRegistry = accounts[2] - - const bmFactory = await ethers.getContractFactory( - 'VRFSubscriptionBalanceMonitorExposed', - owner, - ) - const ltFactory = await ethers.getContractFactory( - 'src/v0.8/shared/test/helpers/LinkTokenTestHelper.sol:LinkTokenTestHelper', - owner, - ) - - const coordinatorFactory = await ethers.getContractFactory( - 'src/v0.8/vrf/VRFCoordinatorV2.sol:VRFCoordinatorV2', - owner, - ) - - lt = await ltFactory.deploy() - coordinator = await coordinatorFactory.deploy( - lt.address, - lt.address, - lt.address, - ) // we don't use BHS or LinkEthFeed - bm = await bmFactory.deploy( - lt.address, - coordinator.address, - keeperRegistry.address, - 0, - ) - - for (let i = 0; i <= 5; i++) { - await coordinator.connect(owner).createSubscription() - } - - // Transfer LINK to stranger. - await lt.transfer(stranger.address, oneHundredLINK) - - // Fund sub 5. - await lt - .connect(owner) - .transferAndCall( - coordinator.address, - oneHundredLINK, - ethers.utils.defaultAbiCoder.encode(['uint256'], ['5']), - ) - - // Fun sub 6. - await lt - .connect(owner) - .transferAndCall( - coordinator.address, - oneHundredLINK, - ethers.utils.defaultAbiCoder.encode(['uint256'], ['6']), - ) - - await Promise.all([bm.deployed(), coordinator.deployed(), lt.deployed()]) - }) - - afterEach(async () => { - await h.reset() - }) - - describe('add funds', () => { - it('Should allow anyone to add funds', async () => { - await lt.transfer(bm.address, oneLINK) - await lt.connect(stranger).transfer(bm.address, oneLINK) - }) - }) - - describe('withdraw()', () => { - beforeEach(async () => { - const tx = await lt.connect(owner).transfer(bm.address, oneLINK) - await tx.wait() - }) - - it('Should allow the owner to withdraw', async () => { - const beforeBalance = await lt.balanceOf(owner.address) - const tx = await bm.connect(owner).withdraw(oneLINK, owner.address) - await tx.wait() - const afterBalance = await lt.balanceOf(owner.address) - assert.isTrue( - afterBalance.gt(beforeBalance), - 'balance did not increase after withdraw', - ) - }) - - it('Should emit an event', async () => { - const tx = await bm.connect(owner).withdraw(oneLINK, owner.address) - await expect(tx) - .to.emit(bm, 'FundsWithdrawn') - .withArgs(oneLINK, owner.address) - }) - - it('Should allow the owner to withdraw to anyone', async () => { - const beforeBalance = await lt.balanceOf(stranger.address) - const tx = await bm.connect(owner).withdraw(oneLINK, stranger.address) - await tx.wait() - const afterBalance = await lt.balanceOf(stranger.address) - assert.isTrue( - beforeBalance.add(oneLINK).eq(afterBalance), - 'balance did not increase after withdraw', - ) - }) - - it('Should not allow strangers to withdraw', async () => { - const tx = bm.connect(stranger).withdraw(oneLINK, owner.address) - await expect(tx).to.be.revertedWith(OWNABLE_ERR) - }) - }) - - describe('pause() / unpause()', () => { - it('Should allow owner to pause / unpause', async () => { - const pauseTx = await bm.connect(owner).pause() - await pauseTx.wait() - const unpauseTx = await bm.connect(owner).unpause() - await unpauseTx.wait() - }) - - it('Should not allow strangers to pause / unpause', async () => { - const pauseTxStranger = bm.connect(stranger).pause() - await expect(pauseTxStranger).to.be.revertedWith(OWNABLE_ERR) - const pauseTxOwner = await bm.connect(owner).pause() - await pauseTxOwner.wait() - const unpauseTxStranger = bm.connect(stranger).unpause() - await expect(unpauseTxStranger).to.be.revertedWith(OWNABLE_ERR) - }) - }) - - describe('setWatchList() / getWatchList() / getAccountInfo()', () => { - it('Should allow owner to set the watchlist', async () => { - // should start unactive - assert.isFalse((await bm.getSubscriptionInfo(sub1)).isActive) - // add first watchlist - let setTx = await bm - .connect(owner) - .setWatchList([sub1], [oneLINK], [twoLINK]) - await setTx.wait() - let watchList = await bm.getWatchList() - assert.deepEqual(toNums(watchList), toNums([sub1])) - const subInfo = await bm.getSubscriptionInfo(1) - assert.isTrue(subInfo.isActive) - expect(subInfo.minBalanceJuels).to.equal(oneLINK) - expect(subInfo.topUpAmountJuels).to.equal(twoLINK) - // add more to watchlist - setTx = await bm - .connect(owner) - .setWatchList( - [1, 2, 3], - [oneLINK, twoLINK, threeLINK], - [twoLINK, threeLINK, fiveLINK], - ) - await setTx.wait() - watchList = await bm.getWatchList() - assert.deepEqual(toNums(watchList), toNums([sub1, sub2, sub3])) - let subInfo1 = await bm.getSubscriptionInfo(sub1) - let subInfo2 = await bm.getSubscriptionInfo(sub2) - let subInfo3 = await bm.getSubscriptionInfo(sub3) - expect(subInfo1.isActive).to.be.true - expect(subInfo1.minBalanceJuels).to.equal(oneLINK) - expect(subInfo1.topUpAmountJuels).to.equal(twoLINK) - expect(subInfo2.isActive).to.be.true - expect(subInfo2.minBalanceJuels).to.equal(twoLINK) - expect(subInfo2.topUpAmountJuels).to.equal(threeLINK) - expect(subInfo3.isActive).to.be.true - expect(subInfo3.minBalanceJuels).to.equal(threeLINK) - expect(subInfo3.topUpAmountJuels).to.equal(fiveLINK) - // remove some from watchlist - setTx = await bm - .connect(owner) - .setWatchList([sub3, sub1], [threeLINK, oneLINK], [fiveLINK, twoLINK]) - await setTx.wait() - watchList = await bm.getWatchList() - assert.deepEqual(toNums(watchList), toNums([sub3, sub1])) - subInfo1 = await bm.getSubscriptionInfo(sub1) - subInfo2 = await bm.getSubscriptionInfo(sub2) - subInfo3 = await bm.getSubscriptionInfo(sub3) - expect(subInfo1.isActive).to.be.true - expect(subInfo2.isActive).to.be.false - expect(subInfo3.isActive).to.be.true - }) - - it('Should not allow duplicates in the watchlist', async () => { - const errMsg = `DuplicateSubcriptionId` - const setTx = bm - .connect(owner) - .setWatchList( - [sub1, sub2, sub1], - [oneLINK, twoLINK, threeLINK], - [twoLINK, threeLINK, fiveLINK], - ) - await expect(setTx) - .to.be.revertedWithCustomError(bm, errMsg) - .withArgs(sub1) - }) - - it('Should not allow a topUpAmountJuels les than or equal to minBalance in the watchlist', async () => { - const setTx = bm - .connect(owner) - .setWatchList( - [sub1, sub2, sub1], - [oneLINK, twoLINK, threeLINK], - [zeroLINK, twoLINK, threeLINK], - ) - await expect(setTx).to.be.revertedWithCustomError( - bm, - INVALID_WATCHLIST_ERR, - ) - }) - - it('Should not allow strangers to set the watchlist', async () => { - const setTxStranger = bm - .connect(stranger) - .setWatchList([sub1], [oneLINK], [twoLINK]) - await expect(setTxStranger).to.be.revertedWith(OWNABLE_ERR) - }) - - it('Should revert if the list lengths differ', async () => { - let tx = bm.connect(owner).setWatchList([sub1], [], [twoLINK]) - await expect(tx).to.be.revertedWithCustomError(bm, INVALID_WATCHLIST_ERR) - tx = bm.connect(owner).setWatchList([sub1], [oneLINK], []) - await expect(tx).to.be.revertedWithCustomError(bm, INVALID_WATCHLIST_ERR) - tx = bm.connect(owner).setWatchList([], [oneLINK], [twoLINK]) - await expect(tx).to.be.revertedWithCustomError(bm, INVALID_WATCHLIST_ERR) - }) - - it('Should revert if any of the subIDs are zero', async () => { - let tx = bm - .connect(owner) - .setWatchList([sub1, 0], [oneLINK, oneLINK], [twoLINK, twoLINK]) - await expect(tx).to.be.revertedWithCustomError(bm, INVALID_WATCHLIST_ERR) - }) - - it('Should revert if any of the top up amounts are 0', async () => { - const tx = bm - .connect(owner) - .setWatchList([sub1, sub2], [oneLINK, oneLINK], [twoLINK, zeroLINK]) - await expect(tx).to.be.revertedWithCustomError(bm, INVALID_WATCHLIST_ERR) - }) - }) - - describe('getKeeperRegistryAddress() / setKeeperRegistryAddress()', () => { - const newAddress = ethers.Wallet.createRandom().address - - it('Should initialize with the registry address provided to the constructor', async () => { - const address = await bm.s_keeperRegistryAddress() - assert.equal(address, keeperRegistry.address) - }) - - it('Should allow the owner to set the registry address', async () => { - const setTx = await bm.connect(owner).setKeeperRegistryAddress(newAddress) - await setTx.wait() - const address = await bm.s_keeperRegistryAddress() - assert.equal(address, newAddress) - }) - - it('Should not allow strangers to set the registry address', async () => { - const setTx = bm.connect(stranger).setKeeperRegistryAddress(newAddress) - await expect(setTx).to.be.revertedWith(OWNABLE_ERR) - }) - - it('Should emit an event', async () => { - const setTx = await bm.connect(owner).setKeeperRegistryAddress(newAddress) - await expect(setTx) - .to.emit(bm, 'KeeperRegistryAddressUpdated') - .withArgs(keeperRegistry.address, newAddress) - }) - }) - - describe('getMinWaitPeriodSeconds / setMinWaitPeriodSeconds()', () => { - const newWaitPeriod = BigNumber.from(1) - - it('Should initialize with the wait period provided to the constructor', async () => { - const minWaitPeriod = await bm.s_minWaitPeriodSeconds() - expect(minWaitPeriod).to.equal(0) - }) - - it('Should allow owner to set the wait period', async () => { - const setTx = await bm - .connect(owner) - .setMinWaitPeriodSeconds(newWaitPeriod) - await setTx.wait() - const minWaitPeriod = await bm.s_minWaitPeriodSeconds() - expect(minWaitPeriod).to.equal(newWaitPeriod) - }) - - it('Should not allow strangers to set the wait period', async () => { - const setTx = bm.connect(stranger).setMinWaitPeriodSeconds(newWaitPeriod) - await expect(setTx).to.be.revertedWith(OWNABLE_ERR) - }) - - it('Should emit an event', async () => { - const setTx = await bm - .connect(owner) - .setMinWaitPeriodSeconds(newWaitPeriod) - await expect(setTx) - .to.emit(bm, 'MinWaitPeriodUpdated') - .withArgs(0, newWaitPeriod) - }) - }) - - describe('checkUpkeep() / getUnderfundedSubscriptions()', () => { - beforeEach(async () => { - const setTx = await bm.connect(owner).setWatchList( - [ - sub1, // needs funds - sub5, // funded - sub2, // needs funds - sub6, // funded - sub3, // needs funds - ], - new Array(5).fill(oneLINK), - new Array(5).fill(twoLINK), - ) - await setTx.wait() - }) - - it('Should return list of subscriptions that are underfunded', async () => { - const fundTx = await lt.connect(owner).transfer( - bm.address, - sixLINK, // needs 6 total - ) - await fundTx.wait() - const [should, payload] = await bm.checkUpkeep('0x') - assert.isTrue(should) - let [subs] = ethers.utils.defaultAbiCoder.decode(['uint64[]'], payload) - assert.deepEqual(toNums(subs), toNums([sub1, sub2, sub3])) - // checkUpkeep payload should match getUnderfundedSubscriptions() - subs = await bm.getUnderfundedSubscriptions() - assert.deepEqual(toNums(subs), toNums([sub1, sub2, sub3])) - }) - - it('Should return some results even if contract cannot fund all eligible targets', async () => { - const fundTx = await lt.connect(owner).transfer( - bm.address, - fiveLINK, // needs 6 total - ) - await fundTx.wait() - const [should, payload] = await bm.checkUpkeep('0x') - assert.isTrue(should) - const [subs] = ethers.utils.defaultAbiCoder.decode(['uint64[]'], payload) - assert.deepEqual(toNums(subs), toNums([sub1, sub2])) - }) - - it('Should omit subscriptions that have been funded recently', async () => { - const setWaitPdTx = await bm.setMinWaitPeriodSeconds(3600) // 1 hour - const fundTx = await lt.connect(owner).transfer(bm.address, sixLINK) - await Promise.all([setWaitPdTx.wait(), fundTx.wait()]) - const block = await ethers.provider.getBlock('latest') - const setTopUpTx = await bm.setLastTopUpXXXTestOnly( - sub2, - block.timestamp - 100, - ) - await setTopUpTx.wait() - const [should, payload] = await bm.checkUpkeep('0x') - assert.isTrue(should) - const [subs] = ethers.utils.defaultAbiCoder.decode(['uint64[]'], payload) - assert.deepEqual(toNums(subs), toNums([sub1, sub3])) - }) - - it('Should revert when paused', async () => { - const tx = await bm.connect(owner).pause() - await tx.wait() - const ethCall = bm.checkUpkeep('0x') - await expect(ethCall).to.be.revertedWith(PAUSED_ERR) - }) - }) - - describe('performUpkeep()', () => { - let validPayload: string - let invalidPayload: string - - beforeEach(async () => { - validPayload = ethers.utils.defaultAbiCoder.encode( - ['uint64[]'], - [[sub1, sub2, sub3]], - ) - invalidPayload = ethers.utils.defaultAbiCoder.encode( - ['uint64[]'], - [[sub1, sub2, sub4, sub5]], - ) - const setTx = await bm.connect(owner).setWatchList( - [ - sub1, // needs funds - sub5, // funded - sub2, // needs funds - sub6, // funded - sub3, // needs funds - // sub4 - omitted - ], - new Array(5).fill(oneLINK), - new Array(5).fill(twoLINK), - ) - await setTx.wait() - }) - - it('Should revert when paused', async () => { - const pauseTx = await bm.connect(owner).pause() - await pauseTx.wait() - const performTx = bm.connect(keeperRegistry).performUpkeep(validPayload) - await expect(performTx).to.be.revertedWith(PAUSED_ERR) - }) - - context('when partially funded', () => { - it('Should fund as many subscriptions as possible', async () => { - const fundTx = await lt.connect(owner).transfer( - bm.address, - fiveLINK, // only enough LINK to fund 2 subscriptions - ) - await fundTx.wait() - console.log((await lt.balanceOf(bm.address)).toString()) - await assertWatchlistBalances( - zeroLINK, - zeroLINK, - zeroLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - const performTx = await bm - .connect(keeperRegistry) - .performUpkeep(validPayload, { gasLimit: 2_500_000 }) - - await assertWatchlistBalances( - twoLINK, - twoLINK, - zeroLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - await expect(performTx).to.emit(bm, 'TopUpSucceeded').withArgs(sub1) - await expect(performTx).to.emit(bm, 'TopUpSucceeded').withArgs(sub1) - }) - }) - - context('when fully funded', () => { - beforeEach(async () => { - const fundTx = await lt.connect(owner).transfer(bm.address, tenLINK) - await fundTx.wait() - }) - - it('Should fund the appropriate subscriptions', async () => { - await assertWatchlistBalances( - zeroLINK, - zeroLINK, - zeroLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - const performTx = await bm - .connect(keeperRegistry) - .performUpkeep(validPayload, { gasLimit: 2_500_000 }) - await performTx.wait() - await assertWatchlistBalances( - twoLINK, - twoLINK, - twoLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - }) - - it('Should only fund active, underfunded subscriptions', async () => { - await assertWatchlistBalances( - zeroLINK, - zeroLINK, - zeroLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - const performTx = await bm - .connect(keeperRegistry) - .performUpkeep(invalidPayload, { gasLimit: 2_500_000 }) - await performTx.wait() - await assertWatchlistBalances( - twoLINK, - twoLINK, - zeroLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - }) - - it('Should not fund subscriptions that have been funded recently', async () => { - const setWaitPdTx = await bm.setMinWaitPeriodSeconds(3600) // 1 hour - await setWaitPdTx.wait() - const block = await ethers.provider.getBlock('latest') - const setTopUpTx = await bm.setLastTopUpXXXTestOnly( - sub2, - block.timestamp - 100, - ) - await setTopUpTx.wait() - await assertWatchlistBalances( - zeroLINK, - zeroLINK, - zeroLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - const performTx = await bm - .connect(keeperRegistry) - .performUpkeep(validPayload, { gasLimit: 2_500_000 }) - await performTx.wait() - await assertWatchlistBalances( - twoLINK, - zeroLINK, - twoLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - }) - - it('Should only be callable by the keeper registry contract', async () => { - let performTx = bm.connect(owner).performUpkeep(validPayload) - await expect(performTx).to.be.revertedWithCustomError( - bm, - ONLY_KEEPER_ERR, - ) - performTx = bm.connect(stranger).performUpkeep(validPayload) - await expect(performTx).to.be.revertedWithCustomError( - bm, - ONLY_KEEPER_ERR, - ) - }) - - it('Should protect against running out of gas', async () => { - await assertWatchlistBalances( - zeroLINK, - zeroLINK, - zeroLINK, - zeroLINK, - oneHundredLINK, - oneHundredLINK, - ) - const performTx = await bm - .connect(keeperRegistry) - .performUpkeep(validPayload, { gasLimit: 130_000 }) // too little for all 3 transfers - await performTx.wait() - const balance1 = (await coordinator.getSubscription(sub1)).balance - const balance2 = (await coordinator.getSubscription(sub2)).balance - const balance3 = (await coordinator.getSubscription(sub3)).balance - const balances = [balance1, balance2, balance3].map((n) => n.toString()) - expect(balances) - .to.include(twoLINK.toString()) // expect at least 1 transfer - .to.include(zeroLINK.toString()) // expect at least 1 out of funds - }) - }) - }) - - describe('topUp()', () => { - context('when not paused', () => { - it('Should be callable by anyone', async () => { - const users = [owner, keeperRegistry, stranger] - for (let idx = 0; idx < users.length; idx++) { - const user = users[idx] - await bm.connect(user).topUp([]) - } - }) - }) - context('when paused', () => { - it('Should be callable by no one', async () => { - await bm.connect(owner).pause() - const users = [owner, keeperRegistry, stranger] - for (let idx = 0; idx < users.length; idx++) { - const user = users[idx] - const tx = bm.connect(user).topUp([]) - await expect(tx).to.be.revertedWith(PAUSED_ERR) - } - }) - }) - }) -}) diff --git a/contracts/test/v0.8/vrf/VRFV2Wrapper.test.ts b/contracts/test/v0.8/vrf/VRFV2Wrapper.test.ts deleted file mode 100644 index 54c3b5f99b5..00000000000 --- a/contracts/test/v0.8/vrf/VRFV2Wrapper.test.ts +++ /dev/null @@ -1,656 +0,0 @@ -import { assert, expect } from 'chai' -import { BigNumber, BigNumberish, Signer } from 'ethers' -import { ethers } from 'hardhat' -import { reset, toBytes32String } from '../../test-helpers/helpers' -import { bigNumEquals } from '../../test-helpers/matchers' -import { describe } from 'mocha' -import { - LinkToken, - MockV3Aggregator, - MockV3Aggregator__factory, - VRFCoordinatorV2Mock, - VRFV2Wrapper, - VRFV2WrapperConsumerExample, - VRFV2WrapperOutOfGasConsumerExample, - VRFV2WrapperRevertingConsumerExample, -} from '../../../typechain' - -describe('VRFV2Wrapper', () => { - const pointOneLink = BigNumber.from('100000000000000000') - const pointZeroZeroThreeLink = BigNumber.from('3000000000000000') - const oneHundredLink = BigNumber.from('100000000000000000000') - const oneHundredGwei = BigNumber.from('100000000000') - const fiftyGwei = BigNumber.from('50000000000') - - // Configuration - - // This value is the worst-case gas overhead from the wrapper contract under the following - // conditions, plus some wiggle room: - // - 10 words requested - // - Refund issued to consumer - const wrapperGasOverhead = BigNumber.from(60_000) - const coordinatorGasOverhead = BigNumber.from(52_000) - const wrapperPremiumPercentage = 10 - const maxNumWords = 10 - const weiPerUnitLink = pointZeroZeroThreeLink - const flatFee = pointOneLink - - let wrapper: VRFV2Wrapper - let coordinator: VRFCoordinatorV2Mock - let link: LinkToken - let wrongLink: LinkToken - let linkEthFeed: MockV3Aggregator - let consumer: VRFV2WrapperConsumerExample - let consumerWrongLink: VRFV2WrapperConsumerExample - let consumerRevert: VRFV2WrapperRevertingConsumerExample - let consumerOutOfGas: VRFV2WrapperOutOfGasConsumerExample - - let owner: Signer - let requester: Signer - let consumerOwner: Signer - let withdrawRecipient: Signer - - // This should match implementation in VRFV2Wrapper::calculateGasPriceInternal - const calculatePrice = ( - gasLimit: BigNumberish, - _wrapperGasOverhead: BigNumberish = wrapperGasOverhead, - _coordinatorGasOverhead: BigNumberish = coordinatorGasOverhead, - _gasPriceWei: BigNumberish = oneHundredGwei, - _weiPerUnitLink: BigNumberish = weiPerUnitLink, - _wrapperPremium: BigNumberish = wrapperPremiumPercentage, - _flatFee: BigNumberish = flatFee, - ): BigNumber => { - const totalGas = BigNumber.from(0) - .add(gasLimit) - .add(_wrapperGasOverhead) - .add(_coordinatorGasOverhead) - const baseFee = BigNumber.from('1000000000000000000') - .mul(_gasPriceWei) - .mul(totalGas) - .div(_weiPerUnitLink) - const withPremium = baseFee - .mul(BigNumber.from(100).add(_wrapperPremium)) - .div(100) - return withPremium.add(_flatFee) - } - - before(async () => { - await reset() - }) - - beforeEach(async () => { - const accounts = await ethers.getSigners() - owner = accounts[0] - requester = accounts[1] - consumerOwner = accounts[2] - withdrawRecipient = accounts[3] - - const coordinatorFactory = await ethers.getContractFactory( - 'VRFCoordinatorV2Mock', - owner, - ) - coordinator = await coordinatorFactory.deploy( - pointOneLink, - 1e9, // 0.000000001 LINK per gas - ) - - const linkEthFeedFactory = (await ethers.getContractFactory( - 'src/v0.8/tests/MockV3Aggregator.sol:MockV3Aggregator', - owner, - )) as unknown as MockV3Aggregator__factory - linkEthFeed = await linkEthFeedFactory.deploy(18, weiPerUnitLink) // 1 LINK = 0.003 ETH - - const linkFactory = await ethers.getContractFactory( - 'src/v0.8/shared/test/helpers/LinkTokenTestHelper.sol:LinkTokenTestHelper', - owner, - ) - link = await linkFactory.deploy() - wrongLink = await linkFactory.deploy() - - const wrapperFactory = await ethers.getContractFactory( - 'VRFV2Wrapper', - owner, - ) - wrapper = await wrapperFactory.deploy( - link.address, - linkEthFeed.address, - coordinator.address, - ) - - const consumerFactory = await ethers.getContractFactory( - 'VRFV2WrapperConsumerExample', - consumerOwner, - ) - consumer = await consumerFactory.deploy(link.address, wrapper.address) - consumerWrongLink = await consumerFactory.deploy( - wrongLink.address, - wrapper.address, - ) - consumerRevert = await consumerFactory.deploy(link.address, wrapper.address) - - const revertingConsumerFactory = await ethers.getContractFactory( - 'VRFV2WrapperRevertingConsumerExample', - consumerOwner, - ) - consumerRevert = await revertingConsumerFactory.deploy( - link.address, - wrapper.address, - ) - - const outOfGasConsumerFactory = await ethers.getContractFactory( - 'VRFV2WrapperOutOfGasConsumerExample', - consumerOwner, - ) - consumerOutOfGas = await outOfGasConsumerFactory.deploy( - link.address, - wrapper.address, - ) - }) - - const configure = async (): Promise => { - await expect( - wrapper - .connect(owner) - .setConfig( - wrapperGasOverhead, - coordinatorGasOverhead, - wrapperPremiumPercentage, - toBytes32String('keyHash'), - maxNumWords, - ), - ).to.not.be.reverted - } - - const fund = async (address: string, amount: BigNumber): Promise => { - await expect(link.connect(owner).transfer(address, amount)).to.not.be - .reverted - } - - const fundSub = async (): Promise => { - await expect(coordinator.connect(owner).fundSubscription(1, oneHundredLink)) - .to.not.be.reverted - } - - describe('calculatePrice', async () => { - // Note: This is a meta-test for the calculatePrice func above. It is then assumed correct for - // the remainder of the tests - it('can calculate price at 50 gwei, 100k limit', async () => { - const result = calculatePrice( - 100_000, - wrapperGasOverhead, - coordinatorGasOverhead, - fiftyGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - bigNumEquals(BigNumber.from('3986666666666666666'), result) - }) - - it('can calculate price at 50 gwei, 200k limit', async () => { - const result = calculatePrice( - 200_000, - wrapperGasOverhead, - coordinatorGasOverhead, - fiftyGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - bigNumEquals(BigNumber.from('5820000000000000000'), result) - }) - - it('can calculate price at 200 gwei, 100k limit', async () => { - const result = calculatePrice( - 200_000, - wrapperGasOverhead, - coordinatorGasOverhead, - oneHundredGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - bigNumEquals(BigNumber.from('11540000000000000000'), result) - }) - - it('can calculate price at 200 gwei, 100k limit, 25% premium', async () => { - const result = calculatePrice( - 200_000, - wrapperGasOverhead, - coordinatorGasOverhead, - oneHundredGwei, - weiPerUnitLink, - 25, - flatFee, - ) - bigNumEquals(BigNumber.from('13100000000000000000'), result) - }) - }) - - describe('#setConfig/#getConfig', async () => { - it('can be configured', async () => { - await configure() - - const resp = await wrapper.connect(requester).getConfig() - bigNumEquals(BigNumber.from('4000000000000000'), resp[0]) // fallbackWeiPerUnitLink - bigNumEquals(2_700, resp[1]) // stalenessSeconds - bigNumEquals(BigNumber.from('100000'), resp[2]) // fulfillmentFlatFeeLinkPPM - bigNumEquals(wrapperGasOverhead, resp[3]) - bigNumEquals(coordinatorGasOverhead, resp[4]) - bigNumEquals(wrapperPremiumPercentage, resp[5]) - assert.equal(resp[6], toBytes32String('keyHash')) - bigNumEquals(10, resp[7]) - }) - - it('can be reconfigured', async () => { - await configure() - - await expect( - wrapper.connect(owner).setConfig( - 140_000, // wrapperGasOverhead - 195_000, // coordinatorGasOverhead - 9, // wrapperPremiumPercentage - toBytes32String('keyHash2'), // keyHash - 9, // maxNumWords - ), - ).to.not.be.reverted - - const resp = await wrapper.connect(requester).getConfig() - bigNumEquals(BigNumber.from('4000000000000000'), resp[0]) // fallbackWeiPerUnitLink - bigNumEquals(2_700, resp[1]) // stalenessSeconds - bigNumEquals(BigNumber.from('100000'), resp[2]) // fulfillmentFlatFeeLinkPPM - bigNumEquals(140_000, resp[3]) // wrapperGasOverhead - bigNumEquals(195_000, resp[4]) // coordinatorGasOverhead - bigNumEquals(9, resp[5]) // wrapperPremiumPercentage - assert.equal(resp[6], toBytes32String('keyHash2')) // keyHash - bigNumEquals(9, resp[7]) // maxNumWords - }) - - it('cannot be configured by a non-owner', async () => { - await expect( - wrapper.connect(requester).setConfig( - 10_000, // wrapperGasOverhead - 10_000, // coordinatorGasOverhead - 10, // wrapperPremiumPercentage - toBytes32String('keyHash'), // keyHash - 10, // maxNumWords - ), - ).to.be.reverted - }) - }) - describe('#calculatePrice', async () => { - it('cannot calculate price when not configured', async () => { - await expect(wrapper.connect(requester).calculateRequestPrice(100_000)).to - .be.reverted - }) - it('can calculate price at 50 gwei, 100k gas', async () => { - await configure() - const expected = calculatePrice( - 100_000, - wrapperGasOverhead, - coordinatorGasOverhead, - fiftyGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - const resp = await wrapper - .connect(requester) - .calculateRequestPrice(100_000, { gasPrice: fiftyGwei }) - bigNumEquals(expected, resp) - }) - - it('can calculate price at 100 gwei, 100k gas', async () => { - await configure() - const expected = calculatePrice( - 100_000, - wrapperGasOverhead, - coordinatorGasOverhead, - oneHundredGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - const resp = await wrapper - .connect(requester) - .calculateRequestPrice(100_000, { gasPrice: oneHundredGwei }) - bigNumEquals(expected, resp) - }) - - it('can calculate price at 100 gwei, 200k gas', async () => { - await configure() - const expected = calculatePrice( - 200_000, - wrapperGasOverhead, - coordinatorGasOverhead, - oneHundredGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - const resp = await wrapper - .connect(requester) - .calculateRequestPrice(200_000, { gasPrice: oneHundredGwei }) - bigNumEquals(expected, resp) - }) - }) - - describe('#estimatePrice', async () => { - it('cannot estimate price when not configured', async () => { - await expect( - wrapper - .connect(requester) - .estimateRequestPrice(100_000, oneHundredGwei), - ).to.be.reverted - }) - it('can estimate price at 50 gwei, 100k gas', async () => { - await configure() - const expected = calculatePrice( - 100_000, - wrapperGasOverhead, - coordinatorGasOverhead, - fiftyGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - const resp = await wrapper - .connect(requester) - .estimateRequestPrice(100_000, fiftyGwei) - bigNumEquals(expected, resp) - }) - - it('can estimate price at 100 gwei, 100k gas', async () => { - await configure() - const expected = calculatePrice( - 100_000, - wrapperGasOverhead, - coordinatorGasOverhead, - oneHundredGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - const resp = await wrapper - .connect(requester) - .estimateRequestPrice(100_000, oneHundredGwei) - bigNumEquals(expected, resp) - }) - - it('can estimate price at 100 gwei, 200k gas', async () => { - await configure() - const expected = calculatePrice( - 200_000, - wrapperGasOverhead, - coordinatorGasOverhead, - oneHundredGwei, - weiPerUnitLink, - wrapperPremiumPercentage, - flatFee, - ) - const resp = await wrapper - .connect(requester) - .estimateRequestPrice(200_000, oneHundredGwei) - bigNumEquals(expected, resp) - }) - }) - - describe('#onTokenTransfer/#fulfillRandomWords', async () => { - it('cannot request randomness when not configured', async () => { - await expect( - consumer.connect(consumerOwner).makeRequest(80_000, 3, 2, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.be.reverted - }) - it('can only be called through LinkToken', async () => { - configure() - await expect( - wrongLink - .connect(owner) - .transfer(consumerWrongLink.address, oneHundredLink, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.not.be.reverted - await expect( - consumerWrongLink.connect(consumerOwner).makeRequest(80_000, 3, 2, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.be.reverted - }) - it('can request and fulfill randomness', async () => { - await configure() - await fund(consumer.address, oneHundredLink) - await fundSub() - - await expect( - consumer.connect(consumerOwner).makeRequest(100_000, 3, 1, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.emit(coordinator, 'RandomWordsRequested') - - const price = calculatePrice(100_000) - - // Check that the wrapper has the paid amount - bigNumEquals(price, await link.balanceOf(wrapper.address)) - - const { paid, fulfilled } = await consumer.s_requests(1 /* requestId */) - bigNumEquals(price, paid) - expect(fulfilled).to.be.false - - // fulfill the request - await expect( - coordinator - .connect(owner) - .fulfillRandomWordsWithOverride(1, wrapper.address, [123], { - gasLimit: 1_000_000, - }), - ) - .to.emit(coordinator, 'RandomWordsFulfilled') - .to.emit(consumer, 'WrappedRequestFulfilled') - .withArgs(1, [123], BigNumber.from(price)) - - const expectedBalance = price - const diff = expectedBalance - .sub(await link.balanceOf(wrapper.address)) - .abs() - expect(diff.lt(pointOneLink)).to.be.true - }) - it('does not revert if consumer runs out of gas', async () => { - await configure() - await fund(consumerOutOfGas.address, oneHundredLink) - await fundSub() - - await expect( - consumerOutOfGas.connect(consumerOwner).makeRequest(100_000, 3, 1, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.emit(coordinator, 'RandomWordsRequested') - - const price = calculatePrice(100_000) - - // Check that the wrapper has the paid amount - bigNumEquals(price, await link.balanceOf(wrapper.address)) - - // fulfill the request - await expect( - coordinator - .connect(owner) - .fulfillRandomWordsWithOverride(1, wrapper.address, [123], { - gasLimit: 1_000_000, - }), - ) - .to.emit(coordinator, 'RandomWordsFulfilled') - .to.emit(wrapper, 'WrapperFulfillmentFailed') - }) - it('does not revert if consumer reverts', async () => { - await configure() - await fund(consumerRevert.address, oneHundredLink) - await fundSub() - - await expect( - consumerRevert.connect(consumerOwner).makeRequest(100_000, 3, 1, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.emit(coordinator, 'RandomWordsRequested') - - const price = calculatePrice(100_000) - - // Check that the wrapper has the paid amount - bigNumEquals(price, await link.balanceOf(wrapper.address)) - - // fulfill the request - await expect( - coordinator - .connect(owner) - .fulfillRandomWordsWithOverride(1, wrapper.address, [123]), - ) - .to.emit(coordinator, 'RandomWordsFulfilled') - .to.emit(wrapper, 'WrapperFulfillmentFailed') - - const expectedBalance = price - const diff = expectedBalance - .sub(await link.balanceOf(wrapper.address)) - .abs() - - expect(diff.lt(pointOneLink)).to.be.true - }) - }) - describe('#disable/#enable', async () => { - it('can only calculate price when enabled', async () => { - await configure() - - await expect(wrapper.connect(owner).disable()).to.not.be.reverted - await expect( - wrapper.connect(consumerOwner).calculateRequestPrice(100_000, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.be.reverted - - await expect(wrapper.connect(owner).enable()).to.not.be.reverted - await expect( - wrapper.connect(consumerOwner).calculateRequestPrice(100_000, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.not.be.reverted - }) - - it('can only estimate price when enabled', async () => { - await configure() - - await expect(wrapper.connect(owner).disable()).to.not.be.reverted - await expect( - wrapper - .connect(consumerOwner) - .estimateRequestPrice(100_000, oneHundredGwei), - ).to.be.reverted - - await expect(wrapper.connect(owner).enable()).to.not.be.reverted - await expect( - wrapper - .connect(consumerOwner) - .estimateRequestPrice(100_000, oneHundredGwei), - ).to.not.be.reverted - }) - - it('can be configured while disabled', async () => { - await expect(wrapper.connect(owner).disable()).to.not.be.reverted - await configure() - }) - - it('can only request randomness when enabled', async () => { - await configure() - await fund(consumer.address, oneHundredLink) - await fundSub() - - await expect(wrapper.connect(owner).disable()).to.not.be.reverted - await expect( - consumer.connect(consumerOwner).makeRequest(100_000, 3, 1, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.be.reverted - - await expect(wrapper.connect(owner).enable()).to.not.be.reverted - await expect( - consumer.connect(consumerOwner).makeRequest(100_000, 3, 1, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.not.be.reverted - }) - - it('can fulfill randomness when disabled', async () => { - await configure() - await fund(consumer.address, oneHundredLink) - await fundSub() - - await expect( - consumer.connect(consumerOwner).makeRequest(100_000, 3, 1, { - gasPrice: oneHundredGwei, - gasLimit: 1_000_000, - }), - ).to.not.be.reverted - await expect(wrapper.connect(owner).disable()).to.not.be.reverted - - await expect( - coordinator - .connect(owner) - .fulfillRandomWordsWithOverride(1, wrapper.address, [123], { - gasLimit: 1_000_000, - }), - ) - .to.emit(coordinator, 'RandomWordsFulfilled') - .to.emit(consumer, 'WrappedRequestFulfilled') - }) - }) - - describe('#withdraw', async () => { - it('can withdraw funds to the owner', async () => { - await configure() - await fund(wrapper.address, oneHundredLink) - const recipientAddress = await withdrawRecipient.getAddress() - - // Withdraw half the funds - await expect( - wrapper - .connect(owner) - .withdraw(recipientAddress, oneHundredLink.div(2)), - ).to.not.be.reverted - bigNumEquals( - oneHundredLink.div(2), - await link.balanceOf(recipientAddress), - ) - bigNumEquals(oneHundredLink.div(2), await link.balanceOf(wrapper.address)) - - // Withdraw the rest - await expect( - wrapper - .connect(owner) - .withdraw(recipientAddress, oneHundredLink.div(2)), - ).to.not.be.reverted - bigNumEquals(oneHundredLink, await link.balanceOf(recipientAddress)) - bigNumEquals(0, await link.balanceOf(wrapper.address)) - }) - - it('cannot withdraw funds to non owners', async () => { - await configure() - await fund(wrapper.address, oneHundredLink) - const recipientAddress = await withdrawRecipient.getAddress() - - await expect( - wrapper - .connect(consumerOwner) - .withdraw(recipientAddress, oneHundredLink.div(2)), - ).to.be.reverted - }) - }) -})