Skip to content

Commit 0adc11f

Browse files
committed
feat: improved coverage and first draft of transactionManager.sol
1 parent cd4b1d7 commit 0adc11f

File tree

2 files changed

+829
-1
lines changed

2 files changed

+829
-1
lines changed

src/TransactionManager.sol

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "./ValidatorFactory.sol";
5+
6+
/**
7+
* @title TransactionManager
8+
* @dev Optimistic consensus system with LLM validation and validator signatures
9+
* Features:
10+
* - Transaction proposal with optimistic execution
11+
* - Mock LLM validation (deterministic hash-based)
12+
* - ECDSA signature verification with 3/5 consensus
13+
* - Challenge period for disputes
14+
*/
15+
contract TransactionManager {
16+
// Events
17+
event ProposalSubmitted(bytes32 indexed proposalId, string transaction, address indexed submitter);
18+
event ProposalOptimisticallyApproved(bytes32 indexed proposalId);
19+
event ProposalChallenged(bytes32 indexed proposalId, address indexed challenger);
20+
event ProposalFinalized(bytes32 indexed proposalId, bool approved);
21+
event ValidatorSigned(bytes32 indexed proposalId, address indexed validator);
22+
event LLMValidationResult(bytes32 indexed proposalId, bool isValid);
23+
24+
// State variables
25+
ValidatorFactory public validatorFactory;
26+
27+
mapping(bytes32 => Proposal) public proposals;
28+
mapping(bytes32 => mapping(address => bool)) public hasValidatorSigned;
29+
mapping(bytes32 => address[]) public proposalSigners;
30+
uint256 public proposalCount;
31+
32+
// Constants
33+
uint256 public constant CHALLENGE_PERIOD = 50; // blocks
34+
uint256 public constant REQUIRED_SIGNATURES = 3; // 3 out of 5 validators
35+
uint256 public constant VALIDATOR_SET_SIZE = 5; // top 5 validators for consensus
36+
37+
// Proposal states
38+
enum ProposalState {
39+
Proposed, // Just submitted
40+
OptimisticApproved, // Enough signatures, optimistically approved
41+
Challenged, // Someone challenged the proposal
42+
Finalized, // Final decision made
43+
Reverted // Proposal was invalid/rejected
44+
}
45+
46+
// Structs
47+
struct Proposal {
48+
bytes32 proposalId;
49+
string transaction;
50+
address proposer;
51+
uint256 blockNumber;
52+
uint256 challengeDeadline;
53+
ProposalState state;
54+
address challenger;
55+
uint256 signatureCount;
56+
bool llmValidation;
57+
bool executed;
58+
address[] selectedValidators; // validators selected for this proposal
59+
}
60+
61+
constructor(address _validatorFactory) {
62+
validatorFactory = ValidatorFactory(_validatorFactory);
63+
}
64+
65+
/**
66+
* @dev Submit a proposal for consensus
67+
* @param transaction Transaction string to be validated (e.g., "Approve loan for user X based on LLM analysis")
68+
* @return proposalId Unique identifier for the proposal
69+
*/
70+
function submitProposal(string calldata transaction) external returns (bytes32 proposalId) {
71+
require(bytes(transaction).length > 0, "Empty transaction");
72+
73+
proposalId = keccak256(abi.encodePacked(transaction, block.timestamp, msg.sender));
74+
require(proposals[proposalId].proposalId == bytes32(0), "Proposal already exists");
75+
76+
// Get top validators for this proposal
77+
address[] memory topValidators = _getTopValidators();
78+
require(topValidators.length >= REQUIRED_SIGNATURES, "Not enough validators");
79+
80+
// Perform mock LLM validation
81+
bool llmResult = _mockLLMValidation(transaction);
82+
83+
proposals[proposalId] = Proposal({
84+
proposalId: proposalId,
85+
transaction: transaction,
86+
proposer: msg.sender,
87+
blockNumber: block.number,
88+
state: ProposalState.Proposed,
89+
challengeDeadline: block.number + CHALLENGE_PERIOD,
90+
challenger: address(0),
91+
signatureCount: 0,
92+
llmValidation: llmResult,
93+
executed: false,
94+
selectedValidators: topValidators
95+
});
96+
97+
proposalCount++;
98+
99+
emit ProposalSubmitted(proposalId, transaction, msg.sender);
100+
emit LLMValidationResult(proposalId, llmResult);
101+
102+
return proposalId;
103+
}
104+
105+
/**
106+
* @dev Validators sign a proposal using ECDSA signatures
107+
* @param proposalId Proposal identifier
108+
* @param signature ECDSA signature of the proposal hash
109+
*/
110+
function signProposal(bytes32 proposalId, bytes calldata signature) external {
111+
Proposal storage proposal = proposals[proposalId];
112+
require(proposal.proposalId != bytes32(0), "Proposal not found");
113+
require(proposal.state == ProposalState.Proposed, "Invalid proposal state");
114+
require(block.number <= proposal.challengeDeadline, "Challenge period expired");
115+
require(!hasValidatorSigned[proposalId][msg.sender], "Already signed");
116+
117+
// Verify that sender is one of the selected validators for this proposal
118+
require(_isSelectedValidator(proposalId, msg.sender), "Not a selected validator");
119+
120+
// Verify the signature
121+
bytes32 messageHash = _getProposalHash(proposalId, proposal.transaction);
122+
address recoveredSigner = _recoverSigner(messageHash, signature);
123+
require(recoveredSigner == msg.sender, "Invalid signature");
124+
125+
// Record the signature
126+
hasValidatorSigned[proposalId][msg.sender] = true;
127+
proposalSigners[proposalId].push(msg.sender);
128+
proposal.signatureCount++;
129+
130+
emit ValidatorSigned(proposalId, msg.sender);
131+
132+
// Check if we have enough signatures for optimistic approval
133+
if (proposal.signatureCount >= REQUIRED_SIGNATURES && proposal.llmValidation) {
134+
proposal.state = ProposalState.OptimisticApproved;
135+
proposal.executed = true;
136+
emit ProposalOptimisticallyApproved(proposalId);
137+
}
138+
}
139+
140+
/**
141+
* @dev Challenge a proposal during the challenge period
142+
* @param proposalId Proposal identifier
143+
*/
144+
function challengeProposal(bytes32 proposalId) external {
145+
Proposal storage proposal = proposals[proposalId];
146+
require(proposal.proposalId != bytes32(0), "Proposal not found");
147+
require(proposal.state == ProposalState.OptimisticApproved, "Cannot challenge");
148+
require(block.number <= proposal.challengeDeadline, "Challenge period expired");
149+
require(validatorFactory.isActiveValidator(msg.sender), "Not a validator");
150+
151+
proposal.state = ProposalState.Challenged;
152+
proposal.challenger = msg.sender;
153+
proposal.executed = false; // Revert optimistic execution
154+
155+
emit ProposalChallenged(proposalId, msg.sender);
156+
}
157+
158+
/**
159+
* @dev Finalize a proposal after challenge period or after challenge resolution
160+
* @param proposalId Proposal identifier
161+
*/
162+
function finalizeProposal(bytes32 proposalId) external {
163+
Proposal storage proposal = proposals[proposalId];
164+
require(proposal.proposalId != bytes32(0), "Proposal not found");
165+
require(block.number > proposal.challengeDeadline, "Challenge period not ended");
166+
167+
bool approved = false;
168+
169+
if (proposal.state == ProposalState.OptimisticApproved) {
170+
// No challenge during period - approve
171+
approved = true;
172+
proposal.executed = true;
173+
} else if (proposal.state == ProposalState.Challenged) {
174+
// Resolve challenge based on signatures and LLM validation
175+
approved = proposal.signatureCount >= REQUIRED_SIGNATURES && proposal.llmValidation;
176+
proposal.executed = approved;
177+
}
178+
179+
proposal.state = approved ? ProposalState.Finalized : ProposalState.Reverted;
180+
181+
emit ProposalFinalized(proposalId, approved);
182+
}
183+
184+
/**
185+
* @dev Mock LLM validation using deterministic function
186+
* @param transaction Transaction string to validate
187+
* @return isValid Whether the transaction is valid (based on hash even/odd)
188+
*/
189+
function _mockLLMValidation(string memory transaction) internal pure returns (bool isValid) {
190+
bytes32 hash = keccak256(abi.encodePacked(transaction));
191+
// Return true if hash is even (last bit is 0)
192+
return (uint256(hash) % 2 == 0);
193+
}
194+
195+
/**
196+
* @dev Get top validators for consensus
197+
* @return validators Array of top validator addresses
198+
*/
199+
function _getTopValidators() internal view returns (address[] memory) {
200+
uint256 validatorCount = validatorFactory.getValidatorCount();
201+
if (validatorCount == 0) {
202+
return new address[](0);
203+
}
204+
205+
uint256 count = validatorCount < VALIDATOR_SET_SIZE ? validatorCount : VALIDATOR_SET_SIZE;
206+
(address[] memory topValidators,) = validatorFactory.getTopNValidators(count);
207+
return topValidators;
208+
}
209+
210+
/**
211+
* @dev Check if address is a selected validator for the proposal
212+
* @param proposalId Proposal identifier
213+
* @param validator Validator address
214+
* @return isSelected Whether the validator was selected for this proposal
215+
*/
216+
function _isSelectedValidator(bytes32 proposalId, address validator) internal view returns (bool isSelected) {
217+
address[] memory selectedValidators = proposals[proposalId].selectedValidators;
218+
for (uint256 i = 0; i < selectedValidators.length; i++) {
219+
if (selectedValidators[i] == validator) {
220+
return true;
221+
}
222+
}
223+
return false;
224+
}
225+
226+
/**
227+
* @dev Create proposal hash for signature verification
228+
* @param proposalId Proposal identifier
229+
* @param transaction Transaction string
230+
* @return hash Message hash for signing
231+
*/
232+
function _getProposalHash(bytes32 proposalId, string memory transaction) internal pure returns (bytes32) {
233+
return keccak256(abi.encodePacked(
234+
"\x19Ethereum Signed Message:\n32",
235+
keccak256(abi.encodePacked(proposalId, transaction))
236+
));
237+
}
238+
239+
/**
240+
* @dev Recover signer from signature
241+
* @param messageHash Message hash
242+
* @param signature ECDSA signature
243+
* @return signer Recovered signer address
244+
*/
245+
function _recoverSigner(bytes32 messageHash, bytes memory signature) internal pure returns (address) {
246+
require(signature.length == 65, "Invalid signature length");
247+
248+
bytes32 r;
249+
bytes32 s;
250+
uint8 v;
251+
252+
assembly {
253+
r := mload(add(signature, 32))
254+
s := mload(add(signature, 64))
255+
v := byte(0, mload(add(signature, 96)))
256+
}
257+
258+
if (v < 27) {
259+
v += 27;
260+
}
261+
262+
require(v == 27 || v == 28, "Invalid signature v value");
263+
264+
return ecrecover(messageHash, v, r, s);
265+
}
266+
267+
// ==================== VIEW FUNCTIONS ====================
268+
269+
/**
270+
* @dev Get proposal information
271+
* @param proposalId Proposal identifier
272+
* @return transaction Transaction string
273+
* @return proposer Proposal submitter
274+
* @return blockNumber Block number when submitted
275+
* @return state Current proposal state
276+
* @return challengeDeadline Challenge deadline
277+
* @return challenger Address of challenger
278+
* @return signatureCount Number of validator signatures
279+
* @return llmValidation LLM validation result
280+
* @return executed Whether proposal was executed
281+
*/
282+
function getProposal(bytes32 proposalId) external view returns (
283+
string memory transaction,
284+
address proposer,
285+
uint256 blockNumber,
286+
ProposalState state,
287+
uint256 challengeDeadline,
288+
address challenger,
289+
uint256 signatureCount,
290+
bool llmValidation,
291+
bool executed
292+
) {
293+
Proposal storage proposal = proposals[proposalId];
294+
return (
295+
proposal.transaction,
296+
proposal.proposer,
297+
proposal.blockNumber,
298+
proposal.state,
299+
proposal.challengeDeadline,
300+
proposal.challenger,
301+
proposal.signatureCount,
302+
proposal.llmValidation,
303+
proposal.executed
304+
);
305+
}
306+
307+
/**
308+
* @dev Get selected validators for a proposal
309+
* @param proposalId Proposal identifier
310+
* @return validators Array of selected validator addresses
311+
*/
312+
function getProposalValidators(bytes32 proposalId) external view returns (address[] memory) {
313+
return proposals[proposalId].selectedValidators;
314+
}
315+
316+
/**
317+
* @dev Get signers for a proposal
318+
* @param proposalId Proposal identifier
319+
* @return signers Array of addresses that signed the proposal
320+
*/
321+
function getProposalSigners(bytes32 proposalId) external view returns (address[] memory) {
322+
return proposalSigners[proposalId];
323+
}
324+
325+
/**
326+
* @dev Check if proposal is approved and executed
327+
* @param proposalId Proposal identifier
328+
* @return approved Whether the proposal is approved and executed
329+
*/
330+
function isProposalApproved(bytes32 proposalId) external view returns (bool) {
331+
Proposal storage proposal = proposals[proposalId];
332+
return proposal.state == ProposalState.OptimisticApproved ||
333+
(proposal.state == ProposalState.Finalized && proposal.executed);
334+
}
335+
336+
/**
337+
* @dev Test the mock LLM validation function
338+
* @param transaction Transaction string to test
339+
* @return isValid Result of mock LLM validation
340+
*/
341+
function testLLMValidation(string calldata transaction) external pure returns (bool) {
342+
return _mockLLMValidation(transaction);
343+
}
344+
345+
/**
346+
* @dev Get current validator count
347+
* @return count Number of active validators
348+
*/
349+
function getValidatorCount() external view returns (uint256) {
350+
return validatorFactory.getValidatorCount();
351+
}
352+
353+
/**
354+
* @dev Get top validators currently selected for consensus
355+
* @return validators Array of top validator addresses
356+
*/
357+
function getCurrentTopValidators() external view returns (address[] memory) {
358+
return _getTopValidators();
359+
}
360+
}

0 commit comments

Comments
 (0)