Last active
July 29, 2024 16:59
-
-
Save elmariachi111/330b2206b762e0c91ff978718e0e35da to your computer and use it in GitHub Desktop.
Catalyst Funding Session
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.25; | |
import { Test } from "forge-std/Test.sol"; | |
import "forge-std/console.sol"; | |
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | |
import { Address } from "@openzeppelin/contracts/utils/Address.sol"; | |
import { LinearCurve } from "../../src/curves/LinearCurve.sol"; | |
import { CurveParametersOutOfRange, TradeType } from "../../src/curves/IIPSeedCurve.sol"; | |
import { CommonTest, Defaults } from "../helpers/CommonTest.sol"; | |
import { ParameterMismatch, PremintTooLarge } from "../../src/IPSeedTrust.sol"; | |
import { | |
IPSeed, | |
MarketData, | |
UnauthorizedAccess, | |
TokenAlreadyExists, | |
InvalidTokenId, | |
TradeSizeOutOfRange, | |
InsufficientPayment, | |
PriceDriftTooHigh, | |
BalanceTooLow, | |
TransferRestricted, | |
BadState, | |
MarketState, | |
MarketParameters, | |
BASIS_POINTS | |
} from "../../src/IPSeed.sol"; | |
import { | |
ReentrancyBurnAttacker, ReentrancyWithdrawalsAttacker | |
} from "../helpers/ReentrancyAttacker.sol"; | |
error OwnableUnauthorizedAccount(address account); | |
contract BugBashTest is CommonTest { | |
function testGoodFeeAndSlopeParameters() public { | |
address sourcer = makeAddr("kevin"); | |
uint16 lpFeeBps = 500; | |
uint128 netFundingGoal = 1 ether; | |
uint128 premint = 500 ether; | |
uint256 tokenId = ipSeed.computeTokenId(sourcer, Defaults.projectId); | |
uint256 grossFundingGoal = (netFundingGoal * (BASIS_POINTS + lpFeeBps) / BASIS_POINTS); | |
uint256 slope = curve.computeSlope(grossFundingGoal, 10_000 ether, premint); | |
MarketParameters memory params = MarketParameters({ | |
tokenId: tokenId, | |
projectId: Defaults.projectId, | |
sourcer: sourcer, | |
beneficiary: payable(beneficiaryMultisig), | |
priceCurve: curve, | |
curveParameters: bytes32(abi.encodePacked(uint128(slope), uint128(premint))), | |
fundingGoal: uint128(grossFundingGoal), | |
premint: premint, | |
deadline: uint64(block.timestamp + defaultDeadline) | |
}); | |
vm.startPrank(sourcer); | |
ipSeed.spawn(params); | |
assertEq(ipSeed.totalSupply(), premint); | |
assertEq(ipSeed.balanceOf(beneficiaryMultisig, tokenId), Defaults.defaultPremint); | |
uint256 fundingGoalInTokens = | |
curve.supplyAtCollateral(params.curveParameters, params.fundingGoal); | |
assertEq(fundingGoalInTokens, 10_000.00000019229513999 ether); | |
vm.startPrank(alice); | |
vm.deal(alice, grossFundingGoal); | |
ipSeed.mint{ value: netFundingGoal }(tokenId, 0 ether); | |
assertEq(ipSeed.totalSupply(), 9771.050693198689432819 ether); | |
ipSeed.mint{ value: alice.balance }(tokenId, 0 ether); | |
assertEq(ipSeed.totalSupply(), 10000.000000095020357146 ether); | |
assertEq(ipSeed.collateral(tokenId), grossFundingGoal); | |
} | |
function testContradictingFeeAndSlopeParameters() public { | |
address sourcer = makeAddr("kevin"); | |
uint16 lpFeeBps = 500; | |
uint128 netFundingGoal = 1 ether; | |
uint128 premint = 500 ether; | |
uint256 tokenId = ipSeed.computeTokenId(sourcer, Defaults.projectId); | |
uint256 grossFundingGoal = (netFundingGoal * (BASIS_POINTS + lpFeeBps) / BASIS_POINTS); | |
assertEq(grossFundingGoal, 1.05 ether); | |
uint256 slope = curve.computeSlope(grossFundingGoal, 10_000 ether, premint); | |
MarketParameters memory params = MarketParameters({ | |
tokenId: tokenId, | |
projectId: Defaults.projectId, | |
sourcer: sourcer, | |
beneficiary: payable(beneficiaryMultisig), | |
priceCurve: curve, | |
curveParameters: bytes32(abi.encodePacked(uint128(slope), uint128(premint))), | |
fundingGoal: uint128(netFundingGoal), | |
premint: premint, | |
deadline: uint64(block.timestamp + defaultDeadline) | |
}); | |
vm.startPrank(sourcer); | |
ipSeed.spawn(params); | |
assertEq(ipSeed.totalSupply(), premint); | |
assertEq(ipSeed.balanceOf(beneficiaryMultisig, tokenId), Defaults.defaultPremint); | |
uint256 fundingGoalInTokens = | |
curve.supplyAtCollateral(params.curveParameters, params.fundingGoal); | |
assertEq(fundingGoalInTokens, 9771.050693198726048531 ether); | |
vm.startPrank(alice); | |
vm.deal(alice, grossFundingGoal); | |
ipSeed.mint{ value: netFundingGoal }(tokenId, 0 ether); | |
assertEq(ipSeed.totalSupply(), 9771.050693198689432819 ether); | |
vm.expectRevert(); | |
ipSeed.mint{ value: alice.balance }(tokenId, 0 ether); | |
assertEq(ipSeed.balanceOf(alice, tokenId), 9271.050693198689432819 ether); | |
assertEq(ipSeed.collateral(tokenId), netFundingGoal); | |
} | |
function testParameterConformitySilentlyFails() public { | |
uint256 baseSepoliaFork = vm.createFork(vm.envString("BASE_SEPOLIA_RPC_URL"), 13075689); | |
vm.selectFork(baseSepoliaFork); | |
bytes memory spawnedBytesLog = | |
hex"000000000000000000000000000000000000000000000000069bc4b712c1e79e00000000000000000000000000000000000000000000000000000000000000409e512fa617e26a1075d3c9bd244f6112fa173ff079b11bb42ae671ba8c2e863700000000000000000000000000000000000000000000000000000000000001200000000000000000000000009f5dead94c9861ec9af7e0353cf0a100189c6815000000000000000000000000ddef5eb6768e9c37781b32de644d01d0f56054b70000000000000000000000008ca737e2cdae1ceb332bef7ba9ea711a3a2f8037000000000000000000000002b57613e6000000000000001b1ae4d6e2ef50000000000000000000000000000000000000000000000000000006f05b59d3b2000000000000000000000000000000000000000000000000001b1ae4d6e2ef5000000000000000000000000000000000000000000000000000000000000066c9f395000000000000000000000000000000000000000000000000000000000000003b636174616c7973742d646576656c6f706d656e743a2f2f65663036326531332d383033302d343963662d626430332d6264343435363464323561650000000000"; | |
(uint256 netFundingGoal, MarketParameters memory parameters) = | |
abi.decode(spawnedBytesLog, (uint256, MarketParameters)); | |
LinearCurve curve = LinearCurve(address(parameters.priceCurve)); | |
bytes32 curveParams = parameters.curveParameters; | |
(uint256 m, uint256 p) = curve.decodeParameters(curveParams); | |
//console.log(m, p, netFundingGoal); | |
console.log("Wrong Funding goal (gross,net)", parameters.fundingGoal, netFundingGoal); | |
uint256 finalSupply = curve.supplyAtCollateral(curveParams, parameters.fundingGoal); | |
//the slope above has been wrongly computed for a different funding goal | |
assertEq(finalSupply, 9771.050693198697770147 ether); | |
} | |
// replays a session on a spawned project that Kevin and me created on Thu Jul 25th 24 | |
// here the market parameters funding goal (0.5) and the curve parameters were not in sync | |
/* Replay | |
Spawn 13075577n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x9F5DeAD94C9861eC9af7E0353cf0a100189c6815 476190476190476190n | |
Buy 13075689n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28 100000000000000000n 4646139914567699710827n | |
Buy 13075707n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x8FF4B0B37EEA9Fa19070D5d78B082E2d75cd4c7D 100000000000000000n 6363527298048148723968n | |
Buy 13075739n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x8FF4B0B37EEA9Fa19070D5d78B082E2d75cd4c7D 50000000000000000n 7055622812758093780516n | |
Buy 13075764n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xCAed763276288a8Fe8E274e03f2cdFF0996eAEcb 200000000000000000n 9295290946757250585940n | |
Exit 13075793n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28 95000000000000000n 5149151032189550875113n | |
Buy 13075823n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28 50000000000000000n 5996348188517514331439n | |
Buy 13075878n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x72A960b0F2173af825Ea486a0851dfC375F2501a 31400000000000000n 6467214838836238695857n | |
Buy 13075924n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x8FeEAAae1DB031E5F980F5E63fDbb277731e500e 21718280000000000n 6772250686967915384948n | |
Buy 13076000n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xe127A39da6eA2D7b1979372aE973a20baB08A80A 44000000000000000n 7348717996790147871298n | |
Exit 13076025n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x8FeEAAae1DB031E5F980F5E63fDbb277731e500e 20632366000000000n 7043682148658471182207n | |
Buy 13076128n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xE231B4e55fE1D0Afb3e746e64E78eEffB5b599d1 24599999999999999n 7359200623987468596685n | |
Funding Goal Reached 13076163n 71608873548347908551806579327355212747937605875372970477748660726076739061303n | |
Buy 13076163n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28 1n 7359200623987468596685n | |
Claimed Collateral 13076467n 71608873548347908551806579327355212747937605875372970477748660726076739061303n | |
Succeeded 13085032n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x2854e2ACC2530165a76623B99c2F5b4915484BdC | |
*/ | |
function testBugbashSession1() public { | |
uint256 tokenId = 71608873548347908551806579327355212747937605875372970477748660726076739061303; | |
IPSeed ipSeed = IPSeed(0x1545dAbD87Bd880F4c03Bc4e5BC59d8C7711222d); | |
//spawn +1 | |
uint256 baseSepoliaFork = vm.createFork(vm.envString("BASE_SEPOLIA_RPC_URL"), 13075578); | |
vm.selectFork(baseSepoliaFork); | |
assertEq(ipSeed.collateral(tokenId), 0 ether); | |
assertEq(ipSeed.totalSupply(tokenId), 500 ether); | |
LinearCurve curve = LinearCurve(address(0x8cA737E2cdaE1Ceb332bEf7ba9eA711a3a2f8037)); | |
MarketParameters memory params = ipSeed.getMarketParams(tokenId); | |
uint256 expectedFinalSupply = | |
curve.supplyAtCollateral(params.curveParameters, params.fundingGoal); | |
assertEq(expectedFinalSupply, 9771.050693198697770147 ether); | |
//1 block past funding goal reached: | |
vm.rollFork(baseSepoliaFork, 13076164); | |
assertEq(ipSeed.totalSupply(tokenId), 7359.200623987468596685 ether); | |
assertEq(ipSeed.collateral(tokenId), 0.5 ether); | |
} | |
//what would've happened if we would've used the correct parameters? | |
function testBugbashSession1WithCorrectParameters() public { | |
uint256 baseSepoliaFork = vm.createFork(vm.envString("BASE_SEPOLIA_RPC_URL"), 13075576); | |
vm.selectFork(baseSepoliaFork); | |
uint256 tokenId = 71608873548347908551806579327355212747937605875372970477748660726076739061303; | |
address sourcer = 0x9F5DeAD94C9861eC9af7E0353cf0a100189c6815; | |
IPSeed ipSeed = IPSeed(0x1545dAbD87Bd880F4c03Bc4e5BC59d8C7711222d); | |
LinearCurve curve = LinearCurve(address(0x8cA737E2cdaE1Ceb332bEf7ba9eA711a3a2f8037)); | |
uint256 fundingGoal = 0.55 ether; | |
uint256 premint = 500 ether; | |
uint256 slope = curve.computeSlope(fundingGoal, 10_000 ether, premint); | |
MarketParameters memory betterParameters = MarketParameters({ | |
tokenId: tokenId, | |
projectId: "catalyst-development://ef062e13-8030-49cf-bd03-bd44564d25ae", | |
sourcer: sourcer, | |
beneficiary: payable(0xdDEf5eb6768E9C37781b32De644d01D0F56054B7), | |
priceCurve: curve, | |
curveParameters: bytes32(abi.encodePacked(uint128(slope), uint128(premint))), | |
fundingGoal: uint128(0.55 ether), //<-- this | |
premint: uint128(premint), | |
deadline: uint64(block.timestamp + defaultDeadline) | |
}); | |
vm.startPrank(sourcer); | |
ipSeed.spawn(betterParameters); | |
uint256 expectedFinalSupply = | |
curve.supplyAtCollateral(betterParameters.curveParameters, betterParameters.fundingGoal); | |
assertEq(expectedFinalSupply, 10_000.000000377909357083 ether); | |
// lets replay the trades | |
vm.startPrank(0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28); | |
ipSeed.mint{ value: 0.1 ether }(tokenId, 0); | |
vm.startPrank(0x8FF4B0B37EEA9Fa19070D5d78B082E2d75cd4c7D); | |
ipSeed.mint{ value: 0.1 ether }(tokenId, 0); | |
ipSeed.mint{ value: 0.05 ether }(tokenId, 0); | |
vm.startPrank(0xCAed763276288a8Fe8E274e03f2cdFF0996eAEcb); | |
ipSeed.mint{ value: 0.2 ether }(tokenId, 0); | |
vm.startPrank(0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28); | |
ipSeed.exit(tokenId); | |
vm.startPrank(0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28); | |
ipSeed.mint{ value: 0.05 ether }(tokenId, 0); | |
vm.startPrank(0x72A960b0F2173af825Ea486a0851dfC375F2501a); | |
ipSeed.mint{ value: 0.0314 ether }(tokenId, 0); | |
vm.startPrank(0x8FeEAAae1DB031E5F980F5E63fDbb277731e500e); | |
ipSeed.mint{ value: 0.02171828 ether }(tokenId, 0); | |
vm.startPrank(0xe127A39da6eA2D7b1979372aE973a20baB08A80A); | |
ipSeed.mint{ value: 0.044 ether }(tokenId, 0); | |
vm.startPrank(0x8FeEAAae1DB031E5F980F5E63fDbb277731e500e); | |
ipSeed.exit(tokenId); | |
vm.startPrank(0xE231B4e55fE1D0Afb3e746e64E78eEffB5b599d1); | |
ipSeed.mint{ value: 0.024599999999999999 ether }(tokenId, 0); | |
//--- from here it's different; we now are far from being done: | |
assertEq(ipSeed.collateral(tokenId), 0.5 ether - 1); | |
//lets close it anyway (gross funding goal is 0.55) | |
ipSeed.mint{ value: 0.05 ether + 1 }(tokenId, 0); | |
MarketData memory data = ipSeed.getMarketData(tokenId); | |
assertEq(uint8(data.state), uint8(2)); | |
assertEq(ipSeed.collateral(tokenId), 0.55 ether); | |
//--- that wouldve been the final supply when only the parameters would've been chosen correctly | |
assertEq(ipSeed.totalSupply(tokenId), 7787.97670264404973771 ether); | |
// lets compute the beneficiary's balance share of "IPTs" | |
uint256 beneficiaryShare = ( | |
ipSeed.balanceOf(0xdDEf5eb6768E9C37781b32De644d01D0F56054B7, tokenId) * 1_000_000 ether | |
) / ipSeed.totalSupply(tokenId); | |
//that's 6.4% of the total IPT supply. | |
//with the "bad" parameter set above, we computed 67942.161866091749 / 6.79% instead | |
assertEq(beneficiaryShare, 64201.527442968334535956 ether); | |
} | |
//what would've happened if we would've used the correct parameters and just a happy path? | |
function testBugbashSessionWithHappyPathAndCorrectParameters() public { | |
uint256 baseSepoliaFork = vm.createFork(vm.envString("BASE_SEPOLIA_RPC_URL"), 13075576); | |
vm.selectFork(baseSepoliaFork); | |
uint256 tokenId = 71608873548347908551806579327355212747937605875372970477748660726076739061303; | |
address sourcer = 0x9F5DeAD94C9861eC9af7E0353cf0a100189c6815; | |
IPSeed ipSeed = IPSeed(0x1545dAbD87Bd880F4c03Bc4e5BC59d8C7711222d); | |
LinearCurve curve = LinearCurve(address(0x8cA737E2cdaE1Ceb332bEf7ba9eA711a3a2f8037)); | |
uint256 fundingGoal = 0.55 ether; | |
uint256 premint = 500 ether; | |
uint256 slope = curve.computeSlope(fundingGoal, 10_000 ether, premint); | |
MarketParameters memory betterParameters = MarketParameters({ | |
tokenId: tokenId, | |
projectId: "catalyst-development://ef062e13-8030-49cf-bd03-bd44564d25ae", | |
sourcer: sourcer, | |
beneficiary: payable(0xdDEf5eb6768E9C37781b32De644d01D0F56054B7), | |
priceCurve: curve, | |
curveParameters: bytes32(abi.encodePacked(uint128(slope), uint128(premint))), | |
fundingGoal: uint128(0.55 ether), //<-- this | |
premint: uint128(premint), | |
deadline: uint64(block.timestamp + defaultDeadline) | |
}); | |
vm.startPrank(sourcer); | |
ipSeed.spawn(betterParameters); | |
uint256 expectedFinalSupply = | |
curve.supplyAtCollateral(betterParameters.curveParameters, betterParameters.fundingGoal); | |
assertEq(expectedFinalSupply, 10_000.000000377909357083 ether); | |
// lets just buy | |
vm.startPrank(0x8FF4B0B37EEA9Fa19070D5d78B082E2d75cd4c7D); | |
ipSeed.mint{ value: 0.1 ether }(tokenId, 0); | |
//ipSeed.mint{ value: 0.05 ether }(tokenId, 0); | |
vm.startPrank(0xCAed763276288a8Fe8E274e03f2cdFF0996eAEcb); | |
ipSeed.mint{ value: 0.2 ether }(tokenId, 0); | |
vm.startPrank(0x8FeEAAae1DB031E5F980F5E63fDbb277731e500e); | |
ipSeed.mint{ value: 0.25 ether }(tokenId, 0); | |
MarketData memory data = ipSeed.getMarketData(tokenId); | |
assertEq(uint8(data.state), uint8(2)); | |
assertEq(ipSeed.collateral(tokenId), 0.55 ether); | |
//--- final supply is just great when no one exits | |
assertEq(ipSeed.totalSupply(tokenId), 10000.0 ether); | |
// // lets compute the beneficiary's balance share of "IPTs" | |
uint256 beneficiaryShare = ( | |
ipSeed.balanceOf(0xdDEf5eb6768E9C37781b32De644d01D0F56054B7, tokenId) * 1_000_000 ether | |
) / ipSeed.totalSupply(tokenId); | |
//that's **precisely** 5% of the total IPT supply. | |
assertEq(beneficiaryShare, 50000 ether); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A replay of the events that happened on this token id (see https://sepolia.basescan.org/address/0x1545dabd87bd880f4c03bc4e5bc59d8c7711222d#events) | |
Spawn 13075577n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x9F5DeAD94C9861eC9af7E0353cf0a100189c6815 476190476190476190n | |
Buy 13075689n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28 100000000000000000n 4146139914567699710827n 4646139914567699710827n | |
Buy 13075707n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x8FF4B0B37EEA9Fa19070D5d78B082E2d75cd4c7D 100000000000000000n 1717387383480449013141n 6363527298048148723968n | |
Buy 13075739n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x8FF4B0B37EEA9Fa19070D5d78B082E2d75cd4c7D 50000000000000000n 692095514709945056548n 7055622812758093780516n | |
Buy 13075764n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xCAed763276288a8Fe8E274e03f2cdFF0996eAEcb 200000000000000000n 2239668133999156805424n 9295290946757250585940n | |
Exit 13075793n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28 95000000000000000n 4146139914567699710827n 5149151032189550875113n | |
Buy 13075823n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28 50000000000000000n 847197156327963456326n 5996348188517514331439n | |
Buy 13075878n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x72A960b0F2173af825Ea486a0851dfC375F2501a 31400000000000000n 470866650318724364418n 6467214838836238695857n | |
Buy 13075924n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x8FeEAAae1DB031E5F980F5E63fDbb277731e500e 21718280000000000n 305035848131676689091n 6772250686967915384948n | |
Buy 13076000n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xe127A39da6eA2D7b1979372aE973a20baB08A80A 44000000000000000n 576467309822232486350n 7348717996790147871298n | |
Exit 13076025n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x8FeEAAae1DB031E5F980F5E63fDbb277731e500e 20632366000000000n 305035848131676689091n 7043682148658471182207n | |
Buy 13076128n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xE231B4e55fE1D0Afb3e746e64E78eEffB5b599d1 24599999999999999n 315518475328997414478n 7359200623987468596685n | |
Funding Goal Reached 13076163n 71608873548347908551806579327355212747937605875372970477748660726076739061303n | |
Buy 13076163n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0xB07055Db7Bf811C5b914d60a4a8a5059c8F0Fd28 1n 0n 7359200623987468596685n | |
Claimed Collateral 13076467n 71608873548347908551806579327355212747937605875372970477748660726076739061303n | |
Succeeded 13085032n 71608873548347908551806579327355212747937605875372970477748660726076739061303n 0x2854e2ACC2530165a76623B99c2F5b4915484BdC |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment