diff --git a/lcov.info b/lcov.info index 5d1fbb2..e4e2fc9 100644 --- a/lcov.info +++ b/lcov.info @@ -1,22 +1,27 @@ TN: SF:script/Common.sol -FN:33,Common.setUp -FNDA:22,Common.setUp -DA:35,22 -DA:51,22 -DA:52,22 -DA:53,22 -DA:54,22 -DA:56,22 -FN:72,Common._deployContracts -FNDA:22,Common._deployContracts -DA:73,22 -DA:75,22 -DA:76,22 -FNF:2 -FNH:2 -LF:9 -LH:9 +FN:39,Common.setUp +FNDA:29,Common.setUp +DA:41,29 +DA:57,29 +DA:58,29 +DA:59,29 +DA:60,29 +DA:62,29 +FN:78,Common._deployBuilderManager +FNDA:29,Common._deployBuilderManager +DA:79,29 +DA:81,29 +DA:82,29 +FN:95,Common._deployBuilderManagerAsHarness +FNDA:29,Common._deployBuilderManagerAsHarness +DA:96,29 +DA:98,29 +DA:99,29 +FNF:3 +FNH:3 +LF:12 +LH:12 BRF:0 BRH:0 end_of_record @@ -42,35 +47,35 @@ end_of_record TN: SF:src/contracts/BuildersManager.sol FN:64,BuildersManager.eligible -FNDA:10,BuildersManager.eligible -DA:65,10 +FNDA:23,BuildersManager.eligible +DA:65,23 BRDA:65,0,0,1 -DA:66,9 +DA:66,22 BRDA:66,1,0,1 DA:67,1 BRDA:67,2,0,1 FN:74,BuildersManager. -FNDA:22,BuildersManager. -DA:75,22 +FNDA:58,BuildersManager. +DA:75,58 FN:79,BuildersManager.initialize -FNDA:22,BuildersManager.initialize -DA:86,22 -DA:87,22 +FNDA:58,BuildersManager.initialize +DA:86,58 +DA:87,58 BRDA:87,3,0,- -DA:88,22 +DA:88,58 BRDA:88,4,0,- -DA:89,22 +DA:89,58 BRDA:89,5,0,- -DA:90,22 +DA:90,58 BRDA:90,6,0,- -DA:92,22 -DA:93,22 -DA:95,22 -DA:96,22 -DA:97,22 -DA:99,22 -DA:100,22 -DA:101,66 +DA:92,58 +DA:93,58 +DA:95,58 +DA:96,58 +DA:97,58 +DA:99,58 +DA:100,58 +DA:101,174 FN:108,BuildersManager.vouch FNDA:1,BuildersManager.vouch DA:109,1 @@ -84,8 +89,8 @@ DA:116,5 BRDA:116,9,0,- DA:117,4 FN:121,BuildersManager.vouch -FNDA:4,BuildersManager.vouch -DA:122,4 +FNDA:17,BuildersManager.vouch +DA:122,17 FN:126,BuildersManager.vouch FNDA:1,BuildersManager.vouch DA:127,1 @@ -98,12 +103,12 @@ BRDA:133,11,0,- BRDA:133,11,1,3 DA:134,3 FN:138,BuildersManager.distributeYield -FNDA:0,BuildersManager.distributeYield -DA:139,0 -DA:140,0 -BRDA:140,12,0,- -DA:141,0 -BRDA:141,13,0,- +FNDA:2,BuildersManager.distributeYield +DA:139,2 +DA:140,2 +BRDA:140,12,0,1 +DA:141,1 +BRDA:141,13,0,1 DA:142,0 DA:144,0 DA:145,0 @@ -137,21 +142,21 @@ FN:186,BuildersManager.settings FNDA:3,BuildersManager.settings DA:187,3 FN:191,BuildersManager.currentProjects -FNDA:0,BuildersManager.currentProjects -DA:192,0 +FNDA:6,BuildersManager.currentProjects +DA:192,6 FN:196,BuildersManager.optimismFoundationAttesters -FNDA:0,BuildersManager.optimismFoundationAttesters -DA:197,0 +FNDA:1,BuildersManager.optimismFoundationAttesters +DA:197,1 FN:203,BuildersManager.hashProject -FNDA:22,BuildersManager.hashProject -DA:204,33 +FNDA:29,BuildersManager.hashProject +DA:204,40 FN:228,BuildersManager._vouch -FNDA:10,BuildersManager._vouch -DA:229,7 -DA:230,7 -DA:231,7 -DA:232,7 -BRDA:232,18,0,- +FNDA:23,BuildersManager._vouch +DA:229,20 +DA:230,20 +DA:231,20 +DA:232,20 +BRDA:232,18,0,4 FN:241,BuildersManager._validateOptimismVoter FNDA:10,BuildersManager._validateOptimismVoter DA:242,0 @@ -259,11 +264,11 @@ BRDA:346,40,0,- DA:347,0 DA:348,0 FNF:22 -FNH:18 +FNH:21 LF:115 -LH:75 +LH:80 BRF:58 -BRH:30 +BRH:33 end_of_record TN: SF:test/integration/IntegrationBase.sol @@ -285,18 +290,18 @@ end_of_record TN: SF:test/unit/UnitBuildersManagerBase.sol FN:33,UnitBuildersManagerBase.setUp -FNDA:22,UnitBuildersManagerBase.setUp -DA:34,22 -DA:35,22 -DA:36,22 -DA:38,22 -DA:39,22 -DA:40,22 -DA:42,22 -DA:43,22 -DA:44,22 -DA:46,22 -DA:47,22 +FNDA:29,UnitBuildersManagerBase.setUp +DA:34,29 +DA:35,29 +DA:36,29 +DA:38,29 +DA:39,29 +DA:40,29 +DA:41,29 +DA:43,29 +DA:44,29 +DA:45,29 +DA:47,29 FN:52,UnitBuildersManagerBase._mockVerifyIdentityAttestation FNDA:11,UnitBuildersManagerBase._mockVerifyIdentityAttestation DA:53,11 @@ -306,29 +311,46 @@ DA:61,5 DA:64,5 DA:70,5 DA:76,5 -FN:81,UnitBuildersManagerBase._rsvFromJson -FNDA:22,UnitBuildersManagerBase._rsvFromJson -DA:82,22 -DA:84,22 -DA:85,22 -DA:86,22 -FN:89,UnitBuildersManagerBase._getSchemaHash -FNDA:22,UnitBuildersManagerBase._getSchemaHash -DA:90,22 -DA:91,22 -FN:94,UnitBuildersManagerBase._createOffchainAttestationsFromJson -FNDA:22,UnitBuildersManagerBase._createOffchainAttestationsFromJson -DA:99,22 -DA:101,22 -DA:118,22 -FN:121,UnitBuildersManagerBase._createIdentityAttestations -FNDA:44,UnitBuildersManagerBase._createIdentityAttestations -DA:122,44 -DA:124,44 -FNF:7 -FNH:7 -LF:27 -LH:27 +FN:81,UnitBuildersManagerBase._getSchemaHash +FNDA:29,UnitBuildersManagerBase._getSchemaHash +DA:82,29 +DA:83,29 +FN:86,UnitBuildersManagerBase._createOffchainAttestationsFromJson +FNDA:29,UnitBuildersManagerBase._createOffchainAttestationsFromJson +DA:91,29 +DA:93,29 +DA:110,29 +FN:113,UnitBuildersManagerBase._createIdentityAttestations +FNDA:58,UnitBuildersManagerBase._createIdentityAttestations +DA:114,58 +DA:116,58 +FNF:6 +FNH:6 +LF:23 +LH:23 +BRF:0 +BRH:0 +end_of_record +TN: +SF:test/unit/harness/BuilderManagerHarness.sol +FN:7,BuilderManagerHarness.populateEligibleProjects +FNDA:5,BuilderManagerHarness.populateEligibleProjects +DA:8,5 +DA:9,15 +FN:13,BuilderManagerHarness.populateEligibleVoters +FNDA:5,BuilderManagerHarness.populateEligibleVoters +DA:14,5 +DA:15,20 +FN:19,BuilderManagerHarness.populateCurrentProjects +FNDA:1,BuilderManagerHarness.populateCurrentProjects +DA:20,1 +DA:21,3 +DA:22,3 +DA:23,3 +FNF:3 +FNH:3 +LF:8 +LH:8 BRF:0 BRH:0 end_of_record diff --git a/script/Common.sol b/script/Common.sol index 7e4b32f..13d7a30 100644 --- a/script/Common.sol +++ b/script/Common.sol @@ -1,9 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; +import {BuildersDollar} from '@builders-dollar-token/BuildersDollar.sol'; +import {EIP173ProxyWithReceive} from '@builders-dollar-token/vendor/EIP173ProxyWithReceive.sol'; import {TransparentUpgradeableProxy} from '@oz/proxy/transparent/TransparentUpgradeableProxy.sol'; import {BuildersManager, IBuildersManager} from 'contracts/BuildersManager.sol'; import {Script} from 'forge-std/Script.sol'; +import {BuilderManagerHarness} from 'test/unit/harness/BuilderManagerHarness.sol'; // solhint-disable-next-line import 'script/Registry.sol'; @@ -22,9 +25,10 @@ struct DeploymentParams { * @dev This contract is intended for use in Scripts and Integration Tests */ contract Common is Script { + /// @notice BuildersManager contract IBuildersManager public buildersManager; - // @notice Deployer address will be the owner of the proxy + /// @notice Deployer address will be the owner of the proxy address public deployer; /// @notice Deployment parameters for each chain @@ -69,11 +73,11 @@ contract Common is Script { }); } - function _deployContracts() internal { + function _deployBuildersManager() internal returns (BuildersManager _buildersManager) { DeploymentParams memory _s = _deploymentParams[block.chainid]; address _implementation = address(new BuildersManager()); - buildersManager = BuildersManager( + _buildersManager = BuildersManager( address( new TransparentUpgradeableProxy( _implementation, @@ -85,4 +89,38 @@ contract Common is Script { ) ); } + + function _deployBuildersManagerAsHarness() internal returns (BuilderManagerHarness _buildersManagerHarness) { + DeploymentParams memory _s = _deploymentParams[block.chainid]; + + address _implementation = address(new BuilderManagerHarness()); + _buildersManagerHarness = BuilderManagerHarness( + address( + new TransparentUpgradeableProxy( + _implementation, + deployer, + abi.encodeWithSelector( + IBuildersManager.initialize.selector, _s.token, _s.eas, _s.name, _s.version, _s.settings + ) + ) + ) + ); + } + + function _deployBuildersDollar() internal returns (BuildersDollar _buildersDollar, EIP173ProxyWithReceive _proxy) { + address _dai = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; + address _aDai = 0x82E64f49Ed5EC1bC6e43DAD4FC8Af9bb3A2312EE; + address _aavePool = 0x794a61358D6845594F94dc1DB02A252b5b4814aD; + address _aaveRewards = 0x929EC64c34a17401F460460D4B9390518E5B473e; + string memory _name = 'Builders Dollar'; + string memory _symbol = 'OBDUSD'; + + _buildersDollar = new BuildersDollar(_dai, _aDai, _aavePool, _aaveRewards); + + _proxy = new EIP173ProxyWithReceive( + address(_buildersDollar), + address(this), + abi.encodeWithSelector(_buildersDollar.initialize.selector, _name, _symbol) + ); + } } diff --git a/script/Deploy.sol b/script/Deploy.sol index 5a68c4e..051da52 100644 --- a/script/Deploy.sol +++ b/script/Deploy.sol @@ -13,7 +13,7 @@ contract Deploy is Common { function run() public { vm.startBroadcast(); - _deployContracts(); + _deployBuildersManager(); vm.stopBroadcast(); } } diff --git a/src/contracts/BuildersManager.sol b/src/contracts/BuildersManager.sol index 767e0ce..a645179 100644 --- a/src/contracts/BuildersManager.sol +++ b/src/contracts/BuildersManager.sol @@ -130,7 +130,7 @@ contract BuildersManager is EIP712Upgradeable, Ownable2StepUpgradeable, IBuilder /// @inheritdoc IBuildersManager function validateOptimismVoter(bytes32 _identityAttestation) external returns (bool _verified) { - if (optimismFoundationAttester[msg.sender]) _verified = true; + if (eligibleVoter[msg.sender]) _verified = true; else _verified = _validateOptimismVoter(_identityAttestation, msg.sender); } diff --git a/test/integration/E2EDeploy.t.sol b/test/integration/E2EDeploy.t.sol index 371e338..8e9cb32 100644 --- a/test/integration/E2EDeploy.t.sol +++ b/test/integration/E2EDeploy.t.sol @@ -9,7 +9,7 @@ contract E2EDeploy is Deploy, Test { function setUp() public override { super.setUp(); vm.startPrank(deployer); - _deployContracts(); + buildersManager = _deployBuildersManager(); vm.stopPrank(); } diff --git a/test/integration/IntegrationBase.sol b/test/integration/IntegrationBase.sol index 9b78fde..ab58117 100644 --- a/test/integration/IntegrationBase.sol +++ b/test/integration/IntegrationBase.sol @@ -18,7 +18,7 @@ contract IntegrationBase is Common, Test { vm.createSelectFork(vm.rpcUrl('gnosis')); vm.startPrank(owner); - _deployContracts(); + _deployBuildersManager(); vm.stopPrank(); } } diff --git a/test/unit/UnitBuildersManager.t.sol b/test/unit/UnitBuildersManager.t.sol index 09c0df5..1546a98 100644 --- a/test/unit/UnitBuildersManager.t.sol +++ b/test/unit/UnitBuildersManager.t.sol @@ -7,7 +7,7 @@ import {IBuildersManager} from 'interfaces/IBuildersManager.sol'; import 'script/Registry.sol'; import {UnitBuildersManagerBase} from 'test/unit/UnitBuildersManagerBase.sol'; -// TODO: add initial state tests to deploy test +// TODO: add tests for individual attributes of attestations contract UnitBuildersManagerTestInitialState is UnitBuildersManagerBase { function testInitialState() public view { IBuildersManager.BuilderManagerSettings memory _settings = buildersManager.settings(); @@ -45,6 +45,7 @@ contract UnitBuildersManagerTestAccessControl is UnitBuildersManagerBase { super.setUp(); } + /// @notice test the owner function testOwner() public view { assertEq(Ownable(address(buildersManager)).owner(), owner); } @@ -146,7 +147,7 @@ contract UnitBuildersManagerTestAccessControl is UnitBuildersManagerBase { } } -contract UnitBuildersManagerTestVouch is UnitBuildersManagerBase { +contract UnitBuildersManagerTestVouchCases is UnitBuildersManagerBase { /// @notice test offchain attestation creation function testOffchainAttestationCreation() public view { assertEq(offchainAttestation.version, 1); @@ -296,4 +297,107 @@ contract UnitBuildersManagerTestVouch is UnitBuildersManagerBase { } } -contract UnitBuildersManagerTestDistributeYield is UnitBuildersManagerBase {} +contract UnitBuildersManagerTestHarness is UnitBuildersManagerBase { + bytes32 public projectAttestationHash1 = keccak256('projectAttestationHash1'); + bytes32 public projectAttestationHash2 = keccak256('projectAttestationHash2'); + bytes32 public projectAttestationHash3 = keccak256('projectAttestationHash3'); + + address public voter1 = makeAddr('voter1'); + address public voter2 = makeAddr('voter2'); + address public voter3 = makeAddr('voter3'); + address public voter4 = makeAddr('voter4'); + + address public attacker = makeAddr('attacker'); + bytes32 public invalidProjectAttestationHash = keccak256('invalidProjectAttestationHash'); + + address[] public eligibleVoters = [voter1, voter2, voter3, voter4]; + bytes32[] public eligibleProjects = [projectAttestationHash1, projectAttestationHash2, projectAttestationHash3]; + + function setUp() public override { + super.setUp(); + buildersManagerHarness.populateEligibleProjects(eligibleProjects); + buildersManagerHarness.populateEligibleVoters(eligibleVoters); + } + + /// @notice test the initial state + function testInitialState() public view { + assertTrue(buildersManagerHarness.eligibleProject(projectAttestationHash1) != address(0)); + assertTrue(buildersManagerHarness.eligibleProject(projectAttestationHash2) != address(0)); + assertTrue(buildersManagerHarness.eligibleProject(projectAttestationHash3) != address(0)); + + assertTrue(buildersManagerHarness.eligibleVoter(voter1)); + assertTrue(buildersManagerHarness.eligibleVoter(voter2)); + assertTrue(buildersManagerHarness.eligibleVoter(voter3)); + assertTrue(buildersManagerHarness.eligibleVoter(voter4)); + + assertFalse(buildersManagerHarness.eligibleVoter(attacker)); + assertTrue(buildersManagerHarness.eligibleProject(invalidProjectAttestationHash) == address(0)); + + address[] memory _currentProjects = buildersManagerHarness.currentProjects(); + assertEq(_currentProjects.length, 0); + } + + /// @notice test adding a project after the minimum vouches + function testAddProjectAfterMinimumVouches() public { + vm.prank(voter1); + buildersManagerHarness.vouch(projectAttestationHash1); + + vm.prank(voter2); + buildersManagerHarness.vouch(projectAttestationHash1); + + address[] memory _currentProjectsAfter2Votes = buildersManagerHarness.currentProjects(); + assertEq(_currentProjectsAfter2Votes.length, 0); + + vm.prank(voter3); + buildersManagerHarness.vouch(projectAttestationHash1); + + address[] memory _currentProjectsAfter3Votes = buildersManagerHarness.currentProjects(); + assertEq(_currentProjectsAfter3Votes.length, 1); + assertEq(_currentProjectsAfter3Votes[0], address(buildersManagerHarness.eligibleProject(projectAttestationHash1))); + + vm.prank(voter4); + buildersManagerHarness.vouch(projectAttestationHash1); + + address[] memory _currentProjectsAfter4Votes = buildersManagerHarness.currentProjects(); + assertEq(_currentProjectsAfter4Votes.length, _currentProjectsAfter3Votes.length); + } + + /// @notice test adding a project after the minimum vouches for multiple projects + function testAddProjectAfterMinimumVouchesForMultipleProjects() public { + vm.startPrank(voter1); + buildersManagerHarness.vouch(projectAttestationHash1); + buildersManagerHarness.vouch(projectAttestationHash2); + buildersManagerHarness.vouch(projectAttestationHash3); + vm.stopPrank(); + + vm.startPrank(voter2); + buildersManagerHarness.vouch(projectAttestationHash1); + buildersManagerHarness.vouch(projectAttestationHash2); + buildersManagerHarness.vouch(projectAttestationHash3); + vm.stopPrank(); + + vm.startPrank(voter3); + buildersManagerHarness.vouch(projectAttestationHash1); + buildersManagerHarness.vouch(projectAttestationHash2); + buildersManagerHarness.vouch(projectAttestationHash3); + vm.stopPrank(); + + address[] memory _currentProjectsAfter3X3Votes = buildersManagerHarness.currentProjects(); + assertEq(_currentProjectsAfter3X3Votes.length, 3); + } + + /// @notice test distributing yield when there are no projects + function testDistributeYieldRevertNoProjects() public { + vm.expectRevert(abi.encodeWithSelector(IBuildersManager.YieldNoProjects.selector)); + buildersManagerHarness.distributeYield(); + } + + /// @notice test distributing yield after the cycle is ready + function testDistributeYieldRevertAfterCycleReady() public { + buildersManagerHarness.populateCurrentProjects(eligibleProjects); + vm.expectRevert(abi.encodeWithSelector(IBuildersManager.CycleNotReady.selector)); + buildersManagerHarness.distributeYield(); + } + + // TODO: setup the builders dollar and add tests for the yield distribution +} diff --git a/test/unit/UnitBuildersManagerBase.sol b/test/unit/UnitBuildersManagerBase.sol index 79a0a31..c858fcd 100644 --- a/test/unit/UnitBuildersManagerBase.sol +++ b/test/unit/UnitBuildersManagerBase.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.23; +import {BuildersDollar} from '@builders-dollar-token/BuildersDollar.sol'; +import {EIP173ProxyWithReceive} from '@builders-dollar-token/vendor/EIP173ProxyWithReceive.sol'; import {Attestation, Signature} from '@eas/Common.sol'; import {IEAS} from '@eas/IEAS.sol'; import {ISchemaRegistry, SchemaRecord} from '@eas/ISchemaRegistry.sol'; @@ -10,10 +12,17 @@ import {Test} from 'forge-std/Test.sol'; import {OffchainAttestation} from 'interfaces/IEasExtensions.sol'; import {Common} from 'script/Common.sol'; import 'script/Registry.sol'; +import {BuilderManagerHarness} from 'test/unit/harness/BuilderManagerHarness.sol'; contract UnitBuildersManagerBase is Test, Common { uint256 public constant INIT_BALANCE = 1 ether; + /// @notice BuildersManagerHarness contract for unit tests + BuilderManagerHarness public buildersManagerHarness; + + BuildersDollar public buildersDollar; + EIP173ProxyWithReceive public bdProxy; + string public configPath = string(bytes('./test/unit/example_project_attestation.json')); address public owner = makeAddr('owner'); @@ -36,14 +45,15 @@ contract UnitBuildersManagerBase is Test, Common { deployer = owner; vm.startPrank(owner); - _deployContracts(); + buildersManager = _deployBuildersManager(); + (buildersDollar, bdProxy) = _deployBuildersDollar(); + buildersManagerHarness = _deployBuildersManagerAsHarness(); vm.stopPrank(); (offchainAttestation, offchainAttestationHash) = _createOffchainAttestationsFromJson(); identityAttestation1 = _createIdentityAttestations(ANVIL_VOTER_1); identityAttestation2 = _createIdentityAttestations(ANVIL_VOTER_2); - (r, s, v) = _rsvFromJson(); schemaHash = _getSchemaHash(); } @@ -78,14 +88,6 @@ contract UnitBuildersManagerBase is Test, Common { ); } - function _rsvFromJson() internal view returns (bytes32 _r, bytes32 _s, uint8 _v) { - string memory _configData = vm.readFile(configPath); - - _r = bytes32(stdJson.readBytes32(_configData, '.sig.signature.r')); - _s = bytes32(stdJson.readBytes32(_configData, '.sig.signature.s')); - _v = uint8(stdJson.readUint(_configData, '.sig.signature.v')); - } - function _getSchemaHash() internal view returns (bytes32 _hash) { string memory _configData = vm.readFile(configPath); _hash = bytes32(stdJson.readBytes32(_configData, '.sig.message.schema')); diff --git a/test/unit/harness/BuilderManagerHarness.sol b/test/unit/harness/BuilderManagerHarness.sol new file mode 100644 index 0000000..aaf750c --- /dev/null +++ b/test/unit/harness/BuilderManagerHarness.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {BuildersManager} from 'contracts/BuildersManager.sol'; + +contract BuilderManagerHarness is BuildersManager { + function populateEligibleProjects(bytes32[] memory _projectAttestations) public { + for (uint256 i = 0; i < _projectAttestations.length; i++) { + eligibleProject[_projectAttestations[i]] = address(uint160(0x420 + i)); + } + } + + function populateEligibleVoters(address[] memory _voters) public { + for (uint256 i = 0; i < _voters.length; i++) { + eligibleVoter[_voters[i]] = true; + } + } + + function populateCurrentProjects(bytes32[] memory _projectAttestations) public { + for (uint256 i = 0; i < _projectAttestations.length; i++) { + address _project = address(uint160(0x420 + i)); + eligibleProject[_projectAttestations[i]] = _project; + _currentProjects.push(_project); + } + } +}