Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
9.4 kB · 283 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284// SPDX-License-Identifier: MITpragma solidity ^0.8.24;
import "forge-std/Test.sol";import "../src/OrgStar.sol";
contract Receiver { bool public reject; function set(bool r) external { reject = r; } function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) { return reject ? bytes4(0xdeadbeef) : this.onERC721Received.selector; }}
contract OrgStarTest is Test { OrgStar star; address alice = address(0xA11CE); address bob = address(0xB0B); uint256 constant PRICE = 0.01 ether;
function setUp() public { star = new OrgStar(PRICE); vm.deal(alice, 10 ether); vm.deal(bob, 10 ether); }
// ---- minting ----
function test_MintAtPrice() public { vm.prank(alice); star.mint{value: PRICE}(); assertEq(star.totalSupply(), 1); assertEq(star.ownerOf(1), alice); assertEq(star.balanceOf(alice), 1); assertEq(address(star).balance, PRICE); }
function test_MintRejectsWrongPayment() public { vm.prank(alice); vm.expectRevert(OrgStar.WrongPayment.selector); star.mint{value: PRICE - 1}();
vm.prank(alice); vm.expectRevert(OrgStar.WrongPayment.selector); star.mint{value: PRICE + 1}(); }
function test_OpenEdition_ManyStarsOneWallet() public { for (uint256 i = 0; i < 7; i++) { vm.prank(alice); star.mint{value: PRICE}(); } assertEq(star.totalSupply(), 7); assertEq(star.balanceOf(alice), 7); assertEq(star.ownerOf(7), alice); }
function test_MintToIsOwnerOnly() public { vm.prank(alice); vm.expectRevert(OrgStar.NotOwner.selector); star.mintTo(alice);
star.mintTo(bob); // owner is this test contract assertEq(star.ownerOf(1), bob); }
// ---- the star keeps its face after it trades ----
function test_MinterFixedAtBirth() public { vm.prank(alice); star.mint{value: PRICE}(); assertEq(star.minterOf(1), alice);
vm.prank(alice); star.transferFrom(alice, bob, 1);
assertEq(star.ownerOf(1), bob); assertEq(star.minterOf(1), alice, "the star keeps the face it was born with"); }
function test_TokenURI_UnchangedByTransfer() public { vm.prank(alice); star.mint{value: PRICE}(); string memory before = star.tokenURI(1); vm.prank(alice); star.transferFrom(alice, bob, 1); assertEq(keccak256(bytes(star.tokenURI(1))), keccak256(bytes(before))); }
// ---- the page/chain contract ---- // // These are the values stars.html computes in JS for the same addresses // (FNV-1a over the lowercase "0x…" string). If this test ever fails, the // sky on whistlegraph.org and the art on-chain have drifted apart.
function test_StarHashMatchesThePage() public view { assertEq(star.starHash(0x238c9c645c6EE83d4323A2449C706940321a0cBf), 1692378563); assertEq(star.starHash(0x5e6758C96A4cB5E2A1FE2E2772020dc8ad753b08), 3076528610); assertEq(star.starHash(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045), 3251228244); }
function test_HueAndPointsDerivedFromMinter() public view { // whistlegraph.eth -> hue 203, 7 points (page agrees) uint32 h = star.starHash(0x238c9c645c6EE83d4323A2449C706940321a0cBf); assertEq(h % 360, 203); assertEq(4 + (h % 4), 7); }
// ---- on-chain art ----
function test_TokenURIIsSelfContained() public { vm.prank(alice); star.mint{value: PRICE}(); string memory uri = star.tokenURI(1); assertTrue(_startsWith(uri, "data:application/json;base64,"), "must be a data URI"); // Nothing may point off-chain: no http, no ipfs. assertFalse(_contains(uri, "http"), "no external host"); assertFalse(_contains(uri, "ipfs"), "no ipfs dependency"); }
function test_TokenURIRevertsForNoToken() public { vm.expectRevert(OrgStar.NoToken.selector); star.tokenURI(1); }
// Each calendar year is an epoch, so the boundary is the part that matters: // the last second of a year and the first second of the next must differ. function test_ConstellationYear() public { uint256[5] memory when = [ uint256(1767225600), // 2026-01-01T00:00:00Z 1788296150, // 2026-09-01 1798761599, // 2026-12-31T23:59:59Z — last second 1798761600, // 2027-01-01T00:00:00Z — rollover 4116700800 // 2100-06-15 — a non-leap century ]; uint16[5] memory expected = [uint16(2026), 2026, 2026, 2027, 2100];
for (uint256 i = 0; i < when.length; i++) { vm.warp(when[i]); vm.prank(alice); star.mint{value: PRICE}(); assertEq(star.yearOf(i + 1), expected[i]); } }
// ---- transfers and approvals ----
function test_ApproveThenTransfer() public { vm.prank(alice); star.mint{value: PRICE}(); vm.prank(alice); star.approve(bob, 1); vm.prank(bob); star.transferFrom(alice, bob, 1); assertEq(star.ownerOf(1), bob); assertEq(star.getApproved(1), address(0), "approval clears on transfer"); }
function test_ApprovalForAll() public { vm.prank(alice); star.mint{value: PRICE}(); vm.prank(alice); star.setApprovalForAll(bob, true); vm.prank(bob); star.transferFrom(alice, bob, 1); assertEq(star.ownerOf(1), bob); }
function test_TransferRejectsStranger() public { vm.prank(alice); star.mint{value: PRICE}(); vm.prank(bob); vm.expectRevert(OrgStar.NotAuthorized.selector); star.transferFrom(alice, bob, 1); }
function test_TransferRejectsWrongFrom() public { vm.prank(alice); star.mint{value: PRICE}(); vm.prank(alice); vm.expectRevert(OrgStar.WrongFrom.selector); star.transferFrom(bob, alice, 1); }
function test_TransferRejectsZero() public { vm.prank(alice); star.mint{value: PRICE}(); vm.prank(alice); vm.expectRevert(OrgStar.ZeroAddress.selector); star.transferFrom(alice, address(0), 1); }
function test_SafeTransferChecksReceiver() public { Receiver r = new Receiver(); vm.prank(alice); star.mint{value: PRICE}();
r.set(true); vm.prank(alice); vm.expectRevert(OrgStar.UnsafeReceiver.selector); star.safeTransferFrom(alice, address(r), 1);
r.set(false); vm.prank(alice); star.safeTransferFrom(alice, address(r), 1); assertEq(star.ownerOf(1), address(r)); }
// ---- money ----
function test_Withdraw() public { vm.prank(alice); star.mint{value: PRICE}(); address payable dest = payable(address(0xDEAD)); star.withdraw(dest); assertEq(dest.balance, PRICE); assertEq(address(star).balance, 0); }
function test_WithdrawIsOwnerOnly() public { vm.prank(alice); vm.expectRevert(OrgStar.NotOwner.selector); star.withdraw(payable(alice)); }
function test_Royalty() public view { (address recv, uint256 amt) = star.royaltyInfo(1, 1 ether); assertEq(recv, address(this)); assertEq(amt, 0.1 ether); // 10% }
function test_RoyaltyCapped() public { vm.expectRevert(OrgStar.RoyaltyTooHigh.selector); star.setRoyalty(alice, 2001); star.setRoyalty(alice, 2000); assertEq(star.royaltyBps(), 2000); }
function test_SetPrice() public { star.setPrice(0.02 ether); assertEq(star.price(), 0.02 ether); vm.prank(alice); star.mint{value: 0.02 ether}(); assertEq(star.totalSupply(), 1); }
function test_OwnerHandoff() public { star.setOwner(alice); assertEq(star.owner(), alice); vm.expectRevert(OrgStar.NotOwner.selector); star.setPrice(1); }
function test_Interfaces() public view { assertTrue(star.supportsInterface(0x01ffc9a7)); // ERC-165 assertTrue(star.supportsInterface(0x80ac58cd)); // ERC-721 assertTrue(star.supportsInterface(0x5b5e139f)); // Metadata assertTrue(star.supportsInterface(0x2a55205a)); // EIP-2981 assertFalse(star.supportsInterface(0xffffffff)); }
// ---- helpers ----
function _startsWith(string memory s, string memory p) internal pure returns (bool) { bytes memory b = bytes(s); bytes memory q = bytes(p); if (b.length < q.length) return false; for (uint256 i = 0; i < q.length; i++) if (b[i] != q[i]) return false; return true; }
function _contains(string memory s, string memory needle) internal pure returns (bool) { bytes memory b = bytes(s); bytes memory n = bytes(needle); if (n.length > b.length) return false; for (uint256 i = 0; i <= b.length - n.length; i++) { bool hit = true; for (uint256 j = 0; j < n.length; j++) if (b[i + j] != n[j]) { hit = false; break; } if (hit) return true; } return false; }}