diff --git a/script/Common.sol b/script/Common.sol index d48fccd..8cefddf 100644 --- a/script/Common.sol +++ b/script/Common.sol @@ -1,40 +1,42 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.28; -import {IERC20} from '@openzeppelin/token/ERC20/IERC20.sol'; -import {Greeter, IGreeter} from 'contracts/Greeter.sol'; +import {ProxyAdmin} from '@openzeppelin/proxy/transparent/ProxyAdmin.sol'; +import {TransparentUpgradeableProxy} from '@openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol'; import {Script} from 'forge-std/Script.sol'; -// solhint-disable-next-line -import 'script/Registry.sol'; + +import {SavingCircles} from '../src/contracts/SavingCircles.sol'; /** * @title Common Contract * @author Breadchain - * @notice This contract is used to deploy the Greeter contract + * @notice This contract is used to deploy an upgradable Saving Circles contract * @dev This contract is intended for use in Scripts and Integration Tests */ contract Common is Script { - struct DeploymentParams { - string greeting; - IERC20 token; - } - - IGreeter public greeter; + function setUp() public virtual {} - /// @notice Deployment parameters for each chain - mapping(uint256 _chainId => DeploymentParams _params) internal _deploymentParams; - - function setUp() public virtual { - // Optimism - _deploymentParams[10] = DeploymentParams('Hello, Optimism!', IERC20(OPTIMISM_DAI)); + function _deploySavingCircles() internal returns (SavingCircles) { + return new SavingCircles(); + } - // Gnosis - _deploymentParams[100] = DeploymentParams('Hello, Gnosis!', IERC20(GNOSIS_BREAD)); + function _deployProxyAdmin(address _admin) internal returns (ProxyAdmin) { + return new ProxyAdmin(_admin); } - function _deployContracts() internal { - DeploymentParams memory _params = _deploymentParams[block.chainid]; + function _deployTransparentProxy( + address _implementation, + address _proxyAdmin, + bytes memory _initData + ) internal returns (TransparentUpgradeableProxy) { + return new TransparentUpgradeableProxy(_implementation, _proxyAdmin, _initData); + } - greeter = new Greeter(_params.greeting, _params.token); + function _deployContracts(address _admin) internal returns (TransparentUpgradeableProxy) { + return _deployTransparentProxy( + address(_deploySavingCircles()), + address(_deployProxyAdmin(_admin)), + abi.encodeWithSelector(SavingCircles.initialize.selector, _admin) + ); } } diff --git a/script/Deploy.sol b/script/Deploy.sol index 28bef9e..bb1fdfa 100644 --- a/script/Deploy.sol +++ b/script/Deploy.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.28; import {Common} from 'script/Common.sol'; contract Deploy is Common { - function run() public { + function run(address _admin) public { vm.startBroadcast(); - _deployContracts(); + _deployContracts(_admin); vm.stopBroadcast(); } diff --git a/script/DeploySavingsCircle.s.sol b/script/DeploySavingsCircle.s.sol deleted file mode 100644 index 890baaf..0000000 --- a/script/DeploySavingsCircle.s.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; - -import {SavingCircles} from '../src/contracts/SavingCircles.sol'; - -import {ProxyAdmin} from '@openzeppelin/proxy/transparent/ProxyAdmin.sol'; -import {TransparentUpgradeableProxy} from '@openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol'; -import {Script} from 'forge-std/Script.sol'; - -contract DeploySavingCircles is Script { - function run() external returns (address proxy, address admin) { - vm.startBroadcast(); - - // Deploy implementation - SavingCircles implementation = new SavingCircles(); - - // Deploy ProxyAdmin - ProxyAdmin proxyAdmin = new ProxyAdmin(msg.sender); - - // Encode initialization call - bytes memory initData = abi.encodeWithSelector(SavingCircles.initialize.selector); - - // Deploy proxy - TransparentUpgradeableProxy transparentProxy = - new TransparentUpgradeableProxy(address(implementation), address(proxyAdmin), initData); - - vm.stopBroadcast(); - return (address(transparentProxy), address(proxyAdmin)); - } -} diff --git a/test/integration/Greeter.t.sol b/test/integration/Greeter.t.sol deleted file mode 100644 index 6bd0bdc..0000000 --- a/test/integration/Greeter.t.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.28; - -import {IntegrationBase} from 'test/integration/IntegrationBase.sol'; - -contract IntegrationGreeter is IntegrationBase { - string internal _newGreeting; - - function setUp() public override { - /// @dev override for more specific setup - super.setUp(); - _newGreeting = 'Hello, Breadchain!'; - } - - function test_Greet() public { - DeploymentParams memory _params = _deploymentParams[block.chainid]; - - (string memory _initialGreeting, uint256 _balance) = greeter.greet(); - assertEq(_params.greeting, _initialGreeting); - assertEq(INIT_BALANCE, _balance); - - vm.prank(owner); - greeter.setGreeting(_newGreeting); - - (string memory _currentGreeting,) = greeter.greet(); - assertEq(_currentGreeting, greeter.greeting()); - } -} diff --git a/test/integration/IntegrationBase.sol b/test/integration/IntegrationBase.sol index 3b738cb..afe4d48 100644 --- a/test/integration/IntegrationBase.sol +++ b/test/integration/IntegrationBase.sol @@ -1,30 +1,83 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.28; -import {IERC20} from '@openzeppelin/token/ERC20/IERC20.sol'; import {Test} from 'forge-std/Test.sol'; import {Common} from 'script/Common.sol'; +import {SavingCircles} from '../../src/contracts/SavingCircles.sol'; +import {ISavingCircles} from '../../src/interfaces/ISavingCircles.sol'; +import {MockERC20} from '../mocks/MockERC20.sol'; + // solhint-disable-next-line import 'script/Registry.sol'; contract IntegrationBase is Common, Test { - uint256 public constant INIT_BALANCE = 1 ether; + SavingCircles public circle; + MockERC20 public token; - address public user = makeAddr('user'); + address public alice = makeAddr('alice'); + address public bob = makeAddr('bob'); + address public carol = makeAddr('carol'); address public owner = makeAddr('owner'); + address[] public members; + + ISavingCircles.Circle public baseCircle; - IERC20 public bread = IERC20(GNOSIS_BREAD); + string public constant BASE_CIRCLE_NAME = 'Test Circle'; + uint256 public constant DEPOSIT_AMOUNT = 1000e18; + uint256 public constant DEPOSIT_INTERVAL = 7 days; + uint256 public constant BASE_CURRENT_INDEX = 0; + uint256 public constant BASE_MAX_DEPOSITS = 1000; + bytes32 public constant BASE_CIRCLE_ID = keccak256(abi.encodePacked(BASE_CIRCLE_NAME)); function setUp() public virtual override { super.setUp(); - vm.createSelectFork(vm.rpcUrl('gnosis')); + vm.startPrank(owner); + circle = SavingCircles(address(_deployContracts(owner))); + token = new MockERC20('Test Token', 'TEST'); + vm.stopPrank(); + + _setUpAccounts(); + + baseCircle = ISavingCircles.Circle({ + owner: alice, + name: BASE_CIRCLE_NAME, + members: members, + currentIndex: BASE_CURRENT_INDEX, + circleStart: block.timestamp, + token: address(token), + depositAmount: DEPOSIT_AMOUNT, + depositInterval: DEPOSIT_INTERVAL, + maxDeposits: BASE_MAX_DEPOSITS + }); + } - /// @dev deploy contracts methods are located in script/Common.sol - _deployContracts(); + function createBaseCircle() public { + vm.prank(owner); + circle.setTokenAllowed(address(token), true); + + vm.prank(alice); + circle.create(baseCircle); + } + + function _setUpAccounts() internal { + vm.startPrank(alice); + token.mint(alice, DEPOSIT_AMOUNT * 10); + token.approve(address(circle), type(uint256).max); + members.push(alice); + vm.stopPrank(); + + vm.startPrank(bob); + token.mint(bob, DEPOSIT_AMOUNT * 10); + token.approve(address(circle), type(uint256).max); + members.push(bob); + vm.stopPrank(); + vm.startPrank(carol); + token.mint(carol, DEPOSIT_AMOUNT * 10); + token.approve(address(circle), type(uint256).max); + members.push(carol); vm.stopPrank(); - deal(GNOSIS_BREAD, address(greeter), INIT_BALANCE); } } diff --git a/test/integration/SavingCircles.t.sol b/test/integration/SavingCircles.t.sol index 7f939d7..ca29249 100644 --- a/test/integration/SavingCircles.t.sol +++ b/test/integration/SavingCircles.t.sol @@ -6,84 +6,14 @@ import {ProxyAdmin} from '@openzeppelin/proxy/transparent/ProxyAdmin.sol'; import {TransparentUpgradeableProxy} from '@openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol'; import {Test} from 'forge-std/Test.sol'; -import {SavingCircles} from '../../src/contracts/SavingCircles.sol'; import {ISavingCircles} from '../../src/interfaces/ISavingCircles.sol'; -import {MockERC20} from '../mocks/MockERC20.sol'; +import {IntegrationBase} from 'test/integration/IntegrationBase.sol'; /* solhint-disable func-name-mixedcase */ -contract SavingCirclesIntegration is Test { - SavingCircles public circle; - MockERC20 public token; - - address public alice = makeAddr('alice'); - address public bob = makeAddr('bob'); - address public carol = makeAddr('carol'); - address public owner = makeAddr('owner'); - address[] public members; - - string public constant BASE_CIRCLE_NAME = 'Test Circle'; - uint256 public constant DEPOSIT_AMOUNT = 1000e18; - uint256 public constant DEPOSIT_INTERVAL = 7 days; - uint256 public constant BASE_CURRENT_INDEX = 0; - uint256 public constant BASE_MAX_DEPOSITS = 1000; - bytes32 public constant BASE_CIRCLE_ID = keccak256(abi.encodePacked(BASE_CIRCLE_NAME)); - - ISavingCircles.Circle public baseCircle; - - function setUp() public { - vm.startPrank(owner); - circle = SavingCircles( - address( - new TransparentUpgradeableProxy( - address(new SavingCircles()), - address(new ProxyAdmin(owner)), - abi.encodeWithSelector(SavingCircles.initialize.selector, owner) - ) - ) - ); - - token = new MockERC20('Test Token', 'TEST'); - vm.stopPrank(); - - // Setup test accounts - vm.startPrank(alice); - token.mint(alice, DEPOSIT_AMOUNT * 10); - token.approve(address(circle), type(uint256).max); - members.push(alice); - vm.stopPrank(); - - vm.startPrank(bob); - token.mint(bob, DEPOSIT_AMOUNT * 10); - token.approve(address(circle), type(uint256).max); - members.push(bob); - vm.stopPrank(); - - vm.startPrank(carol); - token.mint(carol, DEPOSIT_AMOUNT * 10); - token.approve(address(circle), type(uint256).max); - members.push(carol); - vm.stopPrank(); - - baseCircle = ISavingCircles.Circle({ - owner: alice, - name: BASE_CIRCLE_NAME, - members: members, - currentIndex: BASE_CURRENT_INDEX, - circleStart: block.timestamp, - token: address(token), - depositAmount: DEPOSIT_AMOUNT, - depositInterval: DEPOSIT_INTERVAL, - maxDeposits: BASE_MAX_DEPOSITS - }); - } - - function createBaseCircle() public { - vm.prank(owner); - circle.setTokenAllowed(address(token), true); - - vm.prank(alice); - circle.create(baseCircle); +contract SavingCirclesIntegration is IntegrationBase { + function setUp() public override { + super.setUp(); } function test_SetTokenAllowed() public { diff --git a/test/unit/Greeter.t.sol b/test/unit/Greeter.t.sol deleted file mode 100644 index d8901b1..0000000 --- a/test/unit/Greeter.t.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.28; - -import {IERC20} from '@openzeppelin/token/ERC20/IERC20.sol'; -import {Greeter, IGreeter} from 'contracts/Greeter.sol'; -import {Test} from 'forge-std/Test.sol'; - -contract UnitGreeter is Test { -/* - address internal _owner = makeAddr('owner'); - IERC20 internal _token = IERC20(makeAddr('token')); - uint256 internal _initialBalance = 100; - string internal _initialGreeting = 'hola'; - - Greeter internal _greeter; - - event GreetingSet(string _greeting); - - function setUp() external { - vm.prank(_owner); - _greeter = new Greeter(_initialGreeting, _token); - - vm.etch(address(_token), new bytes(0x1)); - } - - function test_EmptyTestExample() external { - // it does nothing - vm.skip(true); - } - - function test_ConstructorWhenPassingValidGreetingString() external { - vm.prank(_owner); - - // it deploys - _greeter = new Greeter(_initialGreeting, _token); - - // it sets the greeting string - assertEq(_greeter.greeting(), _initialGreeting); - - // it sets the owner as sender - assertEq(_greeter.OWNER(), _owner); - - // it sets the token used - assertEq(address(_greeter.token()), address(_token)); - } - - function test_ConstructorWhenPassingAnEmptyGreetingString() external { - vm.prank(_owner); - - // it reverts - vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector); - _greeter = new Greeter('', _token); - } - - function test_GreetWhenCalled() external { - vm.mockCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(_initialBalance)); - vm.expectCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector)); - (string memory _greet, uint256 _balance) = _greeter.greet(); - - // it returns the greeting string - assertEq(_greet, _initialGreeting); - - // it returns the token balance of the contract - assertEq(_balance, _initialBalance); - } - - modifier whenCalledByTheOwner() { - vm.startPrank(_owner); - _; - vm.stopPrank(); - } - - function test_SetGreetingWhenPassingAValidGreetingString() external whenCalledByTheOwner { - string memory _newGreeting = 'hello'; - - // it emit GreetingSet - vm.expectEmit(true, true, true, true, address(_greeter)); - emit GreetingSet(_newGreeting); - - _greeter.setGreeting(_newGreeting); - - // it sets the greeting string - assertEq(_greeter.greeting(), _newGreeting); - } - - function test_SetGreetingWhenPassingAnEmptyGreetingString() external whenCalledByTheOwner { - // it reverts - vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector); - _greeter.setGreeting(''); - } - - function test_SetGreetingWhenCalledByANon_owner(address _caller) external { - vm.assume(_caller != _owner); - vm.prank(_caller); - - // it reverts - vm.expectRevert(IGreeter.Greeter_OnlyOwner.selector); - _greeter.setGreeting('new greeting'); - } -*/ -} diff --git a/test/unit/Greeter.tree b/test/unit/Greeter.tree deleted file mode 100644 index bbc74ea..0000000 --- a/test/unit/Greeter.tree +++ /dev/null @@ -1,25 +0,0 @@ -Greeter::constructor -├── when passing valid greeting string -│ ├── it deploys -│ ├── it sets the greeting string -│ ├── it sets the owner as sender -│ └── it sets the token used -└── when passing an empty greeting string - └── it reverts - - -Greeter::greet -└── when called - ├── it returns the greeting string - └── it returns the token balance of the contract - - -Greeter::setGreeting -├── when called by the owner -│ ├── when passing a valid greeting string -│ │ ├── it sets the greeting string -│ │ └── it emit GreetingSet -│ └── when passing an empty greeting string -│ └── it reverts -└── when called by a non-owner - └── it reverts \ No newline at end of file