Skip to content

Commit

Permalink
add a log triggered feed lookup upkeep (#9742)
Browse files Browse the repository at this point in the history
  • Loading branch information
FelixFan1992 committed Jul 4, 2023
1 parent 68e49f6 commit 58496ec
Show file tree
Hide file tree
Showing 6 changed files with 751 additions and 0 deletions.
1 change: 1 addition & 0 deletions contracts/scripts/native_solc_compile_all
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ $SCRIPTPATH/native_solc8_16_compile dev/automation/2_1/KeeperRegistryLogicB2_1.s
$SCRIPTPATH/native_solc8_16_compile dev/automation/2_1/interfaces/IKeeperRegistryMaster.sol
$SCRIPTPATH/native_solc8_16_compile dev/automation/2_1/interfaces/ILogAutomation.sol
$SCRIPTPATH/native_solc8_6_compile dev/automation/tests/LogUpkeepCounter.sol
$SCRIPTPATH/native_solc8_16_compile dev/automation/tests/LogTriggeredFeedLookup.sol
$SCRIPTPATH/native_solc8_6_compile automation/UpkeepTranscoder.sol
$SCRIPTPATH/native_solc8_6_compile tests/VerifiableLoadUpkeep.sol
$SCRIPTPATH/native_solc8_6_compile tests/VerifiableLoadMercuryUpkeep.sol
Expand Down
87 changes: 87 additions & 0 deletions contracts/src/v0.8/dev/automation/tests/DummyProtocol.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

// this struct is the same as LogTriggerConfig defined in KeeperRegistryLogicA2_1 contract
struct LogTriggerConfig {
address contractAddress;
uint8 filterSelector; // denotes which topics apply to filter ex 000, 101, 111...only last 3 bits apply
bytes32 topic0;
bytes32 topic1;
bytes32 topic2;
bytes32 topic3;
}

contract DummyProtocol {
event LimitOrderSent(uint256 indexed amount, uint256 indexed price, address indexed to); // keccak256(LimitOrderSent(uint256,uint256,address)) => 0x3e9c37b3143f2eb7e9a2a0f8091b6de097b62efcfe48e1f68847a832e521750a
event LimitOrderWithdrawn(uint256 indexed amount, uint256 indexed price, address indexed from); // keccak256(LimitOrderWithdrawn(uint256,uint256,address)) => 0x0a71b8ed921ff64d49e4d39449f8a21094f38a0aeae489c3051aedd63f2c229f
event LimitOrderExecuted(uint256 indexed orderId, uint256 indexed amount, address indexed exchange); // keccak(LimitOrderExecuted(uint256,uint256,address)) => 0xd1ffe9e45581c11d7d9f2ed5f75217cd4be9f8b7eee6af0f6d03f46de53956cd

function sendLimitedOrder(uint256 amount, uint256 price, address to) public {
// send an order to an exchange
emit LimitOrderSent(amount, price, to);
}

function withdrawLimit(uint256 amount, uint256 price, address from) public {
// withdraw an order from an exchange
emit LimitOrderSent(amount, price, from);
}

function executeLimitOrder(uint256 orderId, uint256 amount, address exchange) public {
// execute a limit order
emit LimitOrderExecuted(orderId, amount, exchange);
}

/**
* @notice this function generates bytes for a basic log trigger config with no filter selector.
* @param targetContract the address of contract where events will be emitted from
* @param t0 the signature of the event to listen to
*/
function getBasicLogTriggerConfig(
address targetContract,
bytes32 t0
) external view returns (bytes memory logTrigger) {
LogTriggerConfig memory cfg = LogTriggerConfig({
contractAddress: targetContract,
filterSelector: 0,
topic0: t0,
topic1: 0x000000000000000000000000000000000000000000000000000000000000000,
topic2: 0x000000000000000000000000000000000000000000000000000000000000000,
topic3: 0x000000000000000000000000000000000000000000000000000000000000000
});
return abi.encode(cfg);
}

/**
* @notice this function generates bytes for a customizable log trigger config.
* @param targetContract the address of contract where events will be emitted from
* @param selector the filter selector. this denotes which topics apply to filter ex 000, 101, 111....only last 3 bits apply
* if 0, it won't filter based on topic 1, 2, 3.
* if 1, it will filter based on topic 1,
* if 2, it will filter based on topic 2,
* if 3, it will filter based on topic 1 and topic 2,
* if 4, it will filter based on topic 3,
* if 5, it will filter based on topic 1 and topic 3....
* @param t0 the signature of the event to listen to.
* @param t1 the topic 1 of the event.
* @param t2 the topic 2 of the event.
* @param t3 the topic 2 of the event.
*/
function getAdvancedLogTriggerConfig(
address targetContract,
uint8 selector,
bytes32 t0,
bytes32 t1,
bytes32 t2,
bytes32 t3
) external view returns (bytes memory logTrigger) {
LogTriggerConfig memory cfg = LogTriggerConfig({
contractAddress: targetContract,
filterSelector: selector,
topic0: t0,
topic1: t1,
topic2: t2,
topic3: t3
});
return abi.encode(cfg);
}
}
110 changes: 110 additions & 0 deletions contracts/src/v0.8/dev/automation/tests/LogTriggeredFeedLookup.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {ILogAutomation, Log} from "../2_1/interfaces/ILogAutomation.sol";
import "../2_1/interfaces/FeedLookupCompatibleInterface.sol";
import {ArbSys} from "../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";

interface IVerifierProxy {
/**
* @notice Verifies that the data encoded has been signed
* correctly by routing to the correct verifier.
* @param signedReport The encoded data to be verified.
* @return verifierResponse The encoded response from the verifier.
*/
function verify(bytes memory signedReport) external returns (bytes memory verifierResponse);
}

contract LogTriggeredFeedLookup is ILogAutomation, FeedLookupCompatibleInterface {
event PerformingLogTriggerUpkeep(
address indexed from,
uint256 orderId,
uint256 amount,
address exchange,
uint256 blockNumber,
bytes blob,
bytes verified
);

ArbSys internal constant ARB_SYS = ArbSys(0x0000000000000000000000000000000000000064);
IVerifierProxy internal constant VERIFIER = IVerifierProxy(0x09DFf56A4fF44e0f4436260A04F5CFa65636A481);

// for log trigger
bytes32 constant sentSig = 0x3e9c37b3143f2eb7e9a2a0f8091b6de097b62efcfe48e1f68847a832e521750a;
bytes32 constant withdrawnSig = 0x0a71b8ed921ff64d49e4d39449f8a21094f38a0aeae489c3051aedd63f2c229f;
bytes32 constant executedSig = 0xd1ffe9e45581c11d7d9f2ed5f75217cd4be9f8b7eee6af0f6d03f46de53956cd;

// for mercury config
bool public useArbitrumBlockNum;
string[] public feedsHex = ["0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"];
string public feedParamKey = "feedIdHex";
string public timeParamKey = "blockNumber";

constructor(bool _useArbitrumBlockNum) {
useArbitrumBlockNum = _useArbitrumBlockNum;
}

function setTimeParamKey(string memory timeParam) external {
timeParamKey = timeParam;
}

function setFeedParamKey(string memory feedParam) external {
feedParamKey = feedParam;
}

function setFeedsHex(string[] memory newFeeds) external {
feedsHex = newFeeds;
}

function checkLog(Log calldata log) external override returns (bool upkeepNeeded, bytes memory performData) {
uint256 blockNum = getBlockNumber();

// filter by event signature
if (log.topics[0] == executedSig) {
// filter by indexed parameters
bytes memory t1 = abi.encodePacked(log.topics[1]); // bytes32 to bytes
uint256 orderId = abi.decode(t1, (uint256));
bytes memory t2 = abi.encodePacked(log.topics[2]);
uint256 amount = abi.decode(t2, (uint256));
bytes memory t3 = abi.encodePacked(log.topics[3]);
address exchange = abi.decode(t3, (address));

revert FeedLookup(feedParamKey, feedsHex, timeParamKey, blockNum, abi.encode(orderId, amount, exchange));
}
revert("could not find matching event sig");
}

function performUpkeep(bytes calldata performData) external override {
(bytes[] memory values, bytes memory extraData) = abi.decode(performData, (bytes[], bytes));
(uint256 orderId, uint256 amount, address exchange) = abi.decode(extraData, (uint256, uint256, address));

bytes memory verifiedResponse; // = VERIFIER.verify(values[0]);

emit PerformingLogTriggerUpkeep(
tx.origin,
orderId,
amount,
exchange,
getBlockNumber(),
values[0],
verifiedResponse
);
}

function checkCallback(
bytes[] memory values,
bytes memory extraData
) external view override returns (bool upkeepNeeded, bytes memory performData) {
// do sth about the chainlinkBlob data in values and extraData
bytes memory performData = abi.encode(values, extraData);
return (true, performData);
}

function getBlockNumber() internal view returns (uint256) {
if (useArbitrumBlockNum) {
return ARB_SYS.arbBlockNumber();
} else {
return block.number;
}
}
}
Loading

0 comments on commit 58496ec

Please sign in to comment.