diff --git a/brownie-config.yaml b/brownie-config.yaml index 116a835..7c7bb34 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -7,5 +7,3 @@ networks: mnemonic: brownie fork: https://optimism-mainnet.wallet.coinbase.com evm_version: shanghai -dependencies: - - openzeppelin/openzeppelin-contracts@5.0.2 diff --git a/contracts/helpers/FactoryRegistry.sol b/contracts/helpers/FactoryRegistry.sol deleted file mode 100644 index 51089bf..0000000 --- a/contracts/helpers/FactoryRegistry.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.19 <0.9.0; - -import {Ownable} from "openzeppelin/openzeppelin-contracts@5.0.2/contracts/access/Ownable.sol"; -import {EnumerableSet} from "openzeppelin/openzeppelin-contracts@5.0.2/contracts/utils/structs/EnumerableSet.sol"; - -/// @title Sugar Factory Registry -/// @author @velodrome.finance -/// @notice Sugar Factory Registry to keep track of leaf pool factories -contract FactoryRegistry is Ownable { - using EnumerableSet for EnumerableSet.AddressSet; - - /// @dev Array of poolFactories - EnumerableSet.AddressSet private _poolFactories; - - constructor() Ownable(msg.sender) {} - - function approve(address poolFactory) external onlyOwner { - require(!_poolFactories.contains(poolFactory), "AE"); - _poolFactories.add(poolFactory); - } - - function unapprove(address poolFactory) external onlyOwner { - require(_poolFactories.contains(poolFactory), "NE"); - _poolFactories.remove(poolFactory); - } - - function factoriesToPoolFactory(address poolFactory) external pure returns (address, address) { - return (address(0), poolFactory); - } - - function poolFactories() external view returns (address[] memory) { - return _poolFactories.values(); - } -} diff --git a/contracts/helpers/FactoryRegistry.vy b/contracts/helpers/FactoryRegistry.vy new file mode 100644 index 0000000..cf6052f --- /dev/null +++ b/contracts/helpers/FactoryRegistry.vy @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: BUSL-1.1 +# @version ^0.3.10 +# @title Sugar Factory Registry +# @author velodrome.finance +# @notice Sugar Factory Registry to keep track of leaf pool factories + + +MAX_FACTORIES: public(constant(uint256)) = 10 + +owner: public(address) +poolFactories: public(DynArray[address, MAX_FACTORIES]) # camelCase to have same signature as origin registry + +pool_factory_count: uint256 +pool_factory_exists: HashMap[address, bool] + +@external +def __init__(_owner: address): + self.owner = _owner + self.pool_factory_count = 0 + +@internal +def _only_owner(): + assert msg.sender == self.owner, "Ownable: caller is not the owner" + +@external +def approve(pool_factory: address): + self._only_owner() + + # Check if already present + if self.pool_factory_exists[pool_factory]: + raise "Already exists" + + # Add the poolFactory to the list + self.poolFactories[self.pool_factory_count] = pool_factory + self.pool_factory_count += 1 + self.pool_factory_exists[pool_factory] = True + +@external +def unapprove(pool_factory: address): + self._only_owner() + + if self.pool_factory_exists[pool_factory] == False: + raise "Not exists" + + for i in range(MAX_FACTORIES): + if self.poolFactories[i] == pool_factory: + # Remove the pool_factory by shifting elements + for j in range(0, MAX_FACTORIES): + if j < i: continue + if j >= self.pool_factory_count: break + self.poolFactories[j] = self.poolFactories[j + 1] + self.pool_factory_count -= 1 + break + + self.pool_factory_exists[pool_factory] = False + +@external +@view +def factoriesToPoolFactory(poolFactory: address) -> (address, address): + return (empty(address), poolFactory) diff --git a/scripts/deploy.py b/scripts/deploy.py index 6af3ed0..bfbb528 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -1,12 +1,12 @@ # SPDX-License-Identifier: BUSL-1.1 import os -from brownie import accounts, VeSugar, LpSugar, RelaySugar +from brownie import accounts, VeSugar, LpSugar, RelaySugar, FactoryRegistry def main(): contract_name = str(os.getenv('CONTRACT')).lower() - chain_name = str(os.getenv('CHAIN')).upper() + chain_id = os.getenv('CHAIN_ID') if os.getenv('PROD'): account = accounts.load('sugar') @@ -15,26 +15,32 @@ def main(): if 'lp' in contract_name: LpSugar.deploy( - os.getenv(chain_name + '_VOTER'), - os.getenv(chain_name + '_REGISTRY'), - os.getenv(chain_name + '_CONVERTOR'), - os.getenv(chain_name + '_SLIPSTREAM_HELPER'), - os.getenv(chain_name + '_ALM_FACTORY'), + os.getenv(f'VOTER_{chain_id}'), + os.getenv(f'REGISTRY_{chain_id}'), + os.getenv(f'CONVERTOR_{chain_id}'), + os.getenv(f'SLIPSTREAM_HELPER_{chain_id}'), + os.getenv(f'ALM_FACTORY_{chain_id}'), {'from': account} ) if 've' in contract_name: VeSugar.deploy( - os.getenv('VOTER_ADDRESS'), - os.getenv('DIST_ADDRESS'), - os.getenv('GOVERNOR_ADDRESS'), + os.getenv(f'VOTER_{chain_id}'), + os.getenv(f'DIST_{chain_id}'), + os.getenv(f'GOVERNOR_{chain_id}'), {'from': account} ) if 'relay' in contract_name: RelaySugar.deploy( - str(os.getenv('RELAY_REGISTRY_ADDRESSES')).split(','), - os.getenv('VOTER_ADDRESS'), + str(os.getenv(f'RELAY_REGISTRY_ADDRESSES_{chain_id}')).split(','), + os.getenv(f'VOTER_{chain_id}'), + {'from': account} + ) + + if 'registry' in contract_name: + FactoryRegistry.deploy( + account, {'from': account} ) diff --git a/scripts/deployFactoryRegistry.s.sol b/scripts/deployFactoryRegistry.s.sol deleted file mode 100644 index 51adeef..0000000 --- a/scripts/deployFactoryRegistry.s.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.19 <0.9.0; - -import "forge-std/Script.sol"; -import "contracts/helpers/FactoryRegistry.sol"; - -contract DeployFactoryRegistry is Script { - - uint256 public deployPrivateKey = vm.envUint("PRIVATE_KEY_DEPLOY"); - address public deployerAddress = vm.addr(deployPrivateKey); - - function run() public { - - vm.startBroadcast(deployerAddress); - - FactoryRegistry factoryRegistry = new FactoryRegistry(); - - vm.stopBroadcast(); - console.log("FactoryRegistry deployed at: ", address(factoryRegistry)); - - } -} diff --git a/tests/test_factory_registry.py b/tests/test_factory_registry.py new file mode 100644 index 0000000..f75c14d --- /dev/null +++ b/tests/test_factory_registry.py @@ -0,0 +1,46 @@ +import os +import pytest + +from brownie import accounts, FactoryRegistry, reverts + + +@pytest.fixture +def factory_registry(FactoryRegistry, accounts): + # Deploy the contract using the first test account as the owner + yield FactoryRegistry.at(os.getenv('REGISTRY_34443')) + +def test_initial_state(factory_registry): + assert factory_registry.owner() == "0xd42C7914cF8dc24a1075E29C283C581bd1b0d3D3" + + +def test_approve(factory_registry, accounts): + owner = factory_registry.owner() + non_owner = "0x9999999999999999999999999999999999999999" + pool_factory = "0x1111111111111111111111111111111111111111" + pool_factory_count = factory_registry.pool_factory_count() + + # Approve a new pool factory + factory_registry.approve(pool_factory, {'from': owner}) + assert factory_registry.pool_factory_count() == pool_factory_count + 1 + assert factory_registry.poolFactories(0) == pool_factory + assert factory_registry.pool_factory_exists(pool_factory) + +def test_unapprove(factory_registry, accounts): + owner = factory_registry.owner() + pool_factory = "0x1111111111111111111111111111111111111111" + pool_factory_count = factory_registry.pool_factory_count() + + # Approve a pool factory to set up the state for unapprove + factory_registry.approve(pool_factory, {'from': owner}) + assert factory_registry.pool_factory_count() == pool_factory_count + 1 + assert factory_registry.pool_factory_exists(pool_factory) + + # Unapprove the pool factory + factory_registry.unapprove(pool_factory, {'from': owner}) + assert factory_registry.pool_factory_count() == pool_factory_count + assert not factory_registry.pool_factory_exists(pool_factory) + +def test_factories_to_pool_factory(factory_registry): + pool_factory = "0x1111111111111111111111111111111111111111" + result = factory_registry.factoriesToPoolFactory(pool_factory) + assert result == ("0x0000000000000000000000000000000000000000", pool_factory)