Skip to content

Commit

Permalink
feat: bonding curve test and script with token integration
Browse files Browse the repository at this point in the history
  • Loading branch information
VanshSahay committed Feb 12, 2025
1 parent 69d4ffc commit 4b27c75
Show file tree
Hide file tree
Showing 9 changed files with 1,028 additions and 103 deletions.
192 changes: 152 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,178 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

# Dataset Bonding Curve Smart Contracts

This repository contains smart contracts implementing a bonding curve mechanism for dataset tokens. The system allows for the creation and pricing of dataset tokens using a bonding curve pricing model.

## Overview

The project consists of two main contracts:

1. **DatasetToken**: An ERC1155-based token contract for representing datasets
2. **DatasetBondingCurve**: A contract implementing the bonding curve pricing mechanism with the following features:
- Initial price: 0.01 ETH
- Price multiplier: 1.5x increase per token
- Automated price calculation based on token supply

## Contract Functions

### DatasetToken Contract (`DeployDataset.sol`)

#### Core Functions
- `mintDatasetToken(OwnershipShare[], string, string, string, string, uint256, string[])`: Mints a new dataset token with multiple owners, metadata, and initial price
- `purchaseDataset(uint256)`: Allows users to purchase a dataset token at the current bonding curve price
- `setBondingCurve(address)`: Sets the bonding curve contract address (admin only)
- `updatePrice(uint256, uint256)`: Updates the price of a dataset (primary owner only)

#### View Functions
- `getTokensByTag(string)`: Returns all token IDs associated with a specific tag
- `getTokenTags(uint256)`: Returns all tags for a specific token
- `getTokenOwners(uint256)`: Returns ownership information for a token
- `getTotalTokens()`: Returns the total number of tokens minted
- `getTokensByOwner(address)`: Returns all token IDs owned by an address
- `getDatasetIPFSHash(uint256)`: Returns the IPFS hash for a purchased dataset

### DatasetBondingCurve Contract

#### Core Functions
- `setInitialPrice(uint256, uint256)`: Sets the initial price for a token's bonding curve
- `calculatePrice(uint256)`: Calculates the current price for a specific token
- `getCurrentPrice(uint256)`: View function to get the current price
- `recordPurchase(uint256)`: Records a purchase to update the bonding curve
- `updateDatasetTokenAddress(address)`: Updates the dataset token contract address

## User Workflow

### For Dataset Owners

1. **Creating a Dataset Token**
```solidity
// Example ownership structure
OwnershipShare[] shares = [
OwnershipShare(owner1, 7000), // 70%
OwnershipShare(owner2, 3000) // 30%
];
// Mint token with metadata
datasetToken.mintDatasetToken(
shares,
"Dataset Name",
"Description",
"contentHash",
"ipfsHash",
initialPrice,
["tag1", "tag2"]
);
```

2. **Managing Dataset**
- Update price if needed using `updatePrice()`
- Monitor ownership and sales through events
- Add or remove tags as needed

### For Dataset Buyers

1. **Discovering Datasets**
- Browse datasets by tags using `getTokensByTag()`
- View dataset metadata and ownership information
- Check current prices using `getCurrentPrice()`

2. **Purchasing a Dataset**
```solidity
// Get current price
uint256 price = bondingCurve.getCurrentPrice(tokenId);
// Purchase dataset
datasetToken.purchaseDataset{value: price}(tokenId);
```

3. **Accessing Dataset**
- After purchase, retrieve IPFS hash using `getDatasetIPFSHash()`
- Access dataset content through IPFS

### Price Mechanism

The bonding curve implements an automated market maker with the following characteristics:

1. **Initial Pricing**
- Each dataset starts at its set initial price
- Price increases by 1.5x after each purchase

2. **Price Calculation**
```
Current Price = Initial Price * (1.5 ^ Number of Purchases)
```

3. **Revenue Distribution**
- Sales revenue is automatically distributed to owners based on their ownership percentages
- Payments are instant and trustless

## Prerequisites

- [Foundry](https://book.getfoundry.sh/getting-started/installation)
- Ethereum wallet with some ETH for deployment
- Environment variables set up (see Configuration section)

## Installation

1. Clone the repository:
```shell
$ forge build
git clone <repository-url>
cd <repository-name>
```

### Test

2. Install dependencies:
```shell
$ forge test
forge install
```

### Format
## Configuration

```shell
$ forge fmt
Create a `.env` file in the root directory with the following variables:
```
PRIVATE_KEY=your_private_key
RPC_URL=your_rpc_url
```

### Gas Snapshots
## Building

To build the contracts:
```shell
$ forge snapshot
forge build
```

### Anvil
## Testing

Run the test suite:
```shell
$ anvil
forge test
```

### Deploy

For more detailed test output:
```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
forge test -vv
```

### Cast

For gas reports:
```shell
$ cast <subcommand>
forge test --gas-report
```

### Help
## Deployment

The deployment process involves two steps:

1. Deploy the DatasetToken contract
2. Deploy the DatasetBondingCurve contract

To deploy the contracts:
```shell
$ forge --help
$ anvil --help
$ cast --help
source .env
forge script script/DeployBondingCurve.s.sol:DeployBondingCurve --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast
```

## Security

- The contracts use OpenZeppelin's standard implementations for security
- Ownership controls are in place for administrative functions
- Price calculations are done with proper decimal handling to prevent rounding errors
- Reentrancy protection is implemented for all state-changing functions
- Multi-owner support with percentage-based revenue distribution
197 changes: 197 additions & 0 deletions broadcast/DeployBondingCurve.s.sol/84532/run-1739351951.json

Large diffs are not rendered by default.

197 changes: 197 additions & 0 deletions broadcast/DeployBondingCurve.s.sol/84532/run-1739352629.json

Large diffs are not rendered by default.

197 changes: 197 additions & 0 deletions broadcast/DeployBondingCurve.s.sol/84532/run-latest.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ src = "src"
out = "out"
libs = ["lib"]
remappings = ["@openzeppelin/=lib/openzeppelin-contracts/"]
via-ir = true

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

[fmt]
number_underscore = "thousands"
34 changes: 34 additions & 0 deletions script/DeployBondingCurve.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import {Script} from "forge-std/Script.sol";
import {DatasetBondingCurve} from "../src/DatasetBondingCurve.sol";
import {DatasetToken} from "../src/DeployDataset.sol";

contract DeployBondingCurve is Script {
function run() external returns (DatasetBondingCurve, DatasetToken) {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployerAddress = vm.addr(deployerPrivateKey);

vm.startBroadcast(deployerPrivateKey);

// First deploy the DatasetToken contract
DatasetToken datasetToken = new DatasetToken(
"ipfs://",
deployerAddress
);

// Then deploy the BondingCurve contract
DatasetBondingCurve bondingCurve = new DatasetBondingCurve(
address(datasetToken),
deployerAddress
);

// Set the bonding curve in the dataset token contract
datasetToken.setBondingCurve(address(bondingCurve));

vm.stopBroadcast();

return (bondingCurve, datasetToken);
}
}
37 changes: 29 additions & 8 deletions src/DeployDataset.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.18;
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {DatasetBondingCurve} from "./DatasetBondingCurve.sol";

contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {
// Token ID counter
Expand Down Expand Up @@ -33,6 +34,9 @@ contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {
// Mapping from tag to token IDs
mapping(string => uint256[]) private _tagToTokenIds;

// The Bonding Curve contract
DatasetBondingCurve public bondingCurve;

// Events
event DatasetTokenMinted(
address[] owners,
Expand All @@ -54,6 +58,8 @@ contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {

event PriceUpdated(uint256 indexed tokenId, uint256 newPrice);

event BondingCurveUpdated(address indexed newBondingCurve);

/**
* @dev Constructor to initialize the contract.
* @param uri The base URI for metadata.
Expand Down Expand Up @@ -82,6 +88,16 @@ contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {
require(totalPercentage == 10000, "Total percentage must equal 100%");
}

/**
* @dev Set the bonding curve contract address
* @param _bondingCurve The address of the bonding curve contract
*/
function setBondingCurve(address _bondingCurve) external onlyOwner {
require(_bondingCurve != address(0), "Invalid bonding curve address");
bondingCurve = DatasetBondingCurve(_bondingCurve);
emit BondingCurveUpdated(_bondingCurve);
}

/**
* @dev Mint a new dataset token with multiple owners
*/
Expand All @@ -91,14 +107,15 @@ contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {
string memory description,
string memory contentHash,
string memory ipfsHash,
uint256 price,
uint256 initialPrice,
string[] memory tags
) external onlyOwner {
require(bytes(contentHash).length > 0, "Content hash is required");
require(bytes(ipfsHash).length > 0, "IPFS hash is required");
require(price > 0, "Price must be greater than 0");
require(initialPrice > 0, "Initial price must be greater than 0");
require(owners.length > 0, "At least one owner required");
require(tags.length > 0, "At least one tag required");
require(address(bondingCurve) != address(0), "Bonding curve not set");

validateOwnershipShares(owners);

Expand All @@ -109,9 +126,12 @@ contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {
tokenMetadata[tokenId].description = description;
tokenMetadata[tokenId].contentHash = contentHash;
tokenMetadata[tokenId].ipfsHash = ipfsHash;
tokenMetadata[tokenId].price = price;
tokenMetadata[tokenId].price = initialPrice;
tokenMetadata[tokenId].tags = tags;

// Initialize bonding curve for this token
bondingCurve.setInitialPrice(tokenId, initialPrice);

// Store ownership information
for (uint256 i = 0; i < owners.length; i++) {
tokenMetadata[tokenId].owners.push(owners[i]);
Expand Down Expand Up @@ -140,7 +160,7 @@ contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {
name,
contentHash,
ipfsHash,
price,
initialPrice,
tags
);
}
Expand All @@ -150,10 +170,8 @@ contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {
*/
function purchaseDataset(uint256 tokenId) external payable nonReentrant {
require(isListed[tokenId], "Dataset is not listed for sale");
require(
msg.value == tokenMetadata[tokenId].price,
"Incorrect payment amount"
);
uint256 currentPrice = bondingCurve.getCurrentPrice(tokenId);
require(msg.value == currentPrice, "Incorrect payment amount");

DatasetMetadata storage metadata = tokenMetadata[tokenId];
uint256 totalAmount = msg.value;
Expand All @@ -175,6 +193,9 @@ contract DatasetToken is ERC1155, Ownable, ReentrancyGuard {
require(success, "Payment transfer failed");
}

// Record the purchase in the bonding curve
bondingCurve.recordPurchase(tokenId);

// Prepare arrays for event
address[] memory sellers = new address[](metadata.owners.length);
uint256[] memory amounts = new uint256[](metadata.owners.length);
Expand Down
Loading

0 comments on commit 4b27c75

Please sign in to comment.