From 6684b9859fee8f1952aafa02f6c7064d5d4c3944 Mon Sep 17 00:00:00 2001 From: "Eduardo W. da Cunha" Date: Sat, 6 Apr 2024 22:45:37 -0300 Subject: [PATCH 1/4] new level [Crowdfunding] --- contracts/src/levels/Crowdfunding.sol | 48 ++++++++ contracts/src/levels/CrowdfundingFactory.sol | 42 +++++++ contracts/test/levels/Crowdfunding.t.sol | 109 +++++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 contracts/src/levels/Crowdfunding.sol create mode 100644 contracts/src/levels/CrowdfundingFactory.sol create mode 100644 contracts/test/levels/Crowdfunding.t.sol diff --git a/contracts/src/levels/Crowdfunding.sol b/contracts/src/levels/Crowdfunding.sol new file mode 100644 index 000000000..40fa598e2 --- /dev/null +++ b/contracts/src/levels/Crowdfunding.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +contract Crowdfunding { + address public owner; + address public artist; + string public projectName; + bytes public lastSignature; + + receive() external payable {} + + constructor(address owner_, string memory newProjectName) { + owner = owner_; + projectName = newProjectName; + } + + function withdraw() external { + require(msg.sender == artist, "Not artist"); + + (bool success, ) = artist.call{value: address(this).balance}(""); + + require(success, "Withdraw failed"); + } + + function setArtist(address newArtist, bytes calldata signature) external { + bytes32 nameHash = keccak256(abi.encodePacked(projectName)); + bytes32 messageHash = keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", nameHash) + ); + (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature); + require(owner == ecrecover(messageHash, v, r, s)); + + artist = newArtist; + lastSignature = signature; + } + + function splitSignature( + bytes memory signature + ) public pure returns (bytes32 r, bytes32 s, uint8 v) { + if (signature.length == 65) { + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + } + } +} diff --git a/contracts/src/levels/CrowdfundingFactory.sol b/contracts/src/levels/CrowdfundingFactory.sol new file mode 100644 index 000000000..d4a94d30f --- /dev/null +++ b/contracts/src/levels/CrowdfundingFactory.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {Level} from "./base/Level.sol"; +import {Crowdfunding} from "./Crowdfunding.sol"; + +contract CrowdfundingFactory is Level { + bytes public signature; + address public signer; + address public artist; + + constructor(bytes memory signature_, address signer_, address artist_) { + signature = signature_; + signer = signer_; + artist = artist_; + } + + function createInstance( + address _player + ) public payable override returns (address) { + _player; + + string memory projectName = "amazing crowdfunding"; + Crowdfunding crowdfunding = new Crowdfunding(signer, projectName); + + (bool success, ) = address(crowdfunding).call{value: 1 ether}(""); + require(success, "Failed to transfer funds"); + crowdfunding.setArtist(artist, signature); + + return address(crowdfunding); + } + + function validateInstance( + address payable _instance, + address _player + ) public view override returns (bool) { + Crowdfunding crowdfunding = Crowdfunding(_instance); + + return crowdfunding.artist() == _player && _instance.balance == 0; + } +} diff --git a/contracts/test/levels/Crowdfunding.t.sol b/contracts/test/levels/Crowdfunding.t.sol new file mode 100644 index 000000000..329caa6cf --- /dev/null +++ b/contracts/test/levels/Crowdfunding.t.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test, console} from "forge-std/Test.sol"; +import {Utils} from "test/utils/Utils.sol"; + +import {Crowdfunding} from "src/levels/Crowdfunding.sol"; +import {CrowdfundingFactory} from "src/levels/CrowdfundingFactory.sol"; +import {Level} from "src/levels/base/Level.sol"; +import {Ethernaut} from "src/Ethernaut.sol"; + +contract TestCrowdfunding is Test, Utils { + Ethernaut ethernaut; + Crowdfunding instance; + + address payable owner; + address payable player; + + /*////////////////////////////////////////////////////////////// + HELPERS + //////////////////////////////////////////////////////////////*/ + + function setUp() public { + address payable[] memory users = createUsers(2); + + address initialArtist = users[0]; + vm.label(owner, "Owner"); + + player = users[1]; + vm.label(player, "Player"); + + (address owner_, uint256 pk) = makeAddrAndKey("owner"); + owner = payable(owner_); + string memory projectName = "amazing crowdfunding"; + bytes32 nameHash = keccak256(abi.encodePacked(projectName)); + bytes32 messageHash = keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", nameHash) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, messageHash); + bytes memory signature = abi.encodePacked(r, s, v); + + vm.startPrank(owner); + ethernaut = getEthernautWithStatsProxy(owner); + CrowdfundingFactory factory = new CrowdfundingFactory( + signature, + owner, + initialArtist + ); + ethernaut.registerLevel(Level(address(factory))); + vm.stopPrank(); + + vm.startPrank(player); + uint256 gasStart = gasleft(); + instance = Crowdfunding( + payable( + createLevelInstance(ethernaut, Level(address(factory)), 1 ether) + ) + ); + uint256 gasEnd = gasleft(); + console.log("Gas to create instance:", gasStart - gasEnd); + vm.stopPrank(); + } + + function _splitSignature( + bytes memory signature + ) internal pure virtual returns (bytes32 r, bytes32 s, uint8 v) { + if (signature.length == 65) { + assembly { + r := mload(add(signature, 0x20)) + s := mload(add(signature, 0x40)) + v := byte(0, mload(add(signature, 0x60))) + } + } + } + + /*////////////////////////////////////////////////////////////// + TESTS + //////////////////////////////////////////////////////////////*/ + + /// @notice Check the intial state of the level and enviroment. + function testInit() public { + vm.prank(player); + assertFalse(submitLevelInstance(ethernaut, address(instance))); + } + + /// @notice Test the solution for the level. + function testSolve() public { + uint256 n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; + + bytes memory signature = instance.lastSignature(); + (bytes32 r, bytes32 s, uint8 v) = _splitSignature(signature); + + uint8 manipulatedV = v % 2 == 0 ? v - 1 : v + 1; + uint256 manipulatedS = n - uint256(s); + bytes memory manipulatedSignature = abi.encodePacked( + r, + bytes32(manipulatedS), + manipulatedV + ); + + vm.startPrank(player); + instance.setArtist(player, manipulatedSignature); + instance.withdraw(); + + assertTrue(submitLevelInstance(ethernaut, address(instance))); + vm.stopPrank(); + } +} From faaf42631795ce93692481fb7f4f76864f6b0e09 Mon Sep 17 00:00:00 2001 From: "Eduardo W. da Cunha" Date: Sun, 7 Apr 2024 21:00:57 -0300 Subject: [PATCH 2/4] adding info files --- client/src/gamedata/authors.json | 308 +++--- .../en/descriptions/levels/crowdfunding.md | 7 + .../levels/crowdfunding_complete.md | 3 + client/src/gamedata/gamedata.json | 945 +++++++++--------- 4 files changed, 649 insertions(+), 614 deletions(-) create mode 100644 client/src/gamedata/en/descriptions/levels/crowdfunding.md create mode 100644 client/src/gamedata/en/descriptions/levels/crowdfunding_complete.md diff --git a/client/src/gamedata/authors.json b/client/src/gamedata/authors.json index 4f5795c0e..7b69d5342 100644 --- a/client/src/gamedata/authors.json +++ b/client/src/gamedata/authors.json @@ -1,152 +1,162 @@ { - "authors": { - "ajsantander": { - "name": [ - "Alejandro Santander" - ], - "emails": [ - "palebluedot@gmail.com" - ], - "websites": [ - "https://github.com/ajsantander" - ], - "donate": "0x31a3801499618d3c4b0225b9e06e228d4795b55d" - }, - "martriay": { - "name": [ - "Martin Triay" - ], - "emails": [ - "martriay@gmail.com" - ], - "websites": [ - "https://github.com/martriay" - ] - }, - "AgeManning": { - "name": [ - "Adrian Manning" - ], - "emails": [ - "age@agemanning.com" - ], - "websites": [ - "https://github.com/AgeManning" - ], - "donate": "0x0f44CD2Ca92645Ada2E47155e4dFC0025c3E9EEc" - }, - "syncikin": { - "name": [ - "Kyle Riley" - ], - "emails": [ - "kyle@iosiro.com" - ], - "websites": [ - "https://github.com/syncikin" - ] - }, - "34x4p08": { - "name": [ - "Ivan Zakharov" - ], - "emails": [ - "34x4p08@gmail.com" - ], - "websites": [ - "https://github.com/34x4p08" - ] - }, - "0age": { - "name": [ - "0age" - ], - "emails": [ - "0age@protonmail.com" - ], - "websites": [ - "https://github.com/0age" - ] - }, - "nczhu": { - "name": [ - "Nicole Zhu" - ], - "emails": [ - "n@nicole.ai" - ], - "websites": [ - "https://github.com/nczhu" - ] - }, - "patrickalphac": { - "name": [ - "Patrick Collins" - ], - "emails": [ - "patrick@alphachain.io" - ], - "websites": [ - "http://alphachain.io/blogs/" - ], - "donate": "0x874437B5a42aA6E6419eC2447C9e36c278c46532" - }, - "scottt": { - "name": [ - "Scott Tsai" - ], - "emails": [ - "scottt.tw@gmail.com" - ], - "websites": [ - "http://scottt.tw" - ], - "donate": "scottt.eth" - }, - "openzeppelin": { - "name": [ - "OpenZeppelin" - ], - "emails": [ - "ethernaut@zeppelin.solutions" - ], - "websites": [ - "https://openzeppelin.com" - ] - }, - "openzeppelin&forta": { - "name": [ - "OpenZeppelin", - "Forta" - ], - "emails": [ - "ethernaut@zeppelin.solutions", - "info@forta.org" - ], - "websites": [ - "https://openzeppelin.com", - "https://forta.org" - ] - }, - "ericnordelo": { - "name": [ - "Eric Nordelo" - ], - "emails": [ - "eric.nordelo39@gmail.com" - ], - "websites": [ - "https://www.ericnordelo.com/" - ] - }, - "KStasi": { - "name": [ - "Anastasiia Kondaurova" - ], - "emails": [], - "websites": [ - "https://www.linkedin.com/in/kstasi/" - ] + "authors": { + "ajsantander": { + "name": [ + "Alejandro Santander" + ], + "emails": [ + "palebluedot@gmail.com" + ], + "websites": [ + "https://github.com/ajsantander" + ], + "donate": "0x31a3801499618d3c4b0225b9e06e228d4795b55d" + }, + "martriay": { + "name": [ + "Martin Triay" + ], + "emails": [ + "martriay@gmail.com" + ], + "websites": [ + "https://github.com/martriay" + ] + }, + "AgeManning": { + "name": [ + "Adrian Manning" + ], + "emails": [ + "age@agemanning.com" + ], + "websites": [ + "https://github.com/AgeManning" + ], + "donate": "0x0f44CD2Ca92645Ada2E47155e4dFC0025c3E9EEc" + }, + "syncikin": { + "name": [ + "Kyle Riley" + ], + "emails": [ + "kyle@iosiro.com" + ], + "websites": [ + "https://github.com/syncikin" + ] + }, + "34x4p08": { + "name": [ + "Ivan Zakharov" + ], + "emails": [ + "34x4p08@gmail.com" + ], + "websites": [ + "https://github.com/34x4p08" + ] + }, + "0age": { + "name": [ + "0age" + ], + "emails": [ + "0age@protonmail.com" + ], + "websites": [ + "https://github.com/0age" + ] + }, + "nczhu": { + "name": [ + "Nicole Zhu" + ], + "emails": [ + "n@nicole.ai" + ], + "websites": [ + "https://github.com/nczhu" + ] + }, + "patrickalphac": { + "name": [ + "Patrick Collins" + ], + "emails": [ + "patrick@alphachain.io" + ], + "websites": [ + "http://alphachain.io/blogs/" + ], + "donate": "0x874437B5a42aA6E6419eC2447C9e36c278c46532" + }, + "scottt": { + "name": [ + "Scott Tsai" + ], + "emails": [ + "scottt.tw@gmail.com" + ], + "websites": [ + "http://scottt.tw" + ], + "donate": "scottt.eth" + }, + "openzeppelin": { + "name": [ + "OpenZeppelin" + ], + "emails": [ + "ethernaut@zeppelin.solutions" + ], + "websites": [ + "https://openzeppelin.com" + ] + }, + "openzeppelin&forta": { + "name": [ + "OpenZeppelin", + "Forta" + ], + "emails": [ + "ethernaut@zeppelin.solutions", + "info@forta.org" + ], + "websites": [ + "https://openzeppelin.com", + "https://forta.org" + ] + }, + "ericnordelo": { + "name": [ + "Eric Nordelo" + ], + "emails": [ + "eric.nordelo39@gmail.com" + ], + "websites": [ + "https://www.ericnordelo.com/" + ] + }, + "KStasi": { + "name": [ + "Anastasiia Kondaurova" + ], + "emails": [], + "websites": [ + "https://www.linkedin.com/in/kstasi/" + ] + }, + "eduardowestc": { + "name": [ + "Eduardo W. da Cunha" + ], + "emails": [], + "websites": [ + "https://www.linkedin.com/in/eduardo-westphal-da-cunha/" + ], + "donate": "0x8FcB647096a5A5B0Ff6B944C5B2f3d2c9e1f65A0" + } } - } } \ No newline at end of file diff --git a/client/src/gamedata/en/descriptions/levels/crowdfunding.md b/client/src/gamedata/en/descriptions/levels/crowdfunding.md new file mode 100644 index 000000000..fc319aa99 --- /dev/null +++ b/client/src/gamedata/en/descriptions/levels/crowdfunding.md @@ -0,0 +1,7 @@ +A famous artist is organizing a crowdfund for his/her next big project. + +Your goal is to drain all the funds from the crowdfund. + +  +Things that might help +* Look into security issues from Solidity's `ecrecover` built-in function. diff --git a/client/src/gamedata/en/descriptions/levels/crowdfunding_complete.md b/client/src/gamedata/en/descriptions/levels/crowdfunding_complete.md new file mode 100644 index 000000000..d5a9dcafc --- /dev/null +++ b/client/src/gamedata/en/descriptions/levels/crowdfunding_complete.md @@ -0,0 +1,3 @@ +Well done! + +Signature malleability is a big issue when recovering addresses. There is actually a simple way to avoid this problem, as it is done in OpenZeppelin's ECDSA library. Check it out! \ No newline at end of file diff --git a/client/src/gamedata/gamedata.json b/client/src/gamedata/gamedata.json index 3a959ec8f..d2716ae02 100644 --- a/client/src/gamedata/gamedata.json +++ b/client/src/gamedata/gamedata.json @@ -1,466 +1,481 @@ { - "levels": [ - { - "name": "Hello Ethernaut", - "created": "2017-11-01", - "difficulty": "0", - "description": "instances.md", - "completedDescription": "instances_complete.md", - "levelContract": "InstanceFactory.sol", - "instanceContract": "Instance.sol", - "revealCode": false, - "deployParams": [], - "deployFunds": 0, - "deployId": "0", - "instanceGas": 1500000, - "author": "ajsantander" - }, - { - "name": "Fallback", - "created": "2017-11-01", - "difficulty": "1", - "description": "fallback.md", - "completedDescription": "fallback_complete.md", - "levelContract": "FallbackFactory.sol", - "instanceContract": "Fallback.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "1", - "instanceGas": 450000, - "author": "ajsantander" - }, - { - "name": "Fallout", - "created": "2017-11-01", - "difficulty": "2", - "description": "fallout.md", - "completedDescription": "fallout_complete.md", - "levelContract": "FalloutFactory.sol", - "instanceContract": "Fallout.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "2", - "instanceGas": 450000, - "author": "ajsantander" - }, - { - "name": "Coin Flip", - "created": "2018-03-18", - "difficulty": "3", - "description": "coinflip.md", - "completedDescription": "coinflip_complete.md", - "levelContract": "CoinFlipFactory.sol", - "instanceContract": "CoinFlip.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "3", - "instanceGas": 320000, - "author": "syncikin" - }, - { - "name": "Telephone", - "created": "2018-03-18", - "difficulty": "1", - "description": "telephone.md", - "completedDescription": "telephone_complete.md", - "levelContract": "TelephoneFactory.sol", - "instanceContract": "Telephone.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "4", - "instanceGas": 230000, - "author": "syncikin" - }, - { - "name": "Token", - "created": "2017-11-01", - "difficulty": "3", - "description": "token.md", - "completedDescription": "token_complete.md", - "levelContract": "TokenFactory.sol", - "instanceContract": "Token.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "5", - "instanceGas": 330000, - "author": "ajsantander" - }, - { - "name": "Delegation", - "created": "2017-11-01", - "difficulty": "4", - "description": "delegate.md", - "completedDescription": "delegate_complete.md", - "levelContract": "DelegationFactory.sol", - "instanceContract": "Delegation.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "6", - "instanceGas": 250000, - "author": "ajsantander" - }, - { - "name": "Force", - "created": "2017-11-01", - "difficulty": "5", - "description": "force.md", - "completedDescription": "force_complete.md", - "levelContract": "ForceFactory.sol", - "instanceContract": "Force.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "7", - "instanceGas": 150000, - "author": "ajsantander" - }, - { - "name": "Vault", - "created": "2018-03-18", - "difficulty": "3", - "description": "vault.md", - "completedDescription": "vault_complete.md", - "levelContract": "VaultFactory.sol", - "instanceContract": "Vault.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "8", - "instanceGas": 200000, - "author": "syncikin" - }, - { - "name": "King", - "created": "2017-11-24", - "difficulty": "6", - "description": "king.md", - "completedDescription": "king_complete.md", - "levelContract": "KingFactory.sol", - "instanceContract": "King.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0.001, - "deployId": "9", - "instanceGas": 350000, - "author": "ajsantander" - }, - { - "name": "Re-entrancy", - "created": "2017-11-01", - "difficulty": "6", - "description": "reentrancy.md", - "completedDescription": "reentrancy_complete.md", - "levelContract": "ReentranceFactory.sol", - "instanceContract": "Reentrance.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0.001, - "deployId": "10", - "instanceGas": 350000, - "author": "ajsantander" - }, - { - "name": "Elevator", - "created": "2017-01-25", - "difficulty": "4", - "description": "elevator.md", - "completedDescription": "elevator_complete.md", - "levelContract": "ElevatorFactory.sol", - "instanceContract": "Elevator.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "11", - "instanceGas": 250000, - "author": "martriay" - }, - { - "name": "Privacy", - "created": "2018-04-06", - "difficulty": "6", - "description": "privacy.md", - "completedDescription": "privacy_complete.md", - "levelContract": "PrivacyFactory.sol", - "instanceContract": "Privacy.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "12", - "instanceGas": 320000, - "author": "ajsantander" - }, - { - "name": "Gatekeeper One", - "created": "2018-04-26", - "difficulty": "8", - "description": "gatekeeper1.md", - "completedDescription": "gatekeeper1_complete.md", - "levelContract": "GatekeeperOneFactory.sol", - "instanceContract": "GatekeeperOne.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "13", - "instanceGas": 400000, - "author": "0age", - "verificationDetails": { - "contractName": "GatekeeperOne", - "compilerVersion": "v0.8.12+commit.f00d7308", - "optimization": "1", - "runs": "1000", - "licenseTypeId": "3", - "constructorArguements": "" - } - }, - { - "name": "Gatekeeper Two", - "created": "2018-04-26", - "difficulty": "6", - "description": "gatekeeper2.md", - "completedDescription": "gatekeeper2_complete.md", - "levelContract": "GatekeeperTwoFactory.sol", - "instanceContract": "GatekeeperTwo.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "14", - "instanceGas": 250000, - "author": "0age" - }, - { - "name": "Naught Coin", - "created": "2018-03-24", - "difficulty": "5", - "description": "naughtcoin.md", - "completedDescription": "naughtcoin_complete.md", - "levelContract": "NaughtCoinFactory.sol", - "instanceContract": "NaughtCoin.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "15", - "instanceGas": 1300000, - "author": "syncikin" - }, - { - "name": "Preservation", - "created": "2018-05-27", - "difficulty": "8", - "description": "preservation.md", - "completedDescription": "preservation_complete.md", - "levelContract": "PreservationFactory.sol", - "instanceContract": "Preservation.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "16", - "instanceGas": 500000, - "author": "AgeManning" - }, - { - "name": "Recovery", - "created": "2018-05-20", - "difficulty": "6", - "description": "recovery.md", - "completedDescription": "recovery_complete.md", - "levelContract": "RecoveryFactory.sol", - "instanceContract": "Recovery.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0.001, - "deployId": "17", - "instanceGas": 1100000, - "author": "AgeManning" - }, - { - "name": "MagicNumber", - "created": "2018-08-24", - "difficulty": "6", - "description": "magicnum.md", - "completedDescription": "magicnum_complete.md", - "levelContract": "MagicNumFactory.sol", - "instanceContract": "MagicNum.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "18", - "instanceGas": 200000, - "author": "ajsantander" - }, - { - "name": "Alien Codex", - "created": "2018-10-11", - "difficulty": "7", - "description": "aliencodex.md", - "completedDescription": "aliencodex_complete.md", - "levelContract": "AlienCodexFactory.sol", - "instanceContract": "AlienCodex.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "19", - "instanceGas": 520000, - "author": "nczhu" - }, - { - "name": "Denial", - "created": "2018-10-20", - "difficulty": "5", - "description": "denial.md", - "completedDescription": "denial_complete.md", - "levelContract": "DenialFactory.sol", - "instanceContract": "Denial.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0.001, - "deployId": "20", - "instanceGas": 450000, - "author": "AgeManning" - }, - { - "name": "Shop", - "created": "2018-09-08", - "difficulty": "4", - "description": "shop.md", - "completedDescription": "shop_complete.md", - "levelContract": "ShopFactory.sol", - "instanceContract": "Shop.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "21", - "instanceGas": 250000, - "author": "34x4p08" - }, - { - "name": "Dex", - "created": "2021-03-06", - "difficulty": "3", - "description": "dex.md", - "completedDescription": "dex_complete.md", - "levelContract": "DexFactory.sol", - "instanceContract": "Dex.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "22", - "instanceGas": 3600000, - "author": "patrickalphac" - }, - { - "name": "Dex Two", - "created": "2021-08-20", - "difficulty": "4", - "description": "dex2.md", - "completedDescription": "dex2_complete.md", - "levelContract": "DexTwoFactory.sol", - "instanceContract": "DexTwo.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "23", - "instanceGas": 3600000, - "author": "scottt" - }, - { - "name": "Puzzle Wallet", - "created": "2021-10-05", - "difficulty": "7", - "description": "puzzle_wallet.md", - "completedDescription": "puzzle_wallet_complete.md", - "levelContract": "PuzzleWalletFactory.sol", - "instanceContract": "PuzzleWallet.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0.001, - "deployId": "24", - "instanceGas": 3600000, - "author": "openzeppelin" - }, - { - "name": "Motorbike", - "created": "2021-10-07", - "difficulty": "6", - "description": "motorbike.md", - "completedDescription": "motorbike_complete.md", - "levelContract": "MotorbikeFactory.sol", - "instanceContract": "Motorbike.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "25", - "instanceGas": 2000000, - "author": "openzeppelin" - }, - { - "name": "DoubleEntryPoint", - "created": "2022-03-21", - "difficulty": "4", - "description": "doubleentrypoint.md", - "completedDescription": "doubleentrypoint_complete.md", - "levelContract": "DoubleEntryPointFactory.sol", - "instanceContract": "DoubleEntryPoint.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "26", - "instanceGas": 5000000, - "poweredBy": { - "src": "../../imgs/forta.svg", - "href": "https://forta.org" - }, - "author": "openzeppelin&forta" - }, - { - "name": "Good Samaritan", - "created": "2022-8-4", - "difficulty": "5", - "description": "goodsamaritan.md", - "completedDescription": "goodsamaritan_complete.md", - "levelContract": "GoodSamaritanFactory.sol", - "instanceContract": "GoodSamaritan.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "27", - "instanceGas": 2500000, - "author": "ericnordelo" - }, - { - "name": "Gatekeeper Three", - "created": "2019-02-02", - "difficulty": "6", - "description": "gatekeeper3.md", - "completedDescription": "gatekeeper3_complete.md", - "levelContract": "GatekeeperThreeFactory.sol", - "instanceContract": "GatekeeperThree.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "28", - "instanceGas": 1000000, - "author": "KStasi" - }, - { - "name": "Switch", - "created": "2018-11-04", - "difficulty": "8", - "description": "switch.md", - "completedDescription": "switch_complete.md", - "levelContract": "SwitchFactory.sol", - "instanceContract": "Switch.sol", - "revealCode": true, - "deployParams": [], - "deployFunds": 0, - "deployId": "29", - "instanceGas": 250000, - "author": "AgeManning" - } - ] -} + "levels": [ + { + "name": "Hello Ethernaut", + "created": "2017-11-01", + "difficulty": "0", + "description": "instances.md", + "completedDescription": "instances_complete.md", + "levelContract": "InstanceFactory.sol", + "instanceContract": "Instance.sol", + "revealCode": false, + "deployParams": [], + "deployFunds": 0, + "deployId": "0", + "instanceGas": 1500000, + "author": "ajsantander" + }, + { + "name": "Fallback", + "created": "2017-11-01", + "difficulty": "1", + "description": "fallback.md", + "completedDescription": "fallback_complete.md", + "levelContract": "FallbackFactory.sol", + "instanceContract": "Fallback.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "1", + "instanceGas": 450000, + "author": "ajsantander" + }, + { + "name": "Fallout", + "created": "2017-11-01", + "difficulty": "2", + "description": "fallout.md", + "completedDescription": "fallout_complete.md", + "levelContract": "FalloutFactory.sol", + "instanceContract": "Fallout.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "2", + "instanceGas": 450000, + "author": "ajsantander" + }, + { + "name": "Coin Flip", + "created": "2018-03-18", + "difficulty": "3", + "description": "coinflip.md", + "completedDescription": "coinflip_complete.md", + "levelContract": "CoinFlipFactory.sol", + "instanceContract": "CoinFlip.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "3", + "instanceGas": 320000, + "author": "syncikin" + }, + { + "name": "Telephone", + "created": "2018-03-18", + "difficulty": "1", + "description": "telephone.md", + "completedDescription": "telephone_complete.md", + "levelContract": "TelephoneFactory.sol", + "instanceContract": "Telephone.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "4", + "instanceGas": 230000, + "author": "syncikin" + }, + { + "name": "Token", + "created": "2017-11-01", + "difficulty": "3", + "description": "token.md", + "completedDescription": "token_complete.md", + "levelContract": "TokenFactory.sol", + "instanceContract": "Token.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "5", + "instanceGas": 330000, + "author": "ajsantander" + }, + { + "name": "Delegation", + "created": "2017-11-01", + "difficulty": "4", + "description": "delegate.md", + "completedDescription": "delegate_complete.md", + "levelContract": "DelegationFactory.sol", + "instanceContract": "Delegation.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "6", + "instanceGas": 250000, + "author": "ajsantander" + }, + { + "name": "Force", + "created": "2017-11-01", + "difficulty": "5", + "description": "force.md", + "completedDescription": "force_complete.md", + "levelContract": "ForceFactory.sol", + "instanceContract": "Force.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "7", + "instanceGas": 150000, + "author": "ajsantander" + }, + { + "name": "Vault", + "created": "2018-03-18", + "difficulty": "3", + "description": "vault.md", + "completedDescription": "vault_complete.md", + "levelContract": "VaultFactory.sol", + "instanceContract": "Vault.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "8", + "instanceGas": 200000, + "author": "syncikin" + }, + { + "name": "King", + "created": "2017-11-24", + "difficulty": "6", + "description": "king.md", + "completedDescription": "king_complete.md", + "levelContract": "KingFactory.sol", + "instanceContract": "King.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0.001, + "deployId": "9", + "instanceGas": 350000, + "author": "ajsantander" + }, + { + "name": "Re-entrancy", + "created": "2017-11-01", + "difficulty": "6", + "description": "reentrancy.md", + "completedDescription": "reentrancy_complete.md", + "levelContract": "ReentranceFactory.sol", + "instanceContract": "Reentrance.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0.001, + "deployId": "10", + "instanceGas": 350000, + "author": "ajsantander" + }, + { + "name": "Elevator", + "created": "2017-01-25", + "difficulty": "4", + "description": "elevator.md", + "completedDescription": "elevator_complete.md", + "levelContract": "ElevatorFactory.sol", + "instanceContract": "Elevator.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "11", + "instanceGas": 250000, + "author": "martriay" + }, + { + "name": "Privacy", + "created": "2018-04-06", + "difficulty": "6", + "description": "privacy.md", + "completedDescription": "privacy_complete.md", + "levelContract": "PrivacyFactory.sol", + "instanceContract": "Privacy.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "12", + "instanceGas": 320000, + "author": "ajsantander" + }, + { + "name": "Gatekeeper One", + "created": "2018-04-26", + "difficulty": "8", + "description": "gatekeeper1.md", + "completedDescription": "gatekeeper1_complete.md", + "levelContract": "GatekeeperOneFactory.sol", + "instanceContract": "GatekeeperOne.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "13", + "instanceGas": 400000, + "author": "0age", + "verificationDetails": { + "contractName": "GatekeeperOne", + "compilerVersion": "v0.8.12+commit.f00d7308", + "optimization": "1", + "runs": "1000", + "licenseTypeId": "3", + "constructorArguements": "" + } + }, + { + "name": "Gatekeeper Two", + "created": "2018-04-26", + "difficulty": "6", + "description": "gatekeeper2.md", + "completedDescription": "gatekeeper2_complete.md", + "levelContract": "GatekeeperTwoFactory.sol", + "instanceContract": "GatekeeperTwo.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "14", + "instanceGas": 250000, + "author": "0age" + }, + { + "name": "Naught Coin", + "created": "2018-03-24", + "difficulty": "5", + "description": "naughtcoin.md", + "completedDescription": "naughtcoin_complete.md", + "levelContract": "NaughtCoinFactory.sol", + "instanceContract": "NaughtCoin.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "15", + "instanceGas": 1300000, + "author": "syncikin" + }, + { + "name": "Preservation", + "created": "2018-05-27", + "difficulty": "8", + "description": "preservation.md", + "completedDescription": "preservation_complete.md", + "levelContract": "PreservationFactory.sol", + "instanceContract": "Preservation.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "16", + "instanceGas": 500000, + "author": "AgeManning" + }, + { + "name": "Recovery", + "created": "2018-05-20", + "difficulty": "6", + "description": "recovery.md", + "completedDescription": "recovery_complete.md", + "levelContract": "RecoveryFactory.sol", + "instanceContract": "Recovery.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0.001, + "deployId": "17", + "instanceGas": 1100000, + "author": "AgeManning" + }, + { + "name": "MagicNumber", + "created": "2018-08-24", + "difficulty": "6", + "description": "magicnum.md", + "completedDescription": "magicnum_complete.md", + "levelContract": "MagicNumFactory.sol", + "instanceContract": "MagicNum.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "18", + "instanceGas": 200000, + "author": "ajsantander" + }, + { + "name": "Alien Codex", + "created": "2018-10-11", + "difficulty": "7", + "description": "aliencodex.md", + "completedDescription": "aliencodex_complete.md", + "levelContract": "AlienCodexFactory.sol", + "instanceContract": "AlienCodex.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "19", + "instanceGas": 520000, + "author": "nczhu" + }, + { + "name": "Denial", + "created": "2018-10-20", + "difficulty": "5", + "description": "denial.md", + "completedDescription": "denial_complete.md", + "levelContract": "DenialFactory.sol", + "instanceContract": "Denial.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0.001, + "deployId": "20", + "instanceGas": 450000, + "author": "AgeManning" + }, + { + "name": "Shop", + "created": "2018-09-08", + "difficulty": "4", + "description": "shop.md", + "completedDescription": "shop_complete.md", + "levelContract": "ShopFactory.sol", + "instanceContract": "Shop.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "21", + "instanceGas": 250000, + "author": "34x4p08" + }, + { + "name": "Dex", + "created": "2021-03-06", + "difficulty": "3", + "description": "dex.md", + "completedDescription": "dex_complete.md", + "levelContract": "DexFactory.sol", + "instanceContract": "Dex.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "22", + "instanceGas": 3600000, + "author": "patrickalphac" + }, + { + "name": "Dex Two", + "created": "2021-08-20", + "difficulty": "4", + "description": "dex2.md", + "completedDescription": "dex2_complete.md", + "levelContract": "DexTwoFactory.sol", + "instanceContract": "DexTwo.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "23", + "instanceGas": 3600000, + "author": "scottt" + }, + { + "name": "Puzzle Wallet", + "created": "2021-10-05", + "difficulty": "7", + "description": "puzzle_wallet.md", + "completedDescription": "puzzle_wallet_complete.md", + "levelContract": "PuzzleWalletFactory.sol", + "instanceContract": "PuzzleWallet.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0.001, + "deployId": "24", + "instanceGas": 3600000, + "author": "openzeppelin" + }, + { + "name": "Motorbike", + "created": "2021-10-07", + "difficulty": "6", + "description": "motorbike.md", + "completedDescription": "motorbike_complete.md", + "levelContract": "MotorbikeFactory.sol", + "instanceContract": "Motorbike.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "25", + "instanceGas": 2000000, + "author": "openzeppelin" + }, + { + "name": "DoubleEntryPoint", + "created": "2022-03-21", + "difficulty": "4", + "description": "doubleentrypoint.md", + "completedDescription": "doubleentrypoint_complete.md", + "levelContract": "DoubleEntryPointFactory.sol", + "instanceContract": "DoubleEntryPoint.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "26", + "instanceGas": 5000000, + "poweredBy": { + "src": "../../imgs/forta.svg", + "href": "https://forta.org" + }, + "author": "openzeppelin&forta" + }, + { + "name": "Good Samaritan", + "created": "2022-8-4", + "difficulty": "5", + "description": "goodsamaritan.md", + "completedDescription": "goodsamaritan_complete.md", + "levelContract": "GoodSamaritanFactory.sol", + "instanceContract": "GoodSamaritan.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "27", + "instanceGas": 2500000, + "author": "ericnordelo" + }, + { + "name": "Gatekeeper Three", + "created": "2019-02-02", + "difficulty": "6", + "description": "gatekeeper3.md", + "completedDescription": "gatekeeper3_complete.md", + "levelContract": "GatekeeperThreeFactory.sol", + "instanceContract": "GatekeeperThree.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "28", + "instanceGas": 1000000, + "author": "KStasi" + }, + { + "name": "Switch", + "created": "2018-11-04", + "difficulty": "8", + "description": "switch.md", + "completedDescription": "switch_complete.md", + "levelContract": "SwitchFactory.sol", + "instanceContract": "Switch.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0, + "deployId": "29", + "instanceGas": 250000, + "author": "AgeManning" + }, + { + "name": "Crowdfunding", + "created": "2024-04-05", + "difficulty": "7", + "description": "crowdfunding.md", + "completedDescription": "crowdfunding_complete.md", + "levelContract": "CrowdfundingFactory.sol", + "instanceContract": "Crowdfunding.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 1, + "deployId": "30", + "instanceGas": 1000000, + "author": "eduardowestc" + } + ] +} \ No newline at end of file From 2bf8f085419b28a9e20017575776b5ecb955ced9 Mon Sep 17 00:00:00 2001 From: "Eduardo W. da Cunha" Date: Mon, 8 Apr 2024 15:00:05 -0300 Subject: [PATCH 3/4] added one more restriction in Crowdfunding-setArtist function --- contracts/src/levels/Crowdfunding.sol | 4 ++++ contracts/test/levels/Crowdfunding.t.sol | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/src/levels/Crowdfunding.sol b/contracts/src/levels/Crowdfunding.sol index 40fa598e2..2b482eb42 100644 --- a/contracts/src/levels/Crowdfunding.sol +++ b/contracts/src/levels/Crowdfunding.sol @@ -23,6 +23,10 @@ contract Crowdfunding { } function setArtist(address newArtist, bytes calldata signature) external { + require( + keccak256(signature) != keccak256(lastSignature), + "already used signature" + ); bytes32 nameHash = keccak256(abi.encodePacked(projectName)); bytes32 messageHash = keccak256( abi.encodePacked("\x19Ethereum Signed Message:\n32", nameHash) diff --git a/contracts/test/levels/Crowdfunding.t.sol b/contracts/test/levels/Crowdfunding.t.sol index 329caa6cf..cc1754988 100644 --- a/contracts/test/levels/Crowdfunding.t.sol +++ b/contracts/test/levels/Crowdfunding.t.sol @@ -24,13 +24,14 @@ contract TestCrowdfunding is Test, Utils { address payable[] memory users = createUsers(2); address initialArtist = users[0]; - vm.label(owner, "Owner"); + vm.label(initialArtist, "Initial Artist"); player = users[1]; vm.label(player, "Player"); (address owner_, uint256 pk) = makeAddrAndKey("owner"); owner = payable(owner_); + vm.label(owner, "Owner"); string memory projectName = "amazing crowdfunding"; bytes32 nameHash = keccak256(abi.encodePacked(projectName)); bytes32 messageHash = keccak256( From 71608d53d58ae8ea2ad56ebcc820b110198df4b0 Mon Sep 17 00:00:00 2001 From: "Eduardo W. da Cunha" Date: Mon, 8 Apr 2024 16:34:15 -0300 Subject: [PATCH 4/4] simplified contracts --- contracts/src/levels/Crowdfunding.sol | 2 +- contracts/src/levels/CrowdfundingFactory.sol | 13 ++++--------- contracts/test/levels/Crowdfunding.t.sol | 19 +++---------------- 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/contracts/src/levels/Crowdfunding.sol b/contracts/src/levels/Crowdfunding.sol index 2b482eb42..bf6c285a0 100644 --- a/contracts/src/levels/Crowdfunding.sol +++ b/contracts/src/levels/Crowdfunding.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.24; +pragma solidity ^0.8.0; contract Crowdfunding { address public owner; diff --git a/contracts/src/levels/CrowdfundingFactory.sol b/contracts/src/levels/CrowdfundingFactory.sol index d4a94d30f..591c4e9f7 100644 --- a/contracts/src/levels/CrowdfundingFactory.sol +++ b/contracts/src/levels/CrowdfundingFactory.sol @@ -6,15 +6,10 @@ import {Level} from "./base/Level.sol"; import {Crowdfunding} from "./Crowdfunding.sol"; contract CrowdfundingFactory is Level { - bytes public signature; - address public signer; - address public artist; - - constructor(bytes memory signature_, address signer_, address artist_) { - signature = signature_; - signer = signer_; - artist = artist_; - } + bytes public signature = + hex"7986fd095b20021de58a0c43a03a9f18204bc4f0e05d2624cb539174e73e0a4c048155b5a878c337217db7244af6b3930a6ee90ffba2d488f3bce6f7258ca2251c"; + address public signer = 0x7c8999dC9a822c1f0Df42023113EDB4FDd543266; + address public artist = 0x9aF2E2B7e57c1CD7C68C5C3796d8ea67e0018dB7; function createInstance( address _player diff --git a/contracts/test/levels/Crowdfunding.t.sol b/contracts/test/levels/Crowdfunding.t.sol index cc1754988..9fc19f169 100644 --- a/contracts/test/levels/Crowdfunding.t.sol +++ b/contracts/test/levels/Crowdfunding.t.sol @@ -21,7 +21,7 @@ contract TestCrowdfunding is Test, Utils { //////////////////////////////////////////////////////////////*/ function setUp() public { - address payable[] memory users = createUsers(2); + address payable[] memory users = createUsers(3); address initialArtist = users[0]; vm.label(initialArtist, "Initial Artist"); @@ -29,25 +29,12 @@ contract TestCrowdfunding is Test, Utils { player = users[1]; vm.label(player, "Player"); - (address owner_, uint256 pk) = makeAddrAndKey("owner"); - owner = payable(owner_); + owner = users[2]; vm.label(owner, "Owner"); - string memory projectName = "amazing crowdfunding"; - bytes32 nameHash = keccak256(abi.encodePacked(projectName)); - bytes32 messageHash = keccak256( - abi.encodePacked("\x19Ethereum Signed Message:\n32", nameHash) - ); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, messageHash); - bytes memory signature = abi.encodePacked(r, s, v); vm.startPrank(owner); ethernaut = getEthernautWithStatsProxy(owner); - CrowdfundingFactory factory = new CrowdfundingFactory( - signature, - owner, - initialArtist - ); + CrowdfundingFactory factory = new CrowdfundingFactory(); ethernaut.registerLevel(Level(address(factory))); vm.stopPrank();