diff --git a/client/src/gamedata/authors.json b/client/src/gamedata/authors.json index 4f5795c0e..4ec9b0712 100644 --- a/client/src/gamedata/authors.json +++ b/client/src/gamedata/authors.json @@ -147,6 +147,18 @@ "websites": [ "https://www.linkedin.com/in/kstasi/" ] + }, + "OrangeSantra": { + "name": [ + "Orange Santra" + ], + "emails": [ + "soliditydevxyz@gmail.com" + ], + "websites": [ + "https://twitter.com/0xorangesantra" + ], + "donate": "0xc67ADEAeCeEE33FD97a7d7C83755dC3759159884" } } } \ No newline at end of file diff --git a/client/src/gamedata/en/descriptions/levels/bankbuilder.md b/client/src/gamedata/en/descriptions/levels/bankbuilder.md new file mode 100644 index 000000000..1986aa61f --- /dev/null +++ b/client/src/gamedata/en/descriptions/levels/bankbuilder.md @@ -0,0 +1,15 @@ + BankBuilder is the contract used to deploy Bank Contract. When BankBuilder instance is created, Bank contract is also deployed with 0.001 ethers. + + Bank Contract can deploy Recipent contract. + + Bank Contract has grouped the list of 10 addresses of different Recipent contracts in order, but not deployed yet. The address of Recipent contract to be deployed first is at position no. 1 on list, the address to be deployed second is on position no. 2 on list and so on. + + Accidently when Bank Contract is deployed all the funds in Bank Contract is transfered to 5th Recipent address. + Now the funds are stuk on 5th Recipent address. + + Your Goal is to retrive the funds back to BankBuilder contract ! + +  +Things that might help +* Understanding how address of contract is computed. +* Understanding how catching of one datatype to other works. \ No newline at end of file diff --git a/client/src/gamedata/en/descriptions/levels/bankbuilder_complete.md b/client/src/gamedata/en/descriptions/levels/bankbuilder_complete.md new file mode 100644 index 000000000..1d0f2c10b --- /dev/null +++ b/client/src/gamedata/en/descriptions/levels/bankbuilder_complete.md @@ -0,0 +1,7 @@ +When the address of a contract to be deployed using method create or create2 is pre-known before it's deployment and the address have some funds stuck in it, the funds can be retrived back under some conditions. For create method the two pre-requists are address of deployer and nonce of current transaction, where as for create2 the pre-requists are contract's creation code, address of deployer and salt (arbitrary bytes32 data). + +The base of challange is how create and create2 generates the contract's address. Please refer these relevent links:- + +* [Create and Create2](https://medium.com/@coiiasd88/how-to-use-solidity-create-and-create2-792d22dbd573) + +* [How address of a contract computed](https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed) \ No newline at end of file diff --git a/client/src/gamedata/gamedata.json b/client/src/gamedata/gamedata.json index 3a959ec8f..6010c3d2c 100644 --- a/client/src/gamedata/gamedata.json +++ b/client/src/gamedata/gamedata.json @@ -461,6 +461,21 @@ "deployId": "29", "instanceGas": 250000, "author": "AgeManning" + }, + { + "name": "BankBuilder", + "created": "2024-04-11", + "difficulty": "6", + "description": "bankbuilder.md", + "completedDescription": "bankbuilder_complete.md", + "levelContract": "BankBuilderFactory.sol", + "instanceContract": "BankBuilder.sol", + "revealCode": true, + "deployParams": [], + "deployFunds": 0.001, + "deployId": "30", + "instanceGas": 3600000, + "author": "OrangeSantra" } ] } diff --git a/contracts/src/levels/BankBuilder.sol b/contracts/src/levels/BankBuilder.sol new file mode 100644 index 000000000..e5903badc --- /dev/null +++ b/contracts/src/levels/BankBuilder.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract BankBuilder { + uint256 public count; + + function deployBankContract(bytes32 salt, address payable xyz) public payable{ + require(count==0, "Can only be called once"); + // Here salt is catched form of integer 123 in bytes32; + Bank bank = new Bank{salt: salt, value: msg.value}(); + bank.transferFunds(xyz); + count++; + } + +} + + +contract Recipient { + address public desiredRecipintAddress; + constructor(address _recipintAddress) { + desiredRecipintAddress = _recipintAddress; + } + + function killcontract(address payable to) external { + require(address(this) == desiredRecipintAddress); + selfdestruct(payable(to)); + } + +} + +contract Bank { + address public owner; + constructor() payable { + owner = msg.sender; + } + + function transferFunds(address payable addr) public{ + require(msg.sender==owner, "can only be called by owner"); + payable(addr).transfer(address(this).balance); + } + + function deployRecipient(address recipintAddress) public payable returns(address) { + return address(new Recipient(recipintAddress)); + } + +} + + diff --git a/contracts/src/levels/BankBuilderFactory.sol b/contracts/src/levels/BankBuilderFactory.sol new file mode 100644 index 000000000..b5bd45c88 --- /dev/null +++ b/contracts/src/levels/BankBuilderFactory.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./base/Level.sol"; +import "./BankBuilder.sol"; + +contract BankBuilderFactory is Level { + BankBuilder private bankBuilderInstance; + Bank private bankInstance; + uint256 public initialDeposit = 0.001 ether; + function createInstance(address _player) public payable override returns (address) { + _player; + require(msg.value >= initialDeposit); + bankBuilderInstance = new BankBuilder(); + bankBuilderInstance.deployBankContract{value: msg.value}(getBytes32(), payable(generateAddressofRecipient(generateAddressofBank(getBytes32())))); + return address(bankBuilderInstance); + } + + function generateAddressofRecipient(address sender) internal returns (address) { + bytes32 hash = keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), sender, bytes1(0x05))); + address addr = address(uint160(uint256(hash))); + return addr; + } + + function generateAddressofBank(bytes32 salt) public view returns (address) { + address str = address(uint160(uint(keccak256(abi.encodePacked( + bytes1(0xff), + address(bankBuilderInstance), + salt, + keccak256(abi.encodePacked( + type(Bank).creationCode + )) + ))))); + + return str; + } + + function getBytes32() internal pure returns (bytes32) { + uint256 salt = 123; // fixed + return bytes32(salt); + } + + function validateInstance(address payable _instance, address _player) public override returns (bool) { + // _player; + return address(bankBuilderInstance).balance >= initialDeposit; + } + +} diff --git a/contracts/test/levels/BankBuilder.t.sol b/contracts/test/levels/BankBuilder.t.sol new file mode 100644 index 000000000..a6029c2d5 --- /dev/null +++ b/contracts/test/levels/BankBuilder.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import {Utils} from "test/utils/Utils.sol"; + +import {BankBuilder} from "src/levels/BankBuilder.sol"; +import {Bank} from "src/levels/BankBuilder.sol"; +import {Recipient} from "src/levels/BankBuilder.sol"; +import {BankBuilderFactory} from "src/levels/BankBuilderFactory.sol"; +import {Level} from "src/levels/base/Level.sol"; +import {Ethernaut} from "src/Ethernaut.sol"; + +contract TestBankBuilder is Test, Utils { + Ethernaut ethernaut; + BankBuilder instance; + /*////////////////////////////////////////////////////////////// + HELPERS + //////////////////////////////////////////////////////////////*/ + + address payable deployer; + address payable player; + + function setUp() public { + address payable[] memory users = createUsers(2); + + deployer = users[0]; + vm.label(deployer, "Deployer"); + deal(deployer, 10 ether); + + player = users[1]; + vm.label(player, "player"); + deal(player, 10 ether); + + vm.startPrank(deployer); + ethernaut = getEthernautWithStatsProxy(deployer); + BankBuilderFactory factory = new BankBuilderFactory(); + ethernaut.registerLevel(Level(address(factory))); + vm.stopPrank(); + + vm.startPrank(player); + instance = BankBuilder(payable(createLevelInstance(ethernaut, Level(address(factory)), 0.001 ether))); + console.log("Count Value",instance.count()); + vm.stopPrank(); + } + + function testInit() public { + vm.startPrank(player); + assertFalse(submitLevelInstance(ethernaut, address(instance))); + } + + function testSolve() public { + vm.startPrank(player); + Hack hack = new Hack(address(instance)); + hack.PerformAttack(); + assertTrue(submitLevelInstance(ethernaut, address(instance))); + } +} + +contract Hack { + + Bank private bank; + BankBuilder private bankbuilder; + Recipient private recipient; + address public addr; + + constructor(address _target){ + bankbuilder = BankBuilder(payable(_target)); + } + + function PerformAttack() public { + bank= Bank(generateAddressUsingCreate2(getBytes32(123))); + + for (uint256 i=0; i<5; i++){ + addr = bank.deployRecipient(generateAddressUsingCreate(address(bank))); + } + recipient = Recipient(addr); + recipient.killcontract(payable(address(bankbuilder))); + + require(address(bankbuilder).balance>=0.001 ether,"wtf!"); + } + + function getBytes32(uint256 salt) internal pure returns (bytes32) { + return bytes32(salt); + } + + function generateAddressUsingCreate(address sender) internal pure returns (address) { + bytes32 hash = keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), sender, bytes1(0x05))); + address addrA = address(uint160(uint256(hash))); + return addrA; + } + + function generateAddressUsingCreate2(bytes32 salt) public view returns (address) { + address str = address(uint160(uint(keccak256(abi.encodePacked( + bytes1(0xff), + address(bankbuilder), + salt, + keccak256(abi.encodePacked( + type(Bank).creationCode + )) + ))))); + + return str; + } + +} \ No newline at end of file