Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keepers example using function selector checkData #73

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 65 additions & 9 deletions contracts/KeepersCounter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,83 @@ contract Counter is KeeperCompatibleInterface {
*/
uint public counter;

/**
* Public multiplier boolean variable
*/
bool public multiplierEnabled;

/**
* Use an interval in seconds and a timestamp to slow execution of Upkeep
*/
uint public immutable interval;
uint public lastTimeStamp;

// keccak256 hash of empty checkData
bytes32 constant public emptyCheckDataHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;

constructor(uint updateInterval) {
constructor(uint updateInterval, bool _multiplierEnabled) {
interval = updateInterval;
lastTimeStamp = block.timestamp;

counter = 0;
multiplierEnabled = _multiplierEnabled;
}

function setMultiplierEnabled(bool _enabled) external {
multiplierEnabled = _enabled;
}

function checkMultiplier() external view returns (bool upkeepNeeded, bytes memory performData) {
upkeepNeeded = multiplierEnabled && (counter == 3 || counter == 10);
if (upkeepNeeded) {
uint256 multiplier = counter % 2 == 0 ? 10 : 3;
performData = abi.encodeWithSelector(this.multiplyCounter.selector, multiplier);
}
return (upkeepNeeded, performData);
}

function checkUpkeep(bytes calldata /* checkData */) external override returns (bool upkeepNeeded, bytes memory /* performData */) {
upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
// We don't use the checkData in this example. The checkData is defined when the Upkeep was registered.
function checkReset() external view returns (bool upkeepNeeded, bytes memory performData) {
upkeepNeeded = counter > 100;
performData = abi.encodePacked(bytes4(abi.encodeWithSelector(this.resetCounter.selector, "")));
return (upkeepNeeded, performData);
}

function multiplyCounter(uint256 _amount) external {
require(multiplierEnabled && (counter == 3 || counter == 10), "Upkeep not satisfied");
require(counter == _amount, "Only valid multiplier");
counter *= _amount;
}

function resetCounter() external {
require(counter > 100, "Too early");
counter = 0;
}

function checkUpkeep(bytes calldata checkData)
external
view
override
returns (bool upkeepNeeded, bytes memory performData)
{
if (keccak256(checkData) == emptyCheckDataHash) {
// We don't use the checkData in this example. The checkData is defined when the Upkeep was registered.
upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
performData = "";
} else {
(bool success, bytes memory returnedData) = address(this).staticcall(checkData);
require(success);

(upkeepNeeded, performData) = abi.decode(returnedData, (bool, bytes));
}
}

function performUpkeep(bytes calldata /* performData */) external override {
lastTimeStamp = block.timestamp;
counter = counter + 1;
// We don't use the performData in this example. The performData is generated by the Keeper's call to your checkUpkeep function
function performUpkeep(bytes calldata performData) external override {
lastTimeStamp = block.timestamp;
if (keccak256(performData) == emptyCheckDataHash) {
// We don't use the performData in this example. The performData is generated by the Keeper's call to your checkUpkeep function
counter = counter + 1;
} else {
(bool success, ) = address(this).call(performData);
require(success);
}
}
}
3 changes: 2 additions & 1 deletion deploy/04_Deploy_KeepersCounter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ module.exports = async ({
const { deployer } = await getNamedAccounts()
const chainId = await getChainId()
let keepersUpdateInterval = networkConfig[chainId]['keepersUpdateInterval']
let multiplierEnabled = networkConfig[chainId]['multiplierEnabled']
// Price Feed Address, values can be obtained at https://docs.chain.link/docs/reference-contracts
// Default one below is ETH/USD contract on Kovan
const keepersCounter = await deploy('Counter', {
from: deployer,
args: [keepersUpdateInterval],
args: [keepersUpdateInterval, multiplierEnabled],
log: true
})
log("Head to https://keepers.chain.link/ to register your contract for upkeeps. Then run the following command to track the counter updates")
Expand Down
15 changes: 10 additions & 5 deletions helper-hardhat-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ const networkConfig = {
keyHash: '0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4',
jobId: '29fa9aa13bf1468788b7cc4a500a45b8',
fundAmount: "1000000000000000000",
keepersUpdateInterval: "30"
keepersUpdateInterval: "30",
multiplierEnabled: "true"
},
31337: {
name: 'localhost',
fee: '100000000000000000',
keyHash: '0x6c3699283bda56ad74f6b855546325b68d482e983852a7a82979cc4807b641f4',
jobId: '29fa9aa13bf1468788b7cc4a500a45b8',
fundAmount: "1000000000000000000",
keepersUpdateInterval: "30"
keepersUpdateInterval: "30",
multiplierEnabled: "true"
},
42: {
name: 'kovan',
Expand All @@ -25,7 +27,8 @@ const networkConfig = {
jobId: 'd5270d1c311941d0b08bead21fea7747',
fee: '100000000000000000',
fundAmount: "1000000000000000000",
keepersUpdateInterval: "30"
keepersUpdateInterval: "30",
multiplierEnabled: "true"
},
4: {
name: 'rinkeby',
Expand All @@ -37,13 +40,15 @@ const networkConfig = {
jobId: '6b88e0402e5d415eb946e528b8e0c7ba',
fee: '100000000000000000',
fundAmount: "1000000000000000000",
keepersUpdateInterval: "30"
keepersUpdateInterval: "30",
multiplierEnabled: "true"
},
1: {
name: 'mainnet',
linkToken: '0x514910771af9ca656af840dff83e8264ecf986ca',
fundAmount: "0",
keepersUpdateInterval: "30"
keepersUpdateInterval: "30",
multiplierEnabled: "true"
},
5: {
name: 'goerli',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@nomiclabs/hardhat-etherscan": "^2.1.7",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/test-helpers": "^0.5.15",
"chai": "^4.3.4",
"chai-bignumber": "^3.0.0",
"chai-bn": "^0.2.1",
Expand Down
112 changes: 106 additions & 6 deletions test/unit/KeepersCounter_unit_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const skipIf = require('mocha-skip-if')
chai.use(require('chai-bn')(BN))
const { deployments, getChainId } = require('hardhat')
const { networkConfig, developmentChains } = require('../../helper-hardhat-config')
const { expectRevert } = require('@openzeppelin/test-helpers')

skip.if(!developmentChains.includes(network.name)).
describe('Keepers Counter Unit Tests', async function () {
Expand All @@ -14,19 +15,118 @@ skip.if(!developmentChains.includes(network.name)).
await deployments.fixture(['mocks', 'keepers'])
const Counter = await deployments.get("Counter")
counter = await ethers.getContractAt("Counter", Counter.address)
chainId = await getChainId()
})

it('should be able to call checkUpkeep', async () => {
const checkData = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(""));
const { upkeepNeeded, performData } = await counter.checkUpkeep(checkData);
})
context("Basic upkeep", async () => {
it('should be able to call checkUpkeep', async () => {
const checkData = ethers.utils.hexlify("0x");
const { upkeepNeeded, performData } = await counter.checkUpkeep(checkData);
})

it('should be able to call performUpkeep', async () => {
const checkData = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(""));
it('should be able to call performUpkeep', async () => {
const checkData = ethers.utils.hexlify("0x");
await counter.performUpkeep(checkData);
//now get the new counter value
const result = await counter.counter()
console.log("Keepers Counter value: ", new ethers.BigNumber.from(result._hex).toString())
expect(new ethers.BigNumber.from(result).toString()).to.be.a.bignumber.that.is.greaterThan(new ethers.BigNumber.from(0).toString())
})
})

context("Upkeep with checkData - unhappy paths", async () => {
before(function() {
if(networkConfig[chainId].multiplierEnabled == "false") this.skip();
});
it("Should not require upkeep from checkMultiplier if not enabled", async () => {
await counter.setMultiplierEnabled(false);
const returnData = await counter.checkMultiplier()
expect(returnData[0]).to.be.equal(false)
})

it("Should not multiply counter if not enabled", async () => {
await expectRevert(counter.multiplyCounter(3), "Upkeep not satisfied")
})

it("Should not require upkeep from checkMultiplier if not valid value", async () => {
await counter.setMultiplierEnabled(true);
const returnData = await counter.checkMultiplier()
expect(returnData[0]).to.be.equal(false)
})

it("Should not multiply counter if not valid value", async () => {
for(let i=0; i<3; i++) {
await counter.performUpkeep(ethers.utils.hexlify("0x"));
}
await expectRevert(counter.multiplyCounter(2), "Only valid multiplier")
})

it("Should not require upkeep from checkReset if too early", async () => {
const returnData = await counter.checkReset()
expect(returnData[0]).to.be.equal(false)
})

it("Should not reset counter if too early", async () => {
await expectRevert(counter.resetCounter(), "Too early")
})
})

context("Upkeep with checkData - happy paths", async () => {
before(function() {
if(networkConfig[chainId].multiplierEnabled == "false") this.skip();
});
beforeEach(async () => {
for(let i=0; i<3; i++) {
await counter.performUpkeep(ethers.utils.hexlify("0x"));
}
})

it("Should be able to call checkUpkeep for checkMultiplier", async () => {
const abi = ["function checkMultiplier()", "function multiplyCounter(uint)"]
const iface = new ethers.utils.Interface(abi)
const checkData = iface.encodeFunctionData("checkMultiplier", [])
console.log(`checkMultiplier checkData: ${checkData}`)
const { upkeepNeeded, performData } = await counter.checkUpkeep(checkData)
const calldata = iface.encodeFunctionData("multiplyCounter", ["3"])
expect(upkeepNeeded).to.be.equal(true)
expect(performData).to.be.equal(calldata)
})

it("Should call performUpkeep for multiplyCounter", async () => {
const abi = ["function multiplyCounter(uint)"]
const iface = new ethers.utils.Interface(abi)
const checkData = iface.encodeFunctionData("multiplyCounter", ["3"])
await counter.performUpkeep(checkData)
const result = await counter.counter()
expect(result).to.be.equal(new ethers.BigNumber.from("9"))
})

it("Should be able to call checkUpkeep for checkReset", async () => {
await counter.multiplyCounter(3);
await counter.performUpkeep(ethers.utils.hexlify("0x"));
await counter.multiplyCounter(10);
await counter.performUpkeep(ethers.utils.hexlify("0x"));
const abi = ["function checkReset()", "function resetCounter()"]
const iface = new ethers.utils.Interface(abi)
const checkData = iface.encodeFunctionData("checkReset", [])
console.log(`checkReset checkData: ${checkData}`)
const { upkeepNeeded, performData } = await counter.checkUpkeep(checkData)
const calldata = iface.encodeFunctionData("resetCounter", [])
expect(upkeepNeeded).to.be.equal(true)
expect(performData).to.be.equal(calldata)
})

it("Should call performUpkeep for resetCounter", async () => {
await counter.multiplyCounter(3);
await counter.performUpkeep(ethers.utils.hexlify("0x"));
await counter.multiplyCounter(10);
await counter.performUpkeep(ethers.utils.hexlify("0x"));
const abi = ["function resetCounter()"]
const iface = new ethers.utils.Interface(abi)
const checkData = iface.encodeFunctionData("resetCounter", [])
await counter.performUpkeep(checkData)
const result = await counter.counter()
expect(result).to.be.equal(new ethers.BigNumber.from("0"))
})
})
})
Loading