diff --git a/script/LMSR.s.sol b/script/LMSR.s.sol index cdc1fe9..1225ee8 100644 --- a/script/LMSR.s.sol +++ b/script/LMSR.s.sol @@ -1,19 +1,66 @@ +// script/DeployLMSR.s.sol // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.8.19; import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; +import {LMSRMarket} from "../src/LMSR.sol"; +import {MockERC20} from "../test/ERC20.m.sol"; -contract CounterScript is Script { - Counter public counter; +contract DeployLMSR is Script { + LMSRMarket public market; + MockERC20 public usdc; - function setUp() public {} + // Configuration + uint256 constant LIQUIDITY = 1000e18; // Initial liquidity parameter + uint256 constant NUM_OUTCOMES = 2; // Number of outcomes in the market + + function setUp() public { + // Any setup configuration can go here + } function run() public { - vm.startBroadcast(); + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + // For local testing, deploy a mock USDC + // For mainnet, you'd use the real USDC address + if (block.chainid == 31337) { + // Local anvil chain + usdc = new MockERC20("USDC", "USDC", 6); + console.log("Deployed Mock USDC at:", address(usdc)); + + // Mint some initial USDC for testing + usdc.mint(msg.sender, 1000000e6); // 1M USDC + } - counter = new Counter(); + // Deploy LMSR Market + market = new LMSRMarket( + block.chainid == 31337 ? address(usdc) : getUSDCAddress(), + LIQUIDITY, + NUM_OUTCOMES + ); + + console.log("Deployed LMSR Market at:", address(market)); + console.log("USDC address:", address(market.USDC())); + console.log("Number of outcomes:", market.numOutcomes()); + console.log("Liquidity parameter:", market.liquidity()); vm.stopBroadcast(); } + + function getUSDCAddress() internal view returns (address) { + // Return appropriate USDC address for different networks + if (block.chainid == 1) { + // Ethereum Mainnet USDC + return 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + } else if (block.chainid == 137) { + // Polygon Mainnet USDC + return 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; + } else if (block.chainid == 42161) { + // Arbitrum USDC + return 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; + } else { + revert("Unsupported network for USDC"); + } + } } diff --git a/test/ERC20.m.sol b/test/ERC20.m.sol new file mode 100644 index 0000000..68ff438 --- /dev/null +++ b/test/ERC20.m.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {ERC20} from "@openzeppelin/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + uint8 private _decimals; + + mapping(address => bool) public blocked; + bool public transferShouldRevert; + bool public mintShouldRevert; + + constructor( + string memory name, + string memory symbol, + uint8 decimals_ + ) ERC20(name, symbol) { + _decimals = decimals_; + } + + function mint(address to, uint256 amount) public { + require(!mintShouldRevert, "MINT_REVERTED"); + _mint(to, amount); + } + + function burn(address from, uint256 amount) public { + _burn(from, amount); + } + + function decimals() public view override returns (uint8) { + return _decimals; + } + + function blockAddress(address user) external { + blocked[user] = true; + } + + function unblockAddress(address user) external { + blocked[user] = false; + } + + function setTransferShouldRevert(bool shouldRevert) external { + transferShouldRevert = shouldRevert; + } + + function setMintShouldRevert(bool shouldRevert) external { + mintShouldRevert = shouldRevert; + } +} diff --git a/test/LMSR.t.sol b/test/LMSR.t.sol index 54b724f..205a596 100644 --- a/test/LMSR.t.sol +++ b/test/LMSR.t.sol @@ -1,24 +1,101 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +// test/LMSRMarket.t.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; +import "forge-std/Test.sol"; +import "../src/LMSR.sol"; +import {MockERC20} from "./ERC20.m.sol"; -contract CounterTest is Test { - Counter public counter; +contract LMSRMarketTest is Test { + LMSRMarket public market; + MockERC20 public usdc; + + address alice = address(0x1); + address bob = address(0x2); + + uint256 constant INITIAL_BALANCE = 1000000e6; // 1M USDC + uint256 constant LIQUIDITY = 1000e18; + uint256 constant NUM_OUTCOMES = 2; function setUp() public { - counter = new Counter(); - counter.setNumber(0); + // Deploy mock USDC + usdc = new MockERC20("USDC", "USDC", 6); + + // Deploy market + market = new LMSRMarket(address(usdc), LIQUIDITY, NUM_OUTCOMES); + + // Setup test accounts + vm.startPrank(alice); + usdc.mint(alice, INITIAL_BALANCE); + usdc.approve(address(market), type(uint256).max); + vm.stopPrank(); + + vm.startPrank(bob); + usdc.mint(bob, INITIAL_BALANCE); + usdc.approve(address(market), type(uint256).max); + vm.stopPrank(); } - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); + function test_InitialState() public { + assertEq(market.numOutcomes(), NUM_OUTCOMES); + assertEq(market.liquidity(), LIQUIDITY); + assertEq(address(market.USDC()), address(usdc)); + assertEq(market.resolved(), false); + } + + function test_BuyShares() public { + vm.startPrank(alice); + + uint256 amount = 100e6; // 100 USDC worth + uint256 outcome = 0; + + uint256 cost = market.calculateCost(outcome, amount); + uint256 balanceBefore = usdc.balanceOf(alice); + + market.buyShares(outcome, amount); + + assertEq(usdc.balanceOf(alice), balanceBefore - cost); + assertEq(market.getUserPosition(alice, outcome), amount); + assertEq(market.quantities(outcome), amount); } - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); + function test_RevertWhenBuyingAfterResolution() public { + // Resolve market first + market.resolveMarket(0); + + vm.startPrank(alice); + vm.expectRevert(MarketAlreadyResolved.selector); + market.buyShares(0, 100e6); + } + + function test_ClaimWinnings() public { + uint256 amount = 100e6; + uint256 outcome = 0; + + // Buy shares + vm.startPrank(alice); + market.buyShares(outcome, amount); + + // Resolve market + vm.stopPrank(); + market.resolveMarket(outcome); + + // Claim winnings + vm.startPrank(alice); + uint256 balanceBefore = usdc.balanceOf(alice); + market.claimWinnings(amount); + + uint256 expectedPayout = (amount * market.SCALE()) / + (10 ** (18 - market.USDC_DECIMALS())); + assertEq(usdc.balanceOf(alice), balanceBefore + expectedPayout); + assertEq(market.getUserPosition(alice, outcome), 0); + } + + function test_RevertWhenClaimingBeforeResolution() public { + vm.startPrank(alice); + market.buyShares(0, 100e6); + + vm.expectRevert(MarketNotResolved.selector); + market.claimWinnings(100e6); } }