Skip to content

Commit e217f64

Browse files
committed
feat: add EVM assembly programming with Huff
1 parent 5af5c21 commit e217f64

File tree

13 files changed

+548
-0
lines changed

13 files changed

+548
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
course/**/_*
55
course/**/*Solution.sol
66
course/**/*Solution.t.sol
7+
course/**/*Solution.huff
78

89
# Foundry
910
.github/workflows/test.yml

course/evm-with-huff/README.md

+327
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#define macro MAIN() = takes (0) returns (0) {
2+
////////// YOUR CODE GOES HERE //////////
3+
4+
////////// YOUR CODE END //////////
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {HuffUtils} from "../huff-utils/HuffUtils.sol";
5+
import "forge-std/Test.sol";
6+
7+
contract SolverTest is Test {
8+
function test() public {
9+
address solverAddress = new HuffUtils().deploy("course/evm-with-huff/challenge-even/EvenSolver.huff");
10+
_test(solverAddress, 11);
11+
}
12+
13+
function _test(address solverAddress, uint256 requiredSize) private {
14+
for (uint256 i = 0; i < 100; i++) {
15+
(bool success, bytes memory data) = solverAddress.call(abi.encode(i));
16+
require(success);
17+
uint256 isEven = abi.decode(data, (uint256));
18+
assertEq(isEven, uint256((i + 1) % 2));
19+
}
20+
uint256 size = solverAddress.code.length;
21+
emit log_named_uint("Solver code length", size);
22+
assertTrue(size <= requiredSize);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#define macro MAIN() = takes (0) returns (0) {
2+
////////// YOUR CODE GOES HERE //////////
3+
0x2a // [0x2a]
4+
0x00 // [0x00, 0x2a]
5+
mstore // []
6+
0x20 // [0x20]
7+
0x00 // [0x00, 0x20]
8+
return // []
9+
////////// YOUR CODE END //////////
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {HuffUtils} from "../huff-utils/HuffUtils.sol";
5+
import "forge-std/Test.sol";
6+
import {MagicNumberSolverNaive} from "./MagicNumberSolverNaive.sol";
7+
8+
contract SolverTest is Test {
9+
string constant HUFF_FILE = "course/evm-with-huff/challenge-magic-number/MagicNumberSolver.huff";
10+
11+
function test10Bytes() public {
12+
address solverAddress = new HuffUtils().deploy(HUFF_FILE);
13+
_test(solverAddress, 10);
14+
}
15+
16+
function test7Bytes() public {
17+
address solverAddress = new HuffUtils().deploy(HUFF_FILE);
18+
_test(solverAddress, 7);
19+
}
20+
21+
function testNaive() public {
22+
address solverAddress = address(new MagicNumberSolverNaive());
23+
_test(solverAddress, type(uint256).max);
24+
}
25+
26+
function _test(address solverAddress, uint256 requiredSize) private {
27+
Solver solver = Solver(solverAddress);
28+
29+
uint256 magic = solver.whatIsTheMeaningOfLife();
30+
assertEq(magic, 0x2a);
31+
32+
uint256 size = solverAddress.code.length;
33+
emit log_named_uint("Solver code length", size);
34+
assertTrue(size <= requiredSize);
35+
}
36+
}
37+
38+
interface Solver {
39+
function whatIsTheMeaningOfLife() external returns (uint256);
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
contract MagicNumberSolverNaive {
5+
function whatIsTheMeaningOfLife() public pure returns (uint256) {
6+
return 42;
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#define macro MAIN() = takes (0) returns (0) {
2+
////////// YOUR CODE GOES HERE //////////
3+
4+
////////// YOUR CODE END //////////
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {HuffUtils} from "../huff-utils/HuffUtils.sol";
5+
import "forge-std/Test.sol";
6+
7+
contract SolverTest is Test {
8+
function test() public {
9+
bytes memory code = new HuffUtils().compileRuntime("course/evm-with-huff/challenge-quine-hard/QuineSolver.huff");
10+
11+
assertTrue(code.length > 0, "code.length == 0");
12+
assertTrue(check(code), "disallowed instruction");
13+
14+
address addr;
15+
assembly {
16+
addr := create(0, add(code, 0x20), mload(code))
17+
}
18+
assertTrue(keccak256(code) == addr.codehash, "code != addr.codehash");
19+
20+
(bool success, bytes memory result) = addr.staticcall("");
21+
assertTrue(success, "staticcall failed");
22+
assertTrue(keccak256(result) == addr.codehash, "return data != addr.codehash");
23+
24+
emit log_named_uint("Solver code length", code.length);
25+
assertTrue(code.length <= 33);
26+
}
27+
28+
function check(bytes memory code) private pure returns (bool) {
29+
for (uint256 i = 0; i < code.length; i++) {
30+
uint8 op = uint8(code[i]);
31+
32+
if (op >= 0x30 && op <= 0x48) {
33+
return false;
34+
}
35+
36+
if (
37+
op == 0x54 // SLOAD
38+
|| op == 0x55 // SSTORE
39+
|| op == 0xF0 // CREATE
40+
|| op == 0xF1 // CALL
41+
|| op == 0xF2 // CALLCODE
42+
|| op == 0xF4 // DELEGATECALL
43+
|| op == 0xF5 // CREATE2
44+
|| op == 0xFA // STATICCALL
45+
|| op == 0xFF // SELFDESTRUCT
46+
) {
47+
return false;
48+
}
49+
50+
// PUSH
51+
if (op >= 0x60 && op < 0x80) {
52+
i += (op - 0x60) + 1;
53+
}
54+
}
55+
56+
return true;
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#define macro MAIN() = takes (0) returns (0) {
2+
////////// YOUR CODE GOES HERE //////////
3+
4+
////////// YOUR CODE END //////////
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.20;
3+
4+
import {HuffUtils} from "../huff-utils/HuffUtils.sol";
5+
import "forge-std/Test.sol";
6+
7+
contract SolverTest is Test {
8+
function test() public {
9+
bytes memory code = new HuffUtils().compileRuntime("course/evm-with-huff/challenge-quine/QuineSolver.huff");
10+
11+
assertTrue(code.length > 0, "code.length == 0");
12+
13+
address addr;
14+
assembly {
15+
addr := create(0, add(code, 0x20), mload(code))
16+
}
17+
assertTrue(keccak256(code) == addr.codehash, "code != addr.codehash");
18+
19+
(bool success, bytes memory result) = addr.staticcall("");
20+
assertTrue(success, "staticcall failed");
21+
assertTrue(keccak256(result) == addr.codehash, "return data != addr.codehash");
22+
23+
emit log_named_uint("Solver code length", code.length);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity >=0.8.13 <0.9.0;
3+
4+
import {Vm} from "forge-std/Vm.sol";
5+
6+
contract HuffUtils {
7+
Vm public constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))));
8+
9+
function deploy(string memory fileName) public payable returns (address addr) {
10+
bytes memory code = compile(fileName);
11+
uint256 value = msg.value;
12+
13+
vm.prank(msg.sender);
14+
assembly {
15+
addr := create(value, add(code, 0x20), mload(code))
16+
}
17+
}
18+
19+
function compile(string memory fileName) public payable returns (bytes memory code) {
20+
string[] memory cmds = new string[](3);
21+
cmds[0] = "huffc";
22+
cmds[1] = string(fileName);
23+
cmds[2] = "-b";
24+
code = vm.ffi(cmds);
25+
}
26+
27+
function compileRuntime(string memory fileName) public payable returns (bytes memory code) {
28+
string[] memory cmds = new string[](3);
29+
cmds[0] = "huffc";
30+
cmds[1] = string(fileName);
31+
cmds[2] = "-r";
32+
code = vm.ffi(cmds);
33+
}
34+
}

foundry.toml

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ src = "course"
33
out = "out"
44
libs = ["lib"]
55
match_path = "*/*.t.sol"
6+
ffi = true
7+
evm_version = "shanghai"
8+
ignored_error_codes = [
9+
2519, # Warning (2519): This declaration shadows an existing declaration.
10+
1878 # Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.
11+
]
612

713
[rpc_endpoints]
814
mainnet = "https://rpc.ankr.com/eth"

0 commit comments

Comments
 (0)