Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
102 changes: 61 additions & 41 deletions src/contracts/TanssiMetaMiddleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
uint48 lastDistributedEraIndex;
mapping(uint48 eraIndex => EraRoot eraRoot) eraRoot;
mapping(uint48 epoch => uint48[] eraIndexes) eraIndexesPerEpoch;
mapping(
uint48 eraIndex => mapping(address middleware => mapping(address operator => uint256 rewardAmount))
) operatorRewardsPerIndexPerMiddlewarePerOperator;
mapping(uint48 eraIndex => mapping(address middleware => mapping(address operator => uint256 rewardAmount)))
operatorRewardsPerIndexPerMiddlewarePerOperator;
mapping(uint48 eraIndex => mapping(address middleware => DistributionStatus status))
distributionStatusPerEraIndexPerMiddleware;
mapping(uint48 eraIndex => mapping(address middleware => uint256 rewards)) pointsStoredPerEraIndexPerMiddleware;
Expand Down Expand Up @@ -111,25 +110,52 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
/**
* @inheritdoc ITanssiMetaMiddleware
*/
function registerOperator(
address operator,
bytes32 key
) external onlyKnownMiddleware(msg.sender) {
function registerOperator(address operator, bytes32 key) external onlyKnownMiddleware(msg.sender) {
TanssiMetaMiddlewareStorage storage $ = _getTanssiMetaMiddlewareStorage();

_registerOperator($, operator, key, msg.sender);
}

/**
* @dev Method to migrate operators which already belong to a middleware when metamiddelware is introduced.
* @dev It can be removed once the migration is complete.
* @param operators The addresses of the operators to migrate
* @param keys The keys of the operators to migrate
* @param middleware The middleware to migrate the operators to
*/
function migrateOperators(
address[] calldata operators,
bytes32[] memory keys,
address middleware
) external onlyRole(DEFAULT_ADMIN_ROLE) onlyKnownMiddleware(middleware) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this was going to get called directly by the middleware? Why by the admin? It would make sense to do everything on chain from the middleware and make those onlyRole admin

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By doing this we don't even have to pass the address of th emiddleware, but we just check that the sender is a known middleware

uint256 totalOperators = operators.length;
require(keys.length == totalOperators, TanssiMetaMiddleware__InvalidKeysLength());

TanssiMetaMiddlewareStorage storage $ = _getTanssiMetaMiddlewareStorage();
for (uint256 i; i < totalOperators;) {
_registerOperator($, operators[i], keys[i], middleware);
unchecked {
++i;
}
}
}

$.operatorToMiddleware[operator] = msg.sender;
function _registerOperator(
TanssiMetaMiddlewareStorage storage $,
address operator,
bytes32 key,
address middleware
) private {
$.operatorToMiddleware[operator] = middleware;
_setOperatorKey(operator, key);

emit OperatorRegistered(operator, msg.sender);
emit OperatorRegistered(operator, middleware);
}

/**
* @inheritdoc ITanssiMetaMiddleware
*/
function updateOperatorKey(
address operator,
bytes32 newKey
) external onlyKnownMiddleware(msg.sender) {
function updateOperatorKey(address operator, bytes32 newKey) external onlyKnownMiddleware(msg.sender) {
TanssiMetaMiddlewareStorage storage $ = _getTanssiMetaMiddlewareStorage();
require($.operatorToMiddleware[operator] == msg.sender, TanssiMetaMiddleware__UnexpectedMiddleware());

Expand All @@ -143,13 +169,17 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
operator = $.keyToOperator[key];
}

function operatorToMiddleware(
address operator
) external view returns (address middleware) {
TanssiMetaMiddlewareStorage storage $ = _getTanssiMetaMiddlewareStorage();
middleware = $.operatorToMiddleware[operator];
}

/**
* @inheritdoc ITanssiMetaMiddleware
*/
function registerCollateral(
address collateral,
address oracle
) external onlyRole(DEFAULT_ADMIN_ROLE) {
function registerCollateral(address collateral, address oracle) external onlyRole(DEFAULT_ADMIN_ROLE) {
TanssiMetaMiddlewareStorage storage $ = _getTanssiMetaMiddlewareStorage();
require($.collateralToOracle[collateral] == address(0), TanssiMetaMiddleware__CollateralAlreadyRegistered());

Expand Down Expand Up @@ -296,10 +326,7 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
pointsStored = $r.pointsStoredPerEraIndexPerMiddleware[eraIndex][middleware];
}

function storeRewards(
uint48 eraIndex,
OperatorRewardWithProof[] memory operatorRewardsAndProofs
) external {
function storeRewards(uint48 eraIndex, OperatorRewardWithProof[] memory operatorRewardsAndProofs) external {
TanssiMetaMiddlewareRewardsStorage storage $r = _getTanssiMetaMiddlewareRewardsStorage();
EraRoot memory eraRoot = _loadAndVerifyEraRoot($r, eraIndex);

Expand Down Expand Up @@ -332,8 +359,9 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
}
}

rewardsDistributionData = ITanssiCommonMiddleware(middleware)
.prepareRewardsDistributionDataFromOperatorRewards(eraIndex, eraRoot.tokenAddress, operatorRewards);
rewardsDistributionData = ITanssiCommonMiddleware(middleware).prepareRewardsDistributionDataFromOperatorRewards(
eraIndex, eraRoot.tokenAddress, operatorRewards
);
}

function distributeRewardsToMiddlewareManually(
Expand Down Expand Up @@ -384,11 +412,7 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
/**
* @inheritdoc ITanssiMetaMiddleware
*/
function slash(
uint48 epoch,
bytes32 operatorKey,
uint256 percentage
) external onlyRole(GATEWAY_ROLE) {
function slash(uint48 epoch, bytes32 operatorKey, uint256 percentage) external onlyRole(GATEWAY_ROLE) {
TanssiMetaMiddlewareStorage storage $ = _getTanssiMetaMiddlewareStorage();
address operator = $.keyToOperator[operatorKey];
address middleware = $.operatorToMiddleware[operator];
Expand All @@ -406,8 +430,9 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
) internal returns (bool distributionComplete) {
TanssiMetaMiddlewareRewardsStorage storage $r = _getTanssiMetaMiddlewareRewardsStorage();

distributionComplete = ITanssiCommonMiddleware(middleware)
.distributeRewards(eraIndex, eraRoot.tokenAddress, rewardsDistributionData);
distributionComplete = ITanssiCommonMiddleware(middleware).distributeRewards(
eraIndex, eraRoot.tokenAddress, rewardsDistributionData
);

if (distributionComplete) {
$r.distributionStatusPerEraIndexPerMiddleware[eraIndex][middleware] = DistributionStatus.DISTRIBUTED;
Expand Down Expand Up @@ -466,7 +491,7 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
address middleware = $.operatorToMiddleware[operatorReward.operator];

$r.operatorRewardsPerIndexPerMiddlewarePerOperator[eraIndex][middleware][operatorReward.operator] =
operatorReward.rewardAmount;
operatorReward.rewardAmount;
$r.pointsStoredPerEraIndexPerMiddleware[eraIndex][middleware] += totalPoints;
}

Expand Down Expand Up @@ -518,8 +543,9 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
eraRoot.totalAmount.mulDiv(totalPointsForEraAndMiddleware, eraRoot.totalPoints);

IERC20(eraRoot.tokenAddress).approve(middleware, totalRewardsForEraAndMiddleware);
ITanssiCommonMiddleware(middleware)
.transferRewards(eraIndex, eraRoot.tokenAddress, totalRewardsForEraAndMiddleware);
ITanssiCommonMiddleware(middleware).transferRewards(
eraIndex, eraRoot.tokenAddress, totalRewardsForEraAndMiddleware
);

unchecked {
$r.totalRewardsTransferred += totalRewardsForEraAndMiddleware;
Expand All @@ -533,10 +559,7 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
}
}

function _updateLastDistributedEraIndex(
TanssiMetaMiddlewareRewardsStorage storage $r,
uint48 eraIndex
) internal {
function _updateLastDistributedEraIndex(TanssiMetaMiddlewareRewardsStorage storage $r, uint48 eraIndex) internal {
TanssiMetaMiddlewareStorage storage $ = _getTanssiMetaMiddlewareStorage();
address[] memory middlewares = $.middlewares;
bool allDistributed = true;
Expand All @@ -555,10 +578,7 @@ contract TanssiMetaMiddleware is AccessControlUpgradeable, UUPSUpgradeable, ITan
}
}

function _setOperatorKey(
address operator,
bytes32 newKey
) internal {
function _setOperatorKey(address operator, bytes32 newKey) internal {
TanssiMetaMiddlewareStorage storage $ = _getTanssiMetaMiddlewareStorage();
require(!$.usedKeys[newKey], TanssiMetaMiddleware__KeyAlreadyUsed());

Expand Down
20 changes: 10 additions & 10 deletions src/interfaces/ITanssiCommonMiddleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ interface ITanssiCommonMiddleware {
bytes memory rewardsDistributionData
) external returns (bool distributionComplete);

function slash(
uint48 epoch,
address operator,
uint256 percentage
) external;
/**
* @notice Slashes an operator's stake
* @dev Only the gateway can call this function
* @dev This function slashes the operator's stake for the target epoch
* @param epoch The epoch number
* @param operator The address of the operator to slash
* @param percentage Percentage to slash, represented as parts per billion.
*/
function slash(uint48 epoch, address operator, uint256 percentage) external;

function activeOperatorsAtEpoch(
uint48 epoch
) external view returns (address[] memory);

function transferRewards(
uint48 eraIndex,
address tokenAddress,
uint256 totalRewards
) external;
function transferRewards(uint48 eraIndex, address tokenAddress, uint256 totalRewards) external;
}
24 changes: 6 additions & 18 deletions src/interfaces/ITanssiMetaMiddleware.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ interface ITanssiMetaMiddleware {
error TanssiMetaMiddleware__CouldNotDistributeRewardsInASingleCall();
error TanssiMetaMiddleware__EraRootNotSet();
error TanssiMetaMiddleware__InsufficientRewardsReceived();
error TanssiMetaMiddleware__InvalidKeysLength();
error TanssiMetaMiddleware__InvalidProof();
error TanssiMetaMiddleware__KeyAlreadyUsed();
error TanssiMetaMiddleware__MiddlewareAlreadyRegistered();
error TanssiMetaMiddleware__UnexpectedEraIndex();
error TanssiMetaMiddleware__UnexpectedDistributionStatus();
error TanssiMetaMiddleware__UnexpectedEraIndex();
error TanssiMetaMiddleware__UnexpectedMiddleware();
error TanssiMetaMiddleware__UnknownMiddleware();

Expand All @@ -96,20 +97,11 @@ interface ITanssiMetaMiddleware {
address middleware
) external;

function registerCollateral(
address collateral,
address oracle
) external;
function registerCollateral(address collateral, address oracle) external;

function registerOperator(
address operator,
bytes32 key
) external;
function registerOperator(address operator, bytes32 key) external;

function updateOperatorKey(
address operator,
bytes32 newKey
) external;
function updateOperatorKey(address operator, bytes32 newKey) external;

/**
* @notice Distribute rewards for a specific era contained in an epoch by providing a Merkle root, total points, total amount of tokens and the token address of the rewards.
Expand Down Expand Up @@ -139,11 +131,7 @@ interface ITanssiMetaMiddleware {
* @param operatorKey The operator key to slash
* @param percentage Percentage to slash, represented as parts per billion.
*/
function slash(
uint48 epoch,
bytes32 operatorKey,
uint256 percentage
) external;
function slash(uint48 epoch, bytes32 operatorKey, uint256 percentage) external;

function isValidCollateral(
address collateral
Expand Down
78 changes: 66 additions & 12 deletions test/unit/TanssiMetaMiddleware.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,8 @@ contract TanssiMetaMiddlewareTest is Utils {
console2.log("Max proof length for", o, "operators:", proofLength);

bytes32[] memory proof = new bytes32[](proofLength);
ITanssiMetaMiddleware.OperatorRewardWithProof memory mockRewardsWithProof =
ITanssiMetaMiddleware.OperatorRewardWithProof({
operatorKey: OPERATOR1_KEY, totalPoints: 1, proof: proof
});
ITanssiMetaMiddleware.OperatorRewardWithProof memory mockRewardsWithProof = ITanssiMetaMiddleware
.OperatorRewardWithProof({operatorKey: OPERATOR1_KEY, totalPoints: 1, proof: proof});

for (uint256 i = 1; i <= 40; i += 1) {
ITanssiMetaMiddleware.OperatorRewardWithProof[] memory operatorRewardsAndProofs =
Expand Down Expand Up @@ -503,7 +501,8 @@ contract TanssiMetaMiddlewareTest is Utils {
uint48 eraIndex = 1;
(
ITanssiMetaMiddleware.OperatorRewardWithProof[] memory operatorRewardsAndProofs,
address middleware,,
address middleware,
,
uint256 totalPoints
) = _prepare50OperatorsWithRewardsAndProofs(eraIndex);
uint256 removedPoints = operatorRewardsAndProofs[0].totalPoints;
Expand All @@ -521,7 +520,8 @@ contract TanssiMetaMiddlewareTest is Utils {
uint48 eraIndex = 1;
(
ITanssiMetaMiddleware.OperatorRewardWithProof[] memory operatorRewardsAndProofs,
address middleware,,
address middleware,
,
uint256 totalPoints
) = _prepare50OperatorsWithRewardsAndProofs(eraIndex);
operatorRewardsAndProofs[0].proof = new bytes32[](0);
Expand Down Expand Up @@ -559,7 +559,8 @@ contract TanssiMetaMiddlewareTest is Utils {
(
uint48 eraIndex,
address middleware,
ITanssiMetaMiddleware.OperatorRewardWithProof[] memory operatorRewardsAndProofs,,
ITanssiMetaMiddleware.OperatorRewardWithProof[] memory operatorRewardsAndProofs,
,
) = _distributeRewardsTrustingly();

vm.expectRevert(
Expand Down Expand Up @@ -635,8 +636,9 @@ contract TanssiMetaMiddlewareTest is Utils {
// We add 30 more operators so they don't all fit in the first batch, even though they will have no rewards, they must be iterated over by the middleware
for (uint256 i = 50; i < 80;) {
bytes32 operatorKey = bytes32(uint256(i + 1));
TanssiMiddlewareMock(middleware)
.registerOperator(makeAddr(string.concat("operator", vm.toString(i + 1))), operatorKey);
TanssiMiddlewareMock(middleware).registerOperator(
makeAddr(string.concat("operator", vm.toString(i + 1))), operatorKey
);
unchecked {
++i;
}
Expand Down Expand Up @@ -756,9 +758,8 @@ contract TanssiMetaMiddlewareTest is Utils {
uint48 eraIndex
) private view returns (ITanssiMetaMiddleware.OperatorRewardWithProof memory operatorRewardAndProof) {
(,, bytes32[] memory proof, uint32 points,) = _loadRewardsRootAndProof(eraIndex, operator);
operatorRewardAndProof = ITanssiMetaMiddleware.OperatorRewardWithProof({
operatorKey: operatorKey, totalPoints: points, proof: proof
});
operatorRewardAndProof =
ITanssiMetaMiddleware.OperatorRewardWithProof({operatorKey: operatorKey, totalPoints: points, proof: proof});
}

function _getExpectedRewardsAmounts(
Expand All @@ -784,6 +785,59 @@ contract TanssiMetaMiddlewareTest is Utils {
loadRewardsRootAndProof(eraIndex, uint8(operator), "/test/unit/rewards_data.json");
}

// ---------------------- Migration ----------------------

function testCanMigrateOperators() public {
address[] memory operators = new address[](2);
operators[0] = operator1;
operators[1] = operator2;
bytes32[] memory keys = new bytes32[](2);
keys[0] = OPERATOR1_KEY;
keys[1] = OPERATOR2_KEY;

address middleware = makeAddr("middleware");
vm.startPrank(admin);
tanssiMetaMiddleware.registerMiddleware(middleware);

tanssiMetaMiddleware.migrateOperators(operators, keys, middleware);
vm.stopPrank();

assertEq(tanssiMetaMiddleware.keyToOperator(OPERATOR1_KEY), operator1);
assertEq(tanssiMetaMiddleware.keyToOperator(OPERATOR2_KEY), operator2);
assertEq(tanssiMetaMiddleware.operatorToMiddleware(operator1), middleware);
assertEq(tanssiMetaMiddleware.operatorToMiddleware(operator2), middleware);
}

function testCannotMigrateOperatorsToAnUnknownMiddleware() public {
address[] memory operators = new address[](2);
operators[0] = operator1;
operators[1] = operator2;
bytes32[] memory keys = new bytes32[](2);
keys[0] = OPERATOR1_KEY;
keys[1] = OPERATOR2_KEY;

address unknownMiddleware = makeAddr("unknownMiddleware");
vm.expectRevert(abi.encodeWithSelector(ITanssiMetaMiddleware.TanssiMetaMiddleware__UnknownMiddleware.selector));
vm.prank(admin);
tanssiMetaMiddleware.migrateOperators(operators, keys, unknownMiddleware);
}

function testCannotMigrateOperatorsIfKeysLengthIsNotEqualToOperatorsLength() public {
address[] memory operators = new address[](2);
operators[0] = operator1;
operators[1] = operator2;
bytes32[] memory keys = new bytes32[](1);
keys[0] = OPERATOR1_KEY;

address middleware = makeAddr("middleware");
vm.startPrank(admin);
tanssiMetaMiddleware.registerMiddleware(middleware);

vm.expectRevert(abi.encodeWithSelector(ITanssiMetaMiddleware.TanssiMetaMiddleware__InvalidKeysLength.selector));
tanssiMetaMiddleware.migrateOperators(operators, keys, middleware);
vm.stopPrank();
}

// ---------------------- Upgrading ----------------------

function testCanUpgradeIfAdmin() public {
Expand Down
Loading