Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for a new level [33] [BankBuilder]. #722

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions client/src/gamedata/authors.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
15 changes: 15 additions & 0 deletions client/src/gamedata/en/descriptions/levels/bankbuilder.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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)
15 changes: 15 additions & 0 deletions client/src/gamedata/gamedata.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}
48 changes: 48 additions & 0 deletions contracts/src/levels/BankBuilder.sol
Original file line number Diff line number Diff line change
@@ -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));
}

}


49 changes: 49 additions & 0 deletions contracts/src/levels/BankBuilderFactory.sol
Original file line number Diff line number Diff line change
@@ -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;
}

}
106 changes: 106 additions & 0 deletions contracts/test/levels/BankBuilder.t.sol
Original file line number Diff line number Diff line change
@@ -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;
}

}