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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🚀 EVM Launchpad - Gas Efficient Token Launchpad Smart Contract
# 🚀 EVM Launchpad Contract - Gas Efficient Token Launchpad Smart Contract

**EVM-compatible Launchpad System** inspired by [pump.fun](https://pump.fun) featuring **zero-liquidity launches** and **automated price discovery** through an exponential bonding curve. Designed for secure, transparent, and efficient token distribution.
**EVM-compatible Launchpad System** inspired by **pump.fun** featuring **zero-liquidity launches** and **automated price discovery** through an exponential bonding curve. Designed for secure, transparent, and efficient token distribution.

![Solidity Version](https://img.shields.io/badge/Solidity-^0.8.28-informational)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Expand Down
1 change: 0 additions & 1 deletion src/LaunchpadFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ contract LaunchpadFactory is Ownable(msg.sender), Pausable, ILaunchpadFactory {
*/
function createLaunchpad(string memory _name, string memory _symbol)
external
payable
whenNotPaused
returns (address launchpad)
{
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/launchpad/ILaunchpadFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
pragma solidity ^0.8.28;

interface ILaunchpadFactory {
function createLaunchpad(string memory _name, string memory _symbol) external payable returns (address launchpad);
function createLaunchpad(string memory _name, string memory _symbol) external returns (address launchpad);
}
37 changes: 26 additions & 11 deletions test/BondingCurve.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ contract BondingCurveTest is Test {

function setUp() public {}

function testPurchaseBaseCases() public pure {
function test_PurchaseBaseCases() public pure {
// Test purchase with 0 ETH supply and 1 ETH input
// Should return some tokens
uint256 tokensOut = BondingCurve.calculatePurchaseReturn(0, 1 ether);
Expand All @@ -24,7 +24,7 @@ contract BondingCurveTest is Test {
assert(zeroOut == 0);
}

function testPurchasePriceIncrease() public {
function test_PurchasePriceIncrease() public {
// Test that token price increases with ETH supply
uint256 ethIn = 1 ether;
uint256 supply1 = 10 ether;
Expand All @@ -41,13 +41,13 @@ contract BondingCurveTest is Test {
assertTrue(priceDiff > 0 && priceDiff < 50, "Price increase should be reasonable");
}

function testSellBaseCases() public {
function test_SellBaseCases() public {
// Test sell with 0 token input
uint256 ethOut = BondingCurve.calculateSellReturn(1 ether, 0);
assertEq(ethOut, 0, "Should return 0 ETH for 0 tokens");
}

function testSellPriceDecrease() public {
function test_SellPriceDecrease() public {
// Setup initial state
uint256 initialEthSupply = 50 ether;

Expand All @@ -60,7 +60,7 @@ contract BondingCurveTest is Test {
assertTrue(ethOut2 < ethOut1, "Price should decrease after selling");
}

function testCurveSymmetry() public {
function test_CurveSymmetry() public {
uint256 ethSupply = 10 ether;
uint256 ethIn = 1 ether;

Expand All @@ -75,7 +75,7 @@ contract BondingCurveTest is Test {
assertTrue(ethOut > ethIn * 99 / 100, "Slippage should be reasonable");
}

function testExtremeValues() public {
function test_ExtremeValues() public {
// Test with very small amounts
uint256 tinyEth = 1 wei;
uint256 tokensForTiny = BondingCurve.calculatePurchaseReturn(0, tinyEth);
Expand All @@ -96,16 +96,31 @@ contract BondingCurveTest is Test {
assertTrue(tokensForSmall < tokensForLarge, "Larger ETH input should yield more tokens");
}

function testSellRevertOnInvalidAmount() public {
function test_CannotSellOnInvalidAmount() public {
uint256 ethSupply = 10 ether;

// Try to sell more tokens than possible
uint256 maxTokens = BondingCurve.calculatePurchaseReturn(0, ethSupply);
vm.expectRevert();
BondingCurve.calculateSellReturn(ethSupply, maxTokens * 2);
try this.callCalculateSellReturn(ethSupply, maxTokens * 2) {
fail();
} catch Error(string memory) {
fail();
} catch (bytes memory returnData) {
bytes4 expectedSelector = BondingCurve.FormulaInvalidTokenAmount.selector;
bytes4 actualSelector;
assembly {
actualSelector := mload(add(returnData, 0x20))
}
assertEq(actualSelector, expectedSelector, "Wrong error selector");
}
}

// Helper function to call the library function externally
function callCalculateSellReturn(uint256 ethSupply, uint256 tokenAmount) external pure returns (uint256) {
return BondingCurve.calculateSellReturn(ethSupply, tokenAmount);
}

function testPriceProgression() public {
function test_PriceProgression() public {
uint256 ethSupply = 0;
uint256 ethIncrement = 1 ether;
uint256 lastTokenAmount = type(uint256).max;
Expand All @@ -121,7 +136,7 @@ contract BondingCurveTest is Test {
}
}

function testCurveParameters() public {
function test_CurveParameters() public {
// Test that the curve parameters produce expected behavior
uint256 initialPurchase = BondingCurve.calculatePurchaseReturn(0, 1 ether);

Expand Down
8 changes: 4 additions & 4 deletions test/Launchpad.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ contract LaunchpadTest is Test, ArtifactStorage {
require(address(launchpadFactory) != address(0), "LaunchpadFactory deployment failed");
}

function testProxyCreation() public {
function test_ProxyCreation() public {
string memory tokenName = "Test Token";
string memory tokenSymbol = "TTKN";

address launchpad = launchpadFactory.createLaunchpad{value: 1 ether}(tokenName, tokenSymbol);
address launchpad = launchpadFactory.createLaunchpad(tokenName, tokenSymbol);

assertTrue(launchpad != address(0), "Launchpad creation failed");

Expand Down Expand Up @@ -85,11 +85,11 @@ contract LaunchpadTest is Test, ArtifactStorage {
assertTrue(userEthBalance >= ethReceived, "User ETH balance mismatch after token sale");
}

function testLiquidityMigration() public {
function test_LiquidityMigration() public {
string memory tokenName = "Test Token";
string memory tokenSymbol = "TTKN";

address launchpad = launchpadFactory.createLaunchpad{value: 1 ether}(tokenName, tokenSymbol);
address launchpad = launchpadFactory.createLaunchpad(tokenName, tokenSymbol);

assertTrue(launchpad != address(0), "Launchpad creation failed");

Expand Down
2 changes: 1 addition & 1 deletion test/LaunchpadFuzz.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ contract LaunchpadFuzzTest is Test, ArtifactStorage {
require(address(launchpadFactory) != address(0), "LaunchpadFactory deployment failed");

// Create a proxy instance for testing
address launchpad = launchpadFactory.createLaunchpad{value: 1 ether}("Test Token", "TTKN");
address launchpad = launchpadFactory.createLaunchpad("Test Token", "TTKN");
proxy = Launchpad(payable(launchpad));
}

Expand Down
27 changes: 15 additions & 12 deletions test/Proxy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ contract ProxyTest is Test, ArtifactStorage {
vm.deal(bob, INITIAL_ETH_BALANCE);
}

function testInitialState() public view {
function test_InitialState() public view {
assertEq(proxy.ethSupply(), 0, "Initial ETH supply should be 0");
assertEq(proxy.tokenSupply(), INITIAL_TOKEN_SUPPLY, "Initial token supply mismatch");
assertEq(proxy.tokensLiquidity(), INITIAL_LIQUIDITY_TOKENS, "Initial liquidity tokens mismatch");
assertFalse(proxy.isMigrated(), "Should not be migrated initially");
}

function testSingleBuyTokens() public {
function test_SingleBuyTokens() public {
uint256 buyAmount = 1 ether;
uint256 initialTokenBalance = proxy.token().balanceOf(alice);
uint256 initialEthSupply = proxy.ethSupply();
Expand All @@ -90,7 +90,7 @@ contract ProxyTest is Test, ArtifactStorage {
assertEq(IERC20(address(proxy.weth())).balanceOf(address(proxy)), buyAmount, "WETH balance incorrect");
}

function testMultipleBuysIncreasePrices() public {
function test_MultipleBuysIncreasePrices() public {
uint256 buyAmount = 1 ether;

// First buy
Expand All @@ -108,7 +108,7 @@ contract ProxyTest is Test, ArtifactStorage {
assertTrue(secondBuyTokens < firstBuyTokens, "Price should increase after buys");
}

function testSellTokens() public {
function test_SellTokens() public {
// First buy tokens
uint256 buyAmount = 1 ether;
vm.startPrank(alice);
Expand Down Expand Up @@ -136,7 +136,7 @@ contract ProxyTest is Test, ArtifactStorage {
assertEq(proxy.token().balanceOf(alice), 0, "Should have no tokens left");
}

function testPriceDecreasesAfterSell() public {
function test_PriceDecreasesAfterSell() public {
// Initial buy
uint256 buyAmount = 2 ether;
vm.startPrank(alice);
Expand All @@ -156,7 +156,7 @@ contract ProxyTest is Test, ArtifactStorage {
assertTrue(priceAfterSell < priceBeforeSell, "Price should decrease after sell");
}

function testThresholdMigration() public {
function test_ThresholdMigration() public {
vm.startPrank(alice);
uint256 thresholdAmount = proxy.THRESHOLD();

Expand All @@ -170,18 +170,20 @@ contract ProxyTest is Test, ArtifactStorage {
vm.stopPrank();
}

function testFailBuyAfterMigration() public {
// First reach threshold
function test_CannotBuyAfterMigration() public {
// First reach threshold and verify migration
vm.startPrank(alice);
proxy.buyTokens{value: proxy.THRESHOLD()}(0);
uint256 exceedingAmount = proxy.THRESHOLD() + 1 ether;
proxy.buyTokens{value: exceedingAmount}(0);
assertTrue(proxy.isMigrated(), "Should be migrated after threshold");

// Try to buy after migration
// Should revert when trying to buy after migration
vm.expectRevert(abi.encodeWithSignature("LaunchpadInvalidState()"));
proxy.buyTokens{value: 1 ether}(0);
vm.stopPrank();
}

function testMinimumOutputAmount() public {
function test_MinimumOutputAmount() public {
uint256 buyAmount = 1 ether;
uint256 expectedTokens = proxy.getTokensOutAtCurrentSupply(buyAmount);

Expand All @@ -192,7 +194,7 @@ contract ProxyTest is Test, ArtifactStorage {
vm.stopPrank();
}

function testFailSellMinimumOutputTooHigh() public {
function test_CannotBuyWithHighMinimumOutput() public {
uint256 buyAmount = 1 ether;
uint256 expectedTokens = proxy.getTokensOutAtCurrentSupply(buyAmount);

Expand All @@ -203,6 +205,7 @@ contract ProxyTest is Test, ArtifactStorage {

// Try to buy with minimum output higher than possible
vm.startPrank(alice);
vm.expectRevert(abi.encodeWithSignature("LaunchpadInsufficientOutputAmount()"));
proxy.buyTokens{value: buyAmount}(expectedTokens + 1);
vm.stopPrank();

Expand Down
6 changes: 3 additions & 3 deletions test/UniswapV2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ contract UniswapV2 is Test, ArtifactStorage {

function setUp() public {}

function testWethDeployment() public {
function test_WethDeployment() public {
weth = _deployBytecode(ArtifactStorage.wethBytecode);

// Assert deployment succeeded
Expand All @@ -53,7 +53,7 @@ contract UniswapV2 is Test, ArtifactStorage {
assertEq(newBalance, 0.5 ether, "Balance should be 0.5 Ether");
}

function testFactoryDeployment() public {
function test_FactoryDeployment() public {
address feeToSetter = vm.addr(1);
bytes memory constructorArgs = abi.encode(feeToSetter);
bytes memory bytecodeWithArgs = abi.encodePacked(ArtifactStorage.uniswapV2Factory, constructorArgs);
Expand Down Expand Up @@ -82,7 +82,7 @@ contract UniswapV2 is Test, ArtifactStorage {
assertEq(pair, retrievedPair, "Retrieved pair address does not match created pair");
}

function testRouterDeployment() public {
function test_RouterDeployment() public {
// Deploy WETH
weth = _deployBytecode(ArtifactStorage.wethBytecode);
assertTrue(weth != address(0), "WETH deployment failed");
Expand Down