Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
LinearVRGDACorrectnessTest:testParamsSchema() (gas: 9581)
LinearVRGDACorrectnessTest:testSetup_HookInitialized() (gas: 5671)
LinearVRGDACorrectnessTest:testSupportsInterface_RegistryPricingStrategy() (gas: 9685)
LinearVRGDACorrectnessTest:testSupportsInterface_RegistryProductPrice() (gas: 9685)
LinearVRGDATest:testAlwaystargetPriceInRightConditions(uint256) (runs: 256, μ: 14604, ~: 14369)
LinearVRGDATest:testParamsSchema() (gas: 9564)
LinearVRGDATest:testPricingAdjustedByQuantity() (gas: 18788)
Expand All @@ -11,7 +11,7 @@ LinearVRGDATest:testProductPriceEth() (gas: 26435)
LinearVRGDATest:testProductPriceMultiple() (gas: 29695)
LinearVRGDATest:testSetMultiplePrices() (gas: 171215)
LinearVRGDATest:testSetup_HookInitialized() (gas: 5715)
LinearVRGDATest:testSupportsInterface_RegistryPricingStrategy() (gas: 9684)
LinearVRGDATest:testSupportsInterface_RegistryProductPrice() (gas: 9684)
LinearVRGDATest:testTargetPrice() (gas: 11418)
LogisticVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 16809, ~: 16994)
LogisticVRGDATest:testGetTargetSaleTimeDoesNotRevertEarly() (gas: 6630)
Expand All @@ -27,7 +27,7 @@ LogisticVRGDATest:testProductPriceEth() (gas: 28425)
LogisticVRGDATest:testProductPriceMultiple() (gas: 34309)
LogisticVRGDATest:testSetMultiplePrices() (gas: 181254)
LogisticVRGDATest:testSetup_HookInitialized() (gas: 5693)
LogisticVRGDATest:testSupportsInterface_RegistryPricingStrategy() (gas: 9752)
LogisticVRGDATest:testSupportsInterface_RegistryProductPrice() (gas: 9752)
LogisticVRGDATest:testTargetPrice() (gas: 13363)
LogisticVRGDATest:test_RevertOverflow_BeyondLimitTokens(uint256,uint256) (runs: 256, μ: 14963, ~: 14978)
NFTDiscountTest:testDeploy() (gas: 5246)
Expand All @@ -44,4 +44,4 @@ NFTDiscountTest:testSetProductPrice__Edit_Add() (gas: 249809)
NFTDiscountTest:testSetProductPrice__Edit_Remove() (gas: 230519)
NFTDiscountTest:testSetProductPrice__MultipleCurrencies() (gas: 198172)
NFTDiscountTest:testSetup_HookInitialized() (gas: 5715)
NFTDiscountTest:testSupportsInterface_RegistryPricingStrategy() (gas: 9705)
NFTDiscountTest:testSupportsInterface_RegistryProductPrice() (gas: 9705)
181 changes: 96 additions & 85 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,28 @@
# ▼ Slice Hooks

Smart contracts for creating custom pricing strategies and onchain actions for [Slice](https://slice.so) products.
Smart contracts for creating custom pricing strategies and onchain actions for [Slice](https://slice.so) products.

Hooks enable dynamic pricing, purchase restrictions, rewards, integration with external protocols and other custom behaviors when products are bought.

## Repository Structure
## Architecture

```
src/
├── hooks/ # Reusable hooks with registry support
│ ├── actions/ # Onchain actions (gating, rewards, etc.)
│ ├── pricing/ # Pricing strategies (NFT discounts, VRGDA, etc.)
│ └── pricingActions/ # Combined pricing + action hooks
├── examples/ # Product-specific reference implementations
├── interfaces/ # Core hook interfaces
└── utils/ # Base contracts and utilities
```

## Core Concepts

Hooks are built around three main interfaces:

- **[`IOnchainAction`](./src/interfaces/IOnchainAction.sol)**: Execute custom logic during purchases (eligibility checks, rewards, etc.)
- **[`IPricingStrategy`](./src/interfaces/IPricingStrategy.sol)**: Calculate dynamic prices for products
- **[`IHookRegistry`](./src/interfaces/IHookRegistry.sol)**: Enable reusable hooks across multiple products with frontend integration

Hooks can be:

- **Product-specific**: Custom smart contracts tailored for individual products. These are integrated using the `custom` onchain action or pricing strategy in Slice.
- **Registry hooks**: Reusable contracts designed to support multiple products. Registries enable automatic integration with Slice clients.

See [Hook types](#hook-types) for more details.

## Product Purchase Lifecycle
### Product Purchase Lifecycle

Here's how hooks integrate into the product purchase flow:

```
Checkout
Checkout
┌─────────────────────┐
Price Fetching │ ← `productPrice` called here
(before purchase) │ (IPricingStrategy)
│ Price Fetching │ ← `IProductPrice.productPrice()`
│ (before purchase)
└─────────────────────┘
┌─────────────────────┐
│ Purchase Execution │ ← `onProductPurchase` called here
│ (during purchase) │ (IOnchainAction)
│ Purchase Execution │ ← `IProductAction.onProductPurchase()`
│ (during purchase) │
└─────────────────────┘
Expand All @@ -63,86 +37,123 @@ Here's how hooks integrate into the product purchase flow:
- Validate purchase eligibility
- Execute custom logic (gating, minting, rewards, etc.)

## Hook Types

### Registry Hooks (Reusable)
### Core Interfaces

Deploy once, use across multiple products with frontend integration:
Hooks are built around three main interfaces:

- **[Actions](./src/hooks/actions/)**: See available onchain actions and implementation guide
- **[Pricing](./src/hooks/pricing/)**: See available pricing strategies and implementation guide
- **[Pricing Actions](./src/hooks/pricingActions/)**: See combined pricing + action hooks
**IProductPrice** - Calculate dynamic prices for products:
```solidity
function productPrice(
uint256 slicerId,
uint256 productId,
address currency,
uint256 quantity,
address buyer,
bytes memory data
) external view returns (uint256 ethPrice, uint256 currencyPrice);
```

### Product-Specific Hooks
**IProductAction** - Execute custom logic during purchases (eligibility checks, rewards, etc.):
```solidity
function isPurchaseAllowed(
uint256 slicerId,
uint256 productId,
address buyer,
uint256 quantity,
bytes memory slicerCustomData,
bytes memory buyerCustomData
) external view returns (bool);

function onProductPurchase(
uint256 slicerId,
uint256 productId,
address buyer,
uint256 quantity,
bytes memory slicerCustomData,
bytes memory buyerCustomData
) external payable;
```

Tailored implementations for individual products:
**IHookRegistry** - Enable reusable hooks across multiple products with frontend integration:
```solidity
function configureProduct(uint256 slicerId, uint256 productId, bytes memory params) external;
function paramsSchema() external pure returns (string memory);
```

- **[Examples](./src/examples/)**: See real-world implementations and creation guide
### Hook Types

## Base Contracts
#### Registry Hooks (Reusable)

The base contracts in `src/utils` are designed to be inherited, providing essential building blocks for developing custom Slice hooks efficiently.
Reusable contracts designed to support multiple products, automatically integrated with Slice clients.

### Registry (Reusable):
- **[Actions](./src/hooks/actions/)** - Purchase restrictions and onchain effects
- **[Pricing](./src/hooks/pricing/)** - Dynamic pricing strategies
- **[PricingActions](./src/hooks/pricingActions/)** - Pricing + actions in one contract

- **`RegistryOnchainAction`**: Base for reusable onchain actions
- **`RegistryPricingStrategy`**: Base for reusable pricing strategies
- **`RegistryPricingStrategyAction`**: Base for reusable pricing + action hooks
#### Product-Specific Hooks

### Product-Specific
Custom smart contracts tailored for individual products, integrated using the `custom` onchain action or pricing strategy in Slice.

- **`OnchainAction`**: Base for product-specific onchain actions
- **`PricingStrategy`**: Base for product-specific pricing strategies
- **`PricingStrategyAction`**: Base for product-specific pricing + action hooks
- **[Examples](./src/examples/)**: Reference implementations and templates

## Quick Start
All hooks inherit from base contracts in `src/utils/`.

- **For reusable actions**: See detailed guides in [`/src/hooks/actions`](./src/hooks/actions)
- **For reusable pricing strategies**: See detailed guides in [`/src/hooks/pricing`](./src/hooks/pricing)
- **For reusable pricing strategy actions**: See detailed guides in [`/src/hooks/pricingActions`](./src/hooks/pricingActions)
- **For product-specific hooks**: See implementation examples in [`/src/examples/`](./src/examples/)
## Contributing

## Development
### Quick Start

```bash
forge soldeer install # Install dependencies
forge test # Run tests
forge build # Build
forge soldeer install # Install dependencies
forge build # Compile contracts
forge test # Run test suite
```

Requires [Foundry](https://book.getfoundry.sh/getting-started/installation).

### Deployment
Deploy by running `./script/deploy.sh` and following instructions

To deploy hooks, use the deployment script:
### Building a Hook

The quickest way to create a new hook is using the interactive generator:

```bash
./script/deploy.sh
./script/generate-hook.sh
```

The script will present you with a list of available contracts to deploy. Select the contract you want to deploy and follow the prompts.

### Testing
This will guide you through:
1. Choosing hook scope (Registry or Product-specific)
2. Selecting hook type (Action, Pricing Strategy, or Pricing Action)
3. Naming your contract
4. Setting authorship (optional)

When writing tests for your hooks, inherit from the appropriate base test contract:
The script automatically:
- Creates the contract file with appropriate template
- Adds imports to aggregator contracts (for registry hooks)
- Generates test files with proper structure (for registry hooks)

- **`RegistryOnchainActionTest`**: For testing `RegistryOnchainAction` contracts
- **`RegistryPricingStrategyTest`**: For testing `RegistryPricingStrategy` contracts
- **`RegistryPricingStrategyActionTest`**: For testing `RegistryPricingStrategyAction` contracts
- **`OnchainActionTest`**: For testing `OnchainAction` contracts
- **`PricingStrategyTest`**: For testing `PricingStrategy` contracts
- **`PricingStrategyActionTest`**: For testing `PricingStrategyAction` contracts
Once the hook is generated, add your custom contract logic to the and write tests for it.

Inheriting the appropriate test contract for your hook allows you to focus your tests solely on your custom hook logic.
For more detailed information, follow the appropriate guide for your hook type:
- [Actions](./src/hooks/actions/README.md)
- [Pricing](./src/hooks/pricing/README.md)
- [PricingActions](./src/hooks/pricingActions/README.md)

## Contributing
### Repository Structure

To contribute a new hook to this repository:
```
src/
├── hooks/ # Reusable hooks with registry support
│ ├── actions/ # Onchain actions (gating, rewards, etc.)
│ ├── pricing/ # Pricing strategies (NFT discounts, VRGDA, etc.)
│ └── pricingActions/ # Combined pricing + action hooks
├── examples/ # Product-specific reference implementations
├── interfaces/ # Core hook interfaces
└── utils/ # Base contracts and utilities
```

1. **Choose the appropriate hook type** based on your needs (registry vs product-specific)
2. **Implement your hook** following the existing patterns in the codebase
3. **Write comprehensive tests** using the appropriate test base contract
4. **Add documentation** explaining your hook's purpose and usage
5. **Submit a pull request** against this repository
### Resources

Make sure your contribution follows the existing code style and includes proper documentation.
- [Actions Guide](./src/hooks/actions/README.md)
- [Pricing Strategies Guide](./src/hooks/pricing/README.md)
- [Pricing + Actions Guide](./src/hooks/pricingActions/README.md)
- [Example Implementations](./src/examples/README.md)
81 changes: 78 additions & 3 deletions deployments/addresses.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,80 @@
{
"actions": {},
"pricingStrategies": {},
"pricingStrategyActions": {}
"actions": {
"Allowlisted": [
{
"address": "0x157428DD791E03c20880D22C3dA2B66A36B5cF26",
"blockNumber": 33510607,
"paramsSchema": "bytes32 merkleRoot",
"transactionHash": "0x6af9ac700f1c9a38de57fa4bc13262162d9b674649fd417ccd50237b6cfbc178"
}
],
"ERC20Gated": [
{
"address": "0x26A1C86B555013995Fc72864D261fDe984752E7c",
"blockNumber": 33558792,
"paramsSchema": "(address erc20,uint256 amount)[] erc20Gates",
"transactionHash": "0x3a7c01ede05a34280073479d5cdf1f35e41d9f08c36a71f358b9c503ccc54526"
}
],
"ERC20Mint": [
{
"address": "0x67f9799FaC1D53C63217BEE47f553150F5BB0836",
"blockNumber": 33520592,
"paramsSchema": "string name,string symbol,uint256 premintAmount,address premintReceiver,bool revertOnMaxSupplyReached,uint256 maxSupply,uint256 tokensPerUnit",
"transactionHash": "0xfcd7e8fe47aa509afa0acdef86b741656c2602626f3258f8a83b359c665d6b04"
}
],
"ERC721Mint": [
{
"address": "0x2b6488115FAa50142E140172CbCd60e6370675F7",
"blockNumber": 33511082,
"paramsSchema": "string name,string symbol,address royaltyReceiver,uint256 royaltyFraction,string baseURI,string tokenURI,bool revertOnMaxSupplyReached,uint256 maxSupply",
"transactionHash": "0x4981b3b67c0b8abe6a942b3ca86643f2b1dfdbcce59d6c819ad20a82814956e0"
}
],
"NFTGated": [
{
"address": "0xD4eF7A46bF4c58036eaCA886119F5230e5a2C25d",
"blockNumber": 33563508,
"paramsSchema": "(address nft,uint8 nftType,uint80 id,uint8 minQuantity)[] nftGates,uint256 minOwned",
"transactionHash": "0x9dd101a6155b432849cb33f65c5d4b9f6875e6b1e7bf806005a8a6b0d070f634"
}
]
},
"pricing": {
"NFTDiscount": [
{
"address": "0xb830a457d2f51d4cA1136b97FB30DF6366CFe2f5",
"blockNumber": 33596708,
"paramsSchema": "(address nft,uint80 discount,uint8 minQuantity,uint8 nftType,uint256 tokenId)[] discounts",
"transactionHash": "0x966be5fa1da3fab7c5027c9acb3f118307997e30687188b4745d74fd26ad9e7e"
}
],
"LinearVRGDAPrices": [
{
"address": "0xEC68E30182F4298b7032400B7ce809da613e4449",
"blockNumber": 33511188,
"paramsSchema": "(address currency,int128 targetPrice,uint128 min,int256 perTimeUnit)[] linearParams,int256 priceDecayPercent",
"transactionHash": "0xf2667ce20d07561e59c8d3e3de135bbd895c5b08bb57fe487c6c8197c91a0d73"
}
],
"LogisticVRGDAPrices": [
{
"address": "0x2b02cC8528EF18abf8185543CEC29A94F0542c8F",
"blockNumber": 33511209,
"paramsSchema": "(address currency,int128 targetPrice,uint128 min,int256 timeScale)[] logisticParams,int256 priceDecayPercent",
"transactionHash": "0x2b27380661a38b54dbccf634305622296034ddfccf5865a07fbfb7810ea41025"
}
]
},
"pricingActions": {
"FirstForFree": [
{
"address": "0x2C18D37b8229233F672bF406bCe8799BCfD43B5A",
"blockNumber": 33510960,
"paramsSchema": "uint256 usdcPrice,(address tokenAddress,uint8 tokenType,uint88 tokenId,uint8 minQuantity)[] eligibleTokens,address mintToken,uint88 mintTokenId,uint8 freeUnits",
"transactionHash": "0x8bd0b9de8edd704899210f8450096adf7f4f853030a7ba0a2f64494bc1ba726e"
}
]
}
}
11 changes: 7 additions & 4 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ dynamic_test_linking = true
libs = ["dependencies", "../core/src", "../core/dependencies"]
fs_permissions = [{ access = "read", path = "./src"}, { access= "read", path = "./broadcast/Deploy.s.sol/8453/run-latest.json"}, { access = "read-write", path = "./deployments"}, { access = "read", path = "./out"}]
remappings = [
"slice/=dependencies/slice-0.0.4/",
"slice/=dependencies/slice-0.0.8/",
"@openzeppelin-4.8.0/=dependencies/@openzeppelin-contracts-4.8.0/",
"@openzeppelin-upgradeable-4.8.0/=dependencies/@openzeppelin-contracts-upgradeable-4.8.0/",
"@erc721a/=dependencies/erc721a-4.3.0/contracts/",
"@murky/=dependencies/murky-0.1.0/src/",
"forge-std/=dependencies/forge-std-1.9.7/src/",
"@test/=test/",
"@/=src/"
Expand Down Expand Up @@ -44,9 +44,12 @@ remappings_generate = false
remappings_regenerate = false

[dependencies]
slice = "0.0.4"
slice = "0.0.8"
forge-std = "1.9.7"
"@openzeppelin-contracts" = "4.8.0"
"@openzeppelin-contracts-upgradeable" = "4.8.0"
erc721a = "4.3.0"
murky = "0.1.0"

[lint]
ignore = ["test/**/*.sol","script/**/*.sol", "src/interfaces/*.sol", "src/utils/*.sol", "src/hooks/actions/actions.sol", "src/hooks/pricing/pricing.sol", "src/hooks/pricingActions/pricingActions.sol", "src/examples/actions/BaseGirlsScout.sol"]
exclude_lints=["mixed-case-function", "mixed-case-variable", "pascal-case-struct"]
Loading