Skip to content
Merged
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
45 changes: 45 additions & 0 deletions src/middleware/examples/ECDSAVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.29;

import {
OwnableBasedApp
} from "@ssv/src/middleware/modules/core+roles/OwnableBasedApp.sol";

import {
SignatureChecker
} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";

contract ECDSAVerifier is OwnableBasedApp {
mapping(address => bool) public hasOptedIn;

error InvalidSignature();
error SignerAlreadyOptedIn();

constructor(
address _basedAppManager,
address _initOwner
) OwnableBasedApp(_basedAppManager, _initOwner) {}

function optInToBApp(
uint32,
address[] calldata,
uint32[] calldata,
bytes calldata data
) external override onlySSVBasedAppManager returns (bool success) {
(address signer, bytes32 messageHash, bytes memory signature) = abi
.decode(data, (address, bytes32, bytes));

if (hasOptedIn[signer]) {
revert SignerAlreadyOptedIn();
}

success = SignatureChecker.isValidSignatureNow(
signer,
messageHash,
signature
);

if (success) hasOptedIn[signer] = true;
else revert InvalidSignature();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
AccessControl
} from "@openzeppelin/contracts/access/AccessControl.sol";

import { IBasedApp } from "@ssv/src/middleware/interfaces/IBasedApp.sol";
import {
BasedAppCore
} from "@ssv/src/middleware/modules/core/BasedAppCore.sol";
Expand Down
1 change: 0 additions & 1 deletion src/middleware/modules/core+roles/OwnableBasedApp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma solidity 0.8.29;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { IBasedApp } from "@ssv/src/middleware/interfaces/IBasedApp.sol";
import {
BasedAppCore
} from "@ssv/src/middleware/modules/core/BasedAppCore.sol";
Expand Down
7 changes: 7 additions & 0 deletions test/helpers/Setup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
import {
WhitelistExample
} from "@ssv/src/middleware/examples/WhitelistExample.sol";
import { ECDSAVerifier } from "@ssv/src/middleware/examples/ECDSAVerifier.sol";
import { IBasedApp } from "@ssv/src/middleware/interfaces/IBasedApp.sol";

contract Setup is Test {
Expand All @@ -53,6 +54,7 @@ contract Setup is Test {
BasedAppMock4 public bApp4;
NonCompliantBApp public nonCompliantBApp;
WhitelistExample public whitelistExample;
ECDSAVerifier public ecdsaVerifierExample;
// Tokens
IERC20 public erc20mock;
IERC20 public erc20mock2;
Expand Down Expand Up @@ -157,6 +159,10 @@ contract Setup is Test {

nonCompliantBApp = new NonCompliantBApp(address(proxiedManager));
whitelistExample = new WhitelistExample(address(proxiedManager), USER1);
ecdsaVerifierExample = new ECDSAVerifier(
address(proxiedManager),
USER1
);

bApps.push(bApp1);
bApps.push(bApp2);
Expand All @@ -170,6 +176,7 @@ contract Setup is Test {
vm.label(address(bApp4), "BasedApp4");
vm.label(address(nonCompliantBApp), "NonCompliantBApp");
vm.label(address(whitelistExample), "WhitelistExample");
vm.label(address(ecdsaVerifierExample), "ECDSAVerifierExample");
vm.label(address(proxiedManager), "BasedAppManagerProxy");

vm.deal(USER1, INITIAL_USER1_BALANCE_ETH);
Expand Down
144 changes: 144 additions & 0 deletions test/middleware/examples/ECDSAVerifier/ECDSAVerifier.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.29;

import { IBasedApp } from "@ssv/test/helpers/Setup.t.sol";
import { UtilsTest } from "@ssv/test/helpers/Utils.t.sol";
import { ICore } from "@ssv/src/core/interfaces/ICore.sol";
import { ECDSAVerifier } from "@ssv/src/middleware/examples/ECDSAVerifier.sol";

contract WhitelistExampleTest is UtilsTest {
function testCreateStrategies() public {
vm.startPrank(USER1);
erc20mock.approve(address(proxiedManager), INITIAL_USER1_BALANCE_ERC20);
erc20mock2.approve(
address(proxiedManager),
INITIAL_USER1_BALANCE_ERC20
);
uint32 strategyId1 = proxiedManager.createStrategy(
STRATEGY1_INITIAL_FEE,
""
);
assertEq(strategyId1, STRATEGY1, "Should set the correct strategy ID");
(address owner, uint32 delegationFeeOnRewards) = proxiedManager
.strategies(strategyId1);
assertEq(owner, USER1, "Should set the correct strategy owner");
assertEq(
delegationFeeOnRewards,
STRATEGY1_INITIAL_FEE,
"Should set the correct strategy fee"
);
vm.stopPrank();
vm.prank(USER2);
uint32 strategyId2 = proxiedManager.createStrategy(
STRATEGY1_INITIAL_FEE,
""
);
assertEq(strategyId2, STRATEGY2, "Should set the correct strategy ID");
}

function testRegisterECDSAVerifierExampleBApp() public {
vm.startPrank(USER1);
ICore.TokenConfig[] memory tokenConfigsInput = createSingleTokenConfig(
address(erc20mock),
102
);
ecdsaVerifierExample.registerBApp(tokenConfigsInput, "");
checkBAppInfo(
tokenConfigsInput,
address(ecdsaVerifierExample),
proxiedManager
);
vm.stopPrank();
}

function testRevertOptInToBAppWithUnauthorizedCaller() public {
vm.prank(USER1);
(
address[] memory tokensInput,
uint32[] memory riskLevelInput
) = createSingleTokenAndSingleRiskLevel(address(erc20mock), 10_000);
vm.expectRevert(
abi.encodeWithSelector(IBasedApp.UnauthorizedCaller.selector)
);
ecdsaVerifierExample.optInToBApp(
STRATEGY1,
tokensInput,
riskLevelInput,
""
);
}

function testOptInToBApp() public {
testCreateStrategies();
testRegisterECDSAVerifierExampleBApp();
(
address[] memory tokensInput,
uint32[] memory riskLevelInput
) = createSingleTokenAndSingleRiskLevel(address(erc20mock), 10_000);
address signer = 0x763569566a3CE4f8D73f96c4996aBdf297f74ADE;
bytes32 messageHash = 0x5b001f2ad81fe86899545b51f8ecd1ca08674437d5c4748e1b70ba5dcf85ed86;
bytes
memory signature = hex"ddc30e871857b9a4d2dce47f49aec426404e69c98e05abc345df2f096e47fcb33d3e061da2435584d92df8731522d35ae485447311eb4a3c0bfdb09150cbad081b";

bytes memory data = abi.encode(signer, messageHash, signature);
vm.prank(USER1);
proxiedManager.optInToBApp(
STRATEGY1,
address(ecdsaVerifierExample),
tokensInput,
riskLevelInput,
data
);
}

function testRevertOptInToBAppWithInvalidSignature() public {
testCreateStrategies();
testRegisterECDSAVerifierExampleBApp();
(
address[] memory tokensInput,
uint32[] memory riskLevelInput
) = createSingleTokenAndSingleRiskLevel(address(erc20mock), 10_000);
address signer = 0x763569566a3CE4f8D73f96c4996aBdf297f74ADE;
bytes32 messageHash = 0x5b001f2ad81fe86899545b51f8ecd1ca08674437d5c4748e1b70ba5dcf85ed86;
bytes
memory signature = hex"ddc30e871857b9a4d2dce47f49aec426404e69c98e05abc345df2f096e47fcb33d3e061da2435584d92df8731522d35ae485447311eb4a3c0bfdb09150cbad081c";

bytes memory data = abi.encode(signer, messageHash, signature);
vm.prank(USER1);
vm.expectRevert(
abi.encodeWithSelector(ECDSAVerifier.InvalidSignature.selector)
);
proxiedManager.optInToBApp(
STRATEGY1,
address(ecdsaVerifierExample),
tokensInput,
riskLevelInput,
data
);
}

function testRevertReplayAttackOnAnotherStrategy() public {
testOptInToBApp();
(
address[] memory tokensInput,
uint32[] memory riskLevelInput
) = createSingleTokenAndSingleRiskLevel(address(erc20mock), 10_000);
address signer = 0x763569566a3CE4f8D73f96c4996aBdf297f74ADE;
bytes32 messageHash = 0x5b001f2ad81fe86899545b51f8ecd1ca08674437d5c4748e1b70ba5dcf85ed86;
bytes
memory signature = hex"ddc30e871857b9a4d2dce47f49aec426404e69c98e05abc345df2f096e47fcb33d3e061da2435584d92df8731522d35ae485447311eb4a3c0bfdb09150cbad081b";

bytes memory data = abi.encode(signer, messageHash, signature);
vm.prank(USER2);
vm.expectRevert(
abi.encodeWithSelector(ECDSAVerifier.SignerAlreadyOptedIn.selector)
);
proxiedManager.optInToBApp(
STRATEGY2,
address(ecdsaVerifierExample),
tokensInput,
riskLevelInput,
data
);
}
}
122 changes: 122 additions & 0 deletions test/middleware/examples/ECDSAVerifier/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions test/middleware/examples/ECDSAVerifier/client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "ecdsa-verifier-client",
"version": "1.0.0",
"main": "signature.js",
"type": "module",
"author": "",
"license": "UNLICENSED",
"description": "client for generating a signed message with ECDSA",
"dependencies": {
"ethers": "^6.14.3"
},
"scripts": {
"start": "node signature.js"
}
}
Loading