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

Added chainlink automation #45

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ docs/

.history/

**/node_modules/
**/node_modules/
.vscode/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@
[submodule "lib/middleware-sdk"]
path = lib/middleware-sdk
url = https://github.com/symbioticfi/middleware-sdk
[submodule "lib/chainlink-brownie-contracts"]
path = lib/chainlink-brownie-contracts
url = https://github.com/smartcontractkit/chainlink-brownie-contracts
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ remappings = [
'@symbiotic-collateral=lib/collateral/src/',
'@symbiotic-middleware/=lib/middleware-sdk/src/',
'@tanssi-bridge-relayer/=lib/tanssi-bridge-relayer/',
"@chainlink/=lib/chainlink-brownie-contracts/contracts/src/v0.8/",
# These are for snowbridge. Needs to emulate their remappings.
'openzeppelin-contracts/=lib/openzeppelin-contracts/',
'openzeppelin/=lib/openzeppelin-contracts/contracts/',
Expand Down
1 change: 1 addition & 0 deletions lib/chainlink-brownie-contracts
92 changes: 85 additions & 7 deletions src/contracts/middleware/Middleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
// along with Tanssi. If not, see <http://www.gnu.org/licenses/>
pragma solidity 0.8.25;

//**************************************************************************************************
// CHAINLINK
//**************************************************************************************************
import "@chainlink/automation/interfaces/AutomationCompatibleInterface.sol";

//**************************************************************************************************
// OPENZEPPELIN
//**************************************************************************************************
Expand Down Expand Up @@ -55,6 +60,7 @@ contract Middleware is
KeyManager256,
OzAccessControl,
EpochCapture,
AutomationCompatibleInterface,
IMiddleware
{
using QuickSort for ValidatorData[];
Expand Down Expand Up @@ -88,6 +94,9 @@ contract Middleware is
// */
mapping(uint48 epoch => mapping(address operator => uint256 amount)) public s_operatorStakeCache;
IOGateway private s_gateway;
uint256 public s_lastTimestamp;
address public s_forwarderAddress;
uint256 public s_interval;
//TODO End of TODO

uint256 public constant PARTS_PER_BILLION = 1_000_000_000;
Expand All @@ -110,6 +119,13 @@ contract Middleware is
_;
}

modifier onlyIfGatewayExists() {
if (address(s_gateway) == address(0)) {
revert Middleware__GatewayNotSet();
}
_;
}

/*
* @notice Constructor for the middleware
* @param operatorRewards The operator rewards address
Expand Down Expand Up @@ -153,6 +169,8 @@ contract Middleware is
if (slashingWindow < epochDuration) {
revert Middleware__SlashingWindowTooShort();
}
s_lastTimestamp = Time.timestamp();
s_interval = epochDuration;

__BaseMiddleware_init(network, slashingWindow, vaultRegistry, operatorRegistry, operatorNetOptin, reader);
__OzAccessControl_init(owner);
Expand All @@ -172,7 +190,7 @@ contract Middleware is
function _beforeRegisterSharedVault(
address sharedVault,
IODefaultStakerRewards.InitParams memory stakerRewardsParams
) internal virtual override {
) internal override {
stakerRewardsParams.vault = sharedVault;
address stakerRewards = IODefaultStakerRewardsFactory(i_stakerRewardsFactory).create(stakerRewardsParams);
IODefaultOperatorRewards(i_operatorRewards).setStakerRewardContract(stakerRewards, sharedVault);
Expand All @@ -192,6 +210,32 @@ contract Middleware is
s_gateway = IOGateway(_gateway);
}

// /**
// * @inheritdoc IMiddleware
// */
function setInterval(
uint256 interval
) external checkAccess {
if (interval == 0) {
revert Middleware__InvalidInterval();
}

s_interval = interval;
}

// /**
// * @inheritdoc IMiddleware
// */
function setForwarder(
address forwarder
) external checkAccess {
if (forwarder == address(0)) {
revert Middleware__InvalidAddress();
}

s_forwarderAddress = forwarder;
}

// /**
// * @inheritdoc IMiddleware
// */
Expand Down Expand Up @@ -226,18 +270,52 @@ contract Middleware is
// /**
// * @inheritdoc IMiddleware
// */
// TODO: this function should be split to allow to be called by chainlink in case
function sendCurrentOperatorsKeys() external returns (bytes32[] memory sortedKeys) {
if (address(s_gateway) == address(0)) {
revert Middleware__GatewayNotSet();
}

function sendCurrentOperatorsKeys() external onlyIfGatewayExists returns (bytes32[] memory sortedKeys) {
uint48 epoch = getCurrentEpoch();
sortedKeys = sortOperatorsByVaults(epoch);

s_gateway.sendOperatorsData(sortedKeys, epoch);
}

/**
* @inheritdoc AutomationCompatibleInterface
* @dev Called by chainlink nodes off-chain to check if the upkeep is needed
* @return upkeepNeeded boolean to indicate whether the keeper should call performUpkeep or not.
* @return performData bytes of the sorted operators' keys and the epoch that will be used by the keeper when calling performUpkeep, if upkeep is needed.
*/
function checkUpkeep(
bytes calldata /* checkData */
) external view override returns (bool upkeepNeeded, bytes memory performData) {
uint48 epoch = getCurrentEpoch();
bytes32[] memory sortedKeys = sortOperatorsByVaults(epoch);

// Should it be epochDuration? Because we wanna send it once per network epoch
upkeepNeeded = (block.timestamp - s_lastTimestamp) > s_interval;

performData = abi.encode(sortedKeys, epoch);
}

/**
* @inheritdoc AutomationCompatibleInterface
* @dev Called by chainlink nodes off-chain to perform the upkeep. It will send the sorted keys to the gateway
*/
function performUpkeep(
bytes calldata performData
) external override onlyIfGatewayExists {
if (msg.sender != s_forwarderAddress) {
revert Middleware__NotForwarder();
}

if ((block.timestamp - s_lastTimestamp) > s_interval) {
s_lastTimestamp = block.timestamp;

// Decode the sorted keys and the epoch from performData
(bytes32[] memory sortedKeys, uint48 epoch) = abi.decode(performData, (bytes32[], uint48));

s_gateway.sendOperatorsData(sortedKeys, epoch);
}
}

// /**
// * @inheritdoc IMiddleware
// */
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/middleware/IMiddleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface IMiddleware {
// Errors
error Middleware__NotOperator();
error Middleware__NotVault();
error Middleware__NotForwarder();
error Middleware__CallerNotGateway();
error Middleware__GatewayNotSet();
error Middleware__OperatorRewardsNotSet();
Expand All @@ -39,6 +40,7 @@ interface IMiddleware {
error Middleware__TooOldEpoch();
error Middleware__InvalidEpoch();
error Middleware__InvalidAddress();
error Middleware__InvalidInterval();
error Middleware__InsufficientBalance();
error Middleware__SlashingWindowTooShort();
error Middleware__UnknownSlasherType();
Expand Down
37 changes: 28 additions & 9 deletions test/integration/Middleware.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -987,15 +987,6 @@ contract MiddlewareTest is Test {
return paraID;
}

// function testSendingOperatorsDataToGateway() public {
// IOGateway gateway = IOGateway(address(_createGateway()));
// _createParaIDAndAgent(gateway);
// vm.startPrank(owner);
// middleware.setGateway(address(gateway));
// middleware.sendCurrentOperatorsKeys();
// vm.stopPrank();
// }

function _addOperatorsToNetwork(
uint256 _count
) public {
Expand Down Expand Up @@ -1230,6 +1221,34 @@ contract MiddlewareTest is Test {
vm.stopPrank();
}

// ************************************************************************************************
// * UPKEEP
// ************************************************************************************************

function testUpkeep() public {
address forwarder = makeAddr("forwarder");
vm.prank(owner);
middleware.setForwarder(forwarder);

// It's not needed, it's just for explaining and showing the flow
address offlineKeepers = makeAddr("offlineKeepers");
vm.prank(offlineKeepers);
(bool upkeepNeeded, bytes memory performData) = middleware.checkUpkeep(hex"");
assertEq(upkeepNeeded, false);

vm.warp(block.timestamp + NETWORK_EPOCH_DURATION + 1);
(upkeepNeeded, performData) = middleware.checkUpkeep(hex"");
assertEq(upkeepNeeded, true);

bytes32[] memory sortedKeys = abi.decode(performData, (bytes32[]));
assertEq(sortedKeys.length, 3);

vm.prank(forwarder);
vm.expectEmit(true, false, false, false);
emit IOGateway.OperatorsDataCreated(sortedKeys.length, hex"");
middleware.performUpkeep(performData);
}

function testWhenRegisteringVaultThenStakerRewardsAreDeployed() public {
vm.startPrank(owner);
uint256 totalEntities = stakerRewardsFactory.totalEntities();
Expand Down
Loading
Loading