Skip to content

Commit 1b5c742

Browse files
Example ECDSA verifier (#72)
1 parent bcc903e commit 1b5c742

File tree

10 files changed

+355
-7
lines changed

10 files changed

+355
-7
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity 0.8.29;
3+
4+
import {
5+
OwnableBasedApp
6+
} from "@ssv/src/middleware/modules/core+roles/OwnableBasedApp.sol";
7+
8+
import {
9+
SignatureChecker
10+
} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
11+
12+
contract ECDSAVerifier is OwnableBasedApp {
13+
mapping(address => bool) public hasOptedIn;
14+
15+
error InvalidSignature();
16+
error SignerAlreadyOptedIn();
17+
18+
constructor(
19+
address _basedAppManager,
20+
address _initOwner
21+
) OwnableBasedApp(_basedAppManager, _initOwner) {}
22+
23+
function optInToBApp(
24+
uint32,
25+
address[] calldata,
26+
uint32[] calldata,
27+
bytes calldata data
28+
) external override onlySSVBasedAppManager returns (bool success) {
29+
(address signer, bytes32 messageHash, bytes memory signature) = abi
30+
.decode(data, (address, bytes32, bytes));
31+
32+
if (hasOptedIn[signer]) {
33+
revert SignerAlreadyOptedIn();
34+
}
35+
36+
success = SignatureChecker.isValidSignatureNow(
37+
signer,
38+
messageHash,
39+
signature
40+
);
41+
42+
if (success) hasOptedIn[signer] = true;
43+
else revert InvalidSignature();
44+
}
45+
}

src/middleware/modules/core+roles/AccessControlBasedApp.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
AccessControl
66
} from "@openzeppelin/contracts/access/AccessControl.sol";
77

8-
import { IBasedApp } from "@ssv/src/middleware/interfaces/IBasedApp.sol";
98
import {
109
BasedAppCore
1110
} from "@ssv/src/middleware/modules/core/BasedAppCore.sol";

src/middleware/modules/core+roles/OwnableBasedApp.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ pragma solidity 0.8.29;
33

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

6-
import { IBasedApp } from "@ssv/src/middleware/interfaces/IBasedApp.sol";
76
import {
87
BasedAppCore
98
} from "@ssv/src/middleware/modules/core/BasedAppCore.sol";

test/helpers/Setup.t.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import {
3434
WhitelistExample
3535
} from "@ssv/src/middleware/examples/WhitelistExample.sol";
36+
import { ECDSAVerifier } from "@ssv/src/middleware/examples/ECDSAVerifier.sol";
3637
import { IBasedApp } from "@ssv/src/middleware/interfaces/IBasedApp.sol";
3738

3839
contract Setup is Test {
@@ -53,6 +54,7 @@ contract Setup is Test {
5354
BasedAppMock4 public bApp4;
5455
NonCompliantBApp public nonCompliantBApp;
5556
WhitelistExample public whitelistExample;
57+
ECDSAVerifier public ecdsaVerifierExample;
5658
// Tokens
5759
IERC20 public erc20mock;
5860
IERC20 public erc20mock2;
@@ -157,6 +159,10 @@ contract Setup is Test {
157159

158160
nonCompliantBApp = new NonCompliantBApp(address(proxiedManager));
159161
whitelistExample = new WhitelistExample(address(proxiedManager), USER1);
162+
ecdsaVerifierExample = new ECDSAVerifier(
163+
address(proxiedManager),
164+
USER1
165+
);
160166

161167
bApps.push(bApp1);
162168
bApps.push(bApp2);
@@ -170,6 +176,7 @@ contract Setup is Test {
170176
vm.label(address(bApp4), "BasedApp4");
171177
vm.label(address(nonCompliantBApp), "NonCompliantBApp");
172178
vm.label(address(whitelistExample), "WhitelistExample");
179+
vm.label(address(ecdsaVerifierExample), "ECDSAVerifierExample");
173180
vm.label(address(proxiedManager), "BasedAppManagerProxy");
174181

175182
vm.deal(USER1, INITIAL_USER1_BALANCE_ETH);
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity 0.8.29;
3+
4+
import { IBasedApp } from "@ssv/test/helpers/Setup.t.sol";
5+
import { UtilsTest } from "@ssv/test/helpers/Utils.t.sol";
6+
import { ICore } from "@ssv/src/core/interfaces/ICore.sol";
7+
import { ECDSAVerifier } from "@ssv/src/middleware/examples/ECDSAVerifier.sol";
8+
9+
contract WhitelistExampleTest is UtilsTest {
10+
function testCreateStrategies() public {
11+
vm.startPrank(USER1);
12+
erc20mock.approve(address(proxiedManager), INITIAL_USER1_BALANCE_ERC20);
13+
erc20mock2.approve(
14+
address(proxiedManager),
15+
INITIAL_USER1_BALANCE_ERC20
16+
);
17+
uint32 strategyId1 = proxiedManager.createStrategy(
18+
STRATEGY1_INITIAL_FEE,
19+
""
20+
);
21+
assertEq(strategyId1, STRATEGY1, "Should set the correct strategy ID");
22+
(address owner, uint32 delegationFeeOnRewards) = proxiedManager
23+
.strategies(strategyId1);
24+
assertEq(owner, USER1, "Should set the correct strategy owner");
25+
assertEq(
26+
delegationFeeOnRewards,
27+
STRATEGY1_INITIAL_FEE,
28+
"Should set the correct strategy fee"
29+
);
30+
vm.stopPrank();
31+
vm.prank(USER2);
32+
uint32 strategyId2 = proxiedManager.createStrategy(
33+
STRATEGY1_INITIAL_FEE,
34+
""
35+
);
36+
assertEq(strategyId2, STRATEGY2, "Should set the correct strategy ID");
37+
}
38+
39+
function testRegisterECDSAVerifierExampleBApp() public {
40+
vm.startPrank(USER1);
41+
ICore.TokenConfig[] memory tokenConfigsInput = createSingleTokenConfig(
42+
address(erc20mock),
43+
102
44+
);
45+
ecdsaVerifierExample.registerBApp(tokenConfigsInput, "");
46+
checkBAppInfo(
47+
tokenConfigsInput,
48+
address(ecdsaVerifierExample),
49+
proxiedManager
50+
);
51+
vm.stopPrank();
52+
}
53+
54+
function testRevertOptInToBAppWithUnauthorizedCaller() public {
55+
vm.prank(USER1);
56+
(
57+
address[] memory tokensInput,
58+
uint32[] memory riskLevelInput
59+
) = createSingleTokenAndSingleRiskLevel(address(erc20mock), 10_000);
60+
vm.expectRevert(
61+
abi.encodeWithSelector(IBasedApp.UnauthorizedCaller.selector)
62+
);
63+
ecdsaVerifierExample.optInToBApp(
64+
STRATEGY1,
65+
tokensInput,
66+
riskLevelInput,
67+
""
68+
);
69+
}
70+
71+
function testOptInToBApp() public {
72+
testCreateStrategies();
73+
testRegisterECDSAVerifierExampleBApp();
74+
(
75+
address[] memory tokensInput,
76+
uint32[] memory riskLevelInput
77+
) = createSingleTokenAndSingleRiskLevel(address(erc20mock), 10_000);
78+
address signer = 0x763569566a3CE4f8D73f96c4996aBdf297f74ADE;
79+
bytes32 messageHash = 0x5b001f2ad81fe86899545b51f8ecd1ca08674437d5c4748e1b70ba5dcf85ed86;
80+
bytes
81+
memory signature = hex"ddc30e871857b9a4d2dce47f49aec426404e69c98e05abc345df2f096e47fcb33d3e061da2435584d92df8731522d35ae485447311eb4a3c0bfdb09150cbad081b";
82+
83+
bytes memory data = abi.encode(signer, messageHash, signature);
84+
vm.prank(USER1);
85+
proxiedManager.optInToBApp(
86+
STRATEGY1,
87+
address(ecdsaVerifierExample),
88+
tokensInput,
89+
riskLevelInput,
90+
data
91+
);
92+
}
93+
94+
function testRevertOptInToBAppWithInvalidSignature() public {
95+
testCreateStrategies();
96+
testRegisterECDSAVerifierExampleBApp();
97+
(
98+
address[] memory tokensInput,
99+
uint32[] memory riskLevelInput
100+
) = createSingleTokenAndSingleRiskLevel(address(erc20mock), 10_000);
101+
address signer = 0x763569566a3CE4f8D73f96c4996aBdf297f74ADE;
102+
bytes32 messageHash = 0x5b001f2ad81fe86899545b51f8ecd1ca08674437d5c4748e1b70ba5dcf85ed86;
103+
bytes
104+
memory signature = hex"ddc30e871857b9a4d2dce47f49aec426404e69c98e05abc345df2f096e47fcb33d3e061da2435584d92df8731522d35ae485447311eb4a3c0bfdb09150cbad081c";
105+
106+
bytes memory data = abi.encode(signer, messageHash, signature);
107+
vm.prank(USER1);
108+
vm.expectRevert(
109+
abi.encodeWithSelector(ECDSAVerifier.InvalidSignature.selector)
110+
);
111+
proxiedManager.optInToBApp(
112+
STRATEGY1,
113+
address(ecdsaVerifierExample),
114+
tokensInput,
115+
riskLevelInput,
116+
data
117+
);
118+
}
119+
120+
function testRevertReplayAttackOnAnotherStrategy() public {
121+
testOptInToBApp();
122+
(
123+
address[] memory tokensInput,
124+
uint32[] memory riskLevelInput
125+
) = createSingleTokenAndSingleRiskLevel(address(erc20mock), 10_000);
126+
address signer = 0x763569566a3CE4f8D73f96c4996aBdf297f74ADE;
127+
bytes32 messageHash = 0x5b001f2ad81fe86899545b51f8ecd1ca08674437d5c4748e1b70ba5dcf85ed86;
128+
bytes
129+
memory signature = hex"ddc30e871857b9a4d2dce47f49aec426404e69c98e05abc345df2f096e47fcb33d3e061da2435584d92df8731522d35ae485447311eb4a3c0bfdb09150cbad081b";
130+
131+
bytes memory data = abi.encode(signer, messageHash, signature);
132+
vm.prank(USER2);
133+
vm.expectRevert(
134+
abi.encodeWithSelector(ECDSAVerifier.SignerAlreadyOptedIn.selector)
135+
);
136+
proxiedManager.optInToBApp(
137+
STRATEGY2,
138+
address(ecdsaVerifierExample),
139+
tokensInput,
140+
riskLevelInput,
141+
data
142+
);
143+
}
144+
}

test/middleware/examples/ECDSAVerifier/client/package-lock.json

Lines changed: 122 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "ecdsa-verifier-client",
3+
"version": "1.0.0",
4+
"main": "signature.js",
5+
"type": "module",
6+
"author": "",
7+
"license": "UNLICENSED",
8+
"description": "client for generating a signed message with ECDSA",
9+
"dependencies": {
10+
"ethers": "^6.14.3"
11+
},
12+
"scripts": {
13+
"start": "node signature.js"
14+
}
15+
}

0 commit comments

Comments
 (0)