1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.17;3
4import "memmove/Mapping.sol";5import "solvm/Evm.sol";6
7struct Solution {8 uint8 stop;9 uint8 ctxSetting;10 bytes code;11}12
13contract MiniMutant is IPuzzle {14 using EvmLib for Evm;15 using MappingLib for Mapping;16
17 function name() external pure returns (string memory) {18 return "Mini Mutant";19 }20
21 function generate(address _seed) external returns (uint256) {22 return uint256(keccak256(abi.encodePacked(_seed)));23 }24
25 function verify(uint256 _start, uint256 _solution) external returns (bool) {26
27 Solution memory sol = Solution({28 stop: 0,29 ctxSetting: 0,30 code: abi.encodePacked(_solution)31 });32
33 address origin = tx.origin;34 address caller = msg.sender;35 address execution_address = address(this);36 bytes memory calld = abi.encodePacked(_start);37
38 while (sol.stop == 0) {39 if (sol.ctxSetting == 0) {40 origin = tx.origin;41 caller = msg.sender;42 execution_address = address(this);43 } else if (sol.ctxSetting == 1) {44 origin = msg.sender;45 caller = address(this);46 }47
48 (bool succ, bytes memory ret) = this.run(origin, caller, execution_address, calld, sol.code);49 require(succ, "Script failed");50 require(ret.length > 2, "Bad return length");51 sol.stop = uint8(ret[0]);52 sol.ctxSetting = uint8(ret[1]);53 sol.code = slice(ret, 2, ret.length - 2);54 }55
56 (bool succ, bytes memory ret) = this.run(origin, caller, execution_address, calld, sol.code);57 require(succ, "Script failed");58 uint256 result = abi.decode(ret, (uint256));59 require(result == _start, "Fail");60 return true;61 }62
63 function run(64 address origin,65 address caller,66 address execution_address,67 bytes calldata calld,68 bytes calldata executionCode69 ) external returns (bool, bytes memory) {70 uint256 fee;71 uint256 id;72 assembly("memory-safe") {73 fee := basefee()74 id := chainid()75 }76 EvmContext memory ctx = EvmContext({77 origin: origin,78 caller: caller,79 execution_address: execution_address,80 callvalue: 0,81 coinbase: block.coinbase,82 timestamp: block.timestamp,83 number: block.number,84 gaslimit: block.gaslimit,85 difficulty: block.difficulty,86 chainid: id,87 basefee: fee,88 balances: MappingLib.newMapping(1),89 calld: calld90 });91 Evm evm = EvmLib.newEvm(ctx);92
93 // NOTE: No frame switching opcodes are supported (i.e. Call, Create, etc.)94 return evm.evaluate(executionCode);95 }96
97 function slice(98 bytes memory _bytes,99 uint256 _start,100 uint256 _length101 )102 internal103 pure104 returns (bytes memory)105 {106 require(_length + 31 >= _length, "slice_overflow");107 require(_bytes.length >= _start + _length, "slice_outOfBounds");108
109 bytes memory tempBytes;110
111 assembly {112 switch iszero(_length)113 case 0 {114 // Get a location of some free memory and store it in tempBytes as115 // Solidity does for memory variables.116 tempBytes := mload(0x40)117
118 // The first word of the slice result is potentially a partial119 // word read from the original array. To read it, we calculate120 // the length of that partial word and start copying that many121 // bytes into the array. The first word we copy will start with122 // data we don't care about, but the last `lengthmod` bytes will123 // land at the beginning of the contents of the new array. When124 // we're done copying, we overwrite the full first word with125 // the actual length of the slice.126 let lengthmod := and(_length, 31)127
128 // The multiplication in the next line is necessary129 // because when slicing multiples of 32 bytes (lengthmod == 0)130 // the following copy loop was copying the origin's length131 // and then ending prematurely not copying everything it should.132 let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))133 let end := add(mc, _length)134
135 for {136 // The multiplication in the next line has the same exact purpose137 // as the one above.138 let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)139 } lt(mc, end) {140 mc := add(mc, 0x20)141 cc := add(cc, 0x20)142 } {143 mstore(mc, mload(cc))144 }145
146 mstore(tempBytes, _length)147
148 //update free-memory pointer149 //allocating the array padded to 32 bytes like the compiler does now150 mstore(0x40, and(add(mc, 31), not(31)))151 }152 //if we want a zero-length slice let's just return a zero-length array153 default {154 tempBytes := mload(0x40)155 //zero out the 32 bytes slice we are about to return156 //we need to do it because Solidity does not garbage collect157 mstore(tempBytes, 0)158
159 mstore(0x40, add(tempBytes, 0x20))160 }161 }162
163 return tempBytes;164 }165}166
167interface IPuzzle {168 /// @notice Returns the puzzle's name.169 /// @return The puzzle's name.170 function name() external pure returns (string memory);171
172 /// @notice Generates the puzzle's starting position based on a seed.173 /// @dev The seed is intended to be `msg.sender` of some wrapper function or174 /// call.175 /// @param _seed The seed to use to generate the puzzle.176 /// @return The puzzle's starting position.177 function generate(address _seed) external returns (uint256);178
179 /// @notice Verifies that a solution is valid for the puzzle.180 /// @dev `_start` is intended to be an output from {IPuzzle-generate}.181 /// @param _start The puzzle's starting position.182 /// @param _solution The solution to the puzzle.183 /// @return Whether the solution is valid.184 function verify(uint256 _start, uint256 _solution) external returns (bool);185}186
Time Left
Solve locally (WIP)
- Clone GitHub repo + install deps
git clone https://github.com/waterfall-mkt/curta-puzzles.git && cd curta-puzzles && forge install
- Set
RPC_URL_MAINNET
in.env
.env
RPC_URL_MAINNET=""
- Write solution + run script
forge script <PATH_TO_PUZZLE> -f mainnet -vvv
This is still WIP.