diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 460619f1..0af678be 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,35 +20,44 @@ jobs: name: Foundry project runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Check-out the repo + uses: actions/checkout@v3 with: submodules: recursive - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly + - name: Debug Info + run: | + pwd + ls -la + cat foundry.toml + + - name: Install dependencies + run: | + # Install OpenZeppelin contracts version 5.3.0 + forge install OpenZeppelin/openzeppelin-contracts@v5.3.0 + ls -la lib/ + - name: Run Forge build run: | - forge --version + forge remappings forge build --sizes id: build - name: Run Forge tests - run: | - forge test -vvv + run: forge test -vvv id: test - name: Run gas report - run: | - forge test --gas-report > gas-report.txt + run: forge test --gas-report > gas-report.txt if: always() id: gas - name: Archive gas report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: gas-report - path: gas-report.txt \ No newline at end of file + path: gas-report.txt diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..58d55b4c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[INSERT CONTACT METHOD]. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..be7c9644 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,197 @@ +# Contributing to CC Protocol + +Thank you for your interest in contributing to the Creative Crowdfunding Protocol! This document provides detailed guidelines to help you contribute effectively. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Getting Started](#getting-started) + - [Issues](#issues) + - [Development Environment](#development-environment) +- [Development Workflow](#development-workflow) + - [Branching Strategy](#branching-strategy) + - [Making Changes](#making-changes) + - [Testing](#testing) + - [Documentation](#documentation) +- [Smart Contract Development Guidelines](#smart-contract-development-guidelines) + - [Security Best Practices](#security-best-practices) + - [Gas Optimization](#gas-optimization) + - [Code Style](#code-style) +- [Pull Request Process](#pull-request-process) + - [PR Requirements](#pr-requirements) + - [Review Process](#review-process) +- [Community](#community) + +## Code of Conduct + +Please read our [Code of Conduct](./CODE_OF_CONDUCT.md) to understand the behavior we expect from all contributors. + +## Getting Started + +### Issues + +#### Create a New Issue + +If you want to add or modify the content of this project: + +1. [Search if an issue already exists](https://github.com/ccprotocol/ccprotocol-contracts/issues) +2. If a related issue doesn't exist, create a new issue using the appropriate template +3. Discuss the proposed changes with the community before starting work +4. Wait for issue assignment or approval before submitting a PR + +#### Solve an Issue + +Scan through our [existing issues](https://github.com/ccprotocol/ccprotocol-contracts/issues) to find one that interests you. You can use labels to filter issues: + +- `good first issue`: Suitable for newcomers +- `bug`: Issues with the existing code +- `enhancement`: New features or improvements +- `documentation`: Documentation improvements +- `help wanted`: Issues where help is particularly needed + +### Development Environment + +1. Fork the repository +2. Clone your fork: + ```bash + git clone https://github.com/YOUR_USERNAME/ccprotocol-contracts.git + cd ccprotocol-contracts + ``` +3. Add the original repository as upstream: + ```bash + git remote add upstream https://github.com/ccprotocol/ccprotocol-contracts.git + ``` +4. Install development dependencies: + ```bash + forge install + ``` +5. Copy and configure environment variables: + ```bash + cp .env.example .env + # Edit .env with your settings + ``` + +## Development Workflow + +### Branching Strategy + +- `main`: Production-ready code +- `develop`: Integration branch for features +- Feature branches: Named as `feature/your-feature-name` +- Bug fix branches: Named as `fix/bug-name` + +Always create your working branch from `develop`: + +```bash +git checkout develop +git pull upstream develop +git checkout -b feature/your-feature-name +``` + +### Making Changes + +1. Ensure your changes address a specific issue +2. Make commits with clear, descriptive messages +3. Keep changes focused and atomic +4. Rebase your branch regularly to incorporate upstream changes: + ```bash + git fetch upstream + git rebase upstream/develop + ``` + +### Testing + +All code changes must include appropriate tests: + +1. Write unit tests for new functionality +2. Run the test suite to ensure all tests pass: + ```bash + forge test + ``` +3. For more detailed test output: + ```bash + forge test -vvv + ``` +4. Run gas reports to ensure efficiency: + ```bash + forge test --gas-report + ``` + +### Documentation + +1. Update or add NatSpec comments for all public functions: + ```solidity + /** + * @notice Brief explanation of the function + * @param paramName Description of the parameter + * @return Description of the return value + */ + function exampleFunction(uint256 paramName) public returns (bool) { + // Function implementation + } + ``` +2. Update relevant documentation in the `docs/` directory +3. Include a summary of documentation changes in your PR + +## Smart Contract Development Guidelines + +### Security Best Practices + +1. Follow established security patterns +2. Use OpenZeppelin contracts where appropriate +3. Be aware of common vulnerabilities (reentrancy, frontrunning, etc.) +4. Avoid complex control flows that are difficult to audit +5. Consider formal verification for critical functions + +### Gas Optimization + +1. Be mindful of storage vs. memory usage +2. Batch operations when possible +3. Use appropriate data types (uint256 is often most gas-efficient) +4. Consider gas costs in loops and data structures +5. Include gas reports in PRs for significant changes + +### Code Style + +1. Follow Solidity style guides +2. Use meaningful variable and function names +3. Format your code using the prettier +4. Keep functions small and focused +5. Use appropriate visibility modifiers (public, external, internal, private) + +## Pull Request Process + +1. Update the README or documentation if needed +2. Ensure all CI checks pass +3. Create a pull request to the `develop` branch +4. Fill in the PR template with all required information +5. Request review from relevant team members + +### PR Requirements + +- PR title should be descriptive and reference the issue (e.g., "Fix #123: Add timestamp validation") +- All tests must pass +- Code must be properly formatted +- New code should be covered by tests +- Changes should be well-documented +- Commit history should be clean and logical + +### Review Process + +1. At least one core contributor must review and approve the changes +2. Address all review comments promptly +3. CI checks must pass +4. Changes may require revision based on feedback +5. Once approved, a maintainer will merge the PR + +## Community + +- **GitHub Issues**: For bugs and feature requests +- **Discord**: For quick questions and community discussions +- **Pull Requests**: For code review discussions + +Join our community on [Discord](https://discord.gg/4tR9rWc3QE). + +## License + +By contributing to CC Protocol, you agree that your contributions will be licensed under the project's [MIT License](./LICENSE). diff --git a/README.md b/README.md index 79080955..68de14d6 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ CC Protocol is a decentralized crowdfunding protocol designed to help creators l - [Foundry](https://book.getfoundry.sh/) - Solidity ^0.8.20 -- Node.js (recommended) ## Installation @@ -38,11 +37,7 @@ forge install cp .env.example .env ``` -4. Configure your `.env` file with: - -- Private key -- RPC URL -- (Optional) Contract addresses for reuse +4. Configure your `.env` file following the template in `.env.example` ## Documentation @@ -56,7 +51,6 @@ Comprehensive documentation is available in the `docs/` folder: To view the documentation: ```bash -# Navigate to docs folder cd docs ``` @@ -93,18 +87,17 @@ anvil forge script script/DeployAll.s.sol:DeployAll --rpc-url http://localhost:8545 --private-key $PRIVATE_KEY --broadcast ``` -#### Testnet Deployment +#### Network Deployment ```bash -# Deploy to testnet -forge script script/DeployAll.s.sol:DeployAll --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv +# Deploy to any configured network +forge script script/DeployAll.s.sol:DeployAll --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast ``` ## Contract Architecture ### Core Contracts -- `TestUSD`: Mock ERC20 token for testing - `GlobalParams`: Protocol-wide parameter management - `CampaignInfoFactory`: Campaign creation and management - `TreasuryFactory`: Treasury contract deployment @@ -113,28 +106,66 @@ forge script script/DeployAll.s.sol:DeployAll --rpc-url $RPC_URL --private-key $ - `AllOrNothing`: Funds refunded if campaign goal not met +### Notes on Mock Contracts + +- `TestToken` is a mock ERC20 token used **only for testing and development purposes**. +- It is located in the `mocks/` directory and should **not be included in production deployments**. + ## Deployment Workflow -1. Deploy `TestUSD` -2. Deploy `GlobalParams` -3. Deploy `TreasuryFactory` -4. Deploy `CampaignInfoFactory` +1. Deploy `GlobalParams` +2. Deploy `TreasuryFactory` +3. Deploy `CampaignInfoFactory` + +> For local testing or development, the `TestToken` mock token needs to be deployed before interacting with contracts requiring an ERC20 token. ## Environment Variables -Key environment variables in `.env`: +Key environment variables to configure in `.env`: - `PRIVATE_KEY`: Deployment wallet private key -- `RPC_URL`: Network RPC endpoint +- `RPC_URL`: Network RPC endpoint (can be configured for any network) - `SIMULATE`: Toggle simulation mode - Contract address variables for reuse -## Troubleshooting +For a complete list of variables, refer to `.env.example`. + +## Security + +### Audits + +Security audit reports can be found in the [`audits/`](./audits/) folder. We regularly conduct security audits to ensure the safety and reliability of the protocol. + +## Contributing + +We welcome all contributions to the Creative Crowdfunding Protocol. If you're interested in helping, here's how you can contribute: + +- **Report bugs** by opening issues +- **Suggest enhancements** or new features +- **Submit pull requests** to improve the codebase +- **Improve documentation** to make the project more accessible + +Before contributing, please read our detailed [Contributing Guidelines](./CONTRIBUTING.md) for comprehensive information on: +- Development workflow +- Coding standards +- Testing requirements +- Pull request process +- Smart contract security considerations + +### Community + +Join our community on [Discord](https://discord.gg/4tR9rWc3QE) for questions and discussions. + +Read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectful. + +## Contributors + + + + -- Ensure sufficient network gas tokens -- Verify RPC URL connectivity -- Check contract dependencies +Made with [contrib.rocks](https://contrib.rocks). ## License -[SPDX-License-Identifier: UNLICENSED] +This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). \ No newline at end of file diff --git a/audits/PeckShield-Audit-Report-CreativeCrowdfunding_v1.0.pdf b/audits/PeckShield-Audit-Report-CreativeCrowdfunding_v1.0.pdf new file mode 100644 index 00000000..d301a934 Binary files /dev/null and b/audits/PeckShield-Audit-Report-CreativeCrowdfunding_v1.0.pdf differ diff --git a/docs/book.toml b/docs/book.toml index b93f4313..d8a41741 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -7,7 +7,7 @@ no-section-label = true additional-js = ["solidity.min.js"] additional-css = ["book.css"] mathjax-support = true -git-repository-url = "https://github.com/ccprotocol/reference-client-sc" +git-repository-url = "https://github.com/ccprotocol/ccprotocol-contracts" [output.html.fold] enable = true diff --git a/docs/src/README.md b/docs/src/README.md index a5e70a42..68de14d6 100644 --- a/docs/src/README.md +++ b/docs/src/README.md @@ -1,67 +1,171 @@ -# CCP Contracts -This repository contains the smart contracts source code and campaign configuration for Creative Crowdfunding Protocol - CCP. The repository uses Foundry as development environment for compilation, testing and deployment tasks. +# Creative Crowdfunding Protocol (CC Protocol) Smart Contracts -## What is CCP? -CCP is a protocol for crowdfunding campaigns that allows creators to multilist campaigns across different crowdfunding platforms. It provides infrastructure tooling and support for platforms to create and manage campaigns in web3. +## Overview -## Documentation -The detailed technical documentation for the protocol can be found in the [docs](./docs/src/SUMMARY.md) folder. +CC Protocol is a decentralized crowdfunding protocol designed to help creators launch and manage campaigns across multiple platforms. By providing a standardized infrastructure, the protocol simplifies the process of creating, funding, and managing crowdfunding initiatives in web3 across different platforms. + +## Features + +- Cross-listable campaign creation +- Multiple treasury models +- Secure fund management +- Customizable protocol parameters -## Getting Started -### Prerequisites -The following tools are required to be installed in your system: -- [Foundry](https://book.getfoundry.sh/getting-started/installation) -- [Node.js](https://nodejs.org/en/download/) +## Prerequisites -### Installation +- [Foundry](https://book.getfoundry.sh/) +- Solidity ^0.8.20 -```shell -$ npm install +## Installation + +1. Clone the repository: + +```bash +git clone https://github.com/ccprotocol/ccprotocol-contracts.git +cd ccprotocol-contracts ``` -### Build +2. Install dependencies: -```shell -$ forge build +```bash +forge install ``` -### Test +3. Copy environment template: + +```bash +cp .env.example .env +``` + +4. Configure your `.env` file following the template in `.env.example` + +## Documentation + +Comprehensive documentation is available in the `docs/` folder: -```shell -$ forge test +- Technical specifications +- Contract interfaces +- Deployment guides +- Development setup instructions + +To view the documentation: + +```bash +cd docs ``` -### Format +## Development + +### Compile Contracts -```shell -$ forge fmt +```bash +forge build ``` -### Gas Snapshots +### Run Tests -```shell -$ forge snapshot +```bash +# Run all tests +forge test + +# Run specific test +forge test --match-test testFunctionName + +# Run tests with more verbose output +forge test -vvv ``` -## Deploy -### Environment Variables +### Deploy Contracts + +#### Local Deployment -Create an environment file named `.env`, fill the environment variables following the `.env.example` file and source the file using the following command: +```bash +# Start local blockchain +anvil -```shell -$ source .env +# Deploy to local network +forge script script/DeployAll.s.sol:DeployAll --rpc-url http://localhost:8545 --private-key $PRIVATE_KEY --broadcast ``` -### Local Deployment -To deploy the contracts locally, run the following command: +#### Network Deployment -```shell -$ forge script script/Setup.s.sol:SetupScript +```bash +# Deploy to any configured network +forge script script/DeployAll.s.sol:DeployAll --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast ``` -### Remote Deployment -To deploy the contracts to a remote network, run the following command: +## Contract Architecture + +### Core Contracts + +- `GlobalParams`: Protocol-wide parameter management +- `CampaignInfoFactory`: Campaign creation and management +- `TreasuryFactory`: Treasury contract deployment + +### Treasury Models + +- `AllOrNothing`: Funds refunded if campaign goal not met + +### Notes on Mock Contracts + +- `TestToken` is a mock ERC20 token used **only for testing and development purposes**. +- It is located in the `mocks/` directory and should **not be included in production deployments**. + +## Deployment Workflow + +1. Deploy `GlobalParams` +2. Deploy `TreasuryFactory` +3. Deploy `CampaignInfoFactory` + +> For local testing or development, the `TestToken` mock token needs to be deployed before interacting with contracts requiring an ERC20 token. + +## Environment Variables + +Key environment variables to configure in `.env`: + +- `PRIVATE_KEY`: Deployment wallet private key +- `RPC_URL`: Network RPC endpoint (can be configured for any network) +- `SIMULATE`: Toggle simulation mode +- Contract address variables for reuse + +For a complete list of variables, refer to `.env.example`. + +## Security + +### Audits + +Security audit reports can be found in the [`audits/`](./audits/) folder. We regularly conduct security audits to ensure the safety and reliability of the protocol. + +## Contributing + +We welcome all contributions to the Creative Crowdfunding Protocol. If you're interested in helping, here's how you can contribute: + +- **Report bugs** by opening issues +- **Suggest enhancements** or new features +- **Submit pull requests** to improve the codebase +- **Improve documentation** to make the project more accessible + +Before contributing, please read our detailed [Contributing Guidelines](./CONTRIBUTING.md) for comprehensive information on: +- Development workflow +- Coding standards +- Testing requirements +- Pull request process +- Smart contract security considerations + +### Community + +Join our community on [Discord](https://discord.gg/4tR9rWc3QE) for questions and discussions. + +Read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectful. + +## Contributors + + + + + +Made with [contrib.rocks](https://contrib.rocks). + +## License -```shell -$ forge script script/Setup.s.sol:SetupScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY -``` \ No newline at end of file +This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). \ No newline at end of file diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index ca711d33..aafa9997 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -13,7 +13,6 @@ - [❱ treasuries](src/treasuries/README.md) - [AllOrNothing](src/treasuries/AllOrNothing.sol/contract.AllOrNothing.md) - [❱ utils](src/utils/README.md) - - [AddressCalculator](src/utils/AddressCalculator.sol/library.AddressCalculator.md) - [AdminAccessChecker](src/utils/AdminAccessChecker.sol/abstract.AdminAccessChecker.md) - [BaseTreasury](src/utils/BaseTreasury.sol/abstract.BaseTreasury.md) - [CampaignAccessChecker](src/utils/CampaignAccessChecker.sol/abstract.CampaignAccessChecker.md) @@ -25,5 +24,4 @@ - [CampaignInfo](src/CampaignInfo.sol/contract.CampaignInfo.md) - [CampaignInfoFactory](src/CampaignInfoFactory.sol/contract.CampaignInfoFactory.md) - [GlobalParams](src/GlobalParams.sol/contract.GlobalParams.md) - - [TestUSD](src/TestUSD.sol/contract.TestUSD.md) - [TreasuryFactory](src/TreasuryFactory.sol/contract.TreasuryFactory.md) diff --git a/docs/src/src/CampaignInfo.sol/contract.CampaignInfo.md b/docs/src/src/CampaignInfo.sol/contract.CampaignInfo.md index dfb8fae7..3543572d 100644 --- a/docs/src/src/CampaignInfo.sol/contract.CampaignInfo.md +++ b/docs/src/src/CampaignInfo.sol/contract.CampaignInfo.md @@ -1,5 +1,5 @@ # CampaignInfo -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/CampaignInfo.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/CampaignInfo.sol) **Inherits:** [ICampaignData](/src/interfaces/ICampaignData.sol/interface.ICampaignData.md), [ICampaignInfo](/src/interfaces/ICampaignInfo.sol/interface.ICampaignInfo.md), Ownable, [PausableCancellable](/src/utils/PausableCancellable.sol/abstract.PausableCancellable.md), [TimestampChecker](/src/utils/TimestampChecker.sol/abstract.TimestampChecker.md), [AdminAccessChecker](/src/utils/AdminAccessChecker.sol/abstract.AdminAccessChecker.md), Initializable @@ -15,24 +15,31 @@ CampaignData private s_campaignData; ``` -### s_selectedPlatformHash +### s_platformTreasuryAddress ```solidity -mapping(bytes32 => bool) private s_selectedPlatformHash; +mapping(bytes32 => address) private s_platformTreasuryAddress; ``` -### s_platformTreasuryAddress +### s_platformFeePercent ```solidity -mapping(bytes32 => address) private s_platformTreasuryAddress; +mapping(bytes32 => uint256) private s_platformFeePercent; ``` -### s_platformFeePercent +### s_isSelectedPlatform ```solidity -mapping(bytes32 => uint256) private s_platformFeePercent; +mapping(bytes32 => bool) private s_isSelectedPlatform; +``` + + +### s_isApprovedPlatform + +```solidity +mapping(bytes32 => bool) private s_isApprovedPlatform; ``` @@ -43,10 +50,10 @@ mapping(bytes32 => bytes32) private s_platformData; ``` -### s_approvedplatformHash +### s_approvedPlatformHashes ```solidity -bytes32[] private s_approvedplatformHash; +bytes32[] private s_approvedPlatformHashes; ``` @@ -74,7 +81,8 @@ function initialize( IGlobalParams globalParams, bytes32[] calldata selectedPlatformHash, bytes32[] calldata platformDataKey, - bytes32[] calldata platformDataValue + bytes32[] calldata platformDataValue, + CampaignData calldata campaignData ) external initializer; ``` @@ -106,6 +114,27 @@ function checkIfPlatformSelected(bytes32 platformHash) public view override retu |``|`bool`|True if the platform is selected, false otherwise.| +### checkIfPlatformApproved + +*Check if a platform is already approved* + + +```solidity +function checkIfPlatformApproved(bytes32 platformHash) public view returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`platformHash`|`bytes32`|The bytes32 identifier of the platform.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the platform is already approved, false otherwise.| + + ### owner Returns the owner of the contract. @@ -121,34 +150,34 @@ function owner() public view override(ICampaignInfo, Ownable) returns (address a |`account`|`address`|The address of the contract owner.| -### getTotalRaisedAmount +### getProtocolAdminAddress -Retrieves the total amount raised in the campaign. +Retrieves the address of the protocol administrator. ```solidity -function getTotalRaisedAmount() external view override returns (uint256); +function getProtocolAdminAddress() public view override returns (address); ``` **Returns** |Name|Type|Description| |----|----|-----------| -|``|`uint256`|The total amount raised in the campaign.| +|``|`address`|The address of the protocol administrator.| -### getProtocolAdminAddress +### getTotalRaisedAmount -Retrieves the address of the protocol administrator. +Retrieves the total amount raised in the campaign. ```solidity -function getProtocolAdminAddress() external view override returns (address); +function getTotalRaisedAmount() external view override returns (uint256); ``` **Returns** |Name|Type|Description| |----|----|-----------| -|``|`address`|The address of the protocol administrator.| +|``|`uint256`|The total amount raised in the campaign.| ### getPlatformAdminAddress @@ -193,7 +222,7 @@ Retrieves the campaign's deadline. ```solidity -function getDeadline() external view override returns (uint256); +function getDeadline() public view override returns (uint256); ``` **Returns** @@ -329,7 +358,12 @@ Can only be called by the current owner.* ```solidity -function transferOwnership(address newOwner) public override(ICampaignInfo, Ownable) onlyOwner whenNotPaused; +function transferOwnership(address newOwner) + public + override(ICampaignInfo, Ownable) + onlyOwner + whenNotPaused + whenNotCancelled; ``` ### updateLaunchTime @@ -338,7 +372,13 @@ Updates the campaign's launch time. ```solidity -function updateLaunchTime(uint256 launchTime) external override onlyOwner currentTimeIsLess(launchTime) whenNotPaused; +function updateLaunchTime(uint256 launchTime) + external + override + onlyOwner + currentTimeIsLess(getLaunchTime()) + whenNotPaused + whenNotCancelled; ``` **Parameters** @@ -358,7 +398,8 @@ function updateDeadline(uint256 deadline) override onlyOwner currentTimeIsLess(getLaunchTime()) - whenNotPaused; + whenNotPaused + whenNotCancelled; ``` **Parameters** @@ -377,8 +418,9 @@ function updateGoalAmount(uint256 goalAmount) external override onlyOwner - currentTimeIsLess(s_campaignData.launchTime) - whenNotPaused; + currentTimeIsLess(getLaunchTime()) + whenNotPaused + whenNotCancelled; ``` **Parameters** @@ -391,14 +433,17 @@ function updateGoalAmount(uint256 goalAmount) Updates the selection status of a platform for the campaign. +*It can only be called for a platform if its not approved i.e. the platform treasury is not deployed* + ```solidity function updateSelectedPlatform(bytes32 platformHash, bool selection) external override onlyOwner - currentTimeIsLess(s_campaignData.launchTime) - whenNotPaused; + currentTimeIsLess(getLaunchTime()) + whenNotPaused + whenNotCancelled; ``` **Parameters** @@ -432,7 +477,7 @@ function _unpauseCampaign(bytes32 message) external onlyProtocolAdmin; ```solidity -function _cancelCampaign(bytes32 message) external onlyProtocolAdmin; +function _cancelCampaign(bytes32 message) external; ``` ### _setPlatformInfo @@ -452,21 +497,6 @@ function _setPlatformInfo(bytes32 platformHash, address platformTreasuryAddress) ## Events -### CampaignInfoPlatformSelected -*Emitted when a platform is selected for the campaign.* - - -```solidity -event CampaignInfoPlatformSelected(bytes32 indexed platformHash, address indexed platformTreasury); -``` - -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`platformHash`|`bytes32`|The bytes32 identifier of the platform.| -|`platformTreasury`|`address`|The address of the platform's treasury.| - ### CampaignInfoLaunchTimeUpdated *Emitted when the launch time of the campaign is updated.* @@ -539,21 +569,6 @@ event CampaignInfoPlatformInfoUpdated(bytes32 indexed platformHash, address inde |`platformHash`|`bytes32`|The bytes32 identifier of the platform.| |`platformTreasury`|`address`|The address of the platform's treasury.| -### CampaignInfoOwnershipTransferred -*Emitted when ownership of the contract is transferred.* - - -```solidity -event CampaignInfoOwnershipTransferred(address indexed previousOwner, address indexed newOwner); -``` - -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`previousOwner`|`address`|The address of the previous owner.| -|`newOwner`|`address`|The address of the new owner.| - ## Errors ### CampaignInfoInvalidPlatformUpdate *Emitted when an invalid platform update is attempted.* @@ -600,6 +615,20 @@ error CampaignInfoPlatformNotSelected(bytes32 platformHash); |----|----|-----------| |`platformHash`|`bytes32`|The bytes32 identifier of the platform.| +### CampaignInfoPlatformAlreadyApproved +*Emitted when a platform is already approved for the campaign.* + + +```solidity +error CampaignInfoPlatformAlreadyApproved(bytes32 platformHash); +``` + +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`platformHash`|`bytes32`|The bytes32 identifier of the platform.| + ## Structs ### Config @@ -609,9 +638,6 @@ struct Config { address token; uint256 protocolFeePercent; bytes32 identifierHash; - uint256 launchTime; - uint256 deadline; - uint256 goalAmount; } ``` diff --git a/docs/src/src/CampaignInfoFactory.sol/contract.CampaignInfoFactory.md b/docs/src/src/CampaignInfoFactory.sol/contract.CampaignInfoFactory.md index e987f012..17439f11 100644 --- a/docs/src/src/CampaignInfoFactory.sol/contract.CampaignInfoFactory.md +++ b/docs/src/src/CampaignInfoFactory.sol/contract.CampaignInfoFactory.md @@ -1,8 +1,8 @@ # CampaignInfoFactory -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/CampaignInfoFactory.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/CampaignInfoFactory.sol) **Inherits:** -[ICampaignInfoFactory](/src/interfaces/ICampaignInfoFactory.sol/interface.ICampaignInfoFactory.md), Ownable +Initializable, [ICampaignInfoFactory](/src/interfaces/ICampaignInfoFactory.sol/interface.ICampaignInfoFactory.md), Ownable Factory contract for creating campaign information contracts. @@ -71,7 +71,7 @@ constructor(IGlobalParams globalParams, address campaignImplementation) Ownable( ```solidity -function _initialize(address treasuryFactoryAddress, address globalParams) external onlyOwner; +function _initialize(address treasuryFactoryAddress, address globalParams) external onlyOwner initializer; ``` **Parameters** @@ -83,6 +83,8 @@ function _initialize(address treasuryFactoryAddress, address globalParams) exter ### createCampaign +Creates a new campaign information contract. + ```solidity function createCampaign( @@ -94,6 +96,32 @@ function createCampaign( CampaignData calldata campaignData ) external override; ``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`creator`|`address`|The address of the creator of the campaign.| +|`identifierHash`|`bytes32`|The unique identifier hash of the campaign.| +|`selectedPlatformHash`|`bytes32[]`|An array of platform identifiers selected for the campaign.| +|`platformDataKey`|`bytes32[]`|An array of platform-specific data keys.| +|`platformDataValue`|`bytes32[]`|An array of platform-specific data values.| +|`campaignData`|`CampaignData`|The struct containing campaign launch details.| + + +### updateImplementation + +Updates the campaign implementation address. + + +```solidity +function updateImplementation(address newImplementation) external override onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newImplementation`|`address`|The address of the camapaignInfo implementation contract.| + ## Errors ### CampaignInfoFactoryAlreadyInitialized @@ -112,12 +140,12 @@ error CampaignInfoFactoryAlreadyInitialized(); error CampaignInfoFactoryInvalidInput(); ``` -### CampaignInfoFactoryCampaignCreationFailed +### CampaignInfoFactoryCampaignInitializationFailed *Emitted when campaign creation fails.* ```solidity -error CampaignInfoFactoryCampaignCreationFailed(); +error CampaignInfoFactoryCampaignInitializationFailed(); ``` ### CampaignInfoFactoryPlatformNotListed diff --git a/docs/src/src/GlobalParams.sol/contract.GlobalParams.md b/docs/src/src/GlobalParams.sol/contract.GlobalParams.md index 62896299..c48beca0 100644 --- a/docs/src/src/GlobalParams.sol/contract.GlobalParams.md +++ b/docs/src/src/GlobalParams.sol/contract.GlobalParams.md @@ -1,8 +1,8 @@ # GlobalParams -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/GlobalParams.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/GlobalParams.sol) **Inherits:** -[IGlobalParams](/src/interfaces/IGlobalParams.sol/interface.IGlobalParams.md), Ownable, Pausable +[IGlobalParams](/src/interfaces/IGlobalParams.sol/interface.IGlobalParams.md), Ownable Manages global parameters and platform information. @@ -127,27 +127,6 @@ constructor(address protocolAdminAddress, address tokenAddress, uint256 protocol |`protocolFeePercent`|`uint256`|The protocol fee percentage.| -### checkIfPlatformIsListed - -Checks if a platform is listed in the protocol. - - -```solidity -function checkIfPlatformIsListed(bytes32 platformHash) public view override returns (bool); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`platformHash`|`bytes32`|| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`bool`|True if the platform is listed; otherwise, false.| - - ### getPlatformAdminAddress Retrieves the admin address of a platform. @@ -266,12 +245,7 @@ Retrieves the owner of platform-specific data. ```solidity -function getPlatformDataOwner(bytes32 platformDataKey) - external - view - override - platformIsListed(platformHash) - returns (bytes32 platformHash); +function getPlatformDataOwner(bytes32 platformDataKey) external view override returns (bytes32 platformHash); ``` **Parameters** @@ -286,6 +260,27 @@ function getPlatformDataOwner(bytes32 platformDataKey) |`platformHash`|`bytes32`|The platform identifier associated with the data.| +### checkIfPlatformIsListed + +Checks if a platform is listed in the protocol. + + +```solidity +function checkIfPlatformIsListed(bytes32 platformHash) public view override returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`platformHash`|`bytes32`|| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the platform is listed; otherwise, false.| + + ### checkIfPlatformDataKeyValid Checks if a platform-specific data key is valid. @@ -311,11 +306,14 @@ function checkIfPlatformDataKeyValid(bytes32 platformDataKey) external view over Enlists a platform with its admin address and fee percentage. +*The platformFeePercent can be any value including zero.* + ```solidity function enlistPlatform(bytes32 platformHash, address platformAdminAddress, uint256 platformFeePercent) external - onlyOwner; + onlyOwner + notAddressZero(platformAdminAddress); ``` **Parameters** @@ -332,7 +330,7 @@ Delists a platform. ```solidity -function delistPlatform(bytes32 platformHash) external onlyOwner; +function delistPlatform(bytes32 platformHash) external onlyOwner platformIsListed(platformHash); ``` **Parameters** @@ -449,13 +447,13 @@ function updatePlatformAdminAddress(bytes32 platformHash, address platformAdminA |`platformAdminAddress`|`address`|| -### _checkIfAddressZero +### _revertIfAddressZero *Reverts if the input address is zero.* ```solidity -function _checkIfAddressZero(address account) internal pure; +function _revertIfAddressZero(address account) internal pure; ``` ### _onlyPlatformAdmin diff --git a/docs/src/src/README.md b/docs/src/src/README.md index 7423e1d4..7592aab6 100644 --- a/docs/src/src/README.md +++ b/docs/src/src/README.md @@ -7,5 +7,4 @@ - [CampaignInfo](CampaignInfo.sol/contract.CampaignInfo.md) - [CampaignInfoFactory](CampaignInfoFactory.sol/contract.CampaignInfoFactory.md) - [GlobalParams](GlobalParams.sol/contract.GlobalParams.md) -- [TestUSD](TestUSD.sol/contract.TestUSD.md) - [TreasuryFactory](TreasuryFactory.sol/contract.TreasuryFactory.md) diff --git a/docs/src/src/TestUSD.sol/contract.TestUSD.md b/docs/src/src/TestUSD.sol/contract.TestUSD.md index 37bc06f1..e317536d 100644 --- a/docs/src/src/TestUSD.sol/contract.TestUSD.md +++ b/docs/src/src/TestUSD.sol/contract.TestUSD.md @@ -1,5 +1,5 @@ # TestUSD -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/TestUSD.sol) +[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/32b7b1617200d0c6f3248845ef972180411f1f65/src/TestUSD.sol) **Inherits:** ERC20, Ownable diff --git a/docs/src/src/TreasuryFactory.sol/contract.TreasuryFactory.md b/docs/src/src/TreasuryFactory.sol/contract.TreasuryFactory.md index 8cc25e3f..17455ce4 100644 --- a/docs/src/src/TreasuryFactory.sol/contract.TreasuryFactory.md +++ b/docs/src/src/TreasuryFactory.sol/contract.TreasuryFactory.md @@ -1,5 +1,5 @@ # TreasuryFactory -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/TreasuryFactory.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/TreasuryFactory.sol) **Inherits:** [ITreasuryFactory](/src/interfaces/ITreasuryFactory.sol/interface.ITreasuryFactory.md), [AdminAccessChecker](/src/utils/AdminAccessChecker.sol/abstract.AdminAccessChecker.md) @@ -9,14 +9,14 @@ ### implementationMap ```solidity -mapping(bytes32 => mapping(uint256 => address)) implementationMap; +mapping(bytes32 => mapping(uint256 => address)) private implementationMap; ``` ### approvedImplementations ```solidity -mapping(address => bool) approvedImplementations; +mapping(address => bool) private approvedImplementations; ``` @@ -185,3 +185,9 @@ error TreasuryFactoryImplementationNotSetOrApproved(); error TreasuryFactoryTreasuryInitializationFailed(); ``` +### TreasuryFactorySettingPlatformInfoFailed + +```solidity +error TreasuryFactorySettingPlatformInfoFailed(); +``` + diff --git a/docs/src/src/interfaces/ICampaignData.sol/interface.ICampaignData.md b/docs/src/src/interfaces/ICampaignData.sol/interface.ICampaignData.md index b2d2439e..c159c65d 100644 --- a/docs/src/src/interfaces/ICampaignData.sol/interface.ICampaignData.md +++ b/docs/src/src/interfaces/ICampaignData.sol/interface.ICampaignData.md @@ -1,5 +1,5 @@ # ICampaignData -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/interfaces/ICampaignData.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/interfaces/ICampaignData.sol) An interface for managing campaign data in a CCP. diff --git a/docs/src/src/interfaces/ICampaignInfo.sol/interface.ICampaignInfo.md b/docs/src/src/interfaces/ICampaignInfo.sol/interface.ICampaignInfo.md index 8905cd40..09b0d8e5 100644 --- a/docs/src/src/interfaces/ICampaignInfo.sol/interface.ICampaignInfo.md +++ b/docs/src/src/interfaces/ICampaignInfo.sol/interface.ICampaignInfo.md @@ -1,5 +1,5 @@ # ICampaignInfo -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/interfaces/ICampaignInfo.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/interfaces/ICampaignInfo.sol) An interface for managing campaign information in a crowdfunding system. @@ -288,6 +288,8 @@ function updateGoalAmount(uint256 goalAmount) external; Updates the selection status of a platform for the campaign. +*It can only be called for a platform if its not approved i.e. the platform treasury is not deployed* + ```solidity function updateSelectedPlatform(bytes32 platformHash, bool selection) external; diff --git a/docs/src/src/interfaces/ICampaignInfoFactory.sol/interface.ICampaignInfoFactory.md b/docs/src/src/interfaces/ICampaignInfoFactory.sol/interface.ICampaignInfoFactory.md index 59a31f5e..b82c3002 100644 --- a/docs/src/src/interfaces/ICampaignInfoFactory.sol/interface.ICampaignInfoFactory.md +++ b/docs/src/src/interfaces/ICampaignInfoFactory.sol/interface.ICampaignInfoFactory.md @@ -1,5 +1,5 @@ # ICampaignInfoFactory -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/interfaces/ICampaignInfoFactory.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/interfaces/ICampaignInfoFactory.sol) **Inherits:** [ICampaignData](/src/interfaces/ICampaignData.sol/interface.ICampaignData.md) @@ -35,6 +35,21 @@ function createCampaign( |`campaignData`|`CampaignData`|The struct containing campaign launch details.| +### updateImplementation + +Updates the campaign implementation address. + + +```solidity +function updateImplementation(address newImplementation) external; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newImplementation`|`address`|The address of the camapaignInfo implementation contract.| + + ## Events ### CampaignInfoFactoryCampaignCreated Emitted when a campaign is successfully created. @@ -51,3 +66,11 @@ event CampaignInfoFactoryCampaignCreated(bytes32 indexed identifierHash, address |`identifierHash`|`bytes32`|The unique identifier hash of the campaign.| |`campaignInfoAddress`|`address`|The address of the created campaign information contract.| +### CampaignInfoFactoryCampaignInitialized +Emitted when the campaign after creation is initialized. + + +```solidity +event CampaignInfoFactoryCampaignInitialized(); +``` + diff --git a/docs/src/src/interfaces/ICampaignTreasury.sol/interface.ICampaignTreasury.md b/docs/src/src/interfaces/ICampaignTreasury.sol/interface.ICampaignTreasury.md index bbfe9c72..bfa758f0 100644 --- a/docs/src/src/interfaces/ICampaignTreasury.sol/interface.ICampaignTreasury.md +++ b/docs/src/src/interfaces/ICampaignTreasury.sol/interface.ICampaignTreasury.md @@ -1,5 +1,5 @@ # ICampaignTreasury -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/interfaces/ICampaignTreasury.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/interfaces/ICampaignTreasury.sol) An interface for managing campaign treasury contracts. @@ -38,13 +38,13 @@ function claimRefund(uint256 tokenId) external; |`tokenId`|`uint256`|The unique identifier of the refundable token.| -### getplatformHash +### getPlatformHash Retrieves the platform identifier associated with the treasury. ```solidity -function getplatformHash() external view returns (bytes32); +function getPlatformHash() external view returns (bytes32); ``` **Returns** @@ -53,13 +53,13 @@ function getplatformHash() external view returns (bytes32); |``|`bytes32`|The platform identifier as a bytes32 value.| -### getplatformFeePercent +### getPlatformFeePercent Retrieves the platform fee percentage for the treasury. ```solidity -function getplatformFeePercent() external view returns (uint256); +function getPlatformFeePercent() external view returns (uint256); ``` **Returns** diff --git a/docs/src/src/interfaces/IGlobalParams.sol/interface.IGlobalParams.md b/docs/src/src/interfaces/IGlobalParams.sol/interface.IGlobalParams.md index 840d1619..447c2b9a 100644 --- a/docs/src/src/interfaces/IGlobalParams.sol/interface.IGlobalParams.md +++ b/docs/src/src/interfaces/IGlobalParams.sol/interface.IGlobalParams.md @@ -1,5 +1,5 @@ # IGlobalParams -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/interfaces/IGlobalParams.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/interfaces/IGlobalParams.sol) An interface for accessing and managing global parameters of the protocol. diff --git a/docs/src/src/interfaces/IItem.sol/interface.IItem.md b/docs/src/src/interfaces/IItem.sol/interface.IItem.md index e1eef137..1371e884 100644 --- a/docs/src/src/interfaces/IItem.sol/interface.IItem.md +++ b/docs/src/src/interfaces/IItem.sol/interface.IItem.md @@ -1,5 +1,5 @@ # IItem -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/interfaces/IItem.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/interfaces/IItem.sol) An interface for managing items and their attributes. diff --git a/docs/src/src/interfaces/IReward.sol/interface.IReward.md b/docs/src/src/interfaces/IReward.sol/interface.IReward.md index 46b2cc6d..bd0fdf23 100644 --- a/docs/src/src/interfaces/IReward.sol/interface.IReward.md +++ b/docs/src/src/interfaces/IReward.sol/interface.IReward.md @@ -1,5 +1,5 @@ # IReward -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/interfaces/IReward.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/interfaces/IReward.sol) An interface for managing rewards in a campaign. diff --git a/docs/src/src/interfaces/ITreasuryFactory.sol/interface.ITreasuryFactory.md b/docs/src/src/interfaces/ITreasuryFactory.sol/interface.ITreasuryFactory.md index 92e54b15..5acfbf2b 100644 --- a/docs/src/src/interfaces/ITreasuryFactory.sol/interface.ITreasuryFactory.md +++ b/docs/src/src/interfaces/ITreasuryFactory.sol/interface.ITreasuryFactory.md @@ -1,5 +1,5 @@ # ITreasuryFactory -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/interfaces/ITreasuryFactory.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/interfaces/ITreasuryFactory.sol) *Interface for the TreasuryFactory contract, which registers, approves, and deploys treasury clones.* diff --git a/docs/src/src/treasuries/AllOrNothing.sol/contract.AllOrNothing.md b/docs/src/src/treasuries/AllOrNothing.sol/contract.AllOrNothing.md index ef5a9767..5d719fbc 100644 --- a/docs/src/src/treasuries/AllOrNothing.sol/contract.AllOrNothing.md +++ b/docs/src/src/treasuries/AllOrNothing.sol/contract.AllOrNothing.md @@ -1,5 +1,5 @@ # AllOrNothing -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/treasuries/AllOrNothing.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/treasuries/AllOrNothing.sol) **Inherits:** [IReward](/src/interfaces/IReward.sol/interface.IReward.md), [BaseTreasury](/src/utils/BaseTreasury.sol/abstract.BaseTreasury.md), [TimestampChecker](/src/utils/TimestampChecker.sol/abstract.TimestampChecker.md), ERC721Burnable @@ -8,10 +8,17 @@ A contract for handling crowdfunding campaigns with rewards. ## State Variables -### s_tokenToCollectedAmount +### s_tokenToTotalCollectedAmount ```solidity -mapping(uint256 => uint256) private s_tokenToCollectedAmount; +mapping(uint256 => uint256) private s_tokenToTotalCollectedAmount; +``` + + +### s_tokenToPledgedAmount + +```solidity +mapping(uint256 => uint256) private s_tokenToPledgedAmount; ``` @@ -119,35 +126,18 @@ function getRaisedAmount() external view override returns (uint256); |``|`uint256`|The total raised amount as a uint256 value.| -### addReward - -Adds a reward to the campaign. - - -```solidity -function addReward(bytes32 rewardName, Reward calldata reward) - external - onlyCampaignOwner - whenCampaignNotPaused - whenNotPaused - whenCampaignNotCancelled - whenNotCancelled; -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`rewardName`|`bytes32`|The name of the reward.| -|`reward`|`Reward`|The details of the reward as a `Reward` struct.| - - -### addRewardsBatch +### addRewards Adds multiple rewards in a batch. +*This function allows for both reward tiers and non-reward tiers. +For both types, rewards must have non-zero value. +If items are specified (non-empty arrays), the itemId, itemValue, and itemQuantity arrays must match in length. +Empty arrays are allowed for both reward tiers and non-reward tiers.* + ```solidity -function addRewardsBatch(bytes32[] calldata rewardNames, Reward[] calldata rewards) +function addRewards(bytes32[] calldata rewardNames, Reward[] calldata rewards) external onlyCampaignOwner whenCampaignNotPaused @@ -188,6 +178,9 @@ function removeReward(bytes32 rewardName) Allows a backer to pledge for a reward. +*The first element of the `reward` array must be a reward tier and the other elements can be either reward tiers or non-reward tiers. +The non-reward tiers cannot be pledged for without a reward.* + ```solidity function pledgeForAReward(address backer, uint256 shippingFee, bytes32[] calldata reward) @@ -275,20 +268,6 @@ function withdraw() public override whenNotPaused whenNotCancelled; function cancelTreasury(bytes32 message) public override; ``` -### _pledge - - -```solidity -function _pledge( - address backer, - bytes32 reward, - uint256 pledgeAmount, - uint256 shippingFee, - uint256 tokenId, - bytes32[] memory rewards -) internal; -``` - ### _checkSuccessCondition *Internal function to check the success condition for fee disbursement.* @@ -304,6 +283,20 @@ function _checkSuccessCondition() internal view virtual override returns (bool); |``|`bool`|Whether the success condition is met.| +### _pledge + + +```solidity +function _pledge( + address backer, + bytes32 reward, + uint256 pledgeAmount, + uint256 shippingFee, + uint256 tokenId, + bytes32[] memory rewards +) private; +``` + ### supportsInterface @@ -338,20 +331,20 @@ event Receipt( |`tokenId`|`uint256`|The ID of the token representing the pledge.| |`rewards`|`bytes32[]`|An array of reward names.| -### RewardAdded -*Emitted when a reward is added to the campaign.* +### RewardsAdded +*Emitted when rewards are added to the campaign.* ```solidity -event RewardAdded(bytes32 indexed rewardName, Reward reward); +event RewardsAdded(bytes32[] rewardNames, Reward[] rewards); ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`rewardName`|`bytes32`|The name of the reward.| -|`reward`|`Reward`|The details of the reward.| +|`rewardNames`|`bytes32[]`|The names of the rewards.| +|`rewards`|`Reward[]`|The details of the rewards.| ### RewardRemoved *Emitted when a reward is removed from the campaign.* diff --git a/docs/src/src/treasuries/MinimumOrder.sol/contract.MinimumOrder.md b/docs/src/src/treasuries/MinimumOrder.sol/contract.MinimumOrder.md deleted file mode 100644 index 2addf548..00000000 --- a/docs/src/src/treasuries/MinimumOrder.sol/contract.MinimumOrder.md +++ /dev/null @@ -1,290 +0,0 @@ -# MinimumOrder - -[Git Source](https://github.com/ccprotocol/campaign-utils-contracts-aggregator/blob/79d78188e565502f83e2c0309c9a4ea3b35cee91/src/treasuries/MinimumOrder.sol) - -**Inherits:** -[BaseTreasury](/src/utils/BaseTreasury.sol/abstract.BaseTreasury.md), ERC721Burnable, [TimestampChecker](/src/utils/TimestampChecker.sol/abstract.TimestampChecker.md) - -A Solidity contract for managing minimum order-based campaigns. -Users can pre-order items or rewards, and when a predefined success metric is reached, -the campaign succeeds, and backers receive their rewards. - -## State Variables - -### SUCCESS_METRIC - -```solidity -uint256 internal immutable SUCCESS_METRIC; -``` - -### s_preOrderValueAmount - -```solidity -uint256 private s_preOrderValueAmount; -``` - -### s_platformFeePercent - -```solidity -uint256 private s_platformFeePercent; -``` - -### s_tokenToPledgedAmount - -```solidity -mapping(uint256 => uint256) private s_tokenToPledgedAmount; -``` - -### s_reward - -```solidity -mapping(bytes32 => Reward) private s_reward; -``` - -### s_tokenIdCounter - -```solidity -Counters.Counter private s_tokenIdCounter; -``` - -### s_numberOfPreOrders - -```solidity -Counters.Counter internal s_numberOfPreOrders; -``` - -## Functions - -### constructor - -_Constructor for the MinimumOrder contract._ - -```solidity -constructor(bytes32 platformHash, address infoAddress) ERC721("", "") BaseTreasury(platformHash, infoAddress); -``` - -**Parameters** - -| Name | Type | Description | -| -------------- | --------- | -------------------------------------------------------------------- | -| `platformHash` | `bytes32` | The unique identifier of the platform. | -| `infoAddress` | `address` | The address of the CampaignInfo contract providing campaign details. | - -### getNumberOfOrders - -bytes32 of `PreOrder0MinimumOrder(uint256)` - -Function to get the number of pre-orders made. - -```solidity -function getNumberOfOrders() internal view returns (uint256); -``` - -**Returns** - -| Name | Type | Description | -| -------- | --------- | ------------------------- | -| `` | `uint256` | The number of pre-orders. | - -### getReward - -Function to get reward details by name. - -```solidity -function getReward(bytes32 rewardName) external view returns (Reward memory); -``` - -**Parameters** - -| Name | Type | Description | -| ------------ | --------- | ----------------------- | -| `rewardName` | `bytes32` | The name of the reward. | - -**Returns** - -| Name | Type | Description | -| -------- | -------- | ---------------------------------------------------------------------- | -| `` | `Reward` | The reward details, including value, item IDs, values, and quantities. | - -### getRaisedAmount - -Function to get the total raised amount during the campaign. - -```solidity -function getRaisedAmount() external view returns (uint256); -``` - -**Returns** - -| Name | Type | Description | -| -------- | --------- | ------------------------ | -| `` | `uint256` | The total raised amount. | - -### addReward - -Function to add a new reward to the campaign. -Only the campaign owner can add rewards. - -```solidity -function addReward(bytes32 rewardName, Reward calldata reward) - external - onlyCampaignOwner - whenCampaignNotPaused - whenNotPaused; -``` - -**Parameters** - -| Name | Type | Description | -| ------------ | --------- | ---------------------------------------------------------------------- | -| `rewardName` | `bytes32` | The name of the reward. | -| `reward` | `Reward` | The reward details, including value, item IDs, values, and quantities. | - -### removeReward - -Function to remove a reward from the campaign. -Only the campaign owner can remove rewards. - -```solidity -function removeReward(bytes32 rewardName) external onlyCampaignOwner whenCampaignNotPaused whenNotPaused; -``` - -**Parameters** - -| Name | Type | Description | -| ------------ | --------- | ------------------------------------- | -| `rewardName` | `bytes32` | The name of the reward to be removed. | - -### preOrderForAReward - -Function for backers to pre-order a reward. -The pre-order can only be made within the specified campaign timeframe. - -```solidity -function preOrderForAReward(address backer, bytes32 rewardName) - public - virtual - currentTimeIsWithinRange(INFO.getLaunchTime(), INFO.getDeadline()) - whenCampaignNotPaused - whenNotPaused; -``` - -**Parameters** - -| Name | Type | Description | -| ------------ | --------- | ----------------------------------------------- | -| `backer` | `address` | The address of the backer making the pre-order. | -| `rewardName` | `bytes32` | The name of the reward to pre-order. | - -### claimRefund - -Function for backers to claim a refund if the campaign has not met the success metric. - -```solidity -function claimRefund(uint256 tokenId) external whenCampaignNotPaused whenNotPaused; -``` - -**Parameters** - -| Name | Type | Description | -| --------- | --------- | ----------------------------------------------- | -| `tokenId` | `uint256` | The unique token ID associated with the refund. | - -### \_checkSuccessCondition - -_Internal function to check the success condition for fee disbursement._ - -```solidity -function _checkSuccessCondition() internal view virtual override returns (bool); -``` - -**Returns** - -| Name | Type | Description | -| -------- | ------ | ------------------------------------- | -| `` | `bool` | Whether the success condition is met. | - -### supportsInterface - -Function to check if an address is supported by the ERC721 contract. - -```solidity -function supportsInterface(bytes4 interfaceId) public view override returns (bool); -``` - -**Parameters** - -| Name | Type | Description | -| ------------- | -------- | --------------------------------- | -| `interfaceId` | `bytes4` | The ERC721 interface ID to check. | - -**Returns** - -| Name | Type | Description | -| -------- | ------ | ---------------------------------------------------- | -| `` | `bool` | True if the interface is supported, false otherwise. | - -## Events - -### Receipt - -_Event emitted when a backer makes a pledge._ - -```solidity -event Receipt(address indexed backer, bytes32 indexed reward, uint256 pledgeAmount, uint256 tokenId); -``` - -### RewardAdded - -_Event emitted when a reward is added to the campaign._ - -```solidity -event RewardAdded(bytes32 indexed rewardName, Reward reward); -``` - -### RewardRemoved - -_Event emitted when a reward is removed from the campaign._ - -```solidity -event RewardRemoved(bytes32 indexed rewardName); -``` - -### RefundClaimed - -_Event emitted when a refund is claimed by a backer._ - -```solidity -event RefundClaimed(uint256 tokenId, uint256 refundAmount, address claimer); -``` - -## Errors - -### PreOrderTransferFailed - -_Throws an error indicating that the pre-order transfer failed._ - -```solidity -error PreOrderTransferFailed(); -``` - -### PreOrderInvalidInput - -_Throws an error indicating that the pre-order input is invalid._ - -```solidity -error PreOrderInvalidInput(); -``` - -## Structs - -### Reward - -```solidity -struct Reward { - uint256 rewardValue; - bytes32[] itemId; - uint256[] itemValue; - uint256[] itemQuantity; -} -``` diff --git a/docs/src/src/treasuries/OutOfStock.sol/contract.OutOfStock.md b/docs/src/src/treasuries/OutOfStock.sol/contract.OutOfStock.md deleted file mode 100644 index 2c95f9d2..00000000 --- a/docs/src/src/treasuries/OutOfStock.sol/contract.OutOfStock.md +++ /dev/null @@ -1,58 +0,0 @@ -# OutOfStock - -[Git Source](https://github.com/ccprotocol/campaign-utils-contracts-aggregator/blob/79d78188e565502f83e2c0309c9a4ea3b35cee91/src/treasuries/OutOfStock.sol) - -**Inherits:** -[MinimumOrder](/src/treasuries/MinimumOrder.sol/contract.MinimumOrder.md) - -A Solidity contract for managing minimum order-based campaigns with an out-of-stock limit. -Users can pre-order items or rewards until the out-of-stock limit is reached. -When the predefined success metric is reached or the out-of-stock limit is reached, the campaign ends. - -## Functions - -### constructor - -_Constructor for the OutOfStock contract._ - -```solidity -constructor(bytes32 platformHash, address infoAddress) MinimumOrder(platformHash, infoAddress); -``` - -**Parameters** - -| Name | Type | Description | -| -------------- | --------- | -------------------------------------------------------------------- | -| `platformHash` | `bytes32` | The unique identifier of the platform. | -| `infoAddress` | `address` | The address of the CampaignInfo contract providing campaign details. | - -### preOrderForAReward - -Function for backers to pre-order a reward, checking against the out-of-stock limit. -The pre-order can only be made within the specified campaign timeframe. - -```solidity -function preOrderForAReward(address backer, bytes32 rewardName) - public - override - currentTimeIsWithinRange(INFO.getLaunchTime(), INFO.getDeadline()) - whenCampaignNotPaused - whenNotPaused; -``` - -**Parameters** - -| Name | Type | Description | -| ------------ | --------- | ----------------------------------------------- | -| `backer` | `address` | The address of the backer making the pre-order. | -| `rewardName` | `bytes32` | The name of the reward to pre-order. | - -## Errors - -### OutOfStockLimitReached - -_Throws an error indicating the out-of-stock limit has been reached._ - -```solidity -error OutOfStockLimitReached(); -``` diff --git a/docs/src/src/utils/AddressCalculator.sol/library.AddressCalculator.md b/docs/src/src/utils/AddressCalculator.sol/library.AddressCalculator.md deleted file mode 100644 index 6daa3a46..00000000 --- a/docs/src/src/utils/AddressCalculator.sol/library.AddressCalculator.md +++ /dev/null @@ -1,55 +0,0 @@ -# AddressCalculator -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/AddressCalculator.sol) - -A Solidity library for computing contract addresses and checking if a contract is deployed at a given address. - - -## Functions -### computeAddress - -*Computes the contract address using CREATE2 and checks if the contract is deployed.* - - -```solidity -function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) - internal - view - returns (address addr, bool isValid); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`salt`|`bytes32`|The salt value used for address computation.| -|`bytecodeHash`|`bytes32`|The keccak256 hash of the contract's bytecode.| -|`deployer`|`address`|The address that deploys the contract.| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`addr`|`address`|The computed contract address.| -|`isValid`|`bool`|True if a contract is deployed at the address; otherwise, false.| - - -### checkIfContractDeployed - -*Checks if a contract is deployed at the given address.* - - -```solidity -function checkIfContractDeployed(address addr) internal view returns (bool isValid); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`addr`|`address`|The address to check for contract deployment.| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|`isValid`|`bool`|True if a contract is deployed at the address; otherwise, false.| - - diff --git a/docs/src/src/utils/AdminAccessChecker.sol/abstract.AdminAccessChecker.md b/docs/src/src/utils/AdminAccessChecker.sol/abstract.AdminAccessChecker.md index 6249f4a2..b8065c6a 100644 --- a/docs/src/src/utils/AdminAccessChecker.sol/abstract.AdminAccessChecker.md +++ b/docs/src/src/utils/AdminAccessChecker.sol/abstract.AdminAccessChecker.md @@ -1,5 +1,5 @@ # AdminAccessChecker -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/AdminAccessChecker.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/utils/AdminAccessChecker.sol) *This abstract contract provides access control mechanisms to restrict the execution of specific functions to authorized protocol administrators and platform administrators.* diff --git a/docs/src/src/utils/BaseTreasury.sol/abstract.BaseTreasury.md b/docs/src/src/utils/BaseTreasury.sol/abstract.BaseTreasury.md index 1f9d835e..1b437429 100644 --- a/docs/src/src/utils/BaseTreasury.sol/abstract.BaseTreasury.md +++ b/docs/src/src/utils/BaseTreasury.sol/abstract.BaseTreasury.md @@ -1,5 +1,5 @@ # BaseTreasury -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/BaseTreasury.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/utils/BaseTreasury.sol) **Inherits:** Initializable, [ICampaignTreasury](/src/interfaces/ICampaignTreasury.sol/interface.ICampaignTreasury.md), [CampaignAccessChecker](/src/utils/CampaignAccessChecker.sol/abstract.CampaignAccessChecker.md), [PausableCancellable](/src/utils/PausableCancellable.sol/abstract.PausableCancellable.md) @@ -47,13 +47,6 @@ IERC20 internal TOKEN; ``` -### CAMPAIGN_INFO - -```solidity -ICampaignInfo internal CAMPAIGN_INFO; -``` - - ### s_pledgedAmount ```solidity @@ -92,13 +85,13 @@ modifier whenCampaignNotPaused(); modifier whenCampaignNotCancelled(); ``` -### getplatformHash +### getPlatformHash Retrieves the platform identifier associated with the treasury. ```solidity -function getplatformHash() external view override returns (bytes32); +function getPlatformHash() external view override returns (bytes32); ``` **Returns** @@ -107,13 +100,13 @@ function getplatformHash() external view override returns (bytes32); |``|`bytes32`|The platform identifier as a bytes32 value.| -### getplatformFeePercent +### getPlatformFeePercent Retrieves the platform fee percentage for the treasury. ```solidity -function getplatformFeePercent() external view override returns (uint256); +function getPlatformFeePercent() external view override returns (uint256); ``` **Returns** diff --git a/docs/src/src/utils/CampaignAccessChecker.sol/abstract.CampaignAccessChecker.md b/docs/src/src/utils/CampaignAccessChecker.sol/abstract.CampaignAccessChecker.md index 4c39e4e8..153e499e 100644 --- a/docs/src/src/utils/CampaignAccessChecker.sol/abstract.CampaignAccessChecker.md +++ b/docs/src/src/utils/CampaignAccessChecker.sol/abstract.CampaignAccessChecker.md @@ -1,5 +1,5 @@ # CampaignAccessChecker -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/CampaignAccessChecker.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/utils/CampaignAccessChecker.sol) *This abstract contract provides access control mechanisms to restrict the execution of specific functions to authorized protocol administrators, platform administrators, and campaign owners.* diff --git a/docs/src/src/utils/Counters.sol/library.Counters.md b/docs/src/src/utils/Counters.sol/library.Counters.md index d8d77f16..45805f48 100644 --- a/docs/src/src/utils/Counters.sol/library.Counters.md +++ b/docs/src/src/utils/Counters.sol/library.Counters.md @@ -1,12 +1,5 @@ # Counters -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/Counters.sol) - -**Author:** -Matt Condon (@shrugs) - -*Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number -of elements in a mapping, issuing ERC721 ids, or counting request ids. -Include with `using Counters for Counters.Counter;`* +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/utils/Counters.sol) ## Functions @@ -38,6 +31,15 @@ function decrement(Counter storage counter) internal; function reset(Counter storage counter) internal; ``` +## Errors +### CounterDecrementOverflow +*Error thrown when attempting to decrement a counter with value 0.* + + +```solidity +error CounterDecrementOverflow(); +``` + ## Structs ### Counter diff --git a/docs/src/src/utils/FiatEnabled.sol/abstract.FiatEnabled.md b/docs/src/src/utils/FiatEnabled.sol/abstract.FiatEnabled.md index ee22132e..ee5c2c9d 100644 --- a/docs/src/src/utils/FiatEnabled.sol/abstract.FiatEnabled.md +++ b/docs/src/src/utils/FiatEnabled.sol/abstract.FiatEnabled.md @@ -1,5 +1,5 @@ # FiatEnabled -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/FiatEnabled.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/utils/FiatEnabled.sol) A contract that provides functionality for tracking and managing fiat transactions. This contract allows tracking the amount of fiat raised, individual fiat transactions, and the state of fiat fee disbursement. diff --git a/docs/src/src/utils/ItemRegistry.sol/contract.ItemRegistry.md b/docs/src/src/utils/ItemRegistry.sol/contract.ItemRegistry.md index 7ac56706..2e4d08a6 100644 --- a/docs/src/src/utils/ItemRegistry.sol/contract.ItemRegistry.md +++ b/docs/src/src/utils/ItemRegistry.sol/contract.ItemRegistry.md @@ -1,5 +1,5 @@ # ItemRegistry -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/ItemRegistry.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/utils/ItemRegistry.sol) **Inherits:** [IItem](/src/interfaces/IItem.sol/interface.IItem.md), Context @@ -87,3 +87,12 @@ event ItemAdded(address indexed owner, bytes32 indexed itemId, Item item); |`itemId`|`bytes32`|The unique identifier of the item.| |`item`|`Item`|The item details including actual weight, dimensions, category, and declared currency.| +## Errors +### ItemRegistryMismatchedArraysLength +*Thrown when the input arrays have mismatched lengths.* + + +```solidity +error ItemRegistryMismatchedArraysLength(); +``` + diff --git a/docs/src/src/utils/PausableCancellable.sol/abstract.PausableCancellable.md b/docs/src/src/utils/PausableCancellable.sol/abstract.PausableCancellable.md index d13dcff3..cd083e89 100644 --- a/docs/src/src/utils/PausableCancellable.sol/abstract.PausableCancellable.md +++ b/docs/src/src/utils/PausableCancellable.sol/abstract.PausableCancellable.md @@ -1,5 +1,5 @@ # PausableCancellable -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/PausableCancellable.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/utils/PausableCancellable.sol) Abstract contract providing pause and cancel state management with events and modifiers diff --git a/docs/src/src/utils/PausableWithMsg.sol/abstract.PausableWithMsg.md b/docs/src/src/utils/PausableWithMsg.sol/abstract.PausableWithMsg.md deleted file mode 100644 index b5ad4586..00000000 --- a/docs/src/src/utils/PausableWithMsg.sol/abstract.PausableWithMsg.md +++ /dev/null @@ -1,110 +0,0 @@ -# PausableWithMsg -[Git Source](https://github.com/ccprotocol/campaign-utils-contracts-aggregator/blob/79d78188e565502f83e2c0309c9a4ea3b35cee91/src/utils/PausableWithMsg.sol) - - -## State Variables -### _paused - -```solidity -bool private _paused; -``` - - -## Functions -### constructor - -*Initializes the contract in unpaused state.* - - -```solidity -constructor(); -``` - -### whenNotPaused - -*Modifier to make a function callable only when the contract is not paused. -Requirements: -- The contract must not be paused.* - - -```solidity -modifier whenNotPaused(); -``` - -### whenPaused - -*Modifier to make a function callable only when the contract is paused. -Requirements: -- The contract must be paused.* - - -```solidity -modifier whenPaused(); -``` - -### paused - -*Returns true if the contract is paused, and false otherwise.* - - -```solidity -function paused() public view virtual returns (bool); -``` - -### _requireNotPaused - -*Throws if the contract is paused.* - - -```solidity -function _requireNotPaused() internal view virtual; -``` - -### _requirePaused - -*Throws if the contract is not paused.* - - -```solidity -function _requirePaused() internal view virtual; -``` - -### _pause - -*Triggers stopped state. -Requirements: -- The contract must not be paused.* - - -```solidity -function _pause(bytes32 message) internal virtual whenNotPaused; -``` - -### _unpause - -*Returns to normal state. -Requirements: -- The contract must be paused.* - - -```solidity -function _unpause(bytes32 message) internal virtual whenPaused; -``` - -## Events -### Paused -*Emitted when the pause is triggered by `account`.* - - -```solidity -event Paused(address account, bytes32 message); -``` - -### Unpaused -*Emitted when the pause is lifted by `account`.* - - -```solidity -event Unpaused(address account, bytes32 message); -``` - diff --git a/docs/src/src/utils/README.md b/docs/src/src/utils/README.md index a8b376bf..41bc0ddb 100644 --- a/docs/src/src/utils/README.md +++ b/docs/src/src/utils/README.md @@ -1,7 +1,6 @@ # Contents -- [AddressCalculator](AddressCalculator.sol/library.AddressCalculator.md) - [AdminAccessChecker](AdminAccessChecker.sol/abstract.AdminAccessChecker.md) - [BaseTreasury](BaseTreasury.sol/abstract.BaseTreasury.md) - [CampaignAccessChecker](CampaignAccessChecker.sol/abstract.CampaignAccessChecker.md) diff --git a/docs/src/src/utils/TimestampChecker.sol/abstract.TimestampChecker.md b/docs/src/src/utils/TimestampChecker.sol/abstract.TimestampChecker.md index d75b4cdd..ae8c27a2 100644 --- a/docs/src/src/utils/TimestampChecker.sol/abstract.TimestampChecker.md +++ b/docs/src/src/utils/TimestampChecker.sol/abstract.TimestampChecker.md @@ -1,5 +1,5 @@ # TimestampChecker -[Git Source](https://github.com/ccprotocol/reference-client-sc/blob/13d9d746c7f79b76f03c178fe64b679ba803191a/src/utils/TimestampChecker.sol) +[Git Source](https://github.com/ccprotocol/ccprotocol-contracts/blob/b6945e2b533f7d9aacb156ae915f6d1bb6b199de/src/utils/TimestampChecker.sol) A contract that provides timestamp-related checks for contract functions. @@ -51,13 +51,13 @@ modifier currentTimeIsWithinRange(uint256 initialTime, uint256 finalTime); |`finalTime`|`uint256`|The final timestamp of the range.| -### _checkIfCurrentTimeIsLess +### _revertIfCurrentTimeIsNotLess -*Internal function to check if the current timestamp is less than or equal a specified time.* +*Internal function to revert if the current timestamp is less than or equal a specified time.* ```solidity -function _checkIfCurrentTimeIsLess(uint256 inputTime) internal view virtual; +function _revertIfCurrentTimeIsNotLess(uint256 inputTime) internal view virtual; ``` **Parameters** @@ -66,13 +66,13 @@ function _checkIfCurrentTimeIsLess(uint256 inputTime) internal view virtual; |`inputTime`|`uint256`|The timestamp being checked against.| -### _checkIfCurrentTimeIsGreater +### _revertIfCurrentTimeIsNotGreater -*Internal function to check if the current timestamp is greater than or equal a specified time.* +*Internal function to revert if the current timestamp is not greater than or equal a specified time.* ```solidity -function _checkIfCurrentTimeIsGreater(uint256 inputTime) internal view virtual; +function _revertIfCurrentTimeIsNotGreater(uint256 inputTime) internal view virtual; ``` **Parameters** @@ -81,13 +81,13 @@ function _checkIfCurrentTimeIsGreater(uint256 inputTime) internal view virtual; |`inputTime`|`uint256`|The timestamp being checked against.| -### _checkIfCurrentTimeIsWithinRange +### _revertIfCurrentTimeIsNotWithinRange -*Internal function to check if the current timestamp is within a specified time range.* +*Internal function to revert if the current timestamp is not within a specified time range.* ```solidity -function _checkIfCurrentTimeIsWithinRange(uint256 initialTime, uint256 finalTime) internal view virtual; +function _revertIfCurrentTimeIsNotWithinRange(uint256 initialTime, uint256 finalTime) internal view virtual; ``` **Parameters** diff --git a/env.example b/env.example index 4c5b7440..9265a035 100644 --- a/env.example +++ b/env.example @@ -1,17 +1,71 @@ -# ------------------------- -# Deploy Config -# ------------------------- +# ========================= +# Deploy Configuration +# ========================= + +# Wallet and RPC PRIVATE_KEY= RPC_URL= -# Optionally set this to reuse already-deployed contracts. -# If any are left blank, they will be freshly deployed. +# Optional: Separate RPC URLs for multiple chains +ALFAJORES_RPC_URL= +CELO_RPC_URL= + +# Chain IDs +CHAIN_ID= +ALFAJORES_CHAIN_ID= +CELO_CHAIN_ID= + -# ------------------------- +# ========================= # Contract Addresses -# ------------------------- -TEST_USD_ADDRESS="" -GLOBAL_PARAMS_ADDRESS="" -TREASURY_FACTORY_ADDRESS="" -CAMPAIGN_INFO_FACTORY_ADDRESS="" -SIMULATE=true \ No newline at end of file +# ========================= + +# Optional: Reuse already-deployed contracts +TOKEN_ADDRESS= +GLOBAL_PARAMS_ADDRESS= +TREASURY_FACTORY_ADDRESS= +CAMPAIGN_INFO_FACTORY_ADDRESS= + + +# ========================= +# Protocol Parameters +# ========================= + +PROTOCOL_ADMIN_ADDRESS= +PROTOCOL_FEE_PERCENT= + + +# ========================= +# Platform Parameters +# ========================= + +PLATFORM_NAME= +PLATFORM_ADMIN_ADDRESS= +PLATFORM_FEE_PERCENT= + + +# ========================= +# Token Setup +# ========================= + +# Only required if TOKEN needs to be pre-minted +TOKEN_MINT_AMOUNT= +TOKEN_NAME="" +TOKEN_SYMBOL="" +BACKER1_ADDRESS= +BACKER2_ADDRESS= + + +# ========================= +# Simulation +# ========================= + +# Set to "true" or "false" only +SIMULATE= + + +# ========================= +# Verification +# ========================= + +ETHERSCAN_API_KEY= diff --git a/foundry.toml b/foundry.toml index 3c29c44e..95ee243f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -11,6 +11,6 @@ remappings = [ "@openzeppelin/=lib/openzeppelin-contracts/" ] - [rpc_endpoints] -alfajores = "${ALFAJORES_RPC_URL}" +mainnet = "https://forno.celo.org/" +alfajores = "https://alfajores-forno.celo-testnet.org/" diff --git a/script/DeployAll.s.sol b/script/DeployAll.s.sol index 741a3208..f7936213 100644 --- a/script/DeployAll.s.sol +++ b/script/DeployAll.s.sol @@ -1,21 +1,22 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "forge-std/Script.sol"; -import "./DeployGlobalParams.s.sol"; -import "./DeployTestUSD.s.sol"; -import "./DeployCampaignInfoFactory.s.sol"; -import "./DeployTreasuryFactory.s.sol"; +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; +import {DeployGlobalParams} from "./DeployGlobalParams.s.sol"; +import {DeployTestToken} from "./DeployTestToken.s.sol"; +import {DeployCampaignInfoFactory} from "./DeployCampaignInfoFactory.s.sol"; +import {DeployTreasuryFactory} from "./DeployTreasuryFactory.s.sol"; contract DeployAll is Script { - function deployTestUSD() internal returns (address) { - DeployTestUSD script = new DeployTestUSD(); + function deployTestToken() internal returns (address) { + DeployTestToken script = new DeployTestToken(); return script.deploy(); } - function deployGlobalParams(address testUSD) internal returns (address) { + function deployGlobalParams(address testToken) internal returns (address) { DeployGlobalParams script = new DeployGlobalParams(); - return script.deployWithToken(testUSD); + return script.deployWithToken(testToken); } function deployTreasuryFactory( @@ -41,8 +42,8 @@ contract DeployAll is Script { vm.startBroadcast(deployerKey); } - address testUSD = deployTestUSD(); - address globalParams = deployGlobalParams(testUSD); + address testToken = deployTestToken(); + address globalParams = deployGlobalParams(testToken); address treasuryFactory = deployTreasuryFactory(globalParams); address campaignFactory = deployCampaignFactory( globalParams, @@ -53,7 +54,7 @@ contract DeployAll is Script { vm.stopBroadcast(); } - console2.log("TEST_USD_ADDRESS", testUSD); + console2.log("TOKEN_ADDRESS", testToken); console2.log("GLOBAL_PARAMS_ADDRESS", globalParams); console2.log("TREASURY_FACTORY_ADDRESS", treasuryFactory); console2.log("CAMPAIGN_INFO_FACTORY_ADDRESS", campaignFactory); diff --git a/script/DeployAllAndSetupAllOrNothing.s.sol b/script/DeployAllAndSetupAllOrNothing.s.sol new file mode 100644 index 00000000..d0dc456c --- /dev/null +++ b/script/DeployAllAndSetupAllOrNothing.s.sol @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; +import {TestToken} from "../test/mocks/TestToken.sol"; +import {GlobalParams} from "src/GlobalParams.sol"; +import {CampaignInfoFactory} from "src/CampaignInfoFactory.sol"; +import {CampaignInfo} from "src/CampaignInfo.sol"; +import {TreasuryFactory} from "src/TreasuryFactory.sol"; +import {AllOrNothing} from "src/treasuries/AllOrNothing.sol"; + +/** + * @notice Script to deploy and setup all needed contracts for the protocol + */ +contract DeployAllAndSetupAllOrNothing is Script { + // Customizable values (set through environment variables) + bytes32 platformHash; + uint256 protocolFeePercent; + uint256 platformFeePercent; + uint256 tokenMintAmount; + bool simulate; + + // Contract addresses + address testToken; + address globalParams; + address campaignInfoImplementation; + address treasuryFactory; + address campaignInfoFactory; + address allOrNothingImplementation; + + // User addresses + address deployerAddress; + address finalProtocolAdmin; + address finalPlatformAdmin; + address backer1; + address backer2; + + // Token details + // string tokenName; + // string tokenSymbol; + + // Flags to track what was completed + bool platformEnlisted = false; + bool implementationRegistered = false; + bool implementationApproved = false; + bool adminRightsTransferred = false; + + // Flags for contract deployment or reuse + bool testTokenDeployed = false; + bool globalParamsDeployed = false; + bool treasuryFactoryDeployed = false; + bool campaignInfoFactoryDeployed = false; + bool allOrNothingDeployed = false; + + // Configure parameters based on environment variables + function setupParams() internal { + // Get customizable values + string memory platformName = vm.envOr( + "PLATFORM_NAME", + string("MiniFunder") + ); + + platformHash = keccak256(abi.encodePacked(platformName)); + protocolFeePercent = vm.envOr("PROTOCOL_FEE_PERCENT", uint256(100)); // Default 1% + platformFeePercent = vm.envOr("PLATFORM_FEE_PERCENT", uint256(400)); // Default 4% + tokenMintAmount = vm.envOr("TOKEN_MINT_AMOUNT", uint256(10000000e18)); + simulate = vm.envOr("SIMULATE", false); + + // Get user addresses + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + deployerAddress = vm.addr(deployerKey); + + // These are the final admin addresses that will receive control + finalProtocolAdmin = vm.envOr( + "PROTOCOL_ADMIN_ADDRESS", + deployerAddress + ); + finalPlatformAdmin = vm.envOr( + "PLATFORM_ADMIN_ADDRESS", + deployerAddress + ); + backer1 = vm.envOr("BACKER1_ADDRESS", address(0)); + backer2 = vm.envOr("BACKER2_ADDRESS", address(0)); + + // Check for existing contract addresses + testToken = vm.envOr("TOKEN_ADDRESS", address(0)); + globalParams = vm.envOr("GLOBAL_PARAMS_ADDRESS", address(0)); + treasuryFactory = vm.envOr("TREASURY_FACTORY_ADDRESS", address(0)); + campaignInfoFactory = vm.envOr( + "CAMPAIGN_INFO_FACTORY_ADDRESS", + address(0) + ); + allOrNothingImplementation = vm.envOr( + "ALL_OR_NOTHING_IMPLEMENTATION_ADDRESS", + address(0) + ); + + console2.log("Using platform hash for:", platformName); + console2.log("Protocol fee percent:", protocolFeePercent); + console2.log("Platform fee percent:", platformFeePercent); + console2.log("Simulation mode:", simulate); + console2.log("Deployer address:", deployerAddress); + console2.log("Final protocol admin:", finalProtocolAdmin); + console2.log("Final platform admin:", finalPlatformAdmin); + } + + // Deploy or reuse contracts + function deployContracts() internal { + console2.log("Setting up contracts..."); + + // Deploy or reuse TestToken + + string memory tokenName = vm.envOr("TOKEN_NAME", string("TestToken")); + string memory tokenSymbol = vm.envOr("TOKEN_SYMBOL", string("TST")); + + if (testToken == address(0)) { + testToken = address(new TestToken(tokenName, tokenSymbol)); + testTokenDeployed = true; + console2.log("TestToken deployed at:", testToken); + } else { + console2.log("Reusing TestToken at:", testToken); + } + + // Deploy or reuse GlobalParams + if (globalParams == address(0)) { + globalParams = address( + new GlobalParams( + deployerAddress, // Initially deployer is protocol admin + testToken, + protocolFeePercent + ) + ); + globalParamsDeployed = true; + console2.log("GlobalParams deployed at:", globalParams); + } else { + console2.log("Reusing GlobalParams at:", globalParams); + } + + // We need at least TestToken and GlobalParams to continue + require(testToken != address(0), "TestToken address is required"); + require(globalParams != address(0), "GlobalParams address is required"); + + // Deploy CampaignInfo implementation if needed for new deployments + if (campaignInfoFactory == address(0)) { + campaignInfoImplementation = address( + new CampaignInfo(address(this)) + ); + console2.log( + "CampaignInfo implementation deployed at:", + campaignInfoImplementation + ); + } + + // Deploy or reuse TreasuryFactory + if (treasuryFactory == address(0)) { + treasuryFactory = address( + new TreasuryFactory(GlobalParams(globalParams)) + ); + treasuryFactoryDeployed = true; + console2.log("TreasuryFactory deployed at:", treasuryFactory); + } else { + console2.log("Reusing TreasuryFactory at:", treasuryFactory); + } + + // Deploy or reuse CampaignInfoFactory + if (campaignInfoFactory == address(0)) { + campaignInfoFactory = address( + new CampaignInfoFactory( + GlobalParams(globalParams), + campaignInfoImplementation + ) + ); + CampaignInfoFactory(campaignInfoFactory)._initialize( + treasuryFactory, + globalParams + ); + campaignInfoFactoryDeployed = true; + console2.log( + "CampaignInfoFactory deployed and initialized at:", + campaignInfoFactory + ); + } else { + console2.log( + "Reusing CampaignInfoFactory at:", + campaignInfoFactory + ); + } + + // Deploy or reuse AllOrNothing implementation + if (allOrNothingImplementation == address(0)) { + allOrNothingImplementation = address(new AllOrNothing()); + allOrNothingDeployed = true; + console2.log( + "AllOrNothing implementation deployed at:", + allOrNothingImplementation + ); + } else { + console2.log( + "Reusing AllOrNothing implementation at:", + allOrNothingImplementation + ); + } + } + + // Setup steps when deployer has all roles + function enlistPlatform() internal { + // Skip if we didn't deploy GlobalParams (assuming it's already set up) + if (!globalParamsDeployed) { + console2.log( + "Skipping enlistPlatform - using existing GlobalParams" + ); + platformEnlisted = true; + return; + } + + console2.log("Setting up: enlistPlatform"); + // Only use startPrank in simulation mode + if (simulate) { + vm.startPrank(deployerAddress); + } + + GlobalParams(globalParams).enlistPlatform( + platformHash, + deployerAddress, // Initially deployer is platform admin + platformFeePercent + ); + + if (simulate) { + vm.stopPrank(); + } + platformEnlisted = true; + console2.log("Platform enlisted successfully"); + } + + function registerTreasuryImplementation() internal { + // Skip if we didn't deploy TreasuryFactory (assuming it's already set up) + if (!treasuryFactoryDeployed || !allOrNothingDeployed) { + console2.log( + "Skipping registerTreasuryImplementation - using existing contracts" + ); + implementationRegistered = true; + return; + } + + console2.log("Setting up: registerTreasuryImplementation"); + // Only use startPrank in simulation mode + if (simulate) { + vm.startPrank(deployerAddress); + } + + TreasuryFactory(treasuryFactory).registerTreasuryImplementation( + platformHash, + 0, // Implementation ID + allOrNothingImplementation + ); + + if (simulate) { + vm.stopPrank(); + } + implementationRegistered = true; + console2.log("Treasury implementation registered successfully"); + } + + function approveTreasuryImplementation() internal { + // Skip if we didn't deploy TreasuryFactory (assuming it's already set up) + if (!treasuryFactoryDeployed || !allOrNothingDeployed) { + console2.log( + "Skipping approveTreasuryImplementation - using existing contracts" + ); + implementationApproved = true; + return; + } + + console2.log("Setting up: approveTreasuryImplementation"); + // Only use startPrank in simulation mode + if (simulate) { + vm.startPrank(deployerAddress); + } + + TreasuryFactory(treasuryFactory).approveTreasuryImplementation( + platformHash, + 0 // Implementation ID + ); + + if (simulate) { + vm.stopPrank(); + } + implementationApproved = true; + console2.log("Treasury implementation approved successfully"); + } + + function mintTokens() internal { + // Only mint tokens if we deployed TestToken + if (!testTokenDeployed) { + console2.log("Skipping mintTokens - using existing TestToken"); + return; + } + + if (backer1 != address(0) && backer2 != address(0)) { + console2.log("Minting tokens to test backers"); + TestToken(testToken).mint(backer1, tokenMintAmount); + if (backer1 != backer2) { + TestToken(testToken).mint(backer2, tokenMintAmount); + } + console2.log("Tokens minted successfully"); + } + } + + // Transfer admin rights to final addresses + function transferAdminRights() internal { + // Skip if we didn't deploy GlobalParams (assuming it's already set up) + if (!globalParamsDeployed) { + console2.log( + "Skipping transferAdminRights - using existing GlobalParams" + ); + adminRightsTransferred = true; + return; + } + + console2.log("Transferring admin rights to final addresses..."); + + // Only transfer if the final addresses are different from deployer + if (finalProtocolAdmin != deployerAddress) { + console2.log( + "Transferring protocol admin rights to:", + finalProtocolAdmin + ); + GlobalParams(globalParams).updateProtocolAdminAddress( + finalProtocolAdmin + ); + } + + if (finalPlatformAdmin != deployerAddress) { + console2.log( + "Updating platform admin address for platform hash:", + vm.toString(platformHash) + ); + GlobalParams(globalParams).updatePlatformAdminAddress( + platformHash, + finalPlatformAdmin + ); + } + + adminRightsTransferred = true; + console2.log("Admin rights transferred successfully"); + } + + function run() external { + // Load configuration + setupParams(); + + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + + // Start broadcast with deployer key + vm.startBroadcast(deployerKey); + + // Deploy or reuse contracts + deployContracts(); + + // Setup the protocol with individual transactions in the correct order + // Since deployer is both protocol and platform admin initially, we can do all steps + enlistPlatform(); + registerTreasuryImplementation(); + approveTreasuryImplementation(); + + // Mint tokens if needed + mintTokens(); + + // Finally, transfer admin rights to the final addresses + transferAdminRights(); + + // Stop broadcast + vm.stopBroadcast(); + + // Output summary + console2.log("\n--- Deployment & Setup Summary ---"); + console2.log("Platform Name Hash:", vm.toString(platformHash)); + console2.log("TOKEN_ADDRESS:", testToken); + console2.log("GLOBAL_PARAMS_ADDRESS:", globalParams); + if (campaignInfoImplementation != address(0)) { + console2.log( + "CAMPAIGN_INFO_IMPLEMENTATION_ADDRESS:", + campaignInfoImplementation + ); + } + console2.log("TREASURY_FACTORY_ADDRESS:", treasuryFactory); + console2.log("CAMPAIGN_INFO_FACTORY_ADDRESS:", campaignInfoFactory); + console2.log( + "ALL_OR_NOTHING_IMPLEMENTATION_ADDRESS:", + allOrNothingImplementation + ); + console2.log("Protocol Admin:", finalProtocolAdmin); + console2.log("Platform Admin:", finalPlatformAdmin); + + if (backer1 != address(0)) { + console2.log("Backer1 (tokens minted):", backer1); + } + if (backer2 != address(0) && backer1 != backer2) { + console2.log("Backer2 (tokens minted):", backer2); + } + + console2.log("\nDeployment status:"); + console2.log( + "- TestToken:", + testTokenDeployed ? "Newly deployed" : "Reused existing" + ); + console2.log( + "- GlobalParams:", + globalParamsDeployed ? "Newly deployed" : "Reused existing" + ); + console2.log( + "- TreasuryFactory:", + treasuryFactoryDeployed ? "Newly deployed" : "Reused existing" + ); + console2.log( + "- CampaignInfoFactory:", + campaignInfoFactoryDeployed ? "Newly deployed" : "Reused existing" + ); + console2.log( + "- AllOrNothing Implementation:", + allOrNothingDeployed ? "Newly deployed" : "Reused existing" + ); + + console2.log("\nSetup steps:"); + console2.log("1. Platform enlisted:", platformEnlisted); + console2.log( + "2. Treasury implementation registered:", + implementationRegistered + ); + console2.log( + "3. Treasury implementation approved:", + implementationApproved + ); + console2.log("4. Admin rights transferred:", adminRightsTransferred); + + console2.log("\nDeployment and setup completed successfully!"); + } +} diff --git a/script/DeployAllOrNothingImplementation.s.sol b/script/DeployAllOrNothingImplementation.s.sol new file mode 100644 index 00000000..7c017a50 --- /dev/null +++ b/script/DeployAllOrNothingImplementation.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; +import {AllOrNothing} from "src/treasuries/AllOrNothing.sol"; + +contract DeployAllOrNothingImplementation is Script { + function deploy() public returns (address) { + console2.log("Deploying AllOrNothingImplementation..."); + AllOrNothing allOrNothingImplementation = new AllOrNothing(); + console2.log( + "AllOrNothingImplementation deployed at:", + address(allOrNothingImplementation) + ); + return address(allOrNothingImplementation); + } + + function run() external { + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + bool simulate = vm.envOr("SIMULATE", false); + + if (!simulate) { + vm.startBroadcast(deployerKey); + } + + address implementationAddress = deploy(); + + if (!simulate) { + vm.stopBroadcast(); + } + + console2.log( + "ALL_OR_NOTHING_IMPLEMENTATION_ADDRESS", + implementationAddress + ); + } +} diff --git a/script/DeployCampaignInfoFactory.s.sol b/script/DeployCampaignInfoFactory.s.sol index 6fc116d0..cbf00748 100644 --- a/script/DeployCampaignInfoFactory.s.sol +++ b/script/DeployCampaignInfoFactory.s.sol @@ -1,47 +1,57 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {CampaignInfo} from "../src/CampaignInfo.sol"; -import {CampaignInfoFactory} from "../src/CampaignInfoFactory.sol"; -import {GlobalParams} from "../src/GlobalParams.sol"; -import {TreasuryFactory} from "../src/TreasuryFactory.sol"; +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; +import {CampaignInfoFactory} from "src/CampaignInfoFactory.sol"; +import {CampaignInfo} from "src/CampaignInfo.sol"; +import {GlobalParams} from "src/GlobalParams.sol"; import {DeployBase} from "./lib/DeployBase.s.sol"; contract DeployCampaignInfoFactory is DeployBase { function deploy( - address _globalParams, - address _treasuryFactory + address globalParams, + address treasuryFactory ) public returns (address) { - require(_globalParams != address(0), "GlobalParams not set"); - require(_treasuryFactory != address(0), "TreasuryFactory not set"); + console2.log("Deploying CampaignInfoFactory..."); - // Deploy CampaignInfo implementation - CampaignInfo campaignInfo = new CampaignInfo(msg.sender); + // Properly deploy CampaignInfo with direct instantiation + CampaignInfo campaignInfoImpl = new CampaignInfo(address(this)); + address campaignInfo = address(campaignInfoImpl); + console2.log("CampaignInfo implementation deployed at:", campaignInfo); - // Deploy CampaignInfoFactory - CampaignInfoFactory factory = new CampaignInfoFactory( - GlobalParams(_globalParams), - address(campaignInfo) + // Create and initialize the factory + CampaignInfoFactory campaignInfoFactory = new CampaignInfoFactory( + GlobalParams(globalParams), + campaignInfo ); - // Initialize the factory - factory._initialize(_treasuryFactory, _globalParams); + campaignInfoFactory._initialize(treasuryFactory, globalParams); - return address(factory); + console2.log( + "CampaignInfoFactory deployed and initialized at:", + address(campaignInfoFactory) + ); + return address(campaignInfoFactory); } function run() external { - address globalParams = vm.envOr("GLOBAL_PARAMS_ADDRESS", address(0)); - address treasuryFactory = vm.envOr( - "TREASURY_FACTORY_ADDRESS", - address(0) - ); + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + bool simulate = vm.envOr("SIMULATE", false); + + address globalParams = vm.envAddress("GLOBAL_PARAMS_ADDRESS"); + address treasuryFactory = vm.envAddress("TREASURY_FACTORY_ADDRESS"); + + if (!simulate) { + vm.startBroadcast(deployerKey); + } + + address factoryAddress = deploy(globalParams, treasuryFactory); - require(globalParams != address(0), "GlobalParams must be set"); - require(treasuryFactory != address(0), "TreasuryFactory must be set"); + if (!simulate) { + vm.stopBroadcast(); + } - vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - deploy(globalParams, treasuryFactory); - vm.stopBroadcast(); + console2.log("CAMPAIGN_INFO_FACTORY_ADDRESS", factoryAddress); } -} \ No newline at end of file +} diff --git a/script/DeployCampaignInfoImplementation.s.sol b/script/DeployCampaignInfoImplementation.s.sol new file mode 100644 index 00000000..dd4f4dc6 --- /dev/null +++ b/script/DeployCampaignInfoImplementation.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; +import {CampaignInfo} from "src/CampaignInfo.sol"; + +contract DeployCampaignInfoImplementation is Script { + function deploy() public returns (address) { + console2.log("Deploying CampaignInfo implementation..."); + // Implementation will use the script address as admin, but this will be replaced + // when the factory creates new instances + CampaignInfo campaignInfo = new CampaignInfo(address(this)); + console2.log( + "CampaignInfo implementation deployed at:", + address(campaignInfo) + ); + return address(campaignInfo); + } + + function run() external { + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + bool simulate = vm.envOr("SIMULATE", false); + + if (!simulate) { + vm.startBroadcast(deployerKey); + } + + address implementationAddress = deploy(); + + if (!simulate) { + vm.stopBroadcast(); + } + + console2.log("CAMPAIGN_INFO_ADDRESS", implementationAddress); + } +} diff --git a/script/DeployGlobalParams.s.sol b/script/DeployGlobalParams.s.sol index 22e51ca3..185d3856 100644 --- a/script/DeployGlobalParams.s.sol +++ b/script/DeployGlobalParams.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {GlobalParams} from "../src/GlobalParams.sol"; @@ -16,8 +16,8 @@ contract DeployGlobalParams is DeployBase { function _deploy() internal returns (address) { address deployer = vm.addr(vm.envUint("PRIVATE_KEY")); - address token = vm.envOr("TEST_USD_ADDRESS", address(0)); - require(token != address(0), "TestUSD address must be set"); + address token = vm.envOr("TOKEN_ADDRESS", address(0)); + require(token != address(0), "TestToken address must be set"); return address(new GlobalParams(deployer, token, 200)); } diff --git a/script/DeployTestToken.s.sol b/script/DeployTestToken.s.sol new file mode 100644 index 00000000..93d6d072 --- /dev/null +++ b/script/DeployTestToken.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {TestToken} from "../test/mocks/TestToken.sol"; +import {DeployBase} from "./lib/DeployBase.s.sol"; + +contract DeployTestToken is DeployBase { + function deploy() public returns (address) { + return deployOrUse("TOKEN_ADDRESS", _deploy); + } + + function _deploy() internal returns (address) { + string memory tokenName = vm.envOr("TOKEN_NAME", string("TestToken")); + string memory tokenSymbol = vm.envOr("TOKEN_SYMBOL", string("TST")); + return address(new TestToken(tokenName, tokenSymbol)); + } + + function run() external { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + deploy(); + vm.stopBroadcast(); + } +} diff --git a/script/DeployTestUSD.s.sol b/script/DeployTestUSD.s.sol deleted file mode 100644 index 3a955e91..00000000 --- a/script/DeployTestUSD.s.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; - -import {TestUSD} from "../src/TestUSD.sol"; -import {DeployBase} from "./lib/DeployBase.s.sol"; - -contract DeployTestUSD is DeployBase { - function deploy() public returns (address) { - return deployOrUse("TEST_USD_ADDRESS", _deploy); - } - - function _deploy() internal returns (address) { - return address(new TestUSD()); - } - - function run() external { - vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - deploy(); - vm.stopBroadcast(); - } -} diff --git a/script/DeployTreasuryFactory.s.sol b/script/DeployTreasuryFactory.s.sol index 068cb9ac..4689147f 100644 --- a/script/DeployTreasuryFactory.s.sol +++ b/script/DeployTreasuryFactory.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {TreasuryFactory} from "../src/TreasuryFactory.sol"; @@ -19,4 +19,4 @@ contract DeployTreasuryFactory is DeployBase { deploy(globalParams); vm.stopBroadcast(); } -} \ No newline at end of file +} diff --git a/script/lib/DeployBase.s.sol b/script/lib/DeployBase.s.sol index 14ed974b..e3b38fc7 100644 --- a/script/lib/DeployBase.s.sol +++ b/script/lib/DeployBase.s.sol @@ -1,7 +1,8 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "forge-std/Script.sol"; +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; contract DeployBase is Script { function deployOrUse( diff --git a/src/CampaignInfo.sol b/src/CampaignInfo.sol index d3b795fb..9dc4e9af 100644 --- a/src/CampaignInfo.sol +++ b/src/CampaignInfo.sol @@ -1,17 +1,17 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "@openzeppelin/contracts/proxy/Clones.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "./interfaces/ICampaignInfo.sol"; -import "./interfaces/ICampaignData.sol"; -import "./interfaces/ICampaignTreasury.sol"; -import "./interfaces/IGlobalParams.sol"; -import "./utils/TimestampChecker.sol"; -import "./utils/AdminAccessChecker.sol"; -import "./utils/PausableCancellable.sol"; +import {ICampaignInfo} from "./interfaces/ICampaignInfo.sol"; +import {ICampaignData} from "./interfaces/ICampaignData.sol"; +import {ICampaignTreasury} from "./interfaces/ICampaignTreasury.sol"; +import {IGlobalParams} from "./interfaces/IGlobalParams.sol"; +import {TimestampChecker} from "./utils/TimestampChecker.sol"; +import {AdminAccessChecker} from "./utils/AdminAccessChecker.sol"; +import {PausableCancellable} from "./utils/PausableCancellable.sol"; /** * @title CampaignInfo @@ -28,31 +28,22 @@ contract CampaignInfo is { CampaignData private s_campaignData; - mapping(bytes32 => bool) private s_selectedPlatformHash; mapping(bytes32 => address) private s_platformTreasuryAddress; mapping(bytes32 => uint256) private s_platformFeePercent; + mapping(bytes32 => bool) private s_isSelectedPlatform; + mapping(bytes32 => bool) private s_isApprovedPlatform; mapping(bytes32 => bytes32) private s_platformData; - bytes32[] private s_approvedplatformHash; + bytes32[] private s_approvedPlatformHashes; function getApprovedPlatformHashes() external view returns (bytes32[] memory) { - return s_approvedplatformHash; + return s_approvedPlatformHashes; } - /** - * @dev Emitted when a platform is selected for the campaign. - * @param platformHash The bytes32 identifier of the platform. - * @param platformTreasury The address of the platform's treasury. - */ - event CampaignInfoPlatformSelected( - bytes32 indexed platformHash, - address indexed platformTreasury - ); - /** * @dev Emitted when the launch time of the campaign is updated. * @param newLaunchTime The new launch time. @@ -91,16 +82,6 @@ contract CampaignInfo is address indexed platformTreasury ); - /** - * @dev Emitted when ownership of the contract is transferred. - * @param previousOwner The address of the previous owner. - * @param newOwner The address of the new owner. - */ - event CampaignInfoOwnershipTransferred( - address indexed previousOwner, - address indexed newOwner - ); - /** * @dev Emitted when an invalid platform update is attempted. * @param platformHash The bytes32 identifier of the platform. @@ -127,6 +108,12 @@ contract CampaignInfo is */ error CampaignInfoPlatformNotSelected(bytes32 platformHash); + /** + * @dev Emitted when a platform is already approved for the campaign. + * @param platformHash The bytes32 identifier of the platform. + */ + error CampaignInfoPlatformAlreadyApproved(bytes32 platformHash); + constructor(address creator) Ownable(creator) {} function initialize( @@ -144,7 +131,7 @@ contract CampaignInfo is for (uint256 i = 0; i < len; ++i) { s_platformFeePercent[selectedPlatformHash[i]] = GLOBAL_PARAMS .getPlatformFeePercent(selectedPlatformHash[i]); - s_selectedPlatformHash[selectedPlatformHash[i]] = true; + s_isSelectedPlatform[selectedPlatformHash[i]] = true; } len = platformDataKey.length; bool isValid; @@ -182,7 +169,18 @@ contract CampaignInfo is function checkIfPlatformSelected( bytes32 platformHash ) public view override returns (bool) { - return s_selectedPlatformHash[platformHash]; + return s_isSelectedPlatform[platformHash]; + } + + /** + * @dev Check if a platform is already approved + * @param platformHash The bytes32 identifier of the platform. + * @return True if the platform is already approved, false otherwise. + */ + function checkIfPlatformApproved( + bytes32 platformHash + ) public view returns (bool) { + return s_isApprovedPlatform[platformHash]; } /** @@ -208,8 +206,8 @@ contract CampaignInfo is * @inheritdoc ICampaignInfo */ function getTotalRaisedAmount() external view override returns (uint256) { - bytes32[] memory tempPlatforms = s_approvedplatformHash; - uint256 length = s_approvedplatformHash.length; + bytes32[] memory tempPlatforms = s_approvedPlatformHashes; + uint256 length = s_approvedPlatformHashes.length; uint256 amount; address tempTreasury; for (uint256 i = 0; i < length; i++) { @@ -324,7 +322,13 @@ contract CampaignInfo is */ function transferOwnership( address newOwner - ) public override(ICampaignInfo, Ownable) onlyOwner whenNotPaused whenNotCancelled { + ) + public + override(ICampaignInfo, Ownable) + onlyOwner + whenNotPaused + whenNotCancelled + { super.transferOwnership(newOwner); } @@ -341,7 +345,7 @@ contract CampaignInfo is whenNotPaused whenNotCancelled { - if (launchTime < block.timestamp && getDeadline() <= launchTime) { + if (launchTime < block.timestamp || getDeadline() <= launchTime) { revert CampaignInfoInvalidInput(); } s_campaignData.launchTime = launchTime; @@ -385,6 +389,7 @@ contract CampaignInfo is if (goalAmount == 0) { revert CampaignInfoInvalidInput(); } + s_campaignData.goalAmount = goalAmount; emit CampaignInfoGoalAmountUpdated(goalAmount); } @@ -408,7 +413,17 @@ contract CampaignInfo is if (!GLOBAL_PARAMS.checkIfPlatformIsListed(platformHash)) { revert CampaignInfoInvalidPlatformUpdate(platformHash, selection); } - s_selectedPlatformHash[platformHash] = selection; + + if (!selection && checkIfPlatformApproved(platformHash)) { + revert CampaignInfoPlatformAlreadyApproved(platformHash); + } + s_isSelectedPlatform[platformHash] = selection; + if (selection) { + s_platformFeePercent[platformHash] = GLOBAL_PARAMS + .getPlatformFeePercent(platformHash); + } else { + s_platformFeePercent[platformHash] = 0; + } emit CampaignInfoSelectedPlatformUpdated(platformHash, selection); } @@ -453,8 +468,13 @@ contract CampaignInfo is if (!selected) { revert CampaignInfoPlatformNotSelected(platformHash); } + if (s_isApprovedPlatform[platformHash]) { + revert CampaignInfoPlatformAlreadyApproved(platformHash); + } s_platformTreasuryAddress[platformHash] = platformTreasuryAddress; - s_approvedplatformHash.push(platformHash); + s_approvedPlatformHashes.push(platformHash); + s_isApprovedPlatform[platformHash] = true; + emit CampaignInfoPlatformInfoUpdated( platformHash, platformTreasuryAddress diff --git a/src/CampaignInfoFactory.sol b/src/CampaignInfoFactory.sol index f5d554f4..8342956c 100644 --- a/src/CampaignInfoFactory.sol +++ b/src/CampaignInfoFactory.sol @@ -1,12 +1,12 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "@openzeppelin/contracts/proxy/Clones.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "./interfaces/IGlobalParams.sol"; -import "./interfaces/ICampaignInfoFactory.sol"; +import {IGlobalParams} from "./interfaces/IGlobalParams.sol"; +import {ICampaignInfoFactory} from "./interfaces/ICampaignInfoFactory.sol"; /** * @title CampaignInfoFactory @@ -82,7 +82,7 @@ contract CampaignInfoFactory is Initializable, ICampaignInfoFactory, Ownable { CampaignData calldata campaignData ) external override { if ( - campaignData.launchTime < block.timestamp && + campaignData.launchTime < block.timestamp || campaignData.deadline <= campaignData.launchTime ) { revert CampaignInfoFactoryInvalidInput(); diff --git a/src/GlobalParams.sol b/src/GlobalParams.sol index b228e0a5..202951c4 100644 --- a/src/GlobalParams.sol +++ b/src/GlobalParams.sol @@ -1,10 +1,10 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "@openzeppelin/contracts/access/Ownable.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import "./interfaces/IGlobalParams.sol"; -import "./utils/Counters.sol"; +import {IGlobalParams} from "./interfaces/IGlobalParams.sol"; +import {Counters} from "./utils/Counters.sol"; /** * @title GlobalParams @@ -257,17 +257,8 @@ contract GlobalParams is IGlobalParams, Ownable { */ function getPlatformDataOwner( bytes32 platformDataKey - ) - external - view - override - platformIsListed(platformHash) - returns (bytes32 platformHash) - { + ) external view override returns (bytes32 platformHash) { platformHash = s_platformDataOwner[platformDataKey]; - if (platformHash == ZERO_BYTES) { - revert GlobalParamsInvalidInput(); - } } /** @@ -300,7 +291,7 @@ contract GlobalParams is IGlobalParams, Ownable { address platformAdminAddress, uint256 platformFeePercent ) external onlyOwner notAddressZero(platformAdminAddress) { - if (platformHash == ZERO_BYTES || platformAdminAddress == address(0)) { + if (platformHash == ZERO_BYTES) { revert GlobalParamsInvalidInput(); } if (s_platformIsListed[platformHash]) { @@ -344,12 +335,9 @@ contract GlobalParams is IGlobalParams, Ownable { if (platformDataKey == ZERO_BYTES) { revert GlobalParamsInvalidInput(); } - if (s_platformData[platformDataKey] != false) { + if (s_platformData[platformDataKey]) { revert GlobalParamsPlatformDataAlreadySet(); } - if (s_platformDataOwner[platformDataKey] == platformHash) { - revert GlobalParamsPlatformDataSlotTaken(); - } s_platformData[platformDataKey] = true; s_platformDataOwner[platformDataKey] = platformHash; emit PlatformDataAdded(platformHash, platformDataKey); @@ -367,7 +355,7 @@ contract GlobalParams is IGlobalParams, Ownable { if (platformDataKey == ZERO_BYTES) { revert GlobalParamsInvalidInput(); } - if (s_platformData[platformDataKey] == false) { + if (!s_platformData[platformDataKey]) { revert GlobalParamsPlatformDataNotSet(); } s_platformData[platformDataKey] = false; diff --git a/src/TestUSD.sol b/src/TestUSD.sol deleted file mode 100644 index 19cf382e..00000000 --- a/src/TestUSD.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -/** - * @title TestUSD - * @notice A test token `tUSD` which is used in the tests. - */ -contract TestUSD is ERC20, Ownable { - constructor() ERC20("testUSD", "tUSD") Ownable(msg.sender) {} - - /** - * @notice Mints testUSD token. - * @param to The token receivers address. - * @param amount The amount of tokens to mint. - */ - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); - } -} diff --git a/src/TreasuryFactory.sol b/src/TreasuryFactory.sol index e989b8ad..44f05280 100644 --- a/src/TreasuryFactory.sol +++ b/src/TreasuryFactory.sol @@ -1,15 +1,14 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "@openzeppelin/contracts/proxy/Clones.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -import "./CampaignInfo.sol"; -import "./interfaces/ITreasuryFactory.sol"; -import "./utils/AdminAccessChecker.sol"; +import {ITreasuryFactory} from "./interfaces/ITreasuryFactory.sol"; +import {IGlobalParams, AdminAccessChecker} from "./utils/AdminAccessChecker.sol"; contract TreasuryFactory is ITreasuryFactory, AdminAccessChecker { - mapping(bytes32 => mapping(uint256 => address)) implementationMap; - mapping(address => bool) approvedImplementations; + mapping(bytes32 => mapping(uint256 => address)) private implementationMap; + mapping(address => bool) private approvedImplementations; error TreasuryFactoryUnauthorized(); error TreasuryFactoryInvalidKey(); diff --git a/src/interfaces/ICampaignData.sol b/src/interfaces/ICampaignData.sol index cab7c378..abf5e0ac 100644 --- a/src/interfaces/ICampaignData.sol +++ b/src/interfaces/ICampaignData.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** diff --git a/src/interfaces/ICampaignInfo.sol b/src/interfaces/ICampaignInfo.sol index 509769b1..43771b79 100644 --- a/src/interfaces/ICampaignInfo.sol +++ b/src/interfaces/ICampaignInfo.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** @@ -122,6 +122,7 @@ interface ICampaignInfo { /** * @notice Updates the selection status of a platform for the campaign. + * @dev It can only be called for a platform if its not approved i.e. the platform treasury is not deployed * @param platformHash The bytes32 identifier of the platform. * @param selection The new selection status (true or false). */ diff --git a/src/interfaces/ICampaignInfoFactory.sol b/src/interfaces/ICampaignInfoFactory.sol index 6481dfae..f8b53c35 100644 --- a/src/interfaces/ICampaignInfoFactory.sol +++ b/src/interfaces/ICampaignInfoFactory.sol @@ -1,7 +1,7 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "./ICampaignData.sol"; +import {ICampaignData} from "./ICampaignData.sol"; /** * @title ICampaignInfoFactory @@ -18,11 +18,10 @@ interface ICampaignInfoFactory is ICampaignData { address indexed campaignInfoAddress ); - /** + /** * @notice Emitted when the campaign after creation is initialized. - */ - event CampaignInfoFactoryCampaignInitialized( - ); + */ + event CampaignInfoFactoryCampaignInitialized(); /** * @notice Creates a new campaign information contract. @@ -46,7 +45,5 @@ interface ICampaignInfoFactory is ICampaignData { * @notice Updates the campaign implementation address. * @param newImplementation The address of the camapaignInfo implementation contract. */ - function updateImplementation( - address newImplementation - ) external; + function updateImplementation(address newImplementation) external; } diff --git a/src/interfaces/ICampaignTreasury.sol b/src/interfaces/ICampaignTreasury.sol index 578f3df2..2b6c2b67 100644 --- a/src/interfaces/ICampaignTreasury.sol +++ b/src/interfaces/ICampaignTreasury.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** @@ -26,13 +26,13 @@ interface ICampaignTreasury { * @notice Retrieves the platform identifier associated with the treasury. * @return The platform identifier as a bytes32 value. */ - function getplatformHash() external view returns (bytes32); + function getPlatformHash() external view returns (bytes32); /** * @notice Retrieves the platform fee percentage for the treasury. * @return The platform fee percentage as a uint256 value. */ - function getplatformFeePercent() external view returns (uint256); + function getPlatformFeePercent() external view returns (uint256); /** * @notice Retrieves the total raised amount in the treasury. diff --git a/src/interfaces/IGlobalParams.sol b/src/interfaces/IGlobalParams.sol index fda1b617..4bc1f7dc 100644 --- a/src/interfaces/IGlobalParams.sol +++ b/src/interfaces/IGlobalParams.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** diff --git a/src/interfaces/IItem.sol b/src/interfaces/IItem.sol index e6971d16..95a1aad8 100644 --- a/src/interfaces/IItem.sol +++ b/src/interfaces/IItem.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** diff --git a/src/interfaces/IReward.sol b/src/interfaces/IReward.sol index 890aa12b..92a3212f 100644 --- a/src/interfaces/IReward.sol +++ b/src/interfaces/IReward.sol @@ -1,11 +1,11 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * @title IReward * @notice An interface for managing rewards in a campaign. */ -interface IReward { +interface IReward { struct Reward { uint256 rewardValue; bool isRewardTier; diff --git a/src/interfaces/ITreasuryFactory.sol b/src/interfaces/ITreasuryFactory.sol index dfd8242b..ca3f4b74 100644 --- a/src/interfaces/ITreasuryFactory.sol +++ b/src/interfaces/ITreasuryFactory.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** diff --git a/src/treasuries/AllOrNothing.sol b/src/treasuries/AllOrNothing.sol index 75046efc..2a400a9e 100644 --- a/src/treasuries/AllOrNothing.sol +++ b/src/treasuries/AllOrNothing.sol @@ -1,13 +1,15 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; +import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {ERC721Burnable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; -import "../utils/Counters.sol"; -import "../utils/TimestampChecker.sol"; -import "../utils/BaseTreasury.sol"; -import "../interfaces/IReward.sol"; +import {Counters} from "../utils/Counters.sol"; +import {TimestampChecker} from "../utils/TimestampChecker.sol"; +import {ICampaignTreasury} from "../interfaces/ICampaignTreasury.sol"; +import {BaseTreasury} from "../utils/BaseTreasury.sol"; +import {IReward} from "../interfaces/IReward.sol"; /** * @title AllOrNothing @@ -384,10 +386,10 @@ contract AllOrNothing is uint256 totalAmount = pledgeAmount + shippingFee; TOKEN.safeTransferFrom(backer, address(this), totalAmount); s_tokenIdCounter.increment(); - _safeMint(backer, tokenId, abi.encodePacked(backer, reward)); s_tokenToPledgedAmount[tokenId] = pledgeAmount; s_tokenToTotalCollectedAmount[tokenId] = totalAmount; s_pledgedAmount += pledgeAmount; + _safeMint(backer, tokenId, abi.encodePacked(backer, reward, rewards)); emit Receipt( backer, reward, diff --git a/src/utils/AdminAccessChecker.sol b/src/utils/AdminAccessChecker.sol index c798e730..7c025282 100644 --- a/src/utils/AdminAccessChecker.sol +++ b/src/utils/AdminAccessChecker.sol @@ -1,7 +1,7 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "../interfaces/IGlobalParams.sol"; +import {IGlobalParams} from "../interfaces/IGlobalParams.sol"; /** * @title AdminAccessChecker diff --git a/src/utils/BaseTreasury.sol b/src/utils/BaseTreasury.sol index 33bb602b..78abe289 100644 --- a/src/utils/BaseTreasury.sol +++ b/src/utils/BaseTreasury.sol @@ -1,14 +1,12 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import "../interfaces/ICampaignInfo.sol"; -import "../interfaces/ICampaignTreasury.sol"; -import "./CampaignAccessChecker.sol"; -import "./PausableCancellable.sol"; +import {ICampaignTreasury} from "../interfaces/ICampaignTreasury.sol"; +import {CampaignAccessChecker} from "./CampaignAccessChecker.sol"; +import {PausableCancellable} from "./PausableCancellable.sol"; /** * @title BaseTreasury @@ -23,6 +21,7 @@ abstract contract BaseTreasury is PausableCancellable { using SafeERC20 for IERC20; + bytes32 internal constant ZERO_BYTES = 0x0000000000000000000000000000000000000000000000000000000000000000; uint256 internal constant PERCENT_DIVIDER = 10000; @@ -30,7 +29,6 @@ abstract contract BaseTreasury is bytes32 internal PLATFORM_HASH; uint256 internal PLATFORM_FEE_PERCENT; IERC20 internal TOKEN; - ICampaignInfo internal CAMPAIGN_INFO; uint256 internal s_pledgedAmount; bool internal s_feesDisbursed; @@ -80,7 +78,6 @@ abstract contract BaseTreasury is ) internal { __CampaignAccessChecker_init(infoAddress); PLATFORM_HASH = platformHash; - CAMPAIGN_INFO = ICampaignInfo(infoAddress); TOKEN = IERC20(INFO.getTokenAddress()); PLATFORM_FEE_PERCENT = INFO.getPlatformFeePercent(platformHash); } @@ -101,14 +98,14 @@ abstract contract BaseTreasury is /** * @inheritdoc ICampaignTreasury */ - function getplatformHash() external view override returns (bytes32) { + function getPlatformHash() external view override returns (bytes32) { return PLATFORM_HASH; } /** * @inheritdoc ICampaignTreasury */ - function getplatformFeePercent() external view override returns (uint256) { + function getPlatformFeePercent() external view override returns (uint256) { return PLATFORM_FEE_PERCENT; } diff --git a/src/utils/CampaignAccessChecker.sol b/src/utils/CampaignAccessChecker.sol index c292bef6..060cca68 100644 --- a/src/utils/CampaignAccessChecker.sol +++ b/src/utils/CampaignAccessChecker.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "../interfaces/ICampaignInfo.sol"; -import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {ICampaignInfo} from "../interfaces/ICampaignInfo.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; /** * @title CampaignAccessChecker @@ -22,7 +22,6 @@ abstract contract CampaignAccessChecker { * @dev Constructor to initialize the contract with the address of the campaign information contract. * @param campaignInfo The address of the ICampaignInfo contract. */ - function __CampaignAccessChecker_init(address campaignInfo) internal { INFO = ICampaignInfo(campaignInfo); } diff --git a/src/utils/FiatEnabled.sol b/src/utils/FiatEnabled.sol index 208ca91a..85d98f62 100644 --- a/src/utils/FiatEnabled.sol +++ b/src/utils/FiatEnabled.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** diff --git a/src/utils/ItemRegistry.sol b/src/utils/ItemRegistry.sol index 63f06100..0e6f4282 100644 --- a/src/utils/ItemRegistry.sol +++ b/src/utils/ItemRegistry.sol @@ -1,8 +1,9 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import "@openzeppelin/contracts/utils/Context.sol"; -import "../interfaces/IItem.sol"; +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; + +import {IItem} from "../interfaces/IItem.sol"; /** * @title ItemRegistry @@ -18,7 +19,7 @@ contract ItemRegistry is IItem, Context { * @param item The item details including actual weight, dimensions, category, and declared currency. */ event ItemAdded(address indexed owner, bytes32 indexed itemId, Item item); - + /** * @dev Thrown when the input arrays have mismatched lengths. */ @@ -63,4 +64,4 @@ contract ItemRegistry is IItem, Context { emit ItemAdded(_msgSender(), itemId, item); } } -} \ No newline at end of file +} diff --git a/src/utils/PausableCancellable.sol b/src/utils/PausableCancellable.sol index 6c65c268..db0994d0 100644 --- a/src/utils/PausableCancellable.sol +++ b/src/utils/PausableCancellable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /// @title PausableCancellable diff --git a/src/utils/TimestampChecker.sol b/src/utils/TimestampChecker.sol index e5170999..7ae687f7 100644 --- a/src/utils/TimestampChecker.sol +++ b/src/utils/TimestampChecker.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** @@ -63,7 +63,7 @@ abstract contract TimestampChecker { uint256 inputTime ) internal view virtual { uint256 currentTime = block.timestamp; - if (currentTime > inputTime) { + if (currentTime >= inputTime) { revert CurrentTimeIsGreater(inputTime, currentTime); } } @@ -76,7 +76,7 @@ abstract contract TimestampChecker { uint256 inputTime ) internal view virtual { uint256 currentTime = block.timestamp; - if (currentTime < inputTime) { + if (currentTime <= inputTime) { revert CurrentTimeIsLess(inputTime, currentTime); } } diff --git a/test/foundry/Base.t.sol b/test/foundry/Base.t.sol index a1c21181..ad771340 100644 --- a/test/foundry/Base.t.sol +++ b/test/foundry/Base.t.sol @@ -1,10 +1,10 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import {Users} from "./utils/Types.sol"; import {Defaults} from "./utils/Defaults.sol"; -import {TestUSD} from "src/TestUSD.sol"; +import {TestToken} from "../mocks/TestToken.sol"; import {GlobalParams} from "src/GlobalParams.sol"; import {CampaignInfoFactory} from "src/CampaignInfoFactory.sol"; import {CampaignInfo} from "src/CampaignInfo.sol"; @@ -17,7 +17,7 @@ abstract contract Base_Test is Test, Defaults { Users internal users; //Test Contracts - TestUSD internal testUSD; + TestToken internal testToken; GlobalParams internal globalParams; CampaignInfoFactory internal campaignInfoFactory; TreasuryFactory internal treasuryFactory; @@ -40,16 +40,19 @@ abstract contract Base_Test is Test, Defaults { vm.startPrank(users.contractOwner); // Deploy the base test contracts. - testUSD = new TestUSD(); + testToken = new TestToken(tokenName, tokenSymbol); globalParams = new GlobalParams( users.protocolAdminAddress, - address(testUSD), + address(testToken), PROTOCOL_FEE_PERCENT ); campaignInfo = new CampaignInfo(address(this)); console.log("CampaignInfo address: ", address(campaignInfo)); - campaignInfoFactory = new CampaignInfoFactory(globalParams, address(campaignInfo)); + campaignInfoFactory = new CampaignInfoFactory( + globalParams, + address(campaignInfo) + ); treasuryFactory = new TreasuryFactory(globalParams); //Initialize campaignInfoFactory @@ -60,13 +63,13 @@ abstract contract Base_Test is Test, Defaults { allOrNothingImplementation = new AllOrNothing(); //Mint token to the backer - testUSD.mint(users.backer1Address, TOKEN_MINT_AMOUNT); - testUSD.mint(users.backer2Address, TOKEN_MINT_AMOUNT); + testToken.mint(users.backer1Address, TOKEN_MINT_AMOUNT); + testToken.mint(users.backer2Address, TOKEN_MINT_AMOUNT); vm.stopPrank(); // Label the base test contracts. - vm.label({account: address(testUSD), newLabel: "TestUSD"}); + vm.label({account: address(testToken), newLabel: "TestToken"}); vm.label({ account: address(globalParams), newLabel: "Global Parameter" diff --git a/test/foundry/integration/AllOrNothing/AllOrNothing.t.sol b/test/foundry/integration/AllOrNothing/AllOrNothing.t.sol index 38eba557..2b12aebb 100644 --- a/test/foundry/integration/AllOrNothing/AllOrNothing.t.sol +++ b/test/foundry/integration/AllOrNothing/AllOrNothing.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import {Base_Test} from "../../Base.t.sol"; @@ -10,19 +10,35 @@ import {CampaignInfo} from "src/CampaignInfo.sol"; import {IReward} from "src/interfaces/IReward.sol"; import {LogDecoder} from "../../utils/LogDecoder.sol"; -/// @notice Common testing logic needed by all AllOrNothing integration tests. +/** + * @title AllOrNothing Integration Test Shared Contract + * @notice Common testing logic needed by all AllOrNothing integration tests. + * @dev Abstract contract that provides shared setup and helper functions for AllOrNothing treasury testing. + * Handles platform enrollment, treasury implementation registration, campaign creation, and treasury deployment. + * Also provides utility functions for pledging, refunding, fee disbursement, and withdrawals. + */ abstract contract AllOrNothing_Integration_Shared_Test is IReward, LogDecoder, Base_Test { + /// @dev Address of the created campaign contract address campaignAddress; + + /// @dev Address of the deployed treasury contract address treasuryAddress; + + /// @dev Instance of the AllOrNothing treasury contract AllOrNothing internal allOrNothing; + /// @dev Token ID for pledges that include rewards uint256 pledgeForARewardTokenId; - /// @dev Initial dependent functions setup included for AllOrNothing Integration Tests. + /** + * @notice Initial setup for AllOrNothing integration tests + * @dev Performs the complete setup sequence: platform enrollment, treasury registration, + * campaign creation, and treasury deployment. Called by inheriting test contracts. + */ function setUp() public virtual override { super.setUp(); console.log("setUp: enlistPlatform"); @@ -31,9 +47,11 @@ abstract contract AllOrNothing_Integration_Shared_Test is enlistPlatform(PLATFORM_1_HASH); console.log("enlisted platform"); + //Register Treasury Implementation registerTreasuryImplementation(PLATFORM_1_HASH); console.log("registered treasury"); + //Approve Treasury Implementation approveTreasuryImplementation(PLATFORM_1_HASH); console.log("approved treasury"); @@ -47,8 +65,9 @@ abstract contract AllOrNothing_Integration_Shared_Test is } /** - * @notice Implements enlistPlatform helper function. - * @param platformHash The platform bytes. + * @notice Enlists a platform in the protocol + * @dev Called by protocol admin to register a new platform with specified fee structure + * @param platformHash The unique identifier hash for the platform */ function enlistPlatform(bytes32 platformHash) internal { vm.startPrank(users.protocolAdminAddress); @@ -60,6 +79,11 @@ abstract contract AllOrNothing_Integration_Shared_Test is vm.stopPrank(); } + /** + * @notice Registers a treasury implementation for a platform + * @dev Called by platform admin to register AllOrNothing treasury implementation + * @param platformHash The platform identifier to register the treasury for + */ function registerTreasuryImplementation(bytes32 platformHash) internal { vm.startPrank(users.platform1AdminAddress); treasuryFactory.registerTreasuryImplementation( @@ -70,6 +94,11 @@ abstract contract AllOrNothing_Integration_Shared_Test is vm.stopPrank(); } + /** + * @notice Approves a registered treasury implementation + * @dev Called by protocol admin to approve a platform's treasury implementation + * @param platformHash The platform identifier whose treasury implementation to approve + */ function approveTreasuryImplementation(bytes32 platformHash) internal { vm.startPrank(users.protocolAdminAddress); treasuryFactory.approveTreasuryImplementation(platformHash, 0); @@ -77,8 +106,9 @@ abstract contract AllOrNothing_Integration_Shared_Test is } /** - * @notice Implements createCampaign helper function. It creates new campaign info contract - * @param platformHash The platform bytes. + * @notice Creates a new campaign for testing + * @dev Creates a campaign info contract and extracts the campaign address from emitted events + * @param platformHash The platform identifier to create the campaign on */ function createCampaign(bytes32 platformHash) internal { bytes32 identifierHash = keccak256(abi.encodePacked(platformHash)); @@ -114,7 +144,9 @@ abstract contract AllOrNothing_Integration_Shared_Test is } /** - * @notice Implements deploy helper function. It deploys treasury contract. + * @notice Deploys a treasury contract for the created campaign + * @dev Deploys AllOrNothing treasury and extracts the treasury address from emitted events + * @param platformHash The platform identifier to deploy the treasury for */ function deploy(bytes32 platformHash) internal { vm.startPrank(users.platform1AdminAddress); @@ -141,6 +173,14 @@ abstract contract AllOrNothing_Integration_Shared_Test is allOrNothing = AllOrNothing(treasuryAddress); } + /** + * @notice Adds rewards to a treasury contract + * @dev Helper function to add reward tiers to an AllOrNothing treasury + * @param caller The address that will call the addRewards function + * @param treasury The treasury contract address + * @param rewardNames Array of reward names/identifiers + * @param rewards Array of reward structs containing reward details + */ function addRewards( address caller, address treasury, @@ -153,15 +193,24 @@ abstract contract AllOrNothing_Integration_Shared_Test is } /** - * @notice Implements pledgeForAReward helper function. + * @notice Simulates pledging for a specific reward + * @dev Creates a pledge with reward selection and captures the receipt event + * @param caller The address making the pledge + * @param warpTime The block timestamp to warp to + * @param allOrNothingAddress The treasury contract address + * @param pledgeAmount The amount to pledge (automatically calculated from reward) + * @param shippingFee The shipping fee for the reward + * @param rewardName The identifier of the reward being pledged for + * @return logs The transaction logs + * @return tokenId The NFT token ID representing the pledge + * @return rewards Array of reward names associated with the pledge */ function pledgeForAReward( address caller, - address token, + uint256 warpTime, address allOrNothingAddress, uint256 pledgeAmount, uint256 shippingFee, - uint256 launchTime, bytes32 rewardName ) internal @@ -172,10 +221,10 @@ abstract contract AllOrNothing_Integration_Shared_Test is ) { vm.startPrank(caller); + vm.warp(warpTime); vm.recordLogs(); - testUSD.approve(allOrNothingAddress, pledgeAmount + shippingFee); - vm.warp(launchTime); + testToken.approve(allOrNothingAddress, pledgeAmount + shippingFee); bytes32[] memory reward = new bytes32[](1); reward[0] = rewardName; @@ -194,7 +243,6 @@ abstract contract AllOrNothing_Integration_Shared_Test is allOrNothingAddress ); - // (, tokenId, rewards) = abi.decode(data, (uint256, uint256, bytes32[])); (, , tokenId, rewards) = abi.decode( data, (uint256, uint256, uint256, bytes32[]) @@ -204,20 +252,26 @@ abstract contract AllOrNothing_Integration_Shared_Test is } /** - * @notice Implements pledgeWithoutAReward helper function. + * @notice Simulates pledging without selecting a reward + * @dev Creates a pledge without reward selection and captures the receipt event + * @param caller The address making the pledge + * @param warpTime The block timestamp to warp to + * @param allOrNothingAddress The treasury contract address + * @param pledgeAmount The amount to pledge + * @return logs The transaction logs + * @return tokenId The NFT token ID representing the pledge */ function pledgeWithoutAReward( address caller, - address token, + uint256 warpTime, address allOrNothingAddress, - uint256 pledgeAmount, - uint256 launchTime + uint256 pledgeAmount ) internal returns (Vm.Log[] memory logs, uint256 tokenId) { vm.startPrank(caller); + vm.warp(warpTime); vm.recordLogs(); - testUSD.approve(allOrNothingAddress, pledgeAmount); - vm.warp(launchTime); + testToken.approve(allOrNothingAddress, pledgeAmount); AllOrNothing(allOrNothingAddress).pledgeWithoutAReward( caller, @@ -241,10 +295,20 @@ abstract contract AllOrNothing_Integration_Shared_Test is } /** - * @notice Implements claimRefund helper function. + * @notice Simulates claiming a refund for a failed campaign + * @dev Claims refund for a pledge token and captures the refund event + * @param caller The address claiming the refund + * @param warpTime The block timestamp to warp to + * @param allOrNothingAddress The treasury contract address + * @param tokenId The pledge token ID to refund + * @return logs The transaction logs + * @return refundedTokenId The token ID that was refunded + * @return refundAmount The amount refunded + * @return claimer The address that claimed the refund */ function claimRefund( address caller, + uint256 warpTime, address allOrNothingAddress, uint256 tokenId ) @@ -257,6 +321,7 @@ abstract contract AllOrNothing_Integration_Shared_Test is ) { vm.startPrank(caller); + vm.warp(warpTime); vm.recordLogs(); AllOrNothing(allOrNothingAddress).claimRefund(tokenId); @@ -278,7 +343,13 @@ abstract contract AllOrNothing_Integration_Shared_Test is } /** - * @notice Implements disburseFees helper function. + * @notice Simulates fee disbursement for a successful campaign + * @dev Disburses protocol and platform fees and captures the disbursement event + * @param allOrNothingAddress The treasury contract address + * @param warpTime The block timestamp to warp to + * @return logs The transaction logs + * @return protocolShare The amount allocated to protocol fees + * @return platformShare The amount allocated to platform fees */ function disburseFees( address allOrNothingAddress, @@ -308,7 +379,13 @@ abstract contract AllOrNothing_Integration_Shared_Test is } /** - * @notice Implements withdraw helper function. + * @notice Simulates withdrawal of funds from a successful campaign + * @dev Withdraws remaining funds to campaign creator and captures the withdrawal event + * @param allOrNothingAddress The treasury contract address + * @param warpTime The block timestamp to warp to + * @return logs The transaction logs + * @return to The address that received the withdrawal + * @return amount The amount withdrawn */ function withdraw( address allOrNothingAddress, @@ -336,4 +413,92 @@ abstract contract AllOrNothing_Integration_Shared_Test is return (logs, to, amount); } + + /** + * @notice Removes a reward from a treasury contract + * @dev Helper function to remove a reward from an AllOrNothing treasury + * @param caller The address that will call the removeReward function + * @param treasury The treasury contract address + * @param rewardName The name of the reward to remove + * @return logs The transaction logs + */ + function removeReward( + address caller, + address treasury, + bytes32 rewardName + ) internal returns (Vm.Log[] memory logs) { + vm.startPrank(caller); + vm.recordLogs(); + + AllOrNothing(treasury).removeReward(rewardName); + + logs = vm.getRecordedLogs(); + vm.stopPrank(); + } + + /** + * @notice Pauses a treasury contract + * @dev Helper function to pause an AllOrNothing treasury + * @param caller The address that will call the pauseTreasury function + * @param treasury The treasury contract address + * @param reason The reason for pausing + * @return logs The transaction logs + */ + function pauseTreasury( + address caller, + address treasury, + bytes32 reason + ) internal returns (Vm.Log[] memory logs) { + vm.startPrank(caller); + vm.recordLogs(); + + AllOrNothing(treasury).pauseTreasury(reason); + + logs = vm.getRecordedLogs(); + vm.stopPrank(); + } + + /** + * @notice Unpauses a treasury contract + * @dev Helper function to unpause an AllOrNothing treasury + * @param caller The address that will call the unpauseTreasury function + * @param treasury The treasury contract address + * @param reason The reason for unpausing + * @return logs The transaction logs + */ + function unpauseTreasury( + address caller, + address treasury, + bytes32 reason + ) internal returns (Vm.Log[] memory logs) { + vm.startPrank(caller); + vm.recordLogs(); + + AllOrNothing(treasury).unpauseTreasury(reason); + + logs = vm.getRecordedLogs(); + vm.stopPrank(); + } + + /** + * @notice Cancels a treasury contract + * @dev Helper function to cancel an AllOrNothing treasury + * @param caller The address that will call the cancelTreasury function + * @param treasury The treasury contract address + * @param reason The reason for cancellation + * @return logs The transaction logs + */ + function cancelTreasury( + address caller, + address treasury, + bytes32 reason + ) internal returns (Vm.Log[] memory logs) { + vm.startPrank(caller); + vm.recordLogs(); + + AllOrNothing(treasury).cancelTreasury(reason); + + logs = vm.getRecordedLogs(); + vm.stopPrank(); + } } diff --git a/test/foundry/integration/AllOrNothing/AllOrNothingFunction.t.sol b/test/foundry/integration/AllOrNothing/AllOrNothingFunction.t.sol index c5f0738a..fef07b0d 100644 --- a/test/foundry/integration/AllOrNothing/AllOrNothingFunction.t.sol +++ b/test/foundry/integration/AllOrNothing/AllOrNothingFunction.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "./AllOrNothing.t.sol"; @@ -10,9 +10,22 @@ import {Constants} from "../../utils/Constants.sol"; import {Users} from "../../utils/Types.sol"; import {IReward} from "src/interfaces/IReward.sol"; +/** + * @title AllOrNothing Function Integration Test Contract + * @notice Comprehensive integration tests for AllOrNothing treasury contract functionality + * @dev Inherits from AllOrNothing_Integration_Shared_Test to access common setup and utilities. + * Tests cover the full lifecycle of campaign operations including reward setup, pledging, + * refund claims, fee disbursement, and fund withdrawal scenarios. + */ contract AllOrNothingFunction_Integration_Shared_Test is AllOrNothing_Integration_Shared_Test { + /** + * @notice Tests the addRewards functionality + * @dev Verifies that rewards can be properly added to the treasury contract and that + * all reward properties are stored correctly including values, tiers, items, and quantities. + * Tests multiple rewards with different configurations to ensure proper storage and retrieval. + */ function test_addRewards() external { addRewards( users.creator1Address, @@ -49,6 +62,289 @@ contract AllOrNothingFunction_Integration_Shared_Test is assertEq(REWARDS[2].itemId.length, resultReward3.itemId.length); } + /** + * @notice Tests the removeReward functionality + * @dev Verifies that rewards can be properly removed from the treasury contract and that + * the reward is no longer accessible after removal. Ensures the RewardRemoved event + * is emitted correctly and attempts to access removed rewards result in reverts. + */ + function test_removeReward() external { + addRewards( + users.creator1Address, + address(allOrNothing), + REWARD_NAMES, + REWARDS + ); + + // Verify reward exists before removal + Reward memory existingReward = allOrNothing.getReward(REWARD_NAMES[0]); + assertEq(existingReward.rewardValue, REWARDS[0].rewardValue); + + // Remove the reward using helper function + Vm.Log[] memory logs = removeReward( + users.creator1Address, + address(allOrNothing), + REWARD_NAMES[0] + ); + + // For indexed parameters, we need to check topics + (bytes32[] memory topics, ) = decodeTopicsAndData( + logs, + "RewardRemoved(bytes32)", + address(allOrNothing) + ); + assertEq(topics[1], REWARD_NAMES[0], "Removed reward name should match"); + + // Verify reward no longer exists (should revert) + vm.expectRevert(); + allOrNothing.getReward(REWARD_NAMES[0]); + } + + /** + * @notice Tests the getReward functionality + * @dev Verifies that reward details can be properly retrieved from the treasury contract. + * Tests retrieval of all reward properties including values, tier flags, item arrays, + * and validates that non-existent rewards cause appropriate reverts. + */ + function test_getReward() external { + addRewards( + users.creator1Address, + address(allOrNothing), + REWARD_NAMES, + REWARDS + ); + + // Test getting each reward + for (uint i = 0; i < REWARD_NAMES.length; i++) { + Reward memory retrievedReward = allOrNothing.getReward(REWARD_NAMES[i]); + + assertEq(retrievedReward.rewardValue, REWARDS[i].rewardValue, "Reward value mismatch"); + assertEq(retrievedReward.isRewardTier, REWARDS[i].isRewardTier, "Reward tier flag mismatch"); + assertEq(retrievedReward.itemId.length, REWARDS[i].itemId.length, "Item ID array length mismatch"); + assertEq(retrievedReward.itemValue.length, REWARDS[i].itemValue.length, "Item value array length mismatch"); + assertEq(retrievedReward.itemQuantity.length, REWARDS[i].itemQuantity.length, "Item quantity array length mismatch"); + + // Check array contents + for (uint j = 0; j < retrievedReward.itemId.length; j++) { + assertEq(retrievedReward.itemId[j], REWARDS[i].itemId[j], "Item ID mismatch"); + assertEq(retrievedReward.itemValue[j], REWARDS[i].itemValue[j], "Item value mismatch"); + assertEq(retrievedReward.itemQuantity[j], REWARDS[i].itemQuantity[j], "Item quantity mismatch"); + } + } + + // Test getting non-existent reward (should revert) + vm.expectRevert(); + allOrNothing.getReward(keccak256("NonExistentReward")); + } + + /** + * @notice Tests the getRaisedAmount functionality + * @dev Verifies that the total raised amount is correctly tracked and returned. + * Tests progression from zero to multiple pledges to ensure accurate accumulation. + * Note that raised amount only tracks pledge amounts, not shipping fees. + */ + function test_getRaisedAmount() external { + addRewards( + users.creator1Address, + address(allOrNothing), + REWARD_NAMES, + REWARDS + ); + + // Initially should be zero + uint256 initialRaised = allOrNothing.getRaisedAmount(); + assertEq(initialRaised, 0, "Initial raised amount should be zero"); + + // Make a pledge and check raised amount + pledgeForAReward( + users.backer1Address, + LAUNCH_TIME, + address(allOrNothing), + PLEDGE_AMOUNT, + SHIPPING_FEE, + REWARD_NAME_1_HASH + ); + + uint256 raisedAfterFirstPledge = allOrNothing.getRaisedAmount(); + assertEq(raisedAfterFirstPledge, PLEDGE_AMOUNT, "Raised amount should equal first pledge amount"); + + // Make another pledge and check raised amount + pledgeWithoutAReward( + users.backer2Address, + LAUNCH_TIME, + address(allOrNothing), + GOAL_AMOUNT + ); + + uint256 finalRaised = allOrNothing.getRaisedAmount(); + assertEq(finalRaised, PLEDGE_AMOUNT + GOAL_AMOUNT, "Raised amount should equal sum of all pledges"); + } + + /** + * @notice Tests the pauseTreasury functionality + * @dev Verifies that the treasury can be paused by platform admin and that the paused + * state is correctly set. Validates that the Paused event is emitted from the + * correct contract when the pause operation is executed. + */ + function test_pauseTreasury() external { + bytes32 pauseReason = keccak256("Test pause"); + + assertFalse(allOrNothing.paused(), "Treasury should not be paused initially"); + + Vm.Log[] memory logs = pauseTreasury( + users.platform1AdminAddress, + address(allOrNothing), + pauseReason + ); + + assertTrue(allOrNothing.paused(), "Treasury should be paused"); + + // Use LogDecoder to find and verify the Paused event + Vm.Log memory pausedLog = findLogByTopic( + logs, + keccak256("Paused(address,bytes32)") + ); + + assertEq(pausedLog.emitter, address(allOrNothing), "Event should be emitted by allOrNothing contract"); + } + + /** + * @notice Tests the unpauseTreasury functionality + * @dev Verifies that the treasury can be unpaused by platform admin after being paused. + * Ensures proper state transition from paused to unpaused and validates that the + * Unpaused event is correctly emitted from the treasury contract. + */ + function test_unpauseTreasury() external { + bytes32 pauseReason = keccak256("Test pause"); + bytes32 unpauseReason = keccak256("Test unpause"); + + pauseTreasury(users.platform1AdminAddress, address(allOrNothing), pauseReason); + assertTrue(allOrNothing.paused(), "Treasury should be paused"); + + Vm.Log[] memory logs = unpauseTreasury( + users.platform1AdminAddress, + address(allOrNothing), + unpauseReason + ); + + assertFalse(allOrNothing.paused(), "Treasury should not be paused"); + + // Use LogDecoder to find and verify the Unpaused event + Vm.Log memory unpausedLog = findLogByTopic( + logs, + keccak256("Unpaused(address,bytes32)") + ); + + assertEq(unpausedLog.emitter, address(allOrNothing), "Event should be emitted by allOrNothing contract"); + } + + /** + * @notice Tests the cancelTreasury functionality by platform admin + * @dev Verifies that the treasury can be cancelled by platform admin and that the cancelled + * state is permanently set. Validates that the Cancelled event is emitted correctly + * and that cancellation is an irreversible operation. + */ + function test_cancelTreasury() external { + bytes32 cancelReason = keccak256("Test cancellation"); + + assertFalse(allOrNothing.cancelled(), "Treasury should not be cancelled initially"); + + Vm.Log[] memory logs = cancelTreasury( + users.platform1AdminAddress, + address(allOrNothing), + cancelReason + ); + + assertTrue(allOrNothing.cancelled(), "Treasury should be cancelled"); + + // Use LogDecoder to find and verify the Cancelled event + Vm.Log memory cancelledLog = findLogByTopic( + logs, + keccak256("Cancelled(address,bytes32)") + ); + + assertEq(cancelledLog.emitter, address(allOrNothing), "Event should be emitted by allOrNothing contract"); + } + + /** + * @notice Tests cancelTreasury functionality by campaign owner + * @dev Verifies that the campaign owner can also cancel the treasury, demonstrating + * the dual authorization model where both platform admin and campaign owner + * have cancellation privileges. Validates proper event emission and state change. + */ + function test_cancelTreasuryByCampaignOwner() external { + bytes32 cancelReason = keccak256("Owner cancellation"); + + assertFalse(allOrNothing.cancelled(), "Treasury should not be cancelled initially"); + + // Cancel the treasury as campaign owner using helper function + Vm.Log[] memory logs = cancelTreasury( + users.creator1Address, + address(allOrNothing), + cancelReason + ); + + // Verify treasury is cancelled + assertTrue(allOrNothing.cancelled(), "Treasury should be cancelled by owner"); + + // Use LogDecoder to find and verify the Cancelled event + Vm.Log memory cancelledLog = findLogByTopic( + logs, + keccak256("Cancelled(address,bytes32)") + ); + + assertEq(cancelledLog.emitter, address(allOrNothing), "Event should be emitted by allOrNothing contract"); + } + + /** + * @notice Tests the name functionality + * @dev Verifies that the contract name is correctly returned and matches the value + * that was set during contract initialization. Tests the ERC721 metadata extension. + */ + function test_name() external { + string memory contractName = allOrNothing.name(); + assertEq(contractName, NAME, "Contract name should match initialized name"); + } + + /** + * @notice Tests the symbol functionality + * @dev Verifies that the contract symbol is correctly returned and matches the value + * that was set during contract initialization. Tests the ERC721 metadata extension. + */ + function test_symbol() external { + string memory contractSymbol = allOrNothing.symbol(); + assertEq(contractSymbol, SYMBOL, "Contract symbol should match initialized symbol"); + } + + /** + * @notice Tests the getPlatformHash functionality + * @dev Verifies that the platform hash is correctly returned and matches the value + * that was set during contract initialization. This hash identifies which platform + * the treasury belongs to. + */ + function test_getPlatformHash() external { + bytes32 platformHash = allOrNothing.getPlatformHash(); + assertEq(platformHash, PLATFORM_1_HASH, "Platform hash should match initialized value"); + } + + /** + * @notice Tests the getPlatformFeePercent functionality + * @dev Verifies that the platform fee percentage is correctly returned and matches + * the value that was set during contract initialization. This percentage determines + * the platform's share of successful campaign funds. + */ + function test_getPlatformFeePercent() external { + uint256 platformFeePercent = allOrNothing.getPlatformFeePercent(); + assertEq(platformFeePercent, PLATFORM_FEE_PERCENT, "Platform fee percent should match initialized value"); + } + + /** + * @notice Tests the pledgeForAReward functionality + * @dev Verifies that users can pledge for specific rewards, including proper token transfers, + * NFT minting, and balance updates. Confirms that the backer receives an NFT representing + * their pledge and that funds (pledge amount + shipping fee) are correctly transferred + * to the treasury. Tests the complete reward-based pledging workflow. + */ function test_pledgeForAReward() external { addRewards( users.creator1Address, @@ -63,24 +359,98 @@ contract AllOrNothingFunction_Integration_Shared_Test is bytes32[] memory rewards ) = pledgeForAReward( users.backer1Address, - address(testUSD), + LAUNCH_TIME, address(allOrNothing), PLEDGE_AMOUNT, SHIPPING_FEE, - LAUNCH_TIME, REWARD_NAME_1_HASH ); - uint256 backerBalance = testUSD.balanceOf(users.backer1Address); - uint256 treasuryBalance = testUSD.balanceOf(address(allOrNothing)); + uint256 treasuryBalance = testToken.balanceOf(address(allOrNothing)); uint256 backerNftBalance = allOrNothing.balanceOf(users.backer1Address); - address nftOwnerAddress = allOrNothing.ownerOf(pledgeForARewardTokenId); + address nftOwnerAddress = allOrNothing.ownerOf(tokenId); + + // Verify Receipt event was emitted with correct data + Vm.Log memory receiptLog = findLogByTopic( + logs, + keccak256("Receipt(address,bytes32,uint256,uint256,uint256,bytes32[])") + ); + assertEq(receiptLog.emitter, address(allOrNothing), "Receipt event should be emitted by allOrNothing contract"); + + // Verify state changes + assertEq(users.backer1Address, nftOwnerAddress, "Backer should own the NFT"); + assertEq(PLEDGE_AMOUNT + SHIPPING_FEE, treasuryBalance, "Treasury should contain pledge amount + shipping fee"); + assertEq(1, backerNftBalance, "Backer should have exactly 1 NFT"); + assertEq(rewards[0], REWARD_NAME_1_HASH, "Reward name should match"); + } + + /** + * @notice Tests the pledgeWithoutAReward functionality + * @dev Verifies that users can make pledges without selecting rewards, including proper + * token transfers, NFT minting, and balance updates. Confirms that the backer receives + * an NFT representing their pledge and that only the pledge amount is transferred + * (no shipping fees since no rewards are selected). Tests the basic pledging workflow. + */ + function test_pledgeWithoutAReward() external { + // Get initial balances + uint256 initialBackerBalance = testToken.balanceOf(users.backer1Address); + uint256 initialTreasuryBalance = testToken.balanceOf(address(allOrNothing)); + uint256 initialBackerNftBalance = allOrNothing.balanceOf(users.backer1Address); + + // Make a pledge without reward + (, uint256 tokenId) = pledgeWithoutAReward( + users.backer1Address, + LAUNCH_TIME, + address(allOrNothing), + PLEDGE_AMOUNT + ); + + // Get final balances + uint256 finalBackerBalance = testToken.balanceOf(users.backer1Address); + uint256 finalTreasuryBalance = testToken.balanceOf(address(allOrNothing)); + uint256 finalBackerNftBalance = allOrNothing.balanceOf(users.backer1Address); + address nftOwnerAddress = allOrNothing.ownerOf(tokenId); + + // Verify token transfers + assertEq( + initialBackerBalance - finalBackerBalance, + PLEDGE_AMOUNT, + "Incorrect amount deducted from backer" + ); + assertEq( + finalTreasuryBalance - initialTreasuryBalance, + PLEDGE_AMOUNT, + "Incorrect amount transferred to treasury" + ); + + // Verify NFT minting + assertEq( + finalBackerNftBalance - initialBackerNftBalance, + 1, + "Backer should receive exactly one NFT" + ); + assertEq( + nftOwnerAddress, + users.backer1Address, + "Backer should own the minted NFT" + ); - assertEq(users.backer1Address, nftOwnerAddress); - assertEq(PLEDGE_AMOUNT + SHIPPING_FEE, treasuryBalance); - assertEq(1, backerNftBalance); + // Verify treasury balance matches expected amount (no shipping fees) + assertEq( + finalTreasuryBalance, + PLEDGE_AMOUNT, + "Treasury should only contain the pledge amount" + ); } + /** + * @notice Tests the claimRefund functionality for both reward and non-reward pledges + * @dev Verifies that backers can claim refunds when campaigns fail to meet their goals. + * Tests both reward pledges (with shipping fees) and non-reward pledges, ensuring + * proper refund amounts and that the correct addresses receive refunds for both types. + * Validates that refunds include shipping fees for reward pledges and that NFTs are + * burned upon successful refund claims. + */ function test_claimRefund() external { addRewards( users.creator1Address, @@ -89,36 +459,68 @@ contract AllOrNothingFunction_Integration_Shared_Test is REWARDS ); + // Create a pledge with reward (, uint256 rewardTokenId, ) = pledgeForAReward( users.backer1Address, - address(testUSD), + LAUNCH_TIME, address(allOrNothing), PLEDGE_AMOUNT, SHIPPING_FEE, - LAUNCH_TIME, REWARD_NAME_1_HASH ); - (, uint256 tokenId) = pledgeWithoutAReward( + // Create a pledge without reward + (, uint256 nonRewardTokenId) = pledgeWithoutAReward( users.backer1Address, - address(testUSD), + LAUNCH_TIME, address(allOrNothing), - PLEDGE_AMOUNT, - LAUNCH_TIME + PLEDGE_AMOUNT ); + // Test refund for pledge without reward + ( + , + uint256 refundedNonRewardTokenId, + uint256 nonRewardRefundAmount, + address nonRewardClaimer + ) = claimRefund( + users.backer1Address, + LAUNCH_TIME + 1, + address(allOrNothing), + nonRewardTokenId + ); + + // Verify non-reward refund + assertEq(refundedNonRewardTokenId, nonRewardTokenId, "Incorrect non-reward token ID refunded"); + assertEq(nonRewardRefundAmount, PLEDGE_AMOUNT, "Incorrect non-reward refund amount"); + assertEq(nonRewardClaimer, users.backer1Address, "Incorrect non-reward claimer address"); + + // Test refund for pledge with reward ( - Vm.Log[] memory refundLogs, - uint256 refundedTokenId, - uint256 refundAmount, - address claimer - ) = claimRefund(users.backer1Address, address(allOrNothing), tokenId); + , + uint256 refundedRewardTokenId, + uint256 rewardRefundAmount, + address rewardClaimer + ) = claimRefund( + users.backer1Address, + LAUNCH_TIME + 1, + address(allOrNothing), + rewardTokenId + ); - assertEq(refundedTokenId, tokenId); - assertEq(refundAmount, PLEDGE_AMOUNT); - assertEq(claimer, users.backer1Address); + // Verify reward refund (should include pledge amount + shipping fee) + assertEq(refundedRewardTokenId, rewardTokenId, "Incorrect reward token ID refunded"); + assertEq(rewardRefundAmount, PLEDGE_AMOUNT + SHIPPING_FEE, "Incorrect reward refund amount"); + assertEq(rewardClaimer, users.backer1Address, "Incorrect reward claimer address"); } + /** + * @notice Tests the disburseFees functionality + * @dev Verifies that protocol and platform fees are correctly calculated and distributed + * when a campaign succeeds. Tests the fee calculation logic and ensures proper + * allocation between protocol and platform shares. Only executes after campaign + * deadline and when success conditions are met. + */ function test_disburseFees() external { addRewards( users.creator1Address, @@ -129,19 +531,17 @@ contract AllOrNothingFunction_Integration_Shared_Test is pledgeForAReward( users.backer1Address, - address(testUSD), + LAUNCH_TIME, address(allOrNothing), PLEDGE_AMOUNT, SHIPPING_FEE, - LAUNCH_TIME, REWARD_NAME_1_HASH ); pledgeWithoutAReward( users.backer2Address, - address(testUSD), + LAUNCH_TIME, address(allOrNothing), - GOAL_AMOUNT, - LAUNCH_TIME + GOAL_AMOUNT ); uint256 totalPledged = GOAL_AMOUNT + PLEDGE_AMOUNT; @@ -150,13 +550,20 @@ contract AllOrNothingFunction_Integration_Shared_Test is Vm.Log[] memory logs, uint256 protocolShare, uint256 platformShare - ) = disburseFees(address(allOrNothing), DEADLINE); + ) = disburseFees(address(allOrNothing), DEADLINE + 1); uint256 expectedProtocolShare = (totalPledged * PROTOCOL_FEE_PERCENT) / PERCENT_DIVIDER; uint256 expectedPlatformShare = (totalPledged * PLATFORM_FEE_PERCENT) / PERCENT_DIVIDER; + // Verify FeesDisbursed event was emitted + Vm.Log memory feesLog = findLogByTopic( + logs, + keccak256("FeesDisbursed(uint256,uint256)") + ); + assertEq(feesLog.emitter, address(allOrNothing), "FeesDisbursed event should be emitted by allOrNothing contract"); + assertEq( protocolShare, expectedProtocolShare, @@ -169,6 +576,13 @@ contract AllOrNothingFunction_Integration_Shared_Test is ); } + /** + * @notice Tests the withdraw functionality + * @dev Verifies that campaign creators can withdraw remaining funds after successful + * campaigns and fee disbursement. Tests proper calculation of withdrawal amounts + * after deducting protocol and platform fees, and confirms funds go to the correct + * recipient (campaign owner). Includes shipping fees in the final withdrawal amount. + */ function test_withdraw() external { addRewards( users.creator1Address, @@ -179,23 +593,21 @@ contract AllOrNothingFunction_Integration_Shared_Test is pledgeForAReward( users.backer1Address, - address(testUSD), + LAUNCH_TIME, address(allOrNothing), PLEDGE_AMOUNT, SHIPPING_FEE, - LAUNCH_TIME, REWARD_NAME_1_HASH ); pledgeWithoutAReward( users.backer2Address, - address(testUSD), + LAUNCH_TIME, address(allOrNothing), - GOAL_AMOUNT, - LAUNCH_TIME + GOAL_AMOUNT ); uint256 totalPledged = GOAL_AMOUNT + PLEDGE_AMOUNT; - disburseFees(address(allOrNothing), DEADLINE); + disburseFees(address(allOrNothing), DEADLINE + 1); (Vm.Log[] memory logs, address to, uint256 amount) = withdraw( address(allOrNothing), @@ -211,6 +623,13 @@ contract AllOrNothingFunction_Integration_Shared_Test is protocolShare - platformShare; + // Verify WithdrawalSuccessful event was emitted + Vm.Log memory withdrawalLog = findLogByTopic( + logs, + keccak256("WithdrawalSuccessful(address,uint256)") + ); + assertEq(withdrawalLog.emitter, address(allOrNothing), "WithdrawalSuccessful event should be emitted by allOrNothing contract"); + assertEq( to, users.creator1Address, @@ -218,4 +637,4 @@ contract AllOrNothingFunction_Integration_Shared_Test is ); assertEq(amount, expectedAmount, "Incorrect withdrawal amount"); } -} +} \ No newline at end of file diff --git a/test/foundry/unit/CampaignInfoFactory.t.sol b/test/foundry/unit/CampaignInfoFactory.t.sol index 32a6d313..803a83bb 100644 --- a/test/foundry/unit/CampaignInfoFactory.t.sol +++ b/test/foundry/unit/CampaignInfoFactory.t.sol @@ -1,11 +1,11 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import {CampaignInfoFactory} from "src/CampaignInfoFactory.sol"; import {GlobalParams} from "src/GlobalParams.sol"; import {TreasuryFactory} from "src/TreasuryFactory.sol"; -import {TestUSD} from "src/TestUSD.sol"; +import {TestToken} from "../../mocks/TestToken.sol"; import {Defaults} from "../Base.t.sol"; import {ICampaignData} from "src/interfaces/ICampaignData.sol"; import {CampaignInfo} from "src/CampaignInfo.sol"; @@ -14,22 +14,24 @@ contract CampaignInfoFactory_UnitTest is Test, Defaults { CampaignInfoFactory internal factory; TreasuryFactory internal treasuryFactory; GlobalParams internal globalParams; - TestUSD internal testUSD; + TestToken internal testToken; CampaignInfo internal campaignInfoImplementation; address internal admin = address(0xA11CE); function setUp() public { - - testUSD = new TestUSD(); + testToken = new TestToken(tokenName, tokenSymbol); globalParams = new GlobalParams( admin, - address(testUSD), + address(testToken), PROTOCOL_FEE_PERCENT ); campaignInfoImplementation = new CampaignInfo(address(this)); treasuryFactory = new TreasuryFactory(globalParams); - factory = new CampaignInfoFactory(globalParams, address(campaignInfoImplementation)); + factory = new CampaignInfoFactory( + globalParams, + address(campaignInfoImplementation) + ); vm.startPrank(admin); globalParams.enlistPlatform( PLATFORM_1_HASH, @@ -47,7 +49,6 @@ contract CampaignInfoFactory_UnitTest is Test, Defaults { // vm.stopPrank(); } - function testCreateCampaignDeploysSuccessfully() public { factory._initialize(address(treasuryFactory), address(globalParams)); diff --git a/test/foundry/unit/GlobalParams.t.sol b/test/foundry/unit/GlobalParams.t.sol index ca4f0608..05c82ffa 100644 --- a/test/foundry/unit/GlobalParams.t.sol +++ b/test/foundry/unit/GlobalParams.t.sol @@ -1,19 +1,20 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import {GlobalParams} from "src/GlobalParams.sol"; -import {TestUSD} from "src/TestUSD.sol"; +import {Defaults} from "../Base.t.sol"; +import {TestToken} from "../../mocks/TestToken.sol"; -contract GlobalParams_UnitTest is Test { +contract GlobalParams_UnitTest is Test, Defaults{ GlobalParams internal globalParams; - TestUSD internal token; + TestToken internal token; address internal admin = address(0xA11CE); uint256 internal protocolFee = 300; // 3% function setUp() public { - token = new TestUSD(); + token = new TestToken(tokenName, tokenSymbol); globalParams = new GlobalParams(admin, address(token), protocolFee); } @@ -50,4 +51,4 @@ contract GlobalParams_UnitTest is Test { vm.expectRevert(); globalParams.updateTokenAddress(address(0xBEEF)); } -} \ No newline at end of file +} diff --git a/test/foundry/unit/TestUSD.t.sol b/test/foundry/unit/TestToken.t.sol similarity index 69% rename from test/foundry/unit/TestUSD.t.sol rename to test/foundry/unit/TestToken.t.sol index cd1a2480..0ba3c4ed 100644 --- a/test/foundry/unit/TestUSD.t.sol +++ b/test/foundry/unit/TestToken.t.sol @@ -1,17 +1,18 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; -import {TestUSD} from "src/TestUSD.sol"; +import {TestToken} from "../../mocks/TestToken.sol"; +import {Defaults} from "../Base.t.sol"; -contract TestUSD_UnitTest is Test { - TestUSD internal token; +contract TestToken_UnitTest is Test, Defaults { + TestToken internal token; address internal user = address(0x1234); uint256 internal mintAmount = 1_000 * 1e18; function setUp() public { - token = new TestUSD(); + token = new TestToken(tokenName, tokenSymbol); } function testMintIncreasesBalance() public { @@ -26,4 +27,4 @@ contract TestUSD_UnitTest is Test { token.transfer(recipient, 200 * 1e18); assertEq(token.balanceOf(recipient), 200 * 1e18); } -} \ No newline at end of file +} diff --git a/test/foundry/unit/TreasuryFactory.t.sol b/test/foundry/unit/TreasuryFactory.t.sol index 41187800..3a7a6785 100644 --- a/test/foundry/unit/TreasuryFactory.t.sol +++ b/test/foundry/unit/TreasuryFactory.t.sol @@ -1,16 +1,17 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "forge-std/Test.sol"; import {TreasuryFactory} from "src/TreasuryFactory.sol"; import {GlobalParams} from "src/GlobalParams.sol"; -import {TestUSD} from "src/TestUSD.sol"; +import {TestToken} from "../../mocks/TestToken.sol"; +import {Defaults} from "../Base.t.sol"; import {AdminAccessChecker} from "src/utils/AdminAccessChecker.sol"; -contract TreasuryFactory_UpdatedUnitTest is Test { +contract TreasuryFactory_UpdatedUnitTest is Test, Defaults { TreasuryFactory internal factory; GlobalParams internal globalParams; - TestUSD internal testUSD; + TestToken internal testToken; address internal protocolAdmin = address(0xA11CE); address internal platformAdmin = address(0xBEEF); @@ -23,8 +24,8 @@ contract TreasuryFactory_UpdatedUnitTest is Test { uint256 internal platformFee = 300; // 3% function setUp() public { - testUSD = new TestUSD(); - globalParams = new GlobalParams(protocolAdmin, address(testUSD), 300); + testToken = new TestToken(tokenName, tokenSymbol); + globalParams = new GlobalParams(protocolAdmin, address(testToken), 300); factory = new TreasuryFactory(globalParams); // Label addresses for clarity diff --git a/test/foundry/utils/Constants.sol b/test/foundry/utils/Constants.sol index bf7e111e..f25eb6f0 100644 --- a/test/foundry/utils/Constants.sol +++ b/test/foundry/utils/Constants.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; abstract contract Constants { diff --git a/test/foundry/utils/Defaults.sol b/test/foundry/utils/Defaults.sol index 17815d82..c77b2ea5 100644 --- a/test/foundry/utils/Defaults.sol +++ b/test/foundry/utils/Defaults.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Constants} from "./Constants.sol"; @@ -30,6 +30,10 @@ contract Defaults is Constants, ICampaignData, IReward { uint256 public immutable LAUNCH_TIME; uint256 public immutable DEADLINE; + //Token details + string tokenName = "TestToken"; + string tokenSymbol = "TST"; + //Variables CampaignData public CAMPAIGN_DATA; AllOrNothing.Reward public REWARD1; diff --git a/test/foundry/utils/Types.sol b/test/foundry/utils/Types.sol index df4b5ffc..1331e769 100644 --- a/test/foundry/utils/Types.sol +++ b/test/foundry/utils/Types.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; struct Users { diff --git a/test/mocks/TestToken.sol b/test/mocks/TestToken.sol new file mode 100644 index 00000000..19414ef1 --- /dev/null +++ b/test/mocks/TestToken.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import {Ownable} from "../../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; + +/** + * @title TestToken + * @notice A test token `tUSD` which is used in the tests. + */ +contract TestToken is ERC20, Ownable { + constructor( + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol) Ownable(msg.sender) {} + + /** + * @notice Mints testToken token. + * @param to The token receivers address. + * @param amount The amount of tokens to mint. + */ + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } +}