Skip to content

Commit

Permalink
upgrade opfwdr contracts to v0.8 and implement multiforward (#10933)
Browse files Browse the repository at this point in the history
* upgrade opfwdr contracts to v0.8

* move dirs, port tests

* fix test path

* HH test suite pass on v0.8

* add tests for multiforward

* prettify

* fix linting issues

* undo bad contract copy

* address review comments

* prettier:write

* fix pragma

* rm empty lines

* name mapping k,v
  • Loading branch information
essamhassan authored Oct 19, 2023
1 parent 5b7b401 commit 8134dcf
Show file tree
Hide file tree
Showing 14 changed files with 5,793 additions and 8 deletions.
10 changes: 10 additions & 0 deletions contracts/src/v0.8/dev/shared/interfaces/OwnableInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface OwnableInterface {
function owner() external returns (address);

function transferOwnership(address recipient) external;

function acceptOwnership() external;
}
10 changes: 10 additions & 0 deletions contracts/src/v0.8/interfaces/AuthorizedReceiverInterface.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface AuthorizedReceiverInterface {
function isAuthorizedSender(address sender) external view returns (bool);

function getAuthorizedSenders() external returns (address[] memory);

function setAuthorizedSenders(address[] calldata senders) external;
}
6 changes: 0 additions & 6 deletions contracts/src/v0.8/interfaces/OperatorInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,4 @@ interface OperatorInterface is OracleInterface, ChainlinkRequestInterface {
function ownerTransferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success);

function distributeFunds(address payable[] calldata receivers, uint256[] calldata amounts) external payable;

function getAuthorizedSenders() external returns (address[] memory);

function setAuthorizedSenders(address[] calldata senders) external;

function getForwarder() external returns (address);
}
2 changes: 0 additions & 2 deletions contracts/src/v0.8/interfaces/OracleInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ interface OracleInterface {
bytes32 data
) external returns (bool);

function isAuthorizedSender(address node) external view returns (bool);

function withdraw(address recipient, uint256 amount) external;

function withdrawable() external view returns (uint256);
Expand Down
92 changes: 92 additions & 0 deletions contracts/src/v0.8/operatorforwarder/dev/AuthorizedForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {ConfirmedOwnerWithProposal} from "../../shared/access/ConfirmedOwnerWithProposal.sol";
import {AuthorizedReceiver} from "./AuthorizedReceiver.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

// solhint-disable custom-errors
contract AuthorizedForwarder is ConfirmedOwnerWithProposal, AuthorizedReceiver {
using Address for address;

// solhint-disable-next-line chainlink-solidity/prefix-immutable-variables-with-i
address public immutable linkToken;

event OwnershipTransferRequestedWithMessage(address indexed from, address indexed to, bytes message);

constructor(
address link,
address owner,
address recipient,
bytes memory message
) ConfirmedOwnerWithProposal(owner, recipient) {
require(link != address(0), "Link token cannot be a zero address");
linkToken = link;
if (recipient != address(0)) {
emit OwnershipTransferRequestedWithMessage(owner, recipient, message);
}
}

// solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
string public constant typeAndVersion = "AuthorizedForwarder 1.0.0";

// @notice Forward a call to another contract
// @dev Only callable by an authorized sender
// @param to address
// @param data to forward
function forward(address to, bytes calldata data) external validateAuthorizedSender {
require(to != linkToken, "Cannot forward to Link token");
_forward(to, data);
}

// @notice Forward multiple calls to other contracts in a multicall style
// @dev Only callable by an authorized sender
// @param tos An array of addresses to forward the calls to
// @param datas An array of data to forward to each corresponding address
function multiForward(address[] calldata tos, bytes[] calldata datas) external validateAuthorizedSender {
require(tos.length == datas.length, "Arrays must have the same length");

for (uint256 i = 0; i < tos.length; ++i) {
address to = tos[i];
require(to != linkToken, "Cannot forward to Link token");

// Perform the forward operation
_forward(to, datas[i]);
}
}

// @notice Forward a call to another contract
// @dev Only callable by the owner
// @param to address
// @param data to forward
function ownerForward(address to, bytes calldata data) external onlyOwner {
_forward(to, data);
}

// @notice Transfer ownership with instructions for recipient
// @param to address proposed recipient of ownership
// @param message instructions for recipient upon accepting ownership
function transferOwnershipWithMessage(address to, bytes calldata message) external {
transferOwnership(to);
emit OwnershipTransferRequestedWithMessage(msg.sender, to, message);
}

// @notice concrete implementation of AuthorizedReceiver
// @return bool of whether sender is authorized
function _canSetAuthorizedSenders() internal view override returns (bool) {
return owner() == msg.sender;
}

// @notice common forwarding functionality and validation
function _forward(address to, bytes calldata data) private {
require(to.isContract(), "Must forward to a contract");
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = to.call(data);
if (!success) {
if (result.length == 0) revert("Forwarded call reverted without reason");
assembly {
revert(add(32, result), mload(result))
}
}
}
}
65 changes: 65 additions & 0 deletions contracts/src/v0.8/operatorforwarder/dev/AuthorizedReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {AuthorizedReceiverInterface} from "../../interfaces/AuthorizedReceiverInterface.sol";

// solhint-disable custom-errors
abstract contract AuthorizedReceiver is AuthorizedReceiverInterface {
mapping(address sender => bool authorized) private s_authorizedSenders;
address[] private s_authorizedSenderList;

event AuthorizedSendersChanged(address[] senders, address changedBy);

// @notice Sets the fulfillment permission for a given node. Use `true` to allow, `false` to disallow.
// @param senders The addresses of the authorized Chainlink node
function setAuthorizedSenders(address[] calldata senders) external override validateAuthorizedSenderSetter {
require(senders.length > 0, "Must have at least 1 sender");
// Set previous authorized senders to false
uint256 authorizedSendersLength = s_authorizedSenderList.length;
for (uint256 i = 0; i < authorizedSendersLength; i++) {
s_authorizedSenders[s_authorizedSenderList[i]] = false;
}
// Set new to true
for (uint256 i = 0; i < senders.length; i++) {
require(s_authorizedSenders[senders[i]] == false, "Must not have duplicate senders");
s_authorizedSenders[senders[i]] = true;
}
// Replace list
s_authorizedSenderList = senders;
emit AuthorizedSendersChanged(senders, msg.sender);
}

// @notice Retrieve a list of authorized senders
// @return array of addresses
function getAuthorizedSenders() external view override returns (address[] memory) {
return s_authorizedSenderList;
}

// @notice Use this to check if a node is authorized for fulfilling requests
// @param sender The address of the Chainlink node
// @return The authorization status of the node
function isAuthorizedSender(address sender) public view override returns (bool) {
return s_authorizedSenders[sender];
}

// @notice customizable guard of who can update the authorized sender list
// @return bool whether sender can update authorized sender list
function _canSetAuthorizedSenders() internal virtual returns (bool);

// @notice validates the sender is an authorized sender
function _validateIsAuthorizedSender() internal view {
require(isAuthorizedSender(msg.sender), "Not authorized sender");
}

// @notice prevents non-authorized addresses from calling this method
modifier validateAuthorizedSender() {
_validateIsAuthorizedSender();
_;
}

// @notice prevents non-authorized addresses from calling this method
modifier validateAuthorizedSenderSetter() {
require(_canSetAuthorizedSenders(), "Cannot set authorized senders");
_;
}
}
50 changes: 50 additions & 0 deletions contracts/src/v0.8/operatorforwarder/dev/LinkTokenReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// solhint-disable custom-errors
abstract contract LinkTokenReceiver {
// @notice Called when LINK is sent to the contract via `transferAndCall`
// @dev The data payload's first 2 words will be overwritten by the `sender` and `amount`
// values to ensure correctness. Calls oracleRequest.
// @param sender Address of the sender
// @param amount Amount of LINK sent (specified in wei)
// @param data Payload of the transaction
function onTokenTransfer(
address sender,
uint256 amount,
bytes memory data
) public validateFromLINK permittedFunctionsForLINK(data) {
assembly {
// solhint-disable-next-line avoid-low-level-calls
mstore(add(data, 36), sender) // ensure correct sender is passed
// solhint-disable-next-line avoid-low-level-calls
mstore(add(data, 68), amount) // ensure correct amount is passed0.8.19
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(this).delegatecall(data); // calls oracleRequest
require(success, "Unable to create request");
}

function getChainlinkToken() public view virtual returns (address);

// @notice Validate the function called on token transfer
function _validateTokenTransferAction(bytes4 funcSelector, bytes memory data) internal virtual;

// @dev Reverts if not sent from the LINK token
modifier validateFromLINK() {
require(msg.sender == getChainlinkToken(), "Must use LINK token");
_;
}

// @dev Reverts if the given data does not begin with the `oracleRequest` function selector
// @param data The data payload of the request
modifier permittedFunctionsForLINK(bytes memory data) {
bytes4 funcSelector;
assembly {
// solhint-disable-next-line avoid-low-level-calls
funcSelector := mload(add(data, 32))
}
_validateTokenTransferAction(funcSelector, data);
_;
}
}
Loading

0 comments on commit 8134dcf

Please sign in to comment.