diff --git a/abi/VESTArElection.json b/abi/VESTArElection.json index 0fba8f8..68ed86f 100644 --- a/abi/VESTArElection.json +++ b/abi/VESTArElection.json @@ -298,11 +298,6 @@ "type": "tuple", "internalType": "struct VESTArTypes.ElectionConfig", "components": [ - { - "name": "electionId", - "type": "bytes32", - "internalType": "bytes32" - }, { "name": "seriesId", "type": "bytes32", @@ -542,16 +537,16 @@ "type": "function", "name": "initialize", "inputs": [ + { + "name": "electionId_", + "type": "bytes32", + "internalType": "bytes32" + }, { "name": "config", "type": "tuple", "internalType": "struct VESTArTypes.ElectionConfig", "components": [ - { - "name": "electionId", - "type": "bytes32", - "internalType": "bytes32" - }, { "name": "seriesId", "type": "bytes32", diff --git a/abi/VESTArElectionFactory.json b/abi/VESTArElectionFactory.json index 42c67aa..fb04d34 100644 --- a/abi/VESTArElectionFactory.json +++ b/abi/VESTArElectionFactory.json @@ -30,6 +30,50 @@ ], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "computeElectionId", + "inputs": [ + { + "name": "organizer", + "type": "address", + "internalType": "address" + }, + { + "name": "seriesId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "titleHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "startAt", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "endAt", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "organizerNonce", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "electionId", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "createElection", @@ -39,11 +83,6 @@ "type": "tuple", "internalType": "struct VESTArTypes.ElectionConfig", "components": [ - { - "name": "electionId", - "type": "bytes32", - "internalType": "bytes32" - }, { "name": "seriesId", "type": "bytes32", @@ -239,6 +278,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "nextElectionNonce", + "inputs": [ + { + "name": "organizer", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "organizerRegistry", @@ -324,6 +382,45 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "previewNextElectionId", + "inputs": [ + { + "name": "organizer", + "type": "address", + "internalType": "address" + }, + { + "name": "seriesId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "titleHash", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "startAt", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "endAt", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "electionId", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "setKarmaRegistry", diff --git a/abi/status-testnet.addresses.json b/abi/status-testnet.addresses.json index 3745d6e..c659594 100644 --- a/abi/status-testnet.addresses.json +++ b/abi/status-testnet.addresses.json @@ -2,9 +2,9 @@ "chainName": "Status Network Testnet", "chainId": 1660990954, "rpcUrl": "https://public.sepolia.rpc.status.network", - "OrganizerRegistry": "0x6b6F30c54F0a382328409941A8eB824D02EFCD8f", - "KarmaRegistry": "0x9880c7Ff7Be22A1B2e469E594157eE6C14604472", - "ElectionImplementation": "0x93ec16287D1da13b4af58f3F0B9D717cae8b4A8A", - "VESTArElectionFactory": "0xE9e5AE2892542fd736b5391f0a722C641DC1fEDe", - "MockUSDT": "0x3a91A1b0bF95eE4e3F4e2F3F2A386995D884ee1d" + "OrganizerRegistry": "0xebfABbb617a2bEA8dc2BD5C85ED6CAFdfBaa01F4", + "KarmaRegistry": "0x5e2609C80df13da46c1A9C65A5FF969518913609", + "ElectionImplementation": "0xE8b78126a720b8C7A35D0EC667faB1dAA53B2523", + "VESTArElectionFactory": "0xeEb174bE8A72aA4dDEd6A4f68b0055Ba0Ba446b3", + "MockUSDT": "0xF962A46C1d73440D58efb6b398Eeed6BBb0019Fd" } diff --git a/src/interfaces/vestar/IVESTArElectionFactory.sol b/src/interfaces/vestar/IVESTArElectionFactory.sol index c17dcd2..d96b064 100644 --- a/src/interfaces/vestar/IVESTArElectionFactory.sol +++ b/src/interfaces/vestar/IVESTArElectionFactory.sol @@ -25,9 +25,9 @@ interface IVESTArElectionFactory is IVESTArAdminControl { // 어떤 karma registry를 참조하는지 조회 // karma registry : 유저의 Karma 기반 참여 자격을 판정하는 기준점 - // Status의 Karma, KarmaTiers를 읽음 - // 어떤 주소가 몇 티어인지 계산 - // 최소 티어 이상인지 판정 + // Status의 Karma, KarmaTiers를 읽음 + // 어떤 주소가 몇 티어인지 계산 + // 최소 티어 이상인지 판정 // 바로 Status 컨트랙트를 안 보는 이유 : 의존성을 한 레이어 감싸서 추상화 function karmaRegistry() external view returns (address); @@ -52,6 +52,25 @@ interface IVESTArElectionFactory is IVESTArAdminControl { // 같은 상위 이벤트/시리즈에 속한 election 개수 function totalElectionsInSeries(bytes32 seriesId) external view returns (uint256); + // organizer가 다음 create에서 사용할 nonce를 조회 + function nextElectionNonce(address organizer) external view returns (uint256); + + // 현재 nonce 기준으로 다음 electionId를 미리 계산 + function previewNextElectionId(address organizer, bytes32 seriesId, bytes32 titleHash, uint64 startAt, uint64 endAt) + external + view + returns (bytes32 electionId); + + // 특정 nonce를 넣어 electionId를 계산 + function computeElectionId( + address organizer, + bytes32 seriesId, + bytes32 titleHash, + uint64 startAt, + uint64 endAt, + uint256 organizerNonce + ) external view returns (bytes32 electionId); + // config struct 하나를 받아 새 election을 생성 function createElection(VESTArTypes.ElectionConfig calldata config) external returns (address electionAddress); @@ -59,14 +78,8 @@ interface IVESTArElectionFactory is IVESTArAdminControl { function getElection(bytes32 electionId) external view returns (address electionAddress); // 같은 seriesId를 공유하는 electionId 목록 조회 - function getSeriesElectionIds(bytes32 seriesId) - external - view - returns (bytes32[] memory electionIds); + function getSeriesElectionIds(bytes32 seriesId) external view returns (bytes32[] memory electionIds); // 같은 seriesId를 공유하는 election address 목록 조회 - function getSeriesElectionAddresses(bytes32 seriesId) - external - view - returns (address[] memory electionAddresses); + function getSeriesElectionAddresses(bytes32 seriesId) external view returns (address[] memory electionAddresses); } diff --git a/src/libraries/vestar/VESTArTypes.sol b/src/libraries/vestar/VESTArTypes.sol index 4a84753..3d4fe35 100644 --- a/src/libraries/vestar/VESTArTypes.sol +++ b/src/libraries/vestar/VESTArTypes.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.24; // VESTAr 전역에서 같이 쓰는 enum / struct 타입들을 한 곳에 모아두는 파일 library VESTArTypes { - enum VisibilityMode { OPEN, PRIVATE @@ -56,7 +55,6 @@ library VESTArTypes { // ElectionConfig는 투표를 만들 때 필요한 핵심 설정 묶음 struct ElectionConfig { - bytes32 electionId; // seriesId: "MAMA 2025" 같은 상위 이벤트/시리즈 식별자 // 예: female solo, male solo election이 같은 seriesId를 공유 bytes32 seriesId; diff --git a/src/vestar/election/VESTArElection.sol b/src/vestar/election/VESTArElection.sol index 97ae9d6..fb4d759 100644 --- a/src/vestar/election/VESTArElection.sol +++ b/src/vestar/election/VESTArElection.sol @@ -19,6 +19,7 @@ contract VESTArElection is VESTArElectionCore { // 예시 : organizer가 createElection(config)를 호출하면 factory가 새 election을 만든 뒤 // initialize(...)에서 "이 투표는 누구 것인지, 어느 karma registry를 볼지, 어떤 토큰을 받을지"를 채움 function initialize( + bytes32 electionId_, VESTArTypes.ElectionConfig calldata config, address organizerAddress, bool organizerVerifiedSnapshot_, @@ -29,7 +30,7 @@ contract VESTArElection is VESTArElectionCore { require(!initialized, "VESTAr: already initialized"); require(organizerAddress != address(0), "VESTAr: organizer is zero"); require(platformAdminAddress != address(0), "VESTAr: admin is zero"); - require(config.electionId != bytes32(0), "VESTAr: electionId is zero"); + require(electionId_ != bytes32(0), "VESTAr: electionId is zero"); // clone 배포 관련 코드 : clone은 constructor를 다시 타지 않으므로, // 실제 election 인스턴스의 factory 주소와 owner를 initialize에서 직접 세팅해야 함 @@ -39,6 +40,7 @@ contract VESTArElection is VESTArElectionCore { require(msg.sender == factory, "VESTAr: only factory"); + _electionId = electionId_; _config = config; _organizer = organizerAddress; _organizerVerifiedSnapshot = organizerVerifiedSnapshot_; @@ -58,7 +60,7 @@ contract VESTArElection is VESTArElectionCore { emit ElectionInitialized( config.seriesId, - config.electionId, + electionId_, organizerAddress, config.visibilityMode, organizerVerifiedSnapshot_, @@ -85,9 +87,7 @@ contract VESTArElection is VESTArElectionCore { _config.candidateManifestHash = newCandidateManifestHash; _config.candidateManifestURI = newCandidateManifestURI; - emit ElectionMetadataUpdated( - _config.electionId, newTitleHash, newCandidateManifestHash, newCandidateManifestURI - ); + emit ElectionMetadataUpdated(_electionId, newTitleHash, newCandidateManifestHash, newCandidateManifestURI); } // 후보 등록 관련 코드 : organizer/admin이 투표 시작 전에 후보 hash allowlist를 세팅 @@ -99,7 +99,7 @@ contract VESTArElection is VESTArElectionCore { for (uint256 i = 0; i < candidateHashes.length; ++i) { _allowedCandidateHash[candidateHashes[i]] = allowed; - emit CandidateAllowlistUpdated(_config.electionId, candidateHashes[i], allowed); + emit CandidateAllowlistUpdated(_electionId, candidateHashes[i], allowed); } } diff --git a/src/vestar/election/base/VESTArElectionStorage.sol b/src/vestar/election/base/VESTArElectionStorage.sol index 0d8b268..b7ab0cc 100644 --- a/src/vestar/election/base/VESTArElectionStorage.sol +++ b/src/vestar/election/base/VESTArElectionStorage.sol @@ -20,6 +20,7 @@ abstract contract VESTArElectionStorage { uint256 internal constant UNLIMITED_PAID_COST_AMOUNT = 66_000; VESTArTypes.ElectionConfig internal _config; + bytes32 internal _electionId; VESTArTypes.ResultSummary internal _resultSummary; VESTArTypes.CancellationSummary internal _cancellationSummary; VESTArTypes.SettlementSummary internal _settlementSummary; @@ -239,10 +240,7 @@ abstract contract VESTArElectionStorage { // 관리자 권한 관련 코드 : platform admin 또는 organizer 둘 다 호출 가능하게 허용 function _requirePlatformAdminOrOrganizer() internal view { - require( - msg.sender == _platformAdmin || msg.sender == _organizer, - "VESTAr: only admin or organizer" - ); + require(msg.sender == _platformAdmin || msg.sender == _organizer, "VESTAr: only admin or organizer"); } // key reveal 권한 관련 코드 : platform admin은 항상 가능, 내부 팀 관리자는 별도 whitelist로 위임 가능 @@ -265,10 +263,7 @@ abstract contract VESTArElectionStorage { require(candidateKeys.length > 0, "VESTAr: empty selection"); if (_config.allowMultipleChoice) { - require( - candidateKeys.length <= _config.maxSelectionsPerSubmission, - "VESTAr: too many selections" - ); + require(candidateKeys.length <= _config.maxSelectionsPerSubmission, "VESTAr: too many selections"); } else { require(candidateKeys.length == 1, "VESTAr: single choice only"); } diff --git a/src/vestar/election/modules/VESTArElectionLifecycleImpl.sol b/src/vestar/election/modules/VESTArElectionLifecycleImpl.sol index 80027bf..0a8167c 100644 --- a/src/vestar/election/modules/VESTArElectionLifecycleImpl.sol +++ b/src/vestar/election/modules/VESTArElectionLifecycleImpl.sol @@ -10,7 +10,7 @@ import {VESTArElectionStorage} from "../base/VESTArElectionStorage.sol"; abstract contract VESTArElectionLifecycleImpl is VESTArElectionStorage, IVESTArElectionLifecycle { // 상태 조회 관련 코드 : election 식별자 function electionId() public view virtual returns (bytes32) { - return _config.electionId; + return _electionId; } // 상태 조회 관련 코드 : 같은 이벤트 화면에 묶이는 상위 series 식별자 @@ -73,7 +73,7 @@ abstract contract VESTArElectionLifecycleImpl is VESTArElectionStorage, IVESTArE if (previousState != nextState) { _state = nextState; - emit ElectionStateUpdated(_config.electionId, previousState, nextState); + emit ElectionStateUpdated(_electionId, previousState, nextState); } return _state; @@ -89,12 +89,10 @@ abstract contract VESTArElectionLifecycleImpl is VESTArElectionStorage, IVESTArE require(currentState != VESTArTypes.ElectionState.Finalized, "VESTAr: already finalized"); _cancellationSummary = VESTArTypes.CancellationSummary({ - cancelledBy: msg.sender, - cancelledAt: uint64(block.timestamp), - previousState: currentState + cancelledBy: msg.sender, cancelledAt: uint64(block.timestamp), previousState: currentState }); - emit ElectionCancelled(_config.electionId, msg.sender, currentState, uint64(block.timestamp)); + emit ElectionCancelled(_electionId, msg.sender, currentState, uint64(block.timestamp)); _transitionState(VESTArTypes.ElectionState.Cancelled); } @@ -108,10 +106,7 @@ abstract contract VESTArElectionLifecycleImpl is VESTArElectionStorage, IVESTArE _requirePlatformAdminOrOrganizer(); require(syncState() == VESTArTypes.ElectionState.Active, "VESTAr: not active"); - if ( - _config.visibilityMode == VESTArTypes.VisibilityMode.PRIVATE - && block.timestamp >= _config.resultRevealAt - ) { + if (_config.visibilityMode == VESTArTypes.VisibilityMode.PRIVATE && block.timestamp >= _config.resultRevealAt) { _transitionState(VESTArTypes.ElectionState.KeyRevealPending); return; } @@ -128,13 +123,10 @@ abstract contract VESTArElectionLifecycleImpl is VESTArElectionStorage, IVESTArE require(_isRevealManager(msg.sender), "VESTAr: not reveal manager"); require(syncState() == VESTArTypes.ElectionState.KeyRevealPending, "VESTAr: reveal not ready"); require(_revealedPrivateKey.length == 0, "VESTAr: key already revealed"); - require( - keccak256(privateKeyData) == _config.privateKeyCommitmentHash, - "VESTAr: commitment mismatch" - ); + require(keccak256(privateKeyData) == _config.privateKeyCommitmentHash, "VESTAr: commitment mismatch"); _revealedPrivateKey = privateKeyData; - emit PrivateKeyRevealed(_config.electionId, _config.privateKeyCommitmentHash, privateKeyData); + emit PrivateKeyRevealed(_electionId, _config.privateKeyCommitmentHash, privateKeyData); _transitionState(VESTArTypes.ElectionState.KeyRevealed); } @@ -153,14 +145,14 @@ abstract contract VESTArElectionLifecycleImpl is VESTArElectionStorage, IVESTArE } _resultSummary = resultSummary; - emit ResultFinalized(_config.electionId, resultSummary.resultManifestHash, resultSummary.resultManifestURI); + emit ResultFinalized(_electionId, resultSummary.resultManifestHash, resultSummary.resultManifestURI); _transitionState(VESTArTypes.ElectionState.Finalized); } // 투표 상태 관련 코드 : state 변경 이벤트를 한 곳에서만 찍게 묶어 두는 내부 helper function _transitionState(VESTArTypes.ElectionState nextState) internal { if (_state != nextState) { - emit ElectionStateUpdated(_config.electionId, _state, nextState); + emit ElectionStateUpdated(_electionId, _state, nextState); _state = nextState; } } diff --git a/src/vestar/election/modules/VESTArOpenVoteModuleImpl.sol b/src/vestar/election/modules/VESTArOpenVoteModuleImpl.sol index f148b1f..1b8776b 100644 --- a/src/vestar/election/modules/VESTArOpenVoteModuleImpl.sol +++ b/src/vestar/election/modules/VESTArOpenVoteModuleImpl.sol @@ -30,12 +30,7 @@ abstract contract VESTArOpenVoteModuleImpl is VESTArElectionStorage, IVESTArOpen } emit OpenVoteSubmitted( - _config.electionId, - msg.sender, - candidateKeys.length, - _candidateBatchHash(candidateKeys), - 1, - paymentAmount + _electionId, msg.sender, candidateKeys.length, _candidateBatchHash(candidateKeys), 1, paymentAmount ); } diff --git a/src/vestar/election/modules/VESTArPrivateVoteModuleImpl.sol b/src/vestar/election/modules/VESTArPrivateVoteModuleImpl.sol index 2aa1248..bb41e57 100644 --- a/src/vestar/election/modules/VESTArPrivateVoteModuleImpl.sol +++ b/src/vestar/election/modules/VESTArPrivateVoteModuleImpl.sol @@ -25,13 +25,7 @@ abstract contract VESTArPrivateVoteModuleImpl is VESTArElectionStorage, IVESTArP _recordBallotSubmission(msg.sender, uint64(block.timestamp)); uint256 paymentAmount = _collectPaymentFrom(msg.sender, 1); - emit EncryptedVoteSubmitted( - _config.electionId, - msg.sender, - _hashEncryptedBallot(encryptedBallot), - 1, - paymentAmount - ); + emit EncryptedVoteSubmitted(_electionId, msg.sender, _hashEncryptedBallot(encryptedBallot), 1, paymentAmount); } // 프론트 관련 코드 : private 투표 화면이 이 공개키를 읽어 ballot 암호화에 사용 diff --git a/src/vestar/election/modules/VESTArSettlementModuleImpl.sol b/src/vestar/election/modules/VESTArSettlementModuleImpl.sol index cd85f47..a8309ae 100644 --- a/src/vestar/election/modules/VESTArSettlementModuleImpl.sol +++ b/src/vestar/election/modules/VESTArSettlementModuleImpl.sol @@ -91,7 +91,7 @@ abstract contract VESTArSettlementModuleImpl is VESTArElectionStorage, IVESTArSe _refundSummary.refundsEnabledBy = msg.sender; _refundSummary.refundsEnabled = true; - emit RefundsEnabled(_config.electionId, msg.sender, _refundSummary.totalRefundableAmount); + emit RefundsEnabled(_electionId, msg.sender, _refundSummary.totalRefundableAmount); } function claimRefund() public virtual returns (uint256 refundAmount) { @@ -106,7 +106,7 @@ abstract contract VESTArSettlementModuleImpl is VESTArElectionStorage, IVESTArSe IERC20(_config.paymentToken).safeTransfer(msg.sender, refundAmount); - emit RefundClaimed(_config.electionId, msg.sender, refundAmount); + emit RefundClaimed(_electionId, msg.sender, refundAmount); } // 정산 관련 코드 : Finalized 이후 한 번만 실행하고, organizer가 홀수 잔차를 가져가도록 실제 송금까지 처리 @@ -119,8 +119,7 @@ abstract contract VESTArSettlementModuleImpl is VESTArElectionStorage, IVESTArSe require(!_settlementSummary.settled, "VESTAr: already settled"); require(!_refundSummary.refundsEnabled, "VESTAr: refunds enabled"); - (uint256 platformRevenueAmount, uint256 organizerRevenueAmount) = - _previewSettlementSplit(_totalCollectedAmount); + (uint256 platformRevenueAmount, uint256 organizerRevenueAmount) = _previewSettlementSplit(_totalCollectedAmount); _settlementSummary.paymentToken = _config.paymentToken; _settlementSummary.totalRevenueAmount = _totalCollectedAmount; @@ -143,7 +142,7 @@ abstract contract VESTArSettlementModuleImpl is VESTArElectionStorage, IVESTArSe } emit RevenueSettled( - _config.electionId, + _electionId, _settlementSummary.platformTreasury, _organizer, _settlementSummary.totalRevenueAmount, diff --git a/src/vestar/factory/VESTArElectionFactory.sol b/src/vestar/factory/VESTArElectionFactory.sol index 8702e40..4434f64 100644 --- a/src/vestar/factory/VESTArElectionFactory.sol +++ b/src/vestar/factory/VESTArElectionFactory.sol @@ -23,6 +23,7 @@ contract VESTArElectionFactory is VESTArOwnablePausable, IVESTArElectionFactory address internal _electionImplementation; uint256 internal _totalElections; + mapping(address organizer => uint256 nonce) internal _nextElectionNonceByOrganizer; mapping(bytes32 electionId => address electionAddress) internal _electionById; mapping(bytes32 seriesId => bytes32[] electionIds) internal _electionIdsBySeriesId; @@ -73,6 +74,32 @@ contract VESTArElectionFactory is VESTArOwnablePausable, IVESTArElectionFactory return _electionIdsBySeriesId[seriesId].length; } + function nextElectionNonce(address organizer) public view returns (uint256) { + return _nextElectionNonceByOrganizer[organizer]; + } + + function previewNextElectionId(address organizer, bytes32 seriesId, bytes32 titleHash, uint64 startAt, uint64 endAt) + public + view + returns (bytes32 electionId) + { + return + computeElectionId(organizer, seriesId, titleHash, startAt, endAt, _nextElectionNonceByOrganizer[organizer]); + } + + function computeElectionId( + address organizer, + bytes32 seriesId, + bytes32 titleHash, + uint64 startAt, + uint64 endAt, + uint256 organizerNonce + ) public view returns (bytes32 electionId) { + return keccak256( + abi.encode(address(this), block.chainid, organizer, seriesId, titleHash, startAt, endAt, organizerNonce) + ); + } + // admin 설정 관련 코드 : 운영 중 organizer registry 주소를 바꿔야 할 때 owner가 갱신 function setOrganizerRegistry(address organizerRegistryAddress) external onlyOwner { _organizerRegistry = organizerRegistryAddress; @@ -102,7 +129,6 @@ contract VESTArElectionFactory is VESTArOwnablePausable, IVESTArElectionFactory // 3) 통과하면 새 election 계약을 배포하고 initialize를 호출 // 4) 프론트/백은 ElectionCreated 이벤트를 보고 새 election 주소를 인덱싱 require(config.seriesId != bytes32(0), "VESTAr: seriesId is zero"); - require(_electionById[config.electionId] == address(0), "VESTAr: election exists"); bool verifiedSnapshot = IVESTArOrganizerRegistry(_organizerRegistry).isVerified(msg.sender); uint8 organizerTier = 0; @@ -116,27 +142,29 @@ contract VESTArElectionFactory is VESTArOwnablePausable, IVESTArElectionFactory "VESTAr: organizer not eligible" ); + uint256 organizerNonce = _nextElectionNonceByOrganizer[msg.sender]; + bytes32 generatedElectionId = computeElectionId( + msg.sender, config.seriesId, config.titleHash, config.startAt, config.endAt, organizerNonce + ); + require(_electionById[generatedElectionId] == address(0), "VESTAr: election exists"); + // 배포 관련 코드 : MVP는 clone 대신 new로 직접 배포해서 초기 학습 난도를 낮춤 // 배포 관련 코드 : 구현체를 직접 다시 배포하지 않고 clone을 찍으면 // factory 자체의 initcode/runtime이 작아져 Status RPC의 oversized data 문제를 줄이기 쉬움 VESTArElection election = VESTArElection(_electionImplementation.clone()); election.initialize( - config, - msg.sender, - verifiedSnapshot, - _karmaRegistry, - owner, - _platformTreasury + generatedElectionId, config, msg.sender, verifiedSnapshot, _karmaRegistry, owner, _platformTreasury ); electionAddress = address(election); - _electionById[config.electionId] = electionAddress; - _electionIdsBySeriesId[config.seriesId].push(config.electionId); + _nextElectionNonceByOrganizer[msg.sender] = organizerNonce + 1; + _electionById[generatedElectionId] = electionAddress; + _electionIdsBySeriesId[config.seriesId].push(generatedElectionId); _totalElections += 1; emit ElectionCreated( config.seriesId, - config.electionId, + generatedElectionId, msg.sender, electionAddress, config.visibilityMode, @@ -150,19 +178,11 @@ contract VESTArElectionFactory is VESTArOwnablePausable, IVESTArElectionFactory return _electionById[electionId]; } - function getSeriesElectionIds(bytes32 seriesId) - public - view - returns (bytes32[] memory electionIds) - { + function getSeriesElectionIds(bytes32 seriesId) public view returns (bytes32[] memory electionIds) { return _electionIdsBySeriesId[seriesId]; } - function getSeriesElectionAddresses(bytes32 seriesId) - public - view - returns (address[] memory electionAddresses) - { + function getSeriesElectionAddresses(bytes32 seriesId) public view returns (address[] memory electionAddresses) { bytes32[] storage electionIds = _electionIdsBySeriesId[seriesId]; electionAddresses = new address[](electionIds.length); diff --git a/test/vestar/election/VESTArElectionLifecycle.t.sol b/test/vestar/election/VESTArElectionLifecycle.t.sol index 71862b6..6810a47 100644 --- a/test/vestar/election/VESTArElectionLifecycle.t.sol +++ b/test/vestar/election/VESTArElectionLifecycle.t.sol @@ -16,12 +16,19 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { } function testScheduledToActiveStateTransitionFollowsTime() public { - VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("open-lifecycle"), uint64(block.timestamp + 1 days), uint64(block.timestamp + 3 days) + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(uint64(block.timestamp + 1 days), uint64(block.timestamp + 3 days)); + + election.initialize( + bytes32("open-lifecycle"), + config, + organizer, + false, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury ); - election.initialize(config, organizer, false, address(mockKarmaRegistry), platformAdmin, platformTreasury); - assertEq(uint256(election.state()), uint256(VESTArTypes.ElectionState.Scheduled)); vm.warp(block.timestamp + 1 days); @@ -31,12 +38,19 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { } function testOrganizerCanCancelBeforeStart() public { - VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("cancel-before-start"), uint64(block.timestamp + 1 days), uint64(block.timestamp + 2 days) + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(uint64(block.timestamp + 1 days), uint64(block.timestamp + 2 days)); + + election.initialize( + bytes32("cancel-before-start"), + config, + organizer, + false, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury ); - election.initialize(config, organizer, false, address(mockKarmaRegistry), platformAdmin, platformTreasury); - vm.prank(organizer); election.cancelBeforeStart(); @@ -44,12 +58,19 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { } function testOrganizerCanCancelOpenElectionAfterItStarts() public { - VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("cancel-active-open"), uint64(block.timestamp + 1 days), uint64(block.timestamp + 2 days) + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(uint64(block.timestamp + 1 days), uint64(block.timestamp + 2 days)); + + election.initialize( + bytes32("cancel-active-open"), + config, + organizer, + false, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury ); - election.initialize(config, organizer, false, address(mockKarmaRegistry), platformAdmin, platformTreasury); - vm.warp(block.timestamp + 1 days + 1); vm.prank(organizer); @@ -68,14 +89,21 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { bytes memory privateKeyData = hex"1234abcd"; VESTArTypes.ElectionConfig memory config = _buildPrivateConfig( - bytes32("cancel-key-revealed"), uint64(block.timestamp - 2 days), uint64(block.timestamp - 1 days), uint64(block.timestamp - 1 hours), keccak256(privateKeyData) ); - election.initialize(config, organizer, true, address(mockKarmaRegistry), platformAdmin, platformTreasury); + election.initialize( + bytes32("cancel-key-revealed"), + config, + organizer, + true, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury + ); vm.prank(platformAdmin); election.revealPrivateKey(privateKeyData); @@ -93,12 +121,19 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { function testCannotCancelElectionAfterFinalize() public { vm.warp(10 days); - VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("cancel-after-finalize"), uint64(block.timestamp - 3 days), uint64(block.timestamp - 1 days) + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(uint64(block.timestamp - 3 days), uint64(block.timestamp - 1 days)); + + election.initialize( + bytes32("cancel-after-finalize"), + config, + organizer, + false, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury ); - election.initialize(config, organizer, false, address(mockKarmaRegistry), platformAdmin, platformTreasury); - vm.prank(organizer); election.finalizeResults( VESTArTypes.ResultSummary({ @@ -120,14 +155,21 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { bytes memory privateKeyData = hex"1234abcd"; VESTArTypes.ElectionConfig memory config = _buildPrivateConfig( - bytes32("private-lifecycle"), uint64(block.timestamp - 2 days), uint64(block.timestamp - 1 days), uint64(block.timestamp - 1 hours), keccak256(privateKeyData) ); - election.initialize(config, organizer, true, address(mockKarmaRegistry), platformAdmin, platformTreasury); + election.initialize( + bytes32("private-lifecycle"), + config, + organizer, + true, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury + ); vm.prank(platformAdmin); election.setRevealManager(revealManager, true); @@ -147,14 +189,21 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { bytes memory committedPrivateKey = hex"1234abcd"; bytes memory wrongPrivateKey = hex"9999eeee"; VESTArTypes.ElectionConfig memory config = _buildPrivateConfig( - bytes32("private-mismatch"), uint64(block.timestamp - 2 days), uint64(block.timestamp - 1 days), uint64(block.timestamp - 1 hours), keccak256(committedPrivateKey) ); - election.initialize(config, organizer, true, address(mockKarmaRegistry), platformAdmin, platformTreasury); + election.initialize( + bytes32("private-mismatch"), + config, + organizer, + true, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury + ); vm.prank(platformAdmin); election.setRevealManager(revealManager, true); @@ -166,14 +215,21 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { function testOnlyPlatformAdminCanAssignRevealManager() public { VESTArTypes.ElectionConfig memory config = _buildPrivateConfig( - bytes32("private-admin"), uint64(block.timestamp + 1 days), uint64(block.timestamp + 2 days), uint64(block.timestamp + 3 days), keccak256(hex"1234") ); - election.initialize(config, organizer, true, address(mockKarmaRegistry), platformAdmin, platformTreasury); + election.initialize( + bytes32("private-admin"), + config, + organizer, + true, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury + ); vm.prank(organizer); vm.expectRevert("VESTAr: only platform admin"); @@ -184,14 +240,21 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { vm.warp(10 days); VESTArTypes.ElectionConfig memory config = _buildPrivateConfig( - bytes32("private-finalize"), uint64(block.timestamp - 2 days), uint64(block.timestamp - 1 days), uint64(block.timestamp - 1 hours), keccak256(hex"1234") ); - election.initialize(config, organizer, true, address(mockKarmaRegistry), platformAdmin, platformTreasury); + election.initialize( + bytes32("private-finalize"), + config, + organizer, + true, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury + ); vm.prank(organizer); vm.expectRevert("VESTAr: reveal first"); @@ -207,12 +270,19 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { } function testOrganizerCanUpdateElectionMetadataBeforeStart() public { - VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("metadata-edit"), uint64(block.timestamp + 1 days), uint64(block.timestamp + 3 days) + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(uint64(block.timestamp + 1 days), uint64(block.timestamp + 3 days)); + + election.initialize( + bytes32("metadata-edit"), + config, + organizer, + false, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury ); - election.initialize(config, organizer, false, address(mockKarmaRegistry), platformAdmin, platformTreasury); - bytes32 newTitleHash = keccak256("Lifecycle Open Vote Fixed"); bytes32 newCandidateManifestHash = keccak256("candidates-fixed"); string memory newCandidateManifestURI = "ipfs://candidates-fixed"; @@ -227,12 +297,19 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { } function testOrganizerCannotUpdateElectionMetadataAfterStart() public { - VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("metadata-lock"), uint64(block.timestamp + 1 days), uint64(block.timestamp + 3 days) + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(uint64(block.timestamp + 1 days), uint64(block.timestamp + 3 days)); + + election.initialize( + bytes32("metadata-lock"), + config, + organizer, + false, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury ); - election.initialize(config, organizer, false, address(mockKarmaRegistry), platformAdmin, platformTreasury); - vm.warp(block.timestamp + 1 days); vm.prank(organizer); @@ -245,12 +322,19 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { function testOrganizerCannotEditCandidateAllowlistAfterStart() public { // 실제 사례 : organizer는 시작 전까지만 후보 목록을 확정하고, // 투표가 열린 뒤에는 프론트/백 기준점이 흔들리지 않도록 수정이 막혀야 함 - VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("allowlist-lock"), uint64(block.timestamp + 1 days), uint64(block.timestamp + 3 days) + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(uint64(block.timestamp + 1 days), uint64(block.timestamp + 3 days)); + + election.initialize( + bytes32("allowlist-lock"), + config, + organizer, + false, + address(mockKarmaRegistry), + platformAdmin, + platformTreasury ); - election.initialize(config, organizer, false, address(mockKarmaRegistry), platformAdmin, platformTreasury); - vm.warp(block.timestamp + 1 days); bytes32[] memory candidateHashes = new bytes32[](1); @@ -261,13 +345,12 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { election.setCandidateAllowlist(candidateHashes, true); } - function _buildOpenConfig(bytes32 electionId_, uint64 startAt_, uint64 endAt_) + function _buildOpenConfig(uint64 startAt_, uint64 endAt_) internal view returns (VESTArTypes.ElectionConfig memory) { return VESTArTypes.ElectionConfig({ - electionId: electionId_, seriesId: bytes32("lifecycle-open-series"), visibilityMode: VESTArTypes.VisibilityMode.OPEN, titleHash: keccak256("Lifecycle Open Vote"), @@ -291,15 +374,12 @@ contract VESTArElectionLifecycleTest is VESTArTestBase { }); } - function _buildPrivateConfig( - bytes32 electionId_, - uint64 startAt_, - uint64 endAt_, - uint64 resultRevealAt_, - bytes32 commitmentHash - ) internal view returns (VESTArTypes.ElectionConfig memory) { + function _buildPrivateConfig(uint64 startAt_, uint64 endAt_, uint64 resultRevealAt_, bytes32 commitmentHash) + internal + view + returns (VESTArTypes.ElectionConfig memory) + { return VESTArTypes.ElectionConfig({ - electionId: electionId_, seriesId: bytes32("lifecycle-private-series"), visibilityMode: VESTArTypes.VisibilityMode.PRIVATE, titleHash: keccak256("Lifecycle Private Vote"), diff --git a/test/vestar/factory/VESTArElectionFactory.t.sol b/test/vestar/factory/VESTArElectionFactory.t.sol index ddef687..9eecb4a 100644 --- a/test/vestar/factory/VESTArElectionFactory.t.sol +++ b/test/vestar/factory/VESTArElectionFactory.t.sol @@ -33,11 +33,11 @@ contract VESTArElectionFactoryTest is VESTArTestBase { organizerRegistry.setVerification(organizer, true, 100, 0); VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("verified-open"), - bytes32("verified-series"), - keccak256("factory-open-vote"), - VESTArTypes.PaymentMode.PAID, - 25_000 + bytes32("verified-series"), keccak256("factory-open-vote"), VESTArTypes.PaymentMode.PAID, 25_000 + ); + + bytes32 expectedElectionId = electionFactory.previewNextElectionId( + organizer, config.seriesId, config.titleHash, config.startAt, config.endAt ); vm.prank(organizer); @@ -45,7 +45,8 @@ contract VESTArElectionFactoryTest is VESTArTestBase { VESTArElection election = VESTArElection(electionAddress); - assertEq(electionFactory.getElection(config.electionId), electionAddress); + assertEq(electionFactory.getElection(expectedElectionId), electionAddress); + assertEq(election.electionId(), expectedElectionId); assertEq(election.seriesId(), config.seriesId); assertEq(election.organizer(), organizer); assertEq(election.platformAdmin(), platformAdmin); @@ -57,11 +58,7 @@ contract VESTArElectionFactoryTest is VESTArTestBase { function testUnverifiedOrganizerWithZeroKarmaCannotCreateElection() public { VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("unverified-open"), - bytes32("unverified-series"), - keccak256("factory-open-vote"), - VESTArTypes.PaymentMode.FREE, - 0 + bytes32("unverified-series"), keccak256("factory-open-vote"), VESTArTypes.PaymentMode.FREE, 0 ); vm.prank(organizer); @@ -73,13 +70,8 @@ contract VESTArElectionFactoryTest is VESTArTestBase { // 실제 사례 : verified가 아니더라도 카르마 티어 1 이상이면 organizer 생성 허용 mockKarmaRegistry.setTier(organizer, 1); - VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("karma-open"), - bytes32("karma-series"), - keccak256("factory-open-vote"), - VESTArTypes.PaymentMode.FREE, - 0 - ); + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(bytes32("karma-series"), keccak256("factory-open-vote"), VESTArTypes.PaymentMode.FREE, 0); vm.prank(organizer); address electionAddress = electionFactory.createElection(config); @@ -98,11 +90,7 @@ contract VESTArElectionFactoryTest is VESTArTestBase { organizerRegistry.setVerification(organizer, true, 100, 0); VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("snapshot-open"), - bytes32("snapshot-series"), - keccak256("factory-open-vote"), - VESTArTypes.PaymentMode.FREE, - 0 + bytes32("snapshot-series"), keccak256("factory-open-vote"), VESTArTypes.PaymentMode.FREE, 0 ); vm.prank(organizer); @@ -123,20 +111,26 @@ contract VESTArElectionFactoryTest is VESTArTestBase { bytes32 mamaSeriesId = bytes32("mama-2025"); - VESTArTypes.ElectionConfig memory femaleSoloConfig = _buildOpenConfig( - bytes32("mama-female-solo"), - mamaSeriesId, - keccak256("female-solo"), - VESTArTypes.PaymentMode.FREE, - 0 - ); + VESTArTypes.ElectionConfig memory femaleSoloConfig = + _buildOpenConfig(mamaSeriesId, keccak256("female-solo"), VESTArTypes.PaymentMode.FREE, 0); - VESTArTypes.ElectionConfig memory maleSoloConfig = _buildOpenConfig( - bytes32("mama-male-solo"), - mamaSeriesId, - keccak256("male-solo"), - VESTArTypes.PaymentMode.FREE, - 0 + VESTArTypes.ElectionConfig memory maleSoloConfig = + _buildOpenConfig(mamaSeriesId, keccak256("male-solo"), VESTArTypes.PaymentMode.FREE, 0); + + bytes32 expectedFemaleElectionId = electionFactory.previewNextElectionId( + organizer, + femaleSoloConfig.seriesId, + femaleSoloConfig.titleHash, + femaleSoloConfig.startAt, + femaleSoloConfig.endAt + ); + bytes32 expectedMaleElectionId = electionFactory.computeElectionId( + organizer, + maleSoloConfig.seriesId, + maleSoloConfig.titleHash, + maleSoloConfig.startAt, + maleSoloConfig.endAt, + 1 ); vm.startPrank(organizer); @@ -150,14 +144,38 @@ contract VESTArElectionFactoryTest is VESTArTestBase { assertEq(electionFactory.totalElectionsInSeries(mamaSeriesId), 2); assertEq(electionIds.length, 2); assertEq(electionAddresses.length, 2); - assertEq(electionIds[0], femaleSoloConfig.electionId); - assertEq(electionIds[1], maleSoloConfig.electionId); + assertEq(electionIds[0], expectedFemaleElectionId); + assertEq(electionIds[1], expectedMaleElectionId); assertEq(electionAddresses[0], femaleSoloElection); assertEq(electionAddresses[1], maleSoloElection); } + function testFactoryGeneratesUniqueElectionIdsForRepeatedConfig() public { + vm.prank(platformAdmin); + organizerRegistry.setVerification(organizer, true, 100, 0); + + VESTArTypes.ElectionConfig memory config = + _buildOpenConfig(bytes32("repeat-series"), keccak256("repeat-title"), VESTArTypes.PaymentMode.FREE, 0); + + bytes32 expectedFirstElectionId = electionFactory.previewNextElectionId( + organizer, config.seriesId, config.titleHash, config.startAt, config.endAt + ); + bytes32 expectedSecondElectionId = electionFactory.computeElectionId( + organizer, config.seriesId, config.titleHash, config.startAt, config.endAt, 1 + ); + + vm.startPrank(organizer); + address firstElectionAddress = electionFactory.createElection(config); + address secondElectionAddress = electionFactory.createElection(config); + vm.stopPrank(); + + assertTrue(expectedFirstElectionId != expectedSecondElectionId); + assertEq(electionFactory.getElection(expectedFirstElectionId), firstElectionAddress); + assertEq(electionFactory.getElection(expectedSecondElectionId), secondElectionAddress); + assertEq(electionFactory.nextElectionNonce(organizer), 2); + } + function _buildOpenConfig( - bytes32 electionId_, bytes32 seriesId_, bytes32 titleHash_, VESTArTypes.PaymentMode paymentMode_, @@ -166,7 +184,6 @@ contract VESTArElectionFactoryTest is VESTArTestBase { address paymentToken = paymentMode_ == VESTArTypes.PaymentMode.PAID ? address(mockUSDT) : address(0); return VESTArTypes.ElectionConfig({ - electionId: electionId_, seriesId: seriesId_, visibilityMode: VESTArTypes.VisibilityMode.OPEN, titleHash: titleHash_, diff --git a/test/vestar/integration/VESTArEndToEnd.t.sol b/test/vestar/integration/VESTArEndToEnd.t.sol index 515c792..52f2361 100644 --- a/test/vestar/integration/VESTArEndToEnd.t.sol +++ b/test/vestar/integration/VESTArEndToEnd.t.sol @@ -38,7 +38,6 @@ contract VESTArEndToEndTest is VESTArTestBase { // 3) 유저가 ["IU", "ParkHyoShin"] 다중 선택 ballot 1개를 제출 // 4) 종료 후 organizer가 결과를 finalize하고 수익을 50:50 정산 VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("open-e2e"), bytes32("mama-2025"), keccak256("female-solo"), uint64(block.timestamp + 1 days), @@ -103,7 +102,6 @@ contract VESTArEndToEndTest is VESTArTestBase { bytes32 mamaSeriesId = bytes32("mama-2025"); VESTArTypes.ElectionConfig memory femaleSoloConfig = _buildOpenConfig( - bytes32("mama-female-solo"), mamaSeriesId, keccak256("female-solo"), uint64(block.timestamp + 1 days), @@ -112,7 +110,6 @@ contract VESTArEndToEndTest is VESTArTestBase { ); VESTArTypes.ElectionConfig memory maleSoloConfig = _buildOpenConfig( - bytes32("mama-male-solo"), mamaSeriesId, keccak256("male-solo"), uint64(block.timestamp + 1 days), @@ -120,6 +117,22 @@ contract VESTArEndToEndTest is VESTArTestBase { 25_001 ); + bytes32 expectedFemaleElectionId = electionFactory.previewNextElectionId( + organizer, + femaleSoloConfig.seriesId, + femaleSoloConfig.titleHash, + femaleSoloConfig.startAt, + femaleSoloConfig.endAt + ); + bytes32 expectedMaleElectionId = electionFactory.computeElectionId( + organizer, + maleSoloConfig.seriesId, + maleSoloConfig.titleHash, + maleSoloConfig.startAt, + maleSoloConfig.endAt, + 1 + ); + vm.startPrank(organizer); address femaleSoloElection = electionFactory.createElection(femaleSoloConfig); address maleSoloElection = electionFactory.createElection(maleSoloConfig); @@ -131,8 +144,8 @@ contract VESTArEndToEndTest is VESTArTestBase { assertEq(electionFactory.totalElectionsInSeries(mamaSeriesId), 2); assertEq(electionIds.length, 2); assertEq(electionAddresses.length, 2); - assertEq(electionIds[0], femaleSoloConfig.electionId); - assertEq(electionIds[1], maleSoloConfig.electionId); + assertEq(electionIds[0], expectedFemaleElectionId); + assertEq(electionIds[1], expectedMaleElectionId); assertEq(electionAddresses[0], femaleSoloElection); assertEq(electionAddresses[1], maleSoloElection); assertEq(VESTArElection(femaleSoloElection).seriesId(), mamaSeriesId); @@ -144,7 +157,6 @@ contract VESTArEndToEndTest is VESTArTestBase { // 주최자는 시작 전에 후보 목록을 준비할 수 있지만, // 투표가 열린 뒤에는 프론트/백엔드 집계 기준이 흔들리지 않게 수정이 막혀야 함 VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("lock-after-start"), bytes32("mama-2025"), keccak256("female-solo"), uint64(block.timestamp + 1 days), @@ -176,7 +188,6 @@ contract VESTArEndToEndTest is VESTArTestBase { bytes memory privateKeyData = hex"0123456789"; VESTArTypes.ElectionConfig memory config = _buildPrivateConfig( - bytes32("private-e2e"), bytes32("mma-2025"), keccak256("winner-vote"), uint64(block.timestamp + 1 days), @@ -229,7 +240,6 @@ contract VESTArEndToEndTest is VESTArTestBase { // 2) 프론트는 getElectionConfig를 읽어서 shared seriesId와 category titleHash를 함께 가져간다 bytes32 mamaSeriesId = bytes32("mama-2025"); VESTArTypes.ElectionConfig memory config = _buildOpenConfig( - bytes32("front-read"), mamaSeriesId, keccak256("female-solo"), uint64(block.timestamp + 1 days), @@ -251,8 +261,11 @@ contract VESTArEndToEndTest is VESTArTestBase { election.setCandidateAllowlist(candidateHashes, true); VESTArTypes.ElectionConfig memory storedConfig = election.getElectionConfig(); + bytes32 expectedElectionId = electionFactory.computeElectionId( + organizer, config.seriesId, config.titleHash, config.startAt, config.endAt, 0 + ); - assertEq(storedConfig.electionId, bytes32("front-read")); + assertEq(election.electionId(), expectedElectionId); assertEq(storedConfig.seriesId, mamaSeriesId); assertEq(storedConfig.titleHash, keccak256("female-solo")); assertEq(storedConfig.candidateManifestURI, "ipfs://open-candidates"); @@ -260,7 +273,6 @@ contract VESTArEndToEndTest is VESTArTestBase { } function _buildOpenConfig( - bytes32 electionId_, bytes32 seriesId_, bytes32 titleHash_, uint64 startAt_, @@ -268,7 +280,6 @@ contract VESTArEndToEndTest is VESTArTestBase { uint256 costPerBallot_ ) internal view returns (VESTArTypes.ElectionConfig memory) { return VESTArTypes.ElectionConfig({ - electionId: electionId_, seriesId: seriesId_, visibilityMode: VESTArTypes.VisibilityMode.OPEN, titleHash: titleHash_, @@ -293,7 +304,6 @@ contract VESTArEndToEndTest is VESTArTestBase { } function _buildPrivateConfig( - bytes32 electionId_, bytes32 seriesId_, bytes32 titleHash_, uint64 startAt_, @@ -303,7 +313,6 @@ contract VESTArEndToEndTest is VESTArTestBase { bytes32 commitmentHash ) internal view returns (VESTArTypes.ElectionConfig memory) { return VESTArTypes.ElectionConfig({ - electionId: electionId_, seriesId: seriesId_, visibilityMode: VESTArTypes.VisibilityMode.PRIVATE, titleHash: titleHash_, @@ -327,11 +336,7 @@ contract VESTArEndToEndTest is VESTArTestBase { }); } - function _resultSummary(string memory resultManifestUri) - internal - pure - returns (VESTArTypes.ResultSummary memory) - { + function _resultSummary(string memory resultManifestUri) internal pure returns (VESTArTypes.ResultSummary memory) { return VESTArTypes.ResultSummary({ resultManifestHash: keccak256(bytes(resultManifestUri)), resultManifestURI: resultManifestUri,