diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f9c204..380e6c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,45 +1,40 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -devDependencies: - prettier: - specifier: ^3.0.3 - version: 3.0.3 - prettier-plugin-solidity: - specifier: ^1.1.3 - version: 1.1.3(prettier@3.0.3) +importers: + .: + devDependencies: + prettier: + specifier: ^3.0.3 + version: 3.0.3 + prettier-plugin-solidity: + specifier: ^1.1.3 + version: 1.1.3(prettier@3.0.3) packages: - /@solidity-parser/parser@0.16.1: + '@solidity-parser/parser@0.16.1': resolution: { integrity: sha512-PdhRFNhbTtu3x8Axm0uYpqOy/lODYQK+MlYSgqIsq2L8SFYEHJPHNUiOTAJbDGzNjjr1/n9AcIayxafR/fWmYw==, } - dependencies: - antlr4ts: 0.5.0-alpha.4 - dev: true - /antlr4ts@0.5.0-alpha.4: + antlr4ts@0.5.0-alpha.4: resolution: { integrity: sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==, } - dev: true - /lru-cache@6.0.0: + lru-cache@6.0.0: resolution: { integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, } engines: { node: '>=10' } - dependencies: - yallist: 4.0.0 - dev: true - /prettier-plugin-solidity@1.1.3(prettier@3.0.3): + prettier-plugin-solidity@1.1.3: resolution: { integrity: sha512-fQ9yucPi2sBbA2U2Xjh6m4isUTJ7S7QLc/XDDsktqqxYfTwdYKJ0EnnywXHwCGAaYbQNK+HIYPL1OemxuMsgeg==, @@ -47,43 +42,59 @@ packages: engines: { node: '>=12' } peerDependencies: prettier: '>=2.3.0 || >=3.0.0-alpha.0' - dependencies: - '@solidity-parser/parser': 0.16.1 - prettier: 3.0.3 - semver: 7.5.4 - solidity-comments-extractor: 0.0.7 - dev: true - /prettier@3.0.3: + prettier@3.0.3: resolution: { integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==, } engines: { node: '>=14' } hasBin: true - dev: true - /semver@7.5.4: + semver@7.5.4: resolution: { integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==, } engines: { node: '>=10' } hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - /solidity-comments-extractor@0.0.7: + solidity-comments-extractor@0.0.7: resolution: { integrity: sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==, } - dev: true - /yallist@4.0.0: + yallist@4.0.0: resolution: { integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, } - dev: true + +snapshots: + '@solidity-parser/parser@0.16.1': + dependencies: + antlr4ts: 0.5.0-alpha.4 + + antlr4ts@0.5.0-alpha.4: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + prettier-plugin-solidity@1.1.3(prettier@3.0.3): + dependencies: + '@solidity-parser/parser': 0.16.1 + prettier: 3.0.3 + semver: 7.5.4 + solidity-comments-extractor: 0.0.7 + + prettier@3.0.3: {} + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + solidity-comments-extractor@0.0.7: {} + + yallist@4.0.0: {} diff --git a/src/TinyENS.sol b/src/TinyENS.sol index 84eb379..059bfaf 100644 --- a/src/TinyENS.sol +++ b/src/TinyENS.sol @@ -3,23 +3,23 @@ pragma solidity ^0.8.21; /// @notice Interface for TinyENS. interface ITinyENS { - /// @notice Register a new ENS name and link it to an address. - /// @param name The ENS name to register. - function register(string memory name) external; + /// @notice Register a new ENS name and link it to an address. + /// @param name The ENS name to register. + function register(string memory name) external; - /// @notice Update the ENS name linked to an address. - /// @param newName The new ENS name. - function update(string memory newName) external; + /// @notice Update the ENS name linked to an address. + /// @param newName The new ENS name. + function update(string memory newName) external; - /// @notice Resolve the address associated with a given ENS name. - /// @param name The ENS name to resolve. - /// @return The address associated with the ENS name. - function resolve(string memory name) external view returns (address); + /// @notice Resolve the address associated with a given ENS name. + /// @param name The ENS name to resolve. + /// @return The address associated with the ENS name. + function resolve(string memory name) external view returns (address); - /// @notice Reverse resolve an address to its associated ENS name. - /// @param targetAddress The target address to reverse resolve. - /// @return The ENS name associated with the address. - function reverse(address targetAddress) external view returns (string memory); + /// @notice Reverse resolve an address to its associated ENS name. + /// @param targetAddress The target address to reverse resolve. + /// @return The ENS name associated with the address. + function reverse(address targetAddress) external view returns (string memory); } /// @title Tiny Ethereum Name Service @@ -28,38 +28,40 @@ interface ITinyENS { /// Ethereum addresses like '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' and support the reverse /// resolution. contract TinyENS is ITinyENS { - /// @notice Thrown when trying to register a name already registered. - error AlreadyRegistered(); + /// @notice Thrown when trying to register a name already registered. + error AlreadyRegistered(); - /// @notice Map ENS names to addresses. - mapping(string => address) private registry; + /// @notice Map ENS names to addresses. + mapping(string => address) private registry; - /// @notice Map addresses to ENS names. - mapping(address => string) private reverseRegistry; + /// @notice Map addresses to ENS names. + mapping(address => string) private reverseRegistry; - modifier notRegistered(string memory name) { - if (registry[name] != address(0)) revert AlreadyRegistered(); - _; - } + modifier notRegistered(string memory name) { + if (registry[name] != address(0)) revert AlreadyRegistered(); + _; + } - function register(string memory name) public notRegistered(name) { - registry[name] = msg.sender; - reverseRegistry[msg.sender] = name; - } + function register(string memory name) public notRegistered(name) { + registry[name] = msg.sender; + reverseRegistry[msg.sender] = name; + } - function update(string memory newName) external notRegistered(newName) { - // Unlink the old name and the address. - string memory oldName = reverseRegistry[msg.sender]; - registry[oldName] = address(0); - // Register the new name. - register(newName); - } + function update(string memory newName) external notRegistered(newName) { + // Unlink the old name and the address. + string memory oldName = reverseRegistry[msg.sender]; + registry[oldName] = address(0); + // Register the new name. + register(newName); + } - function resolve(string memory name) external view returns (address) { - return registry[name]; - } + function resolve(string memory name) external view returns (address) { + return registry[name]; + } - function reverse(address targetAddress) external view returns (string memory) { - return reverseRegistry[targetAddress]; - } + function reverse( + address targetAddress + ) external view returns (string memory) { + return reverseRegistry[targetAddress]; + } } diff --git a/src/experiments/LastCallJackpot.sol b/src/experiments/LastCallJackpot.sol index 725dd47..fc46c5a 100644 --- a/src/experiments/LastCallJackpot.sol +++ b/src/experiments/LastCallJackpot.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.21; /// @notice Interface for TinyENS. interface ILastCallJackpot { - /// @notice Call the contract and attempt to win the jackpot. - function call() external payable; + /// @notice Call the contract and attempt to win the jackpot. + function call() external payable; } /// @title A game where the last caller, before a 10-block inactivity period, wins the entire contract's ETH balance. @@ -14,40 +14,40 @@ interface ILastCallJackpot { /// If the contract isn't called for 10 blocks, the last caller gets the entire ETH balance. /// How might this game unfold and end? Describe your thinking. contract LastCallJackpot is ILastCallJackpot { - /// @notice Track the block number when the last call occurred. - uint256 public lastBlock; - /// @notice Store the address of the last caller. - address public lastCaller; - - /// @notice Log each call made to the contract. - event Called(address indexed caller, uint256 blockNumber); - /// @notice Log the transfer of the jackpot amount to the winner. - event WinnerPaid(address indexed winner, uint256 amount); - - constructor() payable { - require(msg.value == 1000 ether, "Fund the contract with 1000 ETH"); - lastBlock = block.number; - } - - function call() external payable { - require(msg.value == 1 ether, "Call the contract with 1 ETH"); - - if (block.number >= lastBlock + 10) { - // Pay the winner. - uint256 balance = address(this).balance; - address winner = lastCaller; - - // Update state before external call - lastBlock = block.number; - lastCaller = msg.sender; - - payable(winner).transfer(balance); - emit WinnerPaid(winner, balance); - } else { - // Update the last block and caller. - lastBlock = block.number; - lastCaller = msg.sender; - emit Called(msg.sender, block.number); - } + /// @notice Track the block number when the last call occurred. + uint256 public lastBlock; + /// @notice Store the address of the last caller. + address public lastCaller; + + /// @notice Log each call made to the contract. + event Called(address indexed caller, uint256 blockNumber); + /// @notice Log the transfer of the jackpot amount to the winner. + event WinnerPaid(address indexed winner, uint256 amount); + + constructor() payable { + require(msg.value == 1000 ether, 'Fund the contract with 1000 ETH'); + lastBlock = block.number; + } + + function call() external payable { + require(msg.value == 1 ether, 'Call the contract with 1 ETH'); + + if (block.number >= lastBlock + 10) { + // Pay the winner. + uint256 balance = address(this).balance; + address winner = lastCaller; + + // Update state before external call + lastBlock = block.number; + lastCaller = msg.sender; + + payable(winner).transfer(balance); + emit WinnerPaid(winner, balance); + } else { + // Update the last block and caller. + lastBlock = block.number; + lastCaller = msg.sender; + emit Called(msg.sender, block.number); } + } } diff --git a/test/TinyENS.t.sol b/test/TinyENS.t.sol index 93e49c6..775ce3b 100644 --- a/test/TinyENS.t.sol +++ b/test/TinyENS.t.sol @@ -1,80 +1,80 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.21; -import "../src/TinyENS.sol"; -import "@forge-std/Test.sol"; +import '../src/TinyENS.sol'; +import '@forge-std/Test.sol'; contract TinyENSTest is Test { - TinyENS private tinyENS; + TinyENS private tinyENS; - address private owner = makeAddr("owner"); - address private alice = makeAddr("alice"); - address private bob = makeAddr("bob"); + address private owner = makeAddr('owner'); + address private alice = makeAddr('alice'); + address private bob = makeAddr('bob'); - function setUp() public { - vm.startPrank(owner); - tinyENS = new TinyENS(); - console2.log("TinyENS deployed"); - vm.stopPrank(); - } + function setUp() public { + vm.startPrank(owner); + tinyENS = new TinyENS(); + console2.log('TinyENS deployed'); + vm.stopPrank(); + } - function test_RegisterNewName() public { - vm.startPrank(alice); - tinyENS.register("alice.eth"); - vm.stopPrank(); + function test_RegisterNewName() public { + vm.startPrank(alice); + tinyENS.register('alice.eth'); + vm.stopPrank(); - assertEq(tinyENS.resolve("alice.eth"), alice); - assertEq(tinyENS.reverse(alice), "alice.eth"); - console2.log("Alice registed alice.eth"); - } + assertEq(tinyENS.resolve('alice.eth'), alice); + assertEq(tinyENS.reverse(alice), 'alice.eth'); + console2.log('Alice registed alice.eth'); + } - function test_RegisterAlreadyRegisteredName() public { - vm.startPrank(alice); - tinyENS.register("alice.eth"); - assertEq(tinyENS.resolve("alice.eth"), alice); - assertEq(tinyENS.reverse(alice), "alice.eth"); - console2.log("Alice registed alice.eth"); + function test_RegisterAlreadyRegisteredName() public { + vm.startPrank(alice); + tinyENS.register('alice.eth'); + assertEq(tinyENS.resolve('alice.eth'), alice); + assertEq(tinyENS.reverse(alice), 'alice.eth'); + console2.log('Alice registed alice.eth'); - console2.log("Bob tries to register alice.eth"); - vm.startPrank(bob); - vm.expectRevert(TinyENS.AlreadyRegistered.selector); - tinyENS.register("alice.eth"); - assertEq(tinyENS.resolve("alice.eth"), alice); - assertEq(tinyENS.reverse(alice), "alice.eth"); - assertEq(tinyENS.reverse(bob), ""); - vm.stopPrank(); - } + console2.log('Bob tries to register alice.eth'); + vm.startPrank(bob); + vm.expectRevert(TinyENS.AlreadyRegistered.selector); + tinyENS.register('alice.eth'); + assertEq(tinyENS.resolve('alice.eth'), alice); + assertEq(tinyENS.reverse(alice), 'alice.eth'); + assertEq(tinyENS.reverse(bob), ''); + vm.stopPrank(); + } - function test_UpdateWithNewName() public { - vm.startPrank(alice); - tinyENS.register("alice.eth"); - assertEq(tinyENS.resolve("alice.eth"), alice); - assertEq(tinyENS.reverse(alice), "alice.eth"); - console2.log("Alice registed alice.eth"); + function test_UpdateWithNewName() public { + vm.startPrank(alice); + tinyENS.register('alice.eth'); + assertEq(tinyENS.resolve('alice.eth'), alice); + assertEq(tinyENS.reverse(alice), 'alice.eth'); + console2.log('Alice registed alice.eth'); - tinyENS.register("alice22.eth"); - assertEq(tinyENS.resolve("alice22.eth"), alice); - assertEq(tinyENS.reverse(alice), "alice22.eth"); - console2.log("Alice updated its name to alice22.eth"); - vm.stopPrank(); - } + tinyENS.register('alice22.eth'); + assertEq(tinyENS.resolve('alice22.eth'), alice); + assertEq(tinyENS.reverse(alice), 'alice22.eth'); + console2.log('Alice updated its name to alice22.eth'); + vm.stopPrank(); + } - function test_UpdateWithAlreadyRegisteredName() public { - vm.startPrank(alice); - tinyENS.register("alice.eth"); - console2.log("Alice registed alice.eth"); + function test_UpdateWithAlreadyRegisteredName() public { + vm.startPrank(alice); + tinyENS.register('alice.eth'); + console2.log('Alice registed alice.eth'); - vm.startPrank(bob); - tinyENS.register("bob.eth"); - console2.log("Bob registed bob.eth"); + vm.startPrank(bob); + tinyENS.register('bob.eth'); + console2.log('Bob registed bob.eth'); - console2.log("Bob tries to update its name to alice.eth"); - vm.expectRevert(TinyENS.AlreadyRegistered.selector); - tinyENS.update("alice.eth"); - assertEq(tinyENS.resolve("alice.eth"), alice); - assertEq(tinyENS.reverse(alice), "alice.eth"); - assertEq(tinyENS.resolve("bob.eth"), bob); - assertEq(tinyENS.reverse(bob), "bob.eth"); - vm.stopPrank(); - } + console2.log('Bob tries to update its name to alice.eth'); + vm.expectRevert(TinyENS.AlreadyRegistered.selector); + tinyENS.update('alice.eth'); + assertEq(tinyENS.resolve('alice.eth'), alice); + assertEq(tinyENS.reverse(alice), 'alice.eth'); + assertEq(tinyENS.resolve('bob.eth'), bob); + assertEq(tinyENS.reverse(bob), 'bob.eth'); + vm.stopPrank(); + } } diff --git a/test/experiments/LastCallJackpot.t.sol b/test/experiments/LastCallJackpot.t.sol index 739f8f2..0ea50c5 100644 --- a/test/experiments/LastCallJackpot.t.sol +++ b/test/experiments/LastCallJackpot.t.sol @@ -1,105 +1,105 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.21; -import "../../src/experiments/LastCallJackpot.sol"; -import "@forge-std/Test.sol"; +import '../../src/experiments/LastCallJackpot.sol'; +import '@forge-std/Test.sol'; contract LastCallJackpotTest is Test { - LastCallJackpot private lastCallJackpot; - - address private owner = makeAddr("owner"); - address private alice = makeAddr("alice"); - address private bob = makeAddr("bob"); - - function setUp() public { - // Fund accounts. - vm.deal(owner, 10000 ether); - vm.deal(alice, 1000 ether); - vm.deal(bob, 1000 ether); - - // Deploy contract. - vm.startPrank(owner); - lastCallJackpot = new LastCallJackpot{value: 1000 ether}(); - console2.log("LastCallJackpot deployed"); - vm.stopPrank(); - } - - // This is just a test to make sure the call method works. - function test_SingleCall() public { - vm.startPrank(alice); - lastCallJackpot.call{value: 1 ether}(); - vm.stopPrank(); - - assertEq(lastCallJackpot.lastBlock(), block.number); - assertEq(lastCallJackpot.lastCaller(), alice); - assertEq(address(lastCallJackpot).balance, 1001 ether); - assertEq(alice.balance, 999 ether); - console2.log("Alice calls the contract"); - } - - // This is another test to make sure that one can win the jackpot according to the rules. - function test_WinJackpot() public { - uint256 bn = block.number; - - // Alice calls the contract. - vm.startPrank(alice); - lastCallJackpot.call{value: 1 ether}(); - vm.stopPrank(); - - assertEq(lastCallJackpot.lastBlock(), block.number); - assertEq(lastCallJackpot.lastCaller(), alice); - assertEq(address(lastCallJackpot).balance, 1001 ether); - assertEq(alice.balance, 999 ether); - console2.log("Alice calls the contract"); - - // 10 blocks have passed without anyone calling the contract. - vm.roll(bn + 10); - console2.log("10 blocks have passed"); - - // Bob calls the contract and Alice wins the jackpot. - vm.startPrank(bob); - lastCallJackpot.call{value: 1 ether}(); - vm.stopPrank(); - - assertEq(lastCallJackpot.lastBlock(), block.number); - assertEq(lastCallJackpot.lastCaller(), bob); - console2.log("Bob calls the contract"); - - assertEq(address(lastCallJackpot).balance, 0 ether); - assertEq(alice.balance, 999 ether + 1_001 ether + 1 ether); - console2.log("Alice wins the jackpot"); - } - - // This is another test to make sure that one can only win the jackpot if nobody called the contract for 10 blocks. - // This scenario - function test_MultiCall() public { - uint256 bn = block.number; - - // Alice calls the contract. - vm.startPrank(alice); - lastCallJackpot.call{value: 1 ether}(); - vm.stopPrank(); - - assertEq(lastCallJackpot.lastBlock(), block.number); - assertEq(lastCallJackpot.lastCaller(), alice); - assertEq(address(lastCallJackpot).balance, 1001 ether); - assertEq(alice.balance, 999 ether); - console2.log("Alice calls the contract"); - - // 9 blocks have passed without anyone calling the contract. - vm.roll(bn + 9); - console2.log("9 blocks have passed"); - - // Bob calls the contract and nobody wins the jackpot. - vm.startPrank(bob); - lastCallJackpot.call{value: 1 ether}(); - vm.stopPrank(); - - assertEq(lastCallJackpot.lastBlock(), block.number); - assertEq(lastCallJackpot.lastCaller(), bob); - assertEq(address(lastCallJackpot).balance, 1002 ether); - assertEq(bob.balance, 999 ether); - console2.log("Bob calls the contract"); - console2.log("Nobody wins the jackpot"); - } + LastCallJackpot private lastCallJackpot; + + address private owner = makeAddr('owner'); + address private alice = makeAddr('alice'); + address private bob = makeAddr('bob'); + + function setUp() public { + // Fund accounts. + vm.deal(owner, 10000 ether); + vm.deal(alice, 1000 ether); + vm.deal(bob, 1000 ether); + + // Deploy contract. + vm.startPrank(owner); + lastCallJackpot = new LastCallJackpot{value: 1000 ether}(); + console2.log('LastCallJackpot deployed'); + vm.stopPrank(); + } + + // This is just a test to make sure the call method works. + function test_SingleCall() public { + vm.startPrank(alice); + lastCallJackpot.call{value: 1 ether}(); + vm.stopPrank(); + + assertEq(lastCallJackpot.lastBlock(), block.number); + assertEq(lastCallJackpot.lastCaller(), alice); + assertEq(address(lastCallJackpot).balance, 1001 ether); + assertEq(alice.balance, 999 ether); + console2.log('Alice calls the contract'); + } + + // This is another test to make sure that one can win the jackpot according to the rules. + function test_WinJackpot() public { + uint256 bn = block.number; + + // Alice calls the contract. + vm.startPrank(alice); + lastCallJackpot.call{value: 1 ether}(); + vm.stopPrank(); + + assertEq(lastCallJackpot.lastBlock(), block.number); + assertEq(lastCallJackpot.lastCaller(), alice); + assertEq(address(lastCallJackpot).balance, 1001 ether); + assertEq(alice.balance, 999 ether); + console2.log('Alice calls the contract'); + + // 10 blocks have passed without anyone calling the contract. + vm.roll(bn + 10); + console2.log('10 blocks have passed'); + + // Bob calls the contract and Alice wins the jackpot. + vm.startPrank(bob); + lastCallJackpot.call{value: 1 ether}(); + vm.stopPrank(); + + assertEq(lastCallJackpot.lastBlock(), block.number); + assertEq(lastCallJackpot.lastCaller(), bob); + console2.log('Bob calls the contract'); + + assertEq(address(lastCallJackpot).balance, 0 ether); + assertEq(alice.balance, 999 ether + 1_001 ether + 1 ether); + console2.log('Alice wins the jackpot'); + } + + // This is another test to make sure that one can only win the jackpot if nobody called the contract for 10 blocks. + // This scenario + function test_MultiCall() public { + uint256 bn = block.number; + + // Alice calls the contract. + vm.startPrank(alice); + lastCallJackpot.call{value: 1 ether}(); + vm.stopPrank(); + + assertEq(lastCallJackpot.lastBlock(), block.number); + assertEq(lastCallJackpot.lastCaller(), alice); + assertEq(address(lastCallJackpot).balance, 1001 ether); + assertEq(alice.balance, 999 ether); + console2.log('Alice calls the contract'); + + // 9 blocks have passed without anyone calling the contract. + vm.roll(bn + 9); + console2.log('9 blocks have passed'); + + // Bob calls the contract and nobody wins the jackpot. + vm.startPrank(bob); + lastCallJackpot.call{value: 1 ether}(); + vm.stopPrank(); + + assertEq(lastCallJackpot.lastBlock(), block.number); + assertEq(lastCallJackpot.lastCaller(), bob); + assertEq(address(lastCallJackpot).balance, 1002 ether); + assertEq(bob.balance, 999 ether); + console2.log('Bob calls the contract'); + console2.log('Nobody wins the jackpot'); + } }