Skip to content

Commit

Permalink
Merge branch 'develop' into feature/KS-592/support-histograms
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickhuie19 committed Dec 4, 2024
2 parents bfb4647 + 030fd7c commit 53c0d80
Show file tree
Hide file tree
Showing 46 changed files with 933 additions and 791 deletions.
5 changes: 5 additions & 0 deletions .changeset/wet-bags-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Change ChainWriter naming to ContractWriter to consolidate Relayer chain interfaces #internal
2 changes: 1 addition & 1 deletion .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ packages:
Codec:
config:
dir: core/services/relay/evm/mocks
ChainWriter:
ContractWriter:
github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp:
config:
dir: core/gethwrappers/ccip/mocks/
Expand Down
9 changes: 9 additions & 0 deletions contracts/.changeset/tender-lemons-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@chainlink/contracts': minor
---

#internal Add supportsInterface to FeeQuoter for Keystone

PR issue: CCIP-4359

Solidity Review issue: CCIP-3966
317 changes: 158 additions & 159 deletions contracts/gas-snapshots/ccip.gas-snapshot

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions contracts/scripts/lcov_prune
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ exclusion_list_ccip=(
"src/v0.8/ConfirmedOwnerWithProposal.sol"
"src/v0.8/tests/MockV3Aggregator.sol"
"src/v0.8/ccip/applications/CCIPClientExample.sol"
"src/v0.8/keystone/*"
)

exclusion_list_shared=(
Expand Down
9 changes: 9 additions & 0 deletions contracts/src/v0.8/ccip/FeeQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {Internal} from "./libraries/Internal.sol";
import {Pool} from "./libraries/Pool.sol";
import {USDPriceWith18Decimals} from "./libraries/USDPriceWith18Decimals.sol";

import {IERC165} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC165.sol";
import {EnumerableSet} from "../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/structs/EnumerableSet.sol";

/// @notice The FeeQuoter contract responsibility is to:
Expand Down Expand Up @@ -493,6 +494,14 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,
}
}

/// @notice Signals which version of the pool interface is supported
function supportsInterface(
bytes4 interfaceId
) public pure override returns (bool) {
return interfaceId == type(IReceiver).interfaceId || interfaceId == type(IFeeQuoter).interfaceId
|| interfaceId == type(ITypeAndVersion).interfaceId || interfaceId == type(IERC165).interfaceId;
}

/// @inheritdoc IReceiver
/// @notice Handles the report containing price feeds and updates the internal price storage.
/// @dev This function is called to process incoming price feed data.
Expand Down
84 changes: 50 additions & 34 deletions contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.onReport.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,42 @@
pragma solidity 0.8.24;

import {KeystoneFeedsPermissionHandler} from "../../../keystone/KeystoneFeedsPermissionHandler.sol";

import {KeystoneForwarder} from "../../../keystone/KeystoneForwarder.sol";
import {FeeQuoter} from "../../FeeQuoter.sol";
import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol";

contract FeeQuoter_onReport is FeeQuoterSetup {
address internal constant FORWARDER_1 = address(0x1);
address internal constant WORKFLOW_OWNER_1 = address(0x3);
bytes10 internal constant WORKFLOW_NAME_1 = "workflow1";
bytes2 internal constant REPORT_NAME_1 = "01";
bytes32 internal constant EXECUTION_ID = hex"6d795f657865637574696f6e5f69640000000000000000000000000000000000";
address internal constant TRANSMITTER = address(50);
bytes32 internal constant WORKFLOW_ID_1 = hex"6d795f6964000000000000000000000000000000000000000000000000000000";
address internal constant WORKFLOW_OWNER_1 = address(51);
bytes10 internal constant WORKFLOW_NAME_1 = hex"000000000000DEADBEEF";
bytes2 internal constant REPORT_NAME_1 = hex"0001";
address internal s_onReportTestToken1;
address internal s_onReportTestToken2;
bytes public encodedPermissionsMetadata;
KeystoneForwarder internal s_forwarder;

function setUp() public virtual override {
super.setUp();

s_forwarder = new KeystoneForwarder();

s_onReportTestToken1 = s_sourceTokens[0];
s_onReportTestToken2 = _deploySourceToken("onReportTestToken2", 0, 20);

KeystoneFeedsPermissionHandler.Permission[] memory permissions = new KeystoneFeedsPermissionHandler.Permission[](1);
permissions[0] = KeystoneFeedsPermissionHandler.Permission({
forwarder: FORWARDER_1,
forwarder: address(s_forwarder),
workflowOwner: WORKFLOW_OWNER_1,
workflowName: WORKFLOW_NAME_1,
reportName: REPORT_NAME_1,
isAllowed: true
});

encodedPermissionsMetadata = abi.encodePacked(WORKFLOW_ID_1, WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1);

FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeeds = new FeeQuoter.TokenPriceFeedUpdate[](2);
tokenPriceFeeds[0] = FeeQuoter.TokenPriceFeedUpdate({
sourceToken: s_onReportTestToken1,
Expand All @@ -39,10 +51,7 @@ contract FeeQuoter_onReport is FeeQuoterSetup {
s_feeQuoter.updateTokenPriceFeeds(tokenPriceFeeds);
}

function test_onReport_Success() public {
bytes memory encodedPermissionsMetadata =
abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1);

function test_onReport() public {
FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](2);
report[0] =
FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)});
Expand All @@ -56,7 +65,7 @@ contract FeeQuoter_onReport is FeeQuoterSetup {
vm.expectEmit();
emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken2, expectedStoredToken2Price, block.timestamp);

changePrank(FORWARDER_1);
changePrank(address(s_forwarder));
s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report));

vm.assertEq(s_feeQuoter.getTokenPrice(report[0].token).value, expectedStoredToken1Price);
Expand All @@ -66,11 +75,34 @@ contract FeeQuoter_onReport is FeeQuoterSetup {
vm.assertEq(s_feeQuoter.getTokenPrice(report[1].token).timestamp, report[1].timestamp);
}

function test_OnReport_StaleUpdate_SkipPriceUpdate_Success() public {
//Creating a correct report
bytes memory encodedPermissionsMetadata =
abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1);
function test_onReport_withKeystoneForwarderContract() public {
FeeQuoter.ReceivedCCIPFeedReport[] memory priceReportRaw = new FeeQuoter.ReceivedCCIPFeedReport[](2);
priceReportRaw[0] =
FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)});
priceReportRaw[1] =
FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken2, price: 4e18, timestamp: uint32(block.timestamp)});

uint224 expectedStoredToken1Price = s_feeQuoter.calculateRebasedValue(18, 18, priceReportRaw[0].price);
uint224 expectedStoredToken2Price = s_feeQuoter.calculateRebasedValue(18, 20, priceReportRaw[1].price);

vm.expectEmit();
emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredToken1Price, block.timestamp);
vm.expectEmit();
emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken2, expectedStoredToken2Price, block.timestamp);

changePrank(address(s_forwarder));
s_forwarder.route(
EXECUTION_ID, TRANSMITTER, address(s_feeQuoter), encodedPermissionsMetadata, abi.encode(priceReportRaw)
);

vm.assertEq(s_feeQuoter.getTokenPrice(priceReportRaw[0].token).value, expectedStoredToken1Price);
vm.assertEq(s_feeQuoter.getTokenPrice(priceReportRaw[0].token).timestamp, priceReportRaw[0].timestamp);

vm.assertEq(s_feeQuoter.getTokenPrice(priceReportRaw[1].token).value, expectedStoredToken2Price);
vm.assertEq(s_feeQuoter.getTokenPrice(priceReportRaw[1].token).timestamp, priceReportRaw[1].timestamp);
}

function test_OnReport_SkipPriceUpdateWhenStaleUpdateReceived() public {
FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1);
report[0] =
FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)});
Expand All @@ -80,7 +112,7 @@ contract FeeQuoter_onReport is FeeQuoterSetup {
vm.expectEmit();
emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredTokenPrice, block.timestamp);

changePrank(FORWARDER_1);
changePrank(address(s_forwarder));
//setting the correct price and time with the correct report
s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report));

Expand All @@ -100,22 +132,18 @@ contract FeeQuoter_onReport is FeeQuoterSetup {
assertEq(vm.getRecordedLogs().length, 0);
}

function test_onReport_TokenNotSupported_Revert() public {
bytes memory encodedPermissionsMetadata =
abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1);
function test_onReport_RevertWhen_TokenNotSupported() public {
FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1);
report[0] =
FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)});

// Revert due to token config not being set with the isEnabled flag
vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1]));
vm.startPrank(FORWARDER_1);
changePrank(address(s_forwarder));
s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report));
}

function test_onReport_InvalidForwarder_Reverts() public {
bytes memory encodedPermissionsMetadata =
abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1);
function test_onReport_RevertWhen_InvalidForwarder() public {
FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1);
report[0] =
FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[0], price: 4e18, timestamp: uint32(block.timestamp)});
Expand All @@ -132,16 +160,4 @@ contract FeeQuoter_onReport is FeeQuoterSetup {
changePrank(STRANGER);
s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report));
}

function test_onReport_UnsupportedToken_Reverts() public {
bytes memory encodedPermissionsMetadata =
abi.encodePacked(keccak256(abi.encode("workflowCID")), WORKFLOW_NAME_1, WORKFLOW_OWNER_1, REPORT_NAME_1);
FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1);
report[0] =
FeeQuoter.ReceivedCCIPFeedReport({token: s_sourceTokens[1], price: 4e18, timestamp: uint32(block.timestamp)});

vm.expectRevert(abi.encodeWithSelector(FeeQuoter.TokenNotSupported.selector, s_sourceTokens[1]));
changePrank(FORWARDER_1);
s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;

import {IReceiver} from "../../../keystone/interfaces/IReceiver.sol";
import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol";

import {IERC165} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC165.sol";
import {IFeeQuoter} from "../../interfaces/IFeeQuoter.sol";
import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol";

contract FeeQuoter_supportsInterface is FeeQuoterSetup {
function test_SupportsInterface_Success() public view {
assertTrue(s_feeQuoter.supportsInterface(type(IReceiver).interfaceId));
assertTrue(s_feeQuoter.supportsInterface(type(ITypeAndVersion).interfaceId));
assertTrue(s_feeQuoter.supportsInterface(type(IFeeQuoter).interfaceId));
assertTrue(s_feeQuoter.supportsInterface(type(IERC165).interfaceId));
}
}
4 changes: 2 additions & 2 deletions contracts/src/v0.8/keystone/KeystoneFeedsConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol";

import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";

contract KeystoneFeedsConsumer is IReceiver, OwnerIsCreator, IERC165 {
contract KeystoneFeedsConsumer is IReceiver, OwnerIsCreator {
event FeedReceived(bytes32 indexed feedId, uint224 price, uint32 timestamp);

error UnauthorizedSender(address sender);
Expand Down Expand Up @@ -101,7 +101,7 @@ contract KeystoneFeedsConsumer is IReceiver, OwnerIsCreator, IERC165 {
return (report.Price, report.Timestamp);
}

function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return interfaceId == type(IReceiver).interfaceId || interfaceId == type(IERC165).interfaceId;
}
}
10 changes: 5 additions & 5 deletions contracts/src/v0.8/keystone/KeystoneFeedsPermissionHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ abstract contract KeystoneFeedsPermissionHandler is Ownable2StepMsgSender {
/// @notice Holds the details for permissions of a report
/// @dev Workflow names and report names are stored as bytes to optimize for gas efficiency.
struct Permission {
address forwarder; // ─────╮ The address of the forwarder (20 bytes)
bytes10 workflowName; // │ The name of the workflow in bytes10
bytes2 reportName; // ─────╯ The name of the report in bytes2
address workflowOwner; // ─╮ The address of the workflow owner (20 bytes)
bool isAllowed; // ────────╯ Whether the report is allowed or not (1 byte)
address forwarder; // ─────╮ The address of the forwarder (20 bytes)
bytes10 workflowName; // │ The name of the workflow in bytes10
bytes2 reportName; // ─────╯ The name of the report in bytes2
address workflowOwner; // ─╮ The address of the workflow owner (20 bytes)
bool isAllowed; // ────────╯ Whether the report is allowed or not (1 byte)
}

/// @notice Event emitted when report permissions are set
Expand Down
5 changes: 4 additions & 1 deletion contracts/src/v0.8/keystone/interfaces/IReceiver.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC165} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol";

/// @title IReceiver - receives keystone reports
interface IReceiver {
/// @notice Implementations must support the IReceiver interface through ERC165.
interface IReceiver is IERC165 {
/// @notice Handles incoming keystone reports.
/// @dev If this function call reverts, it can be retried with a higher gas
/// limit. The receiver is responsible for discarding stale reports.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.24;
import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
import {IReceiver} from "../../interfaces/IReceiver.sol";

contract MaliciousReportReceiver is IReceiver, IERC165 {
contract MaliciousReportReceiver is IReceiver {
event MessageReceived(bytes metadata, bytes[] mercuryReports);
bytes public latestReport;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IReceiver} from "../../interfaces/IReceiver.sol";

/// A malicious receiver that uses max allowed for ERC165 checks and consumes all gas in `onReport()`
/// Causes parent Forwarder contract to revert if it doesn't handle gas tracking accurately
contract MaliciousRevertingReceiver is IReceiver, IERC165 {
contract MaliciousRevertingReceiver is IReceiver {
function onReport(bytes calldata, bytes calldata) external view override {
// consumes about 63/64 of all gas available
uint256 targetGasRemaining = 200;
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/v0.8/keystone/test/mocks/Receiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.24;
import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/interfaces/IERC165.sol";
import {IReceiver} from "../../interfaces/IReceiver.sol";

contract Receiver is IReceiver, IERC165 {
contract Receiver is IReceiver {
event MessageReceived(bytes metadata, bytes[] mercuryReports);
bytes public latestReport;

Expand All @@ -18,7 +18,7 @@ contract Receiver is IReceiver, IERC165 {
emit MessageReceived(metadata, mercuryReports);
}

function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return interfaceId == type(IReceiver).interfaceId || interfaceId == type(IERC165).interfaceId;
}
}
8 changes: 4 additions & 4 deletions core/capabilities/ccip/ocrimpls/contract_transmitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func ToExecCalldata(rawReportCtx [3][32]byte, report []byte, _, _ [][32]byte, _
var _ ocr3types.ContractTransmitter[[]byte] = &commitTransmitter[[]byte]{}

type commitTransmitter[RI any] struct {
cw commontypes.ChainWriter
cw commontypes.ContractWriter
fromAccount ocrtypes.Account
contractName string
method string
Expand All @@ -65,7 +65,7 @@ type commitTransmitter[RI any] struct {
}

func XXXNewContractTransmitterTestsOnly[RI any](
cw commontypes.ChainWriter,
cw commontypes.ContractWriter,
fromAccount ocrtypes.Account,
contractName string,
method string,
Expand All @@ -83,7 +83,7 @@ func XXXNewContractTransmitterTestsOnly[RI any](
}

func NewCommitContractTransmitter[RI any](
cw commontypes.ChainWriter,
cw commontypes.ContractWriter,
fromAccount ocrtypes.Account,
offrampAddress string,
) ocr3types.ContractTransmitter[RI] {
Expand All @@ -98,7 +98,7 @@ func NewCommitContractTransmitter[RI any](
}

func NewExecContractTransmitter[RI any](
cw commontypes.ChainWriter,
cw commontypes.ContractWriter,
fromAccount ocrtypes.Account,
offrampAddress string,
) ocr3types.ContractTransmitter[RI] {
Expand Down
Loading

0 comments on commit 53c0d80

Please sign in to comment.