├── assets ├── huff.jpg └── huff.png ├── test ├── factories │ ├── Factory.t.sol │ └── ProxyFactory.t.sol ├── auth │ ├── mocks │ │ ├── AuthWrappers.huff │ │ ├── OwnedWrappers.huff │ │ └── RolesAuthorityWrappers.huff │ ├── NonPayable.t.sol │ ├── OnlyContract.t.sol │ └── Owned.t.sol ├── tokens │ └── mocks │ │ ├── ERC20Wrappers.huff │ │ ├── ERC20MintableWrappers.huff │ │ ├── ERC1155Wrappers.huff │ │ └── ERC4626Wrappers.huff ├── utils │ ├── mocks │ │ ├── MerkleDistributorWrappers.huff │ │ ├── TSOwnableWrappers.huff │ │ ├── EthersWrappers.huff │ │ ├── MerkleProofLibWrappers.huff │ │ ├── RefundedWrappers.huff │ │ ├── ShufflingWrappers.huff │ │ ├── InsertionSortWrappers.huff │ │ ├── ReentrancyGuardWrappers.huff │ │ ├── PausableWrappers.huff │ │ ├── Create3Wrappers.huff │ │ ├── LibBitWrappers.huff │ │ ├── SafeTransferLibWrappers.huff │ │ ├── SSTORE2Wrappers.huff │ │ ├── CallWrappers.huff │ │ ├── BitPackLibWrappers.huff │ │ ├── ECDSAWrappers.huff │ │ ├── JumpTableUtilWrappers.huff │ │ └── MulticallableWrappers.huff │ ├── safe-transfer-lib-mocks │ │ ├── mocks │ │ │ └── MockERC20.sol │ │ └── weird-tokens │ │ │ ├── RevertingToken.sol │ │ │ ├── ReturnsTwoToken.sol │ │ │ ├── ReturnsFalseToken.sol │ │ │ ├── ReturnsTooLittleToken.sol │ │ │ ├── MissingReturnToken.sol │ │ │ ├── ReturnsTooMuchToken.sol │ │ │ └── ReturnsGarbageToken.sol │ ├── Calls.t.sol │ ├── Shuffling.t.sol │ ├── Pausable.t.sol │ ├── InsertionSort.t.sol │ ├── ReentrancyGuard.t.sol │ ├── Ethers.t.sol │ ├── Refunded.t.sol │ ├── JumpTableUtil.t.sol │ ├── TSOwnable.t.sol │ ├── MerkleProofLib.t.sol │ ├── LibBit.t.sol │ ├── BitPackLib.t.sol │ ├── CREATE3.t.sol │ └── MerkleDistributor.t.sol ├── proxies │ ├── mocks │ │ ├── NotUUPSMockProxiableUUID.sol │ │ ├── MockProxiableUUID.sol │ │ ├── MockReturner.sol │ │ ├── MockBeacon.sol │ │ ├── ClonesWrappers.huff │ │ └── ProxyWrappers.huff │ └── Clones.t.sol ├── test-utils │ ├── FuzzingUtils.sol │ └── NonMatchingSelectorHelper.sol ├── mechanisms │ ├── huff-clones │ │ ├── Interfaces.sol │ │ ├── HuffClone.t.sol │ │ └── HuffCloneFactory.t.sol │ └── huff-vrgda │ │ ├── mocks │ │ ├── LinearVRGDAWrappers.huff │ │ └── LogisticVRGDAWrappers.huff │ │ └── LinearVRGDA.t.sol ├── math │ ├── mocks │ │ ├── TrigonometryWrappers.huff │ │ ├── MathWrappers.huff │ │ └── SafeMathWrappers.huff │ └── SafeMath.t.sol └── data-structures │ ├── mocks │ └── ArrayWrappers.huff │ ├── Arrays.t.sol │ ├── Hashmap.t.sol │ └── Bytes.t.sol ├── .gitignore ├── .gitmodules ├── src ├── utils │ ├── ERC1155Receiver.huff │ ├── Address.huff │ ├── Ethers.huff │ ├── BitPackLib.huff │ ├── Ternary.huff │ ├── Shuffling.huff │ ├── ReentrancyGuard.huff │ ├── ERC20Transfer.huff │ ├── JumpTableUtil.huff │ ├── Refunded.huff │ ├── Pausable.huff │ ├── InsertionSort.huff │ ├── SSTORE2.huff │ └── MerkleProofLib.huff ├── auth │ ├── NonPayable.huff │ ├── OnlyContract.huff │ └── Owned.huff ├── proxies │ ├── ERC1967Proxy.huff │ └── Proxy.huff ├── mechanisms │ ├── huff-vrgda │ │ ├── LinearVRGDA.huff │ │ ├── LogisticVRGDA.huff │ │ └── VRGDA.huff │ └── huff-clones │ │ ├── README.md │ │ ├── ExampleClone.huff │ │ ├── ExampleCloneFactory.huff │ │ └── HuffClone.huff ├── tokens │ └── interfaces │ │ └── IERC20.sol ├── math │ └── SafeMath.huff └── data-structures │ └── Arrays.huff ├── LICENSE ├── .github └── workflows │ └── test.yml └── foundry.toml /assets/huff.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huff-language/huffmate/HEAD/assets/huff.jpg -------------------------------------------------------------------------------- /assets/huff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huff-language/huffmate/HEAD/assets/huff.png -------------------------------------------------------------------------------- /test/factories/Factory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | -------------------------------------------------------------------------------- /test/factories/ProxyFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | 4 | # Foundry 5 | cache/ 6 | out/ 7 | 8 | # huffc output 9 | artifacts/ 10 | 11 | .vscode -------------------------------------------------------------------------------- /test/auth/mocks/AuthWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 3 | AUTH_CONSTRUCTOR() 4 | } 5 | 6 | #define macro MAIN() = takes (0) returns (0) { 7 | pc calldataload 0xe0 shr 8 | AUTH_MAIN() 9 | 0x00 dup1 revert 10 | } 11 | -------------------------------------------------------------------------------- /test/auth/mocks/OwnedWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 3 | OWNED_CONSTRUCTOR() 4 | } 5 | 6 | #define macro MAIN() = takes (0) returns (0) { 7 | pc calldataload 0xe0 shr 8 | OWNED_MAIN() 9 | 0x00 dup1 revert 10 | } 11 | -------------------------------------------------------------------------------- /test/tokens/mocks/ERC20Wrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 3 | ERC20_CONSTRUCTOR() 4 | } 5 | 6 | #define macro MAIN() = { 7 | 0x00 calldataload 0xE0 shr // [func sig] 8 | ERC20_MAIN() 9 | 0x00 dup1 revert 10 | } 11 | -------------------------------------------------------------------------------- /test/auth/mocks/RolesAuthorityWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 3 | AUTH_CONSTRUCTOR() 4 | } 5 | 6 | #define macro MAIN() = takes (0) returns (0) { 7 | pc calldataload 0xe0 shr 8 | ROLES_AUTHORITY_MAIN() 9 | 0x00 dup1 revert 10 | } 11 | -------------------------------------------------------------------------------- /test/utils/mocks/MerkleDistributorWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 3 | MERKLE_DISTRIBUTOR_CONSTRUCTOR() 4 | } 5 | 6 | #define macro MAIN() = takes (0) returns (0) { 7 | pc calldataload 0xE0 shr 8 | MERKLE_DISTRIBUTOR_MAIN() 9 | 0x00 dup1 revert 10 | } 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solmate"] 5 | path = lib/solmate 6 | url = https://github.com/rari-capital/solmate 7 | [submodule "lib/foundry-huff"] 8 | path = lib/foundry-huff 9 | url = https://github.com/huff-language/foundry-huff 10 | -------------------------------------------------------------------------------- /test/utils/mocks/TSOwnableWrappers.huff: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 4 | TSOWNABLE_CONSTRUCTOR() 5 | } 6 | 7 | #define macro MAIN() = takes (0) returns (0) { 8 | pc calldataload 0xe0 shr // [sig] 9 | TSOWNABLE_MAIN() // [sig] 10 | 0x00 dup1 revert // [] 11 | } -------------------------------------------------------------------------------- /test/proxies/mocks/NotUUPSMockProxiableUUID.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.15; 2 | 3 | 4 | import { MockERC1155 } from "solmate/test/utils/mocks/MockERC1155.sol"; 5 | 6 | 7 | contract NotUUPSMockProxiableUUID is MockERC1155 { 8 | bytes32 public uuid; 9 | 10 | function proxiableUUID() external view returns (bytes32 slot) { 11 | assembly { 12 | slot := uuid.slot 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/proxies/mocks/MockProxiableUUID.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.15; 2 | 3 | import { MockERC1155 } from "solmate/test/utils/mocks/MockERC1155.sol"; 4 | 5 | contract MockProxiableUUID is MockERC1155 { 6 | bytes32 public uuid; 7 | 8 | bytes32 constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 9 | 10 | function proxiableUUID() external view returns (bytes32 slot) { 11 | return _IMPLEMENTATION_SLOT; 12 | } 13 | } -------------------------------------------------------------------------------- /test/proxies/mocks/MockReturner.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.15; 2 | 3 | /// @dev Used to test proxies that they can both receive and return data 4 | contract MockReturner { 5 | 6 | function returnBytes(bytes calldata value) public pure returns (bytes calldata) { 7 | return value; 8 | } 9 | 10 | function returnUint(uint256 value) public pure returns (uint256) { 11 | return value; 12 | } 13 | 14 | function returnAddress(address value) public pure returns (address) { 15 | return value; 16 | } 17 | } -------------------------------------------------------------------------------- /src/utils/ERC1155Receiver.huff: -------------------------------------------------------------------------------- 1 | /// @title ERC1155 Receiver 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice A minimal interface for receiving ERC1155 tokens. 5 | 6 | /// @notice On ERC1155 Received 7 | /// @notice Returns the function selector for `onERC1155Received` 8 | #define macro ON_ERC1155_RECEIVED() = takes (0) returns (0) { 9 | // 0xf23a6e61 is the 4byte function selector for `onERC1155Received` 10 | 0xf23a6e61 0x00 mstore // [] 11 | 0x20 0x00 return // [] 12 | } 13 | -------------------------------------------------------------------------------- /test/test-utils/FuzzingUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | // adapter from: https://github.com/transmissions11/solmate/blob/main/src/test/utils/DSTestPlus.sol 5 | abstract contract FuzzingUtils { 6 | function min3( 7 | uint256 a, 8 | uint256 b, 9 | uint256 c 10 | ) internal pure returns (uint256) { 11 | return a > b ? (b > c ? c : b) : (a > c ? c : a); 12 | } 13 | 14 | function min2(uint256 a, uint256 b) internal pure returns (uint256) { 15 | return a > b ? b : a; 16 | } 17 | } -------------------------------------------------------------------------------- /test/utils/safe-transfer-lib-mocks/mocks/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import {ERC20} from "solmate/tokens/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20 { 7 | constructor( 8 | string memory _name, 9 | string memory _symbol, 10 | uint8 _decimals 11 | ) ERC20(_name, _symbol, _decimals) {} 12 | 13 | function mint(address to, uint256 value) public virtual { 14 | _mint(to, value); 15 | } 16 | 17 | function burn(address from, uint256 value) public virtual { 18 | _burn(from, value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/utils/mocks/EthersWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | // Receives ether 3 | #define function isPayable() payable returns (uint256) 4 | #define function nonPayable() nonpayable returns (uint256) 5 | 6 | // Match the function selector 7 | #define macro MAIN() = takes (0) returns (0) { 8 | pc calldataload 0xe0 shr 9 | 10 | dup1 __FUNC_SIG(isPayable) eq payable_jump jumpi 11 | dup1 __FUNC_SIG(nonPayable) eq non_payable_jump jumpi 12 | 13 | // Revert if no function selectors match 14 | reverts: 15 | 0x00 dup1 revert 16 | 17 | non_payable_jump: 18 | callvalue reverts jumpi 19 | payable_jump: 20 | balance 0x00 mstore 21 | 0x20 0x00 return 22 | } -------------------------------------------------------------------------------- /test/utils/mocks/MerkleProofLibWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function verifyProof(bytes32, bytes32, bytes32[] calldata) pure returns (bool) 2 | 3 | #define macro VERIFY_PROOF_WRAPPER() = takes (0) returns (0) { 4 | 0x04 calldataload // [root] 5 | 0x24 calldataload // [leaf, root] 6 | 0x64 // [proof_cd_ptr, leaf, root] 7 | VERIFY_PROOF() // [is_valid] 8 | 0x00 mstore // [] 9 | 0x20 0x00 return 10 | } 11 | 12 | #define macro MAIN() = takes (0) returns (0) { 13 | pc calldataload 0xE0 shr 14 | dup1 __FUNC_SIG(verifyProof) eq verifyProof jumpi 15 | 16 | 0x00 dup1 revert 17 | 18 | verifyProof: 19 | VERIFY_PROOF_WRAPPER() 20 | } 21 | -------------------------------------------------------------------------------- /test/proxies/mocks/MockBeacon.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity 0.8.15; 3 | 4 | /** 5 | * @dev This is the interface that {BeaconProxy} expects of its beacon. 6 | */ 7 | interface IBeacon { 8 | /** 9 | * @dev Must return an address that can be used as a delegate call target. 10 | * 11 | * {BeaconProxy} will check that this address is a contract. 12 | */ 13 | function implementation() external view returns (address); 14 | } 15 | 16 | contract MockBeacon is IBeacon { 17 | address public implementation_; 18 | 19 | constructor(address implementation) { 20 | implementation_ = implementation; 21 | } 22 | 23 | function implementation() external view override returns (address) { 24 | return implementation_; 25 | } 26 | } -------------------------------------------------------------------------------- /test/mechanisms/huff-clones/Interfaces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD 2 | pragma solidity ^0.8.15; 3 | 4 | interface IExampleClone { 5 | function param1() external pure returns (address); 6 | function param2() external pure returns (uint256); 7 | function param3() external pure returns (uint64); 8 | function param4() external pure returns (uint8); 9 | function param5(uint256 arrLen) external pure returns (uint256[] memory); 10 | } 11 | 12 | interface IExampleCloneFactory { 13 | function createClone( 14 | address param1, 15 | uint256 param2, 16 | uint64 param3, 17 | uint8 param4 18 | ) external returns (address clone); 19 | 20 | function createArrClone(uint256[] memory arr) external returns (address clone); 21 | } 22 | -------------------------------------------------------------------------------- /test/utils/mocks/RefundedWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define function refundedCall() payable returns () 3 | #define function nonRefundedCall() payable returns () 4 | 5 | #define event NonRefundedCall(address) 6 | 7 | #define macro MAIN() = takes (0) returns (0) { 8 | pc calldataload 0xe0 shr 9 | 10 | dup1 __FUNC_SIG(refundedCall) eq refunded_call jumpi 11 | dup1 __FUNC_SIG(nonRefundedCall) eq non_refunded_call jumpi 12 | 13 | 0x00 dup1 revert 14 | 15 | refunded_logic: 16 | caller __EVENT_HASH(NonRefundedCall) 0x00 0x00 log2 17 | __Refund_Return_Dest jump 18 | 19 | refunded_call: 20 | REFUNDED(refunded_logic) 21 | stop 22 | non_refunded_call: 23 | caller __EVENT_HASH(NonRefundedCall) 0x00 0x00 log2 24 | stop 25 | } -------------------------------------------------------------------------------- /src/utils/Address.huff: -------------------------------------------------------------------------------- 1 | /// @title Address 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author Jet 4 | /// @notice Macros associated with addresses 5 | 6 | /// @notice Masks an address to 160 bits 7 | #define macro MASK_ADDRESS() = takes (1) returns (1) { 8 | 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff 9 | and 10 | } 11 | 12 | /// @notice Require that the top of the stack is an address 13 | #define macro REQUIRE_ADDR() = takes (1) returns (0) { 14 | // Input Stack: [address] 15 | 16 | // Mask the Address 17 | dup1 MASK_ADDRESS() // [masked_address, address] 18 | 19 | // Revert if the addresses aren't equal 20 | eq addresses_match jumpi // [] 21 | 0x00 dup1 revert // [] 22 | addresses_match: // [] 23 | } -------------------------------------------------------------------------------- /test/math/mocks/TrigonometryWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function sin(uint256) pure returns (int256) 2 | #define function cos(uint256) pure returns (int256) 3 | 4 | #define macro SIN_WRAPPER() = takes (0) returns (0) { 5 | 0x04 calldataload // [angle] 6 | SIN() // [sin(angle)] 7 | 0x00 mstore // [] 8 | 0x20 0x00 return 9 | } 10 | 11 | #define macro COS_WRAPPER() = takes (0) returns (0) { 12 | 0x04 calldataload // [angle] 13 | COS() // [cos(angle)] 14 | 0x00 mstore // [] 15 | 0x20 0x00 return 16 | } 17 | 18 | #define macro MAIN() = takes (0) returns (0) { 19 | pc calldataload 0xE0 shr 20 | dup1 __FUNC_SIG(sin) eq sin jumpi 21 | dup1 __FUNC_SIG(cos) eq cos jumpi 22 | 23 | 0x00 dup1 revert 24 | 25 | sin: 26 | SIN_WRAPPER() 27 | cos: 28 | COS_WRAPPER() 29 | } -------------------------------------------------------------------------------- /src/auth/NonPayable.huff: -------------------------------------------------------------------------------- 1 | /// @title Non Payable 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice Simple macro to revert if a call has a value 5 | 6 | #include "../utils/Errors.huff" 7 | 8 | // "NON_PAYABLE" Revert Message String 9 | #define constant NON_PAYABLE_ERROR = 0xb4e4f4e5f50415941424c45000000000000000000000000000000000000000000 10 | #define constant NON_PAYABLE_LENGTH = 0x0b 11 | 12 | /// @notice Reverts if the call has a non-zero value 13 | /// @notice Reverts with message "NON_PAYABLE" 14 | #define macro NON_PAYABLE() = takes (0) returns (0) { 15 | [NON_PAYABLE_ERROR] // ["NON_PAYABLE"] 16 | [NON_PAYABLE_LENGTH] // [11 (length), "NON_PAYABLE"] 17 | callvalue iszero // [msg.value == 0, 11 (length), "NON_PAYABLE"] 18 | REQUIRE() // [] 19 | } 20 | -------------------------------------------------------------------------------- /test/utils/mocks/ShufflingWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function oneWayShuffle(bytes32 seed, uint256 index, uint256 count, uint256 rounds) view returns (uint256) 2 | 3 | #define macro SHUFFLE_WRAPPER() = takes (0) returns (0) { 4 | 0x64 calldataload // [rounds] 5 | 0x44 calldataload // [count, rounds] 6 | 0x24 calldataload // [index, count, rounds] 7 | 0x04 calldataload // [seed, index, count, rounds] 8 | MECHS__ONE_WAY_SHUFFLE(0x60, 0x80) // [index'] 9 | 0x00 mstore 0x20 0x00 return // [] 10 | } 11 | 12 | #define macro MAIN() = takes (0) returns (0) { 13 | pc calldataload 0xE0 shr // [selector] 14 | 15 | dup1 __FUNC_SIG(oneWayShuffle) eq shuffle jumpi 16 | 17 | 0x00 dup1 revert 18 | 19 | shuffle: 20 | SHUFFLE_WRAPPER() 21 | } 22 | -------------------------------------------------------------------------------- /test/utils/Calls.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import "foundry-huff/HuffDeployer.sol"; 6 | 7 | interface ICalls { 8 | function callFunc() external; 9 | function staticcallFunc() external; 10 | function callcodeFunc() external; 11 | } 12 | 13 | contract CallsTest is Test { 14 | ICalls calls; 15 | 16 | function setUp() public { 17 | string memory wrapper_code = vm.readFile("test/utils/mocks/CallWrappers.huff"); 18 | calls = ICalls(HuffDeployer.deploy_with_code("utils/Calls", wrapper_code)); 19 | } 20 | 21 | function testCall() public { 22 | calls.callFunc(); 23 | } 24 | 25 | function testStaticCall() public { 26 | calls.staticcallFunc(); 27 | } 28 | 29 | function testCallCode() public { 30 | calls.callcodeFunc(); 31 | } 32 | } -------------------------------------------------------------------------------- /src/utils/Ethers.huff: -------------------------------------------------------------------------------- 1 | /// @title Ethers 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice Utilities for working with ether at a low level 5 | 6 | /// @notice Sends an amount of ether to the specified [amount, address] 7 | #define macro SEND_ETH() = takes (2) returns (1) { 8 | // Input Stack: [amount, address] 9 | 10 | // Send the ether 11 | 0x00 // [0, amount, address] 12 | dup1 // [0, 0, amount, address] 13 | dup1 // [0, 0, 0, amount, address] 14 | dup1 // [0, 0, 0, 0, amount, address] 15 | dup5 // [amount, 0, 0, 0, 0, amount, address] 16 | dup7 // [address, amount, 0, 0, 0, 0, amount] 17 | gas // [gas, address, amount, 0, 0, 0, 0, amount, address] 18 | call // [success, amount, address] 19 | 20 | // Clean the stack 21 | swap2 // [address, amount, success] 22 | pop // [amount, success] 23 | pop // [success] 24 | } 25 | -------------------------------------------------------------------------------- /test/utils/mocks/InsertionSortWrappers.huff: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | /// @notice Insertion Sort Function 4 | #define function insertionSort(uint256[]) nonpayable returns (uint256[]) 5 | 6 | /// @notice Performs insertion sort on raw calldata 7 | #define macro INSERTION_SORT() = takes (0) returns (0) { 8 | SORT() // [offset, arrSize] 9 | return // [] 10 | } 11 | 12 | /// @notice The contract entrypoint 13 | #define macro MAIN() = takes(0) returns (0) { 14 | returndatasize calldataload 0xe0 shr // [selector] 15 | 16 | // notice: we don't need to duplicate the selector here 17 | // since the selector is consumed and only used once 18 | __FUNC_SIG(insertionSort) eq jumpSort jumpi // [] 19 | 20 | // Reverts if selector not present. 21 | returndatasize returndatasize revert 22 | 23 | jumpSort: 24 | INSERTION_SORT() 25 | } 26 | -------------------------------------------------------------------------------- /test/utils/mocks/ReentrancyGuardWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define function state() view returns (uint256) 3 | #define function lock() nonpayable returns () 4 | #define function unlock() nonpayable returns () 5 | 6 | #define macro LOCK_WRAPPER() = takes (0) returns (0) { 7 | LOCK() 8 | stop 9 | } 10 | 11 | #define macro UNLOCK_WRAPPER() = takes (0) returns (0) { 12 | UNLOCK() 13 | stop 14 | } 15 | 16 | #define macro GET_STATE() = takes (0) returns (0) { 17 | [LOCKED_SLOT] sload // [LOCKED] 18 | 0x00 mstore // [] 19 | 0x20 0x00 return // [] 20 | } 21 | 22 | #define macro MAIN() = takes (0) returns (0) { 23 | pc calldataload 0xE0 shr // [selector] 24 | 25 | dup1 __FUNC_SIG(state) eq state_jump jumpi 26 | dup1 __FUNC_SIG(lock) eq lock_jump jumpi 27 | dup1 __FUNC_SIG(unlock) eq unlock_jump jumpi 28 | 29 | DISPATCH_ERROR(0x00) 30 | 31 | state_jump: 32 | GET_STATE() 33 | 34 | lock_jump: 35 | LOCK_WRAPPER() 36 | 37 | unlock_jump: 38 | UNLOCK_WRAPPER() 39 | } -------------------------------------------------------------------------------- /src/proxies/ERC1967Proxy.huff: -------------------------------------------------------------------------------- 1 | /// @title ERC1967 Proxy 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice An upgradeable proxy that uses the ERC1967 storage layout. 5 | /// @notice Adapted from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/ERC1967/ERC1967Proxy.sol) 6 | 7 | #include "./Proxy.huff" 8 | #include "./ERC1967Upgrade.huff" 9 | 10 | /// @notice An internal constructor that upgrades the proxy to the implementation and calls the implementation 11 | #define macro ERC1967_PROXY_CONSTRUCTOR() = takes (0) returns (0) { 12 | 0x00 // [forceCall] 13 | 0x24 calldataload // [data, forceCall] 14 | 0x04 calldataload // [address, data, forceCall] 15 | UPGRADE_TO_AND_CALL() // [] 16 | } 17 | 18 | /// @notice Returns the current implementation address 19 | /// @notice Overrideable 20 | #define macro IMPLEMENTATION() = takes (0) returns (1) { 21 | GET_IMPLEMENTATION() 22 | } 23 | -------------------------------------------------------------------------------- /test/utils/mocks/PausableWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function isPaused() view returns (bool) 2 | #define function pause() nonpayable returns () 3 | #define function unpause() nonpayable returns () 4 | 5 | 6 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 7 | PAUSABLE_CONSTRUCTOR() 8 | } 9 | 10 | 11 | #define macro IS_PAUSED_WRAPPER() = takes (0) returns (0) { 12 | PAUSABLE_IS_PAUSED() 13 | } 14 | 15 | #define macro PAUSE_WRAPPER() = takes (0) returns (0) { 16 | PAUSABLE_PAUSE() 17 | } 18 | 19 | #define macro UNPAUSE_WRAPPER() = takes (0) returns (0) { 20 | PAUSABLE_UNPAUSE() 21 | } 22 | 23 | 24 | #define macro MAIN() = takes (0) returns (0) { 25 | pc calldataload 0xe0 shr // [sig] 26 | 27 | dup1 __FUNC_SIG(pause) eq pause jumpi 28 | dup1 __FUNC_SIG(unpause) eq unpause jumpi 29 | dup1 __FUNC_SIG(isPaused) eq is_paused jumpi 30 | 31 | 0x00 dup1 revert 32 | 33 | pause: 34 | PAUSE_WRAPPER() 35 | unpause: 36 | UNPAUSE_WRAPPER() 37 | is_paused: 38 | IS_PAUSED_WRAPPER() 39 | } -------------------------------------------------------------------------------- /src/auth/OnlyContract.huff: -------------------------------------------------------------------------------- 1 | /// @title OnlyContract 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice Reverts if the caller is not a contract with a tx.origin check. 5 | 6 | /// @notice Reverts if the caller is not a contract using tx.origin. 7 | /// @dev WARNING: This is an anti-pattern and prevents interoperability. 8 | /// Additionally, this will become ineffective once either EIP-2938 OR EIP-4337 account abstraction's are implemented. 9 | /// EIP-3074 is another concern as it essentially allows users to delegate control of an EOA to a contract. 10 | /// Additionally, users of wallets like argent, authereum and gnosis multi-sigs are able to "bypass" this access control. 11 | #define macro ONLY_CONTRACT() = takes (0) returns (0) { 12 | caller // [msg.sender] 13 | origin // [tx.origin, msg.sender] 14 | eq iszero // [tx.origin != msg.sender] 15 | continue jumpi // [] 16 | 0x00 dup1 revert // [] 17 | continue: 18 | } 19 | -------------------------------------------------------------------------------- /test/utils/Shuffling.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import { Test } from "forge-std/Test.sol"; 5 | import { console2 } from "forge-std/console2.sol"; 6 | import { HuffDeployer } from "foundry-huff/HuffDeployer.sol"; 7 | 8 | interface IShuffler { 9 | function oneWayShuffle(bytes32 seed, uint256 index, uint256 count, uint256 rounds) external view returns (uint256); 10 | } 11 | 12 | contract ShufflingTest is Test { 13 | IShuffler shuffler; 14 | 15 | function setUp() public { 16 | string memory wrapper_code = vm.readFile("test/utils/mocks/ShufflingWrappers.huff"); 17 | shuffler = IShuffler(HuffDeployer.deploy_with_code("utils/Shuffling", wrapper_code)); 18 | } 19 | 20 | function testShuffle() public { 21 | bytes32 seed = bytes32(abi.encode("RANDOM_SEED")); 22 | uint256 index = 1337; 23 | uint256 count = 10; 24 | uint256 rounds = 10; 25 | uint256 shuffled_index = shuffler.oneWayShuffle(seed, index, count, rounds); 26 | console2.log(shuffled_index); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /test/test-utils/NonMatchingSelectorHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | abstract contract NonMatchingSelectorsHelper { 7 | /// @dev Expected to return false. 8 | function nonMatchingSelectorHelper( 9 | bytes4[] memory func_selectors, 10 | bytes32 callData, 11 | address target 12 | ) internal returns (bool) { 13 | bytes4 func_selector = bytes4(callData); 14 | 15 | for (uint256 i = 0; i < func_selectors.length; i++) { 16 | if (func_selector == func_selectors[i]) { 17 | return false; 18 | } 19 | } 20 | 21 | bool success = false; 22 | assembly { 23 | mstore(0x80, callData) 24 | // if its a state changing call this will return 0 25 | success := staticcall(gas(), target, 0x80, 0x20, 0, 0) 26 | 27 | if iszero(success) { 28 | success := call(gas(), target, 0, 0x80, 0x20, 0, 0) 29 | } 30 | } 31 | return success; 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pentagon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/mechanisms/huff-vrgda/mocks/LinearVRGDAWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define function getTargetSaleTime(int256) view returns (int256) 3 | #define function getVRGDAPrice(int256, uint256) view returns (uint256) 4 | #define function targetPrice() view returns (int256) 5 | #define function decayConstant() view returns (int256) 6 | #define function perTimeUnit() view returns (int256) 7 | 8 | 9 | #define macro MAIN() = { 10 | 0x00 calldataload 0xE0 shr 11 | dup1 __FUNC_SIG(getVRGDAPrice) eq getVRGDAPrice jumpi 12 | dup1 __FUNC_SIG(getTargetSaleTime) eq getTargetSaleTime jumpi 13 | dup1 __FUNC_SIG(targetPrice) eq targetPrice jumpi 14 | dup1 __FUNC_SIG(decayConstant) eq decayConstant jumpi 15 | dup1 __FUNC_SIG(perTimeUnit) eq perTimeUnit jumpi 16 | 17 | fail: 18 | 0x00 dup1 revert 19 | 20 | getVRGDAPrice: 21 | GET_VRGDA_PRICE_EXTERNAL(fail) 22 | getTargetSaleTime: 23 | GET_TARGET_SALE_TIME_EXTERNAL(fail) 24 | targetPrice: 25 | RETURN_CONSTANT(TARGET_PRICE) 26 | decayConstant: 27 | RETURN_CONSTANT(DECAY_CONSTANT) 28 | perTimeUnit: 29 | RETURN_CONSTANT(PER_TIME_UNIT) 30 | } 31 | -------------------------------------------------------------------------------- /test/data-structures/mocks/ArrayWrappers.huff: -------------------------------------------------------------------------------- 1 | // Sets an array from calldata. 2 | // sig: 0x226ce6b7 3 | #define function setArrayFromCalldata(uint256[]) view returns () 4 | 5 | // Returns an array. 6 | // sig: 0xde9ee75c 7 | #define function loadArray() view returns (uint256[] memory) 8 | 9 | #define constant LOCATION = FREE_STORAGE_POINTER() 10 | 11 | #define macro SET_FROM_CALLDATA() = takes(0) returns(0) { 12 | [LOCATION] 0x04 13 | SET_ARRAY_FROM_CALLDATA() 14 | stop 15 | } 16 | 17 | 18 | // Store the value for the given key. 19 | #define macro RETURN() = takes(0) returns(0) { 20 | [LOCATION] 21 | RETURN_ARRAY(0x80) 22 | } 23 | 24 | /* Main Macro - The contract entrypoint */ 25 | #define macro MAIN() = takes(0) returns (0) { 26 | // Identify which function is being called using the 4 byte function signature 27 | 0x00 calldataload 0xE0 shr 28 | dup1 __FUNC_SIG(setArrayFromCalldata) eq set_from_calldata jumpi 29 | dup1 __FUNC_SIG(loadArray) eq load jumpi 30 | 31 | // Revert if otherwise 32 | 0x00 0x00 revert 33 | 34 | set_from_calldata: 35 | SET_FROM_CALLDATA() 36 | load: 37 | RETURN() 38 | } 39 | -------------------------------------------------------------------------------- /test/utils/mocks/Create3Wrappers.huff: -------------------------------------------------------------------------------- 1 | #define function deploy(bytes32, bytes, uint256) payable returns (address) 2 | #define function getDeployed(bytes32) view returns (address) 3 | 4 | #define macro CREATE_3_DEPLOY_WRAPPER() = takes (0) returns (0) { 5 | 0x44 calldataload // [value] 6 | 0x24 calldataload // [&creationCode, value] 7 | 0x04 calldataload // [salt, &creationCode, value] 8 | CREATE_3_DEPLOY() // [deployed] 9 | 0x00 mstore // [] 10 | 0x20 0x00 return // [] 11 | } 12 | 13 | #define macro CREATE_3_GET_DEPLOYED_WRAPPER() = takes (0) returns (0) { 14 | 0x04 calldataload // [salt] 15 | CREATE_3_GET_DEPLOYED() // [deployed] 16 | 0x00 mstore // [] 17 | 0x20 0x00 return // [] 18 | } 19 | 20 | #define macro MAIN() = { 21 | pc calldataload 0xe0 shr 22 | 23 | dup1 __FUNC_SIG(deploy) eq deploy_jump jumpi 24 | dup1 __FUNC_SIG(getDeployed) eq get_deployed_jump jumpi 25 | 26 | // Exit if selector does not match 27 | 0x00 dup1 revert 28 | 29 | deploy_jump: 30 | CREATE_3_DEPLOY_WRAPPER() 31 | get_deployed_jump: 32 | CREATE_3_GET_DEPLOYED_WRAPPER() 33 | } -------------------------------------------------------------------------------- /test/auth/NonPayable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; 6 | import {HuffConfig} from "foundry-huff/HuffConfig.sol"; 7 | 8 | contract NonPayableTest is Test { 9 | address np; 10 | 11 | function setUp() public { 12 | HuffConfig config = HuffDeployer.config().with_code(string.concat( 13 | "#define macro MAIN() = takes(0) returns(0) {\n", 14 | " NON_PAYABLE()\n", 15 | " 0x00 0x00 log0 // anonymous log\n", 16 | " 0x01 0x00 mstore\n", 17 | " 0x20 0x00 return\n", 18 | "}\n" 19 | )); 20 | np = config.deploy("auth/NonPayable"); 21 | } 22 | 23 | /// @notice Test that a non-matching signature reverts 24 | function testNonPayable(bytes32 callData) public { 25 | // Should revert for any call data where a value is sent 26 | vm.expectRevert(); 27 | (bool fails,) = np.call{value: 1 ether}(abi.encode(callData)); 28 | 29 | // Non value calls should succeed 30 | (bool success, bytes memory retBytes) = np.call(abi.encode(callData)); 31 | assert(success); 32 | assertEq(bytes8(retBytes[31]), bytes8(hex"01")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/utils/mocks/LibBitWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function fls(uint256) pure returns (uint256) 2 | #define function ffs(uint256) pure returns (uint256) 3 | #define function popCount(uint256) pure returns (uint256) 4 | #define function isPowOf2(uint256) pure returns (uint256) 5 | 6 | #define macro FLS_WRAPPER() = { 7 | 0x04 calldataload 8 | FLS() 9 | 10 | 0x00 mstore 11 | 0x20 0x00 return 12 | } 13 | 14 | #define macro FFS_WRAPPER() = { 15 | 0x04 calldataload 16 | FFS() 17 | 18 | 0x00 mstore 19 | 0x20 0x00 return 20 | } 21 | 22 | #define macro POP_COUNT_WRAPPER() = { 23 | 0x04 calldataload 24 | POP_COUNT() 25 | 26 | 0x00 mstore 27 | 0x20 0x00 return 28 | } 29 | 30 | #define macro IS_POW_OF_2_WRAPPER() = { 31 | 0x04 calldataload 32 | IS_POW_OF_2() 33 | 34 | 0x00 mstore 35 | 0x20 0x00 return 36 | } 37 | 38 | #define macro MAIN() = { 39 | pc calldataload 0xE0 shr 40 | dup1 __FUNC_SIG(fls) eq fls jumpi 41 | dup1 __FUNC_SIG(ffs) eq ffs jumpi 42 | dup1 __FUNC_SIG(popCount) eq pop_count jumpi 43 | dup1 __FUNC_SIG(isPowOf2) eq is_pow_of_2 jumpi 44 | 45 | 0x00 dup1 revert 46 | 47 | fls: 48 | FLS_WRAPPER() 49 | ffs: 50 | FFS_WRAPPER() 51 | pop_count: 52 | POP_COUNT_WRAPPER() 53 | is_pow_of_2: 54 | IS_POW_OF_2_WRAPPER() 55 | } 56 | -------------------------------------------------------------------------------- /test/math/mocks/MathWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define macro SQRT_WRAPPER() = { 3 | 0x04 calldataload 4 | SQRT() 5 | 0x00 mstore 6 | 0x20 0x00 return 7 | } 8 | 9 | #define macro MAX_WRAPPER() = { 10 | 0x04 calldataload 11 | 0x24 calldataload 12 | MAX() 13 | 0x00 mstore 14 | 0x20 0x00 return 15 | } 16 | 17 | #define macro MIN_WRAPPER() = { 18 | 0x04 calldataload 19 | 0x24 calldataload 20 | MIN() 21 | 0x00 mstore 22 | 0x20 0x00 return 23 | } 24 | 25 | #define macro AVG_WRAPPER() = { 26 | 0x04 calldataload 27 | 0x24 calldataload 28 | AVG() 29 | 0x00 mstore 30 | 0x20 0x00 return 31 | } 32 | 33 | #define macro CEIL_DIV_WRAPPER() = { 34 | 0x24 calldataload 35 | 0x04 calldataload 36 | CEIL_DIV() 37 | 0x00 mstore 38 | 0x20 0x00 return 39 | } 40 | 41 | #define macro MAIN() = { 42 | 0x00 calldataload 0xE0 shr 43 | dup1 __FUNC_SIG(sqrt) eq sqrt jumpi 44 | dup1 __FUNC_SIG(max) eq max jumpi 45 | dup1 __FUNC_SIG(min) eq min jumpi 46 | dup1 __FUNC_SIG(average) eq average jumpi 47 | dup1 __FUNC_SIG(ceilDiv) eq ceilDiv jumpi 48 | 49 | 0x00 0x00 revert 50 | 51 | sqrt: 52 | SQRT_WRAPPER() 53 | max: 54 | MAX_WRAPPER() 55 | min: 56 | MIN_WRAPPER() 57 | average: 58 | AVG_WRAPPER() 59 | ceilDiv: 60 | CEIL_DIV_WRAPPER() 61 | } 62 | -------------------------------------------------------------------------------- /test/utils/Pausable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | 8 | interface IPausable { 9 | function isPaused() external view returns (bool); 10 | function pause() external; 11 | function unpause() external; 12 | } 13 | 14 | 15 | 16 | contract PausableTest is Test { 17 | IPausable pausable; 18 | 19 | function setUp() public { 20 | string memory wrapper_code = vm.readFile("test/utils/mocks/PausableWrappers.huff"); 21 | pausable = IPausable(HuffDeployer.deploy_with_code("utils/Pausable", wrapper_code)); 22 | } 23 | 24 | function testIsPausedByDefault() public { 25 | assertEq(pausable.isPaused(), false); 26 | } 27 | 28 | function testPauseUnPause() public { 29 | pausable.pause(); 30 | assertEq(pausable.isPaused(), true); 31 | 32 | pausable.unpause(); 33 | assertEq(pausable.isPaused(), false); 34 | } 35 | 36 | function testUnpauseWhenUnpaused() public { 37 | assertEq(pausable.isPaused(), false); 38 | 39 | vm.expectRevert(); 40 | pausable.unpause(); 41 | } 42 | 43 | function testPauseWhenPaused() public { 44 | pausable.pause(); 45 | assertEq(pausable.isPaused(), true); 46 | 47 | vm.expectRevert(); 48 | pausable.pause(); 49 | } 50 | 51 | 52 | } -------------------------------------------------------------------------------- /test/tokens/mocks/ERC20MintableWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define function mint(address, uint256) nonpayable returns () 3 | #define function burn(address, uint256) nonpayable returns () 4 | 5 | #define macro BURN() = takes (0) returns (0) { 6 | NON_PAYABLE() 7 | // Setup the stack for the burn function. 8 | 0x04 calldataload // [from] 9 | 0x24 calldataload // [value, from] 10 | 11 | // Call ERC20.huff's _BURN macro 12 | _BURN() // [] 13 | 14 | // Stop Execution 15 | stop // [] 16 | } 17 | 18 | #define macro MINT() = takes (0) returns (0) { 19 | NON_PAYABLE() 20 | 21 | // Setup the stack for the mint function. 22 | 0x04 calldataload // [to] 23 | 0x24 calldataload // [value, to] 24 | 25 | // Call ERC20.huff's _MINT macro 26 | _MINT() // [] 27 | 28 | // Stop Execution 29 | stop // [] 30 | } 31 | 32 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 33 | ERC20_CONSTRUCTOR() 34 | } 35 | 36 | #define macro MAIN() = takes (0) returns (0) { 37 | 0x00 calldataload 0xE0 shr // [sig] 38 | 39 | dup1 __FUNC_SIG(mint) eq mint jumpi // [sig] 40 | dup1 __FUNC_SIG(burn) eq burn jumpi // [sig] 41 | 42 | ERC20_MAIN() // [sig] 43 | 44 | // Revert if no selector matches 45 | 0x00 0x00 revert 46 | 47 | mint: 48 | MINT() 49 | burn: 50 | BURN() 51 | } -------------------------------------------------------------------------------- /src/mechanisms/huff-vrgda/LinearVRGDA.huff: -------------------------------------------------------------------------------- 1 | /// @title Linear Variable Rate Gradual Dutch Auction 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author transmissions11 4 | /// @author FrankieIsLost 5 | /// @author Maddiaa 6 | /// @notice VRGDA with a linear issuance curve. 7 | /// @notice Original Implementation by Maddiaa (https://github.com/cheethas/huff-vrgda/blob/main/src/LinearVRGDA.huff) 8 | 9 | #include "./VRGDA.huff" 10 | 11 | // These will be overriden with the constructor flag 12 | // The constructor logic will need to be copied within deploy scripts 13 | // in order to inline the correct constants 14 | #define constant PER_TIME_UNIT = 0x00 // ( int256 ) 15 | 16 | /// @dev Given a number of tokens sold, return the target time that number of tokens should be sold by. 17 | /// @param {stack} sold A number of tokens sold, scaled by 1e18, to get the corresponding target sale time for. 18 | /// @return The target time the tokens should be sold by, scaled by 1e18, where the time is 19 | /// relative, such that 0 means the tokens should be sold immediately when the VRGDA begins. 20 | #define macro GET_TARGET_SALE_TIME() = takes (1) returns (0) { 21 | // init state = [sold] 22 | [PER_TIME_UNIT] // [perTimeUnit, sold] 23 | swap1 // [sold, perTimeUnit] 24 | UNSAFE_WAD_DIV() // [sold / perTimeUnit] (targetSaleTime) 25 | } 26 | -------------------------------------------------------------------------------- /test/utils/InsertionSort.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | contract InsertionSortTest is Test { 8 | /// @dev Address of the InsertionSort contract. 9 | InsertionSort public insertionSortContract; 10 | 11 | /// @dev Setup the testing environment. 12 | function setUp() public { 13 | string memory wrapper_code = vm.readFile( 14 | "test/utils/mocks/InsertionSortWrappers.huff" 15 | ); 16 | insertionSortContract = InsertionSort( 17 | HuffDeployer.deploy_with_code("utils/InsertionSort", wrapper_code) 18 | ); 19 | } 20 | 21 | /// @dev Ensure that it returns passed arr. 22 | function testInsertionSort() public { 23 | uint256[] memory arr = new uint256[](6); 24 | arr[0] = 77; 25 | arr[1] = 4; 26 | arr[2] = 15; 27 | arr[3] = 6; 28 | arr[4] = 100; 29 | arr[5] = 8; 30 | 31 | uint256[] memory newArr = insertionSortContract.insertionSort(arr); 32 | 33 | assertEq(newArr[0], 4); 34 | assertEq(newArr[1], 6); 35 | assertEq(newArr[2], 8); 36 | assertEq(newArr[3], 15); 37 | assertEq(newArr[4], 77); 38 | assertEq(newArr[5], 100); 39 | } 40 | } 41 | 42 | interface InsertionSort { 43 | function insertionSort(uint256[] memory) 44 | external 45 | returns (uint256[] memory); 46 | } 47 | -------------------------------------------------------------------------------- /test/utils/ReentrancyGuard.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | // Mock Interface 8 | interface IGuard { 9 | function state() external view returns (uint256); 10 | function lock() external; 11 | function unlock() external; 12 | } 13 | 14 | contract ReentranctGuardTest is Test { 15 | IGuard guard; 16 | 17 | function setUp() public { 18 | string memory wrapper_code = vm.readFile("test/utils/mocks/ReentrancyGuardWrappers.huff"); 19 | guard = IGuard(HuffDeployer.deploy_with_code("utils/ReentrancyGuard", wrapper_code)); 20 | } 21 | 22 | /// @notice Test locking 23 | function testLocking() public { 24 | guard.unlock(); 25 | uint256 state = guard.state(); 26 | assertEq(state, 1); 27 | 28 | // We should remain unlocked 29 | guard.unlock(); 30 | state = guard.state(); 31 | assertEq(state, 1); 32 | 33 | // Let's lock 34 | guard.lock(); 35 | state = guard.state(); 36 | assertEq(state, 2); 37 | 38 | // We should be able to unlock 39 | guard.unlock(); 40 | state = guard.state(); 41 | assertEq(state, 1); 42 | 43 | // We cannot lock twice 44 | guard.lock(); 45 | state = guard.state(); 46 | assertEq(state, 2); 47 | 48 | vm.expectRevert(bytes("REENTRANCY")); 49 | guard.lock(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/mechanisms/huff-clones/README.md: -------------------------------------------------------------------------------- 1 | # huff-clones 2 | 3 | Rewrite of [clones-with-immutable-args](https://github.com/wighawag/clones-with-immutable-args) in [Huff](https://github.com/huff-language). 4 | 5 | --- 6 | 7 | Enables creating clone contracts with immutable arguments. 8 | 9 | The immutable arguments are stored in the code region of the created proxy contract, and whenever the proxy is called, it reads the arguments into memory, and then appends them to the calldata of the delegate call to the implementation contract. The implementation contract can thus read the arguments straight from calldata. 10 | 11 | By doing so, the gas cost of creating parametrizable clones is reduced, since there's no need to store the parameters in storage, which you need to do with [EIP-1167](https://eips.ethereum.org/EIPS/eip-1167). The cost of using such clones is also reduced, since storage loads are replaced with calldata reading, which is far cheaper. 12 | 13 | In other words, if you know you are not gonna need parametrization and just want exact copies, then you can keep using EIP-1167, otherwise, clones-with-immutables is cheaper. 14 | 15 | ## Usage 16 | 17 | Clone factory contracts should use the [`HuffCloneLib`](HuffCloneLib.huff) library. `CLONE` is the main macro for creating clones. 18 | 19 | Contracts intended to be cloned should include [`HuffClone`](HuffClone.huff) to get access to the helper macros for reading immutable args. 20 | 21 | To see an example usage of the library, check out [`ExampleClone`](ExampleClone.huff) and [`ExampleCloneFactory`](ExampleCloneFactory.huff). 22 | -------------------------------------------------------------------------------- /test/mechanisms/huff-vrgda/mocks/LogisticVRGDAWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define function getTargetSaleTime(int256) view returns (int256) 3 | #define function getVRGDAPrice(int256, uint256) view returns (uint256) 4 | 5 | // Getters to demonstrate how to override constants in huff 6 | #define function targetPrice() view returns(int256) 7 | #define function decayConstant() view returns(int256) 8 | #define function timeScale() view returns(int256) 9 | #define function logisticLimit() view returns(int256) 10 | #define function logisticLimitDoubled() view returns(int256) 11 | 12 | 13 | #define macro MAIN() = { 14 | pc calldataload 0xE0 shr 15 | dup1 __FUNC_SIG(getVRGDAPrice) eq getVRGDAPrice jumpi 16 | dup1 __FUNC_SIG(getTargetSaleTime) eq getTargetSaleTime jumpi 17 | dup1 __FUNC_SIG(targetPrice) eq targetPrice jumpi 18 | dup1 __FUNC_SIG(decayConstant) eq decayConstant jumpi 19 | dup1 __FUNC_SIG(timeScale) eq timeScale jumpi 20 | dup1 __FUNC_SIG(logisticLimit) eq logisticLimit jumpi 21 | dup1 __FUNC_SIG(logisticLimitDoubled) eq logisticLimitDoubled jumpi 22 | 23 | fail: 24 | 0x00 dup1 revert 25 | 26 | getVRGDAPrice: 27 | GET_VRGDA_PRICE_EXTERNAL(fail) 28 | getTargetSaleTime: 29 | GET_TARGET_SALE_TIME_EXTERNAL(fail) 30 | targetPrice: 31 | RETURN_CONSTANT(TARGET_PRICE) 32 | decayConstant: 33 | RETURN_CONSTANT(DECAY_CONSTANT) 34 | timeScale: 35 | RETURN_CONSTANT(TIME_SCALE) 36 | logisticLimit: 37 | RETURN_CONSTANT(LOGISTIC_LIMIT) 38 | logisticLimitDoubled: 39 | RETURN_CONSTANT(LOGISTIC_LIMIT_DOUBLED) 40 | } 41 | -------------------------------------------------------------------------------- /test/auth/OnlyContract.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | 6 | import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; 7 | import {HuffConfig} from "foundry-huff/HuffConfig.sol"; 8 | 9 | contract OnlyContractTest is Test { 10 | address only_contract; 11 | 12 | function setUp() public { 13 | HuffConfig config = HuffDeployer.config().with_code(string.concat( 14 | "#define macro MAIN() = takes(0) returns(0) {\n", 15 | " ONLY_CONTRACT()\n", 16 | " 0x00 0x00 log0 // anonymous log\n", 17 | " 0x01 0x00 mstore\n", 18 | " 0x20 0x00 return\n", 19 | "}\n" 20 | )); 21 | only_contract = config.deploy("auth/OnlyContract"); 22 | } 23 | 24 | /// @notice Tests that ONLY_CONTRACT macro reverts when tx.origin == msg.sender 25 | function testOnlyContract(address caller) public { 26 | // foundry's tx.origin = 0x00a329c0648769A73afAc7F9381E08FB43dBEA72 27 | vm.assume(caller != tx.origin); 28 | 29 | // Should revert when tx.origin == msg.sender 30 | // vm.startPrank(address,address) sets msg.sender and tx.origin 31 | vm.startPrank(caller, caller); 32 | vm.expectRevert(); 33 | only_contract.call(""); 34 | vm.stopPrank(); 35 | 36 | // Only setting the msg.sender and not tx.origin should succeed, so long as 37 | // caller != 0x00a329c0648769A73afAc7F9381E08FB43dBEA72, which is the default tx.origin 38 | vm.startPrank(caller); 39 | (bool success,) = only_contract.call(""); 40 | assert(success); 41 | vm.stopPrank(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/tokens/mocks/ERC1155Wrappers.huff: -------------------------------------------------------------------------------- 1 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 2 | ERC1155_CONSTRUCTOR() 3 | } 4 | 5 | #define macro MAIN() = { 6 | 0x00 calldataload 0xE0 shr // [function selector on stack] 7 | 8 | dup1 __FUNC_SIG(mint) eq mint jumpi 9 | dup1 __FUNC_SIG(burn) eq burn jumpi 10 | 11 | dup1 __FUNC_SIG(batchMint) eq batchMint jumpi 12 | dup1 __FUNC_SIG(batchBurn) eq batchBurn jumpi 13 | 14 | dup1 __FUNC_SIG(safeTransferFrom) eq safeTransferFrom jumpi 15 | dup1 __FUNC_SIG(safeBatchTransferFrom) eq batchSafeTransferFrom jumpi 16 | 17 | dup1 __FUNC_SIG(isApprovedForAll) eq isApprovedForAll jumpi 18 | dup1 __FUNC_SIG(setApprovalForAll) eq setApprovalForAll jumpi 19 | 20 | dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi 21 | dup1 __FUNC_SIG(balanceOfBatch) eq balanceOfBatch jumpi 22 | 23 | dup1 __FUNC_SIG(name) eq name jumpi 24 | dup1 __FUNC_SIG(symbol) eq symbol jumpi 25 | dup1 __FUNC_SIG(supportsInterface) eq supportsInterface jumpi 26 | 27 | // No fallback function 28 | 0x00 dup1 revert 29 | 30 | mint: 31 | MINT() 32 | burn: 33 | BURN() 34 | 35 | batchMint: 36 | BATCH_MINT() 37 | batchBurn: 38 | BATCH_BURN() 39 | 40 | safeTransferFrom: 41 | SAFE_TRANSFER_FROM() 42 | batchSafeTransferFrom: 43 | BATCH_SAFE_TRANSFER_FROM() 44 | 45 | isApprovedForAll: 46 | IS_APPROVED_FOR_ALL() 47 | setApprovalForAll: 48 | SET_APPROVAL_FOR_ALL() 49 | 50 | balanceOf: 51 | BALANCE_OF() 52 | balanceOfBatch: 53 | BALANCE_OF_BATCH() 54 | 55 | name: 56 | NAME() 57 | symbol: 58 | SYMBOL() 59 | supportsInterface: 60 | SUPPORTS_INTERFACE() 61 | } 62 | -------------------------------------------------------------------------------- /test/utils/Ethers.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import "foundry-huff/HuffDeployer.sol"; 6 | 7 | interface EthersWrappers { 8 | function isPayable() external payable returns (uint256); 9 | function nonPayable() external returns (uint256); 10 | } 11 | 12 | contract EthersTest is Test { 13 | EthersWrappers ethers; 14 | 15 | function setUp() public { 16 | // Deploy Ethers Lib 17 | string memory wrapper_code = vm.readFile("test/utils/mocks/EthersWrappers.huff"); 18 | ethers = EthersWrappers( 19 | HuffDeployer 20 | .config() 21 | .with_code(wrapper_code) 22 | .deploy("utils/Ethers") 23 | ); 24 | } 25 | 26 | function testEthersPayable(uint256 amount) public { 27 | vm.assume(amount > 0); 28 | vm.deal(address(this), amount); 29 | uint256 balance_before = address(ethers).balance; 30 | uint256 result = ethers.isPayable{value: amount}(); 31 | uint256 balance_after = address(ethers).balance; 32 | assert(balance_after > balance_before); 33 | } 34 | 35 | function testEthersNonPayable(uint256 amount) public { 36 | vm.assume(amount > 0); 37 | vm.expectRevert(); 38 | (bool success, bytes memory res) = address(ethers).call{value: amount}(abi.encodeWithSignature("nonPayable()")); 39 | uint256 result = abi.decode(res, (uint256)); 40 | } 41 | 42 | function testEthersNonPayable() public { 43 | uint256 balance_before = address(ethers).balance; 44 | uint256 result = ethers.nonPayable(); 45 | uint256 balance_after = address(ethers).balance; 46 | assertEq(balance_before, balance_after); 47 | assertEq(balance_before, result); 48 | } 49 | } -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push] 4 | 5 | jobs: 6 | check: 7 | name: Foundry project 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | with: 12 | submodules: recursive 13 | - name: Install Foundry 14 | uses: foundry-rs/foundry-toolchain@v1 15 | with: 16 | version: nightly 17 | - name: Install Huff 18 | uses: huff-language/huff-toolchain@v2 19 | with: 20 | version: nightly 21 | - name: Run Forge build 22 | run: forge --version && forge build --sizes 23 | id: build 24 | 25 | tests: 26 | name: Foundry project 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v3 30 | with: 31 | submodules: recursive 32 | - name: Install Foundry 33 | uses: foundry-rs/foundry-toolchain@v1 34 | with: 35 | version: nightly 36 | - name: Install Huff 37 | uses: huff-language/huff-toolchain@v2 38 | with: 39 | version: nightly 40 | - name: Run Forge tests 41 | run: forge test -vvv 42 | id: test 43 | 44 | huff-tests: 45 | name: Tests in Huff 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v3 49 | with: 50 | submodules: recursive 51 | - name: Install Foundry 52 | uses: foundry-rs/foundry-toolchain@v1 53 | with: 54 | version: nightly 55 | - name: Install Huff 56 | uses: huff-language/huff-toolchain@v2 57 | with: 58 | version: nightly 59 | - name: Huff Tests 60 | uses: huff-language/huff-tests-action@v3 61 | with: 62 | with-location: "src" 63 | with-extension: "*.huff" 64 | with-format: "table" 65 | -------------------------------------------------------------------------------- /test/mechanisms/huff-clones/HuffClone.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD 2 | pragma solidity ^0.8.15; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; 6 | import {IExampleClone, IExampleCloneFactory} from "./Interfaces.sol"; 7 | 8 | contract ExampleCloneTest is Test { 9 | IExampleClone internal clone; 10 | IExampleClone internal arrClone; 11 | IExampleCloneFactory internal factory; 12 | 13 | function setUp() public { 14 | IExampleClone impl = IExampleClone(HuffDeployer.deploy("mechanisms/huff-clones/ExampleClone")); 15 | factory = IExampleCloneFactory(HuffDeployer.deploy_with_args("mechanisms/huff-clones/ExampleCloneFactory", abi.encode(address(impl)))); 16 | 17 | // Create (address, uint256, uint64, uint8) clone 18 | clone = IExampleClone(factory.createClone(address(this), type(uint256).max, 8008, 69)); 19 | 20 | // Create (uint256[]) clone 21 | uint256[] memory a = new uint256[](5); 22 | for (uint i; i < 5; ++i) { 23 | a[i] = 256; 24 | } 25 | arrClone = IExampleClone(factory.createArrClone(a)); 26 | } 27 | 28 | /// ----------------------------------------------------------------------- 29 | /// Gas benchmarking 30 | /// ----------------------------------------------------------------------- 31 | 32 | function testGas_param1() public view { 33 | clone.param1(); 34 | } 35 | 36 | function testGas_param2() public view { 37 | clone.param2(); 38 | } 39 | 40 | function testGas_param3() public view { 41 | clone.param3(); 42 | } 43 | 44 | function testGas_param4() public view { 45 | clone.param4(); 46 | } 47 | 48 | function testGas_param5() public view { 49 | arrClone.param5(5); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/utils/mocks/SafeTransferLibWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function safeTransferETH(address, uint256) payable returns () 2 | #define function safeTransferFrom(address, address, address, uint256) nonpayable returns () 3 | #define function safeTransfer(address, address, uint256) nonpayable returns () 4 | #define function safeApprove(address, address, uint256) nonpayable returns () 5 | 6 | #define macro SAFE_TRANSFER_ETH_WRAPPER() = { 7 | 0x24 calldataload // [amount] 8 | 0x04 calldataload // [to, amount] 9 | 10 | SAFE_TRANSFER_ETH() 11 | stop 12 | } 13 | 14 | #define macro SAFE_TRANSFER_FROM_WRAPPER() = { 15 | 0x04 calldataload // [token] 16 | 0x64 calldataload // [amount, token] 17 | 0x44 calldataload // [to, amount, token] 18 | 0x24 calldataload // [from, to, amount, token] 19 | 20 | SAFE_TRANSFER_FROM(0x00) 21 | stop 22 | } 23 | 24 | #define macro SAFE_TRANSFER_WRAPPER() = { 25 | 0x04 calldataload // [token] 26 | 0x44 calldataload // [amount, token] 27 | 0x24 calldataload // [to, amount, token] 28 | 29 | SAFE_TRANSFER(0x00) 30 | stop 31 | } 32 | 33 | #define macro SAFE_APPROVE_WRAPPER() = { 34 | 0x04 calldataload // [token] 35 | 0x44 calldataload // [amount, token] 36 | 0x24 calldataload // [to, amount, token] 37 | 38 | SAFE_APPROVE(0x00) 39 | stop 40 | } 41 | 42 | #define macro MAIN() = { 43 | pc calldataload 0xE0 shr 44 | dup1 __FUNC_SIG(safeTransferETH) eq steth jumpi 45 | dup1 __FUNC_SIG(safeTransferFrom) eq stf jumpi 46 | dup1 __FUNC_SIG(safeTransfer) eq st jumpi 47 | dup1 __FUNC_SIG(safeApprove) eq sa jumpi 48 | 49 | 0x00 dup1 revert 50 | 51 | steth: 52 | SAFE_TRANSFER_ETH_WRAPPER() 53 | stf: 54 | SAFE_TRANSFER_FROM_WRAPPER() 55 | st: 56 | SAFE_TRANSFER_WRAPPER() 57 | sa: 58 | SAFE_APPROVE_WRAPPER() 59 | } 60 | -------------------------------------------------------------------------------- /test/utils/Refunded.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | interface IRefundedMock { 8 | function refundedCall() external payable; 9 | function nonRefundedCall() external payable; 10 | } 11 | 12 | contract RefundedTest is Test { 13 | IRefundedMock refunded; 14 | 15 | function setUp() public { 16 | string memory wrapper_code = vm.readFile("test/utils/mocks/RefundedWrappers.huff"); 17 | refunded = IRefundedMock(HuffDeployer.deploy_with_code("utils/Refunded", wrapper_code)); 18 | } 19 | 20 | function testRefundedCall() public { 21 | // Deal some balances 22 | vm.deal(address(this), 100 ether); 23 | vm.deal(address(refunded), 100 ether); 24 | 25 | // Record the initial balances 26 | uint256 balance = address(this).balance; 27 | uint256 refundedBalance = address(refunded).balance; 28 | 29 | // Call the refunded function 30 | refunded.refundedCall(); 31 | 32 | // We should have gotten a refund 33 | // assertApproxEqAbs(address(this).balance, balance, 1_000_000); 34 | // assertTrue(address(refunded).balance < refundedBalance); 35 | } 36 | 37 | function testRefundedNonCall() public { 38 | // Deal some balances 39 | vm.deal(address(this), 100 ether); 40 | vm.deal(address(refunded), 100 ether); 41 | 42 | // Record the initial balances 43 | uint256 balance = address(this).balance; 44 | uint256 refundedBalance = address(refunded).balance; 45 | 46 | // Call the non refunded function 47 | refunded.nonRefundedCall(); 48 | 49 | // Balances should be the same 50 | assertEq(address(this).balance, balance); 51 | assertEq(address(refunded).balance, refundedBalance); 52 | } 53 | } -------------------------------------------------------------------------------- /test/utils/JumpTableUtil.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | interface IJumpTableUtil { 8 | function getJumpdestMem(uint256) external view returns(uint256); 9 | function getJumpdestStack(uint256) external view returns(uint256); 10 | function getJumpdestMemPacked(uint256) external view returns(uint256); 11 | function getJumpdestStackPacked(uint256) external view returns(uint256); 12 | } 13 | 14 | contract JumpTableUtilTest is Test { 15 | uint constant FIRST_LABEL_PC = 147; 16 | 17 | IJumpTableUtil jtUtil; 18 | 19 | function setUp() public { 20 | /// @notice deploy a new instance of IJumpTableUtil by 21 | /// passing in the address of the deployed Huff contract 22 | string memory wrapper_code = vm.readFile("test/utils/mocks/JumpTableUtilWrappers.huff"); 23 | jtUtil = IJumpTableUtil(HuffDeployer.deploy_with_code("utils/JumpTableUtil", wrapper_code)); 24 | } 25 | 26 | function testGetJumpdestFromJT_Mem() public { 27 | for (uint i; i < 4; i++) { 28 | uint jumpdest = jtUtil.getJumpdestMem(i); 29 | assertEq(jumpdest, FIRST_LABEL_PC + i); 30 | } 31 | } 32 | 33 | function testGetJumpdestFromJT_Stack() public { 34 | for (uint i; i < 4; i++) { 35 | uint jumpdest = jtUtil.getJumpdestStack(i); 36 | assertEq(jumpdest, FIRST_LABEL_PC + i); 37 | } 38 | } 39 | 40 | function testGetJumpdestFromPackedJT_Mem() public { 41 | for (uint i; i < 4; i++) { 42 | uint jumpdest = jtUtil.getJumpdestMemPacked(i); 43 | assertEq(jumpdest, FIRST_LABEL_PC + i); 44 | } 45 | } 46 | 47 | function testGetJumpdestFromPackedJT_Stack() public { 48 | for (uint i; i < 4; i++) { 49 | uint jumpdest = jtUtil.getJumpdestStackPacked(i); 50 | assertEq(jumpdest, FIRST_LABEL_PC + i); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/utils/BitPackLib.huff: -------------------------------------------------------------------------------- 1 | /// @title BitPackLib 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author kadenzipfel 4 | /// @notice Efficient bit packing library 5 | 6 | #define constant MAX = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 7 | 8 | /// @notice Packs value of given length at index of given word 9 | /// @dev Assumes index < 256 - length 10 | #define macro PACK_VALUE() = takes (4) returns (1) { 11 | // Input stack: // [length, index, value, word] 12 | 0x100 sub sub // [shift, value, word] 13 | shl or // [new_word] 14 | } 15 | 16 | /// @notice Unpacks value of given length from right of word 17 | /// @dev word & (~0 >> (256 - length)) 18 | #define macro UNPACK_FROM_RIGHT() = takes (2) returns (1) { 19 | // Input stack: // [length, word] 20 | [MAX] // [not(0), length, word] 21 | swap1 0x100 sub // [offset, not(0), word] 22 | shr and // [value] 23 | } 24 | 25 | /// @notice Unpacks value of given length from left of word 26 | /// @dev word >> (256 - length) 27 | #define macro UNPACK_FROM_LEFT() = takes (2) returns (1) { 28 | // Input stack: // [length, word] 29 | 0x100 sub // [shift, word] 30 | shr // [value] 31 | } 32 | 33 | /// @notice Unpacks value of given length from index of word 34 | /// @dev Assumes index < 256 - length 35 | /// (word & ((~0 >> 256 - length) << (256 - length - index))) >> (256 - length - index) 36 | #define macro UNPACK_FROM_CENTER() = takes (3) returns (1) { 37 | // Input stack: // [length, index, word] 38 | 0x100 sub // [256 - length, index, word] 39 | swap1 dup2 sub // [256 - length - index, 256 - length, word] 40 | [MAX] swap1 swap2 shr // [~0 >> 256 - length, 256 - length - index, word] 41 | dup2 shl swap1 swap2 and // [(word & ((~0 >> 256 - length) << (256 - length - index))), 256 - length - index] 42 | swap1 shr // [value] 43 | } 44 | -------------------------------------------------------------------------------- /test/utils/mocks/SSTORE2Wrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | // #define function read(address, uint256) view returns (bytes memory) 3 | // #define function read(address, uint256, uint256) view returns (bytes memory) 4 | #define function read(address) view returns (bytes memory) 5 | #define function write(bytes memory) nonpayable returns (address) 6 | 7 | #define macro SSTORE2_WRITE_WRAPPER() = takes (0) returns (0) { 8 | SSTORE2_WRITE() // [address] 9 | 0x00 mstore // [] 10 | 0x20 0x00 return // [] 11 | } 12 | 13 | #define macro SSTORE2_READ_WRAPPER() = takes (0) returns (0) { 14 | 0x04 calldataload // [pointer] 15 | SSTORE2_READ() // [data_length_in_memory] 16 | 0x00 return // [] 17 | } 18 | 19 | #define macro SSTORE2_READ_AT_WRAPPER() = takes (0) returns (0) { 20 | 0x24 calldataload // [start] 21 | 0x04 calldataload // [pointer, start] 22 | SSTORE2_READ_AT() // [data_length_in_memory] 23 | 0x00 return // [] 24 | } 25 | 26 | #define macro SSTORE2_READ_BETWEEN_WRAPPER() = takes (0) returns (0) { 27 | 0x44 calldataload // [end] 28 | 0x24 calldataload // [start, end] 29 | 0x04 calldataload // [pointer, start, end] 30 | SSTORE2_READ_BETWEEN() // [data_length_in_memory] 31 | 0x00 return // [] 32 | } 33 | 34 | #define macro MAIN() = takes (0) returns (0) { 35 | pc calldataload 0xE0 shr // [selector] 36 | 37 | dup1 __FUNC_SIG(write) eq write_jump jumpi 38 | dup1 __FUNC_SIG(read) eq read_jump jumpi 39 | dup1 __FUNC_SIG("read(address,uint256)") eq read_at_jump jumpi 40 | dup1 __FUNC_SIG("read(address,uint256,uint256)") eq read_between_jump jumpi 41 | 42 | 0x00 dup1 revert 43 | 44 | write_jump: 45 | SSTORE2_WRITE_WRAPPER() 46 | 47 | read_jump: 48 | SSTORE2_READ_WRAPPER() 49 | 50 | read_at_jump: 51 | SSTORE2_READ_AT_WRAPPER() 52 | 53 | read_between_jump: 54 | SSTORE2_READ_BETWEEN_WRAPPER() 55 | } -------------------------------------------------------------------------------- /test/data-structures/Arrays.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | import "forge-std/console2.sol"; 7 | 8 | interface IArrays { 9 | function setArrayFromCalldata(uint256[] calldata) external; 10 | 11 | function loadArray() external view returns (uint256[] memory); 12 | } 13 | 14 | contract ArraysTest is Test { 15 | IArrays arrays; 16 | 17 | uint256[] array = [1, 2, 3, 4, 5]; 18 | 19 | function setUp() public { 20 | // Read instantiable arrays from file 21 | string memory instantiable_code = vm.readFile( 22 | "test/data-structures/mocks/ArrayWrappers.huff" 23 | ); 24 | 25 | // Create an Instantiable Arrays 26 | HuffConfig config = HuffDeployer.config().with_code(instantiable_code); 27 | arrays = IArrays(config.deploy("data-structures/Arrays")); 28 | 29 | arrays.setArrayFromCalldata(array); 30 | } 31 | 32 | /// @notice Test setting an array and retrieving it 33 | function testSetAndLoadArray() public { 34 | uint256[] memory larray = new uint256[](5); 35 | larray[0] = 6; 36 | larray[1] = 7; 37 | larray[2] = 8; 38 | larray[3] = 9; 39 | larray[4] = 10; 40 | arrays.setArrayFromCalldata(larray); 41 | bytes32 len = vm.load(address(arrays), bytes32(0)); 42 | bytes32 firstElement = vm.load(address(arrays), keccak256(abi.encode(bytes32(0)))); 43 | bytes32 lastElement = vm.load(address(arrays), bytes32(uint256(keccak256(abi.encode(bytes32(0))))+4)); 44 | assertEq(uint256(len), 5); 45 | assertEq(uint256(firstElement), 6); 46 | assertEq(uint256(lastElement), 10); 47 | } 48 | 49 | /// @notice Test setting an array and retrieving it 50 | function testGetArray() public { 51 | uint256[] memory larray = arrays.loadArray(); 52 | assertEq(larray[0], 1); 53 | assertEq(larray[1], 2); 54 | assertEq(larray[2], 3); 55 | assertEq(larray[3], 4); 56 | assertEq(larray[4], 5); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/Ternary.huff: -------------------------------------------------------------------------------- 1 | /// @title Ternary 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice A collection of ternary operators with abstracted conditional logic 5 | 6 | 7 | /// @notice Not Ternary 8 | /// @notice In shorthand, this is `!condition ? x : y`, where the input stack is [condition, x, y] 9 | #define macro NOT_TERNARY() = takes(3) returns(1) { 10 | // Input Stack: [condition, x, y] 11 | 12 | // If the condition is true, jump and pop 13 | __Utils_Misc__ternaryNoSwap jumpi 14 | swap1 // [y, x] 15 | __Utils_Misc__ternaryNoSwap: 16 | // Stack: condition ? [x, y] : [y, x] 17 | pop 18 | 19 | // Output Stack: [condition ? y : x] 20 | } 21 | 22 | /// @notice Ternary 23 | /// @notice In shorthand, this is `condition ? x : y`, where the input stack is [condition, x, y] 24 | /// @dev ((x ^ y) * condition) ^ y 25 | #define macro TERNARY() = takes(3) returns(1) { 26 | // Input Stack: [condition, x, y] 27 | swap1 // [x, condition, y] 28 | dup3 // [y, x, condition, y] 29 | xor mul xor // [((x ^ y) * condition) ^ y] 30 | // Output Stack: [condition ? x : y] 31 | } 32 | 33 | // TESTS 34 | 35 | /// @notice Tests the NOT_TERNARY() macro 36 | #define test TEST_NOT_TERNARY() = { 37 | // Test false should return the first element 38 | 0x02 0x01 0x00 NOT_TERNARY() 39 | 0x01 eq worked jumpi 40 | 0x00 0x00 revert 41 | worked: 42 | 43 | // Test true should return the second element 44 | 0x02 0x01 0x01 NOT_TERNARY() 45 | 0x02 eq worked_again jumpi 46 | 0x00 0x00 revert 47 | worked_again: 48 | } 49 | 50 | /// @notice Tests the TERNARY() macro 51 | #define test TEST_TERNARY() = { 52 | // Test true should return the first element 53 | // 1 ? 1 : 2 == 1 54 | 0x02 0x01 0x01 TERNARY() 55 | 0x01 eq worked jumpi 56 | 0x00 0x00 revert 57 | worked: 58 | 59 | // Test false should return the second element 60 | // 0 ? 1 : 2 == 2 61 | 0x02 0x01 0x00 TERNARY() 62 | 0x02 eq worked_again jumpi 63 | 0x00 0x00 revert 64 | worked_again: 65 | } -------------------------------------------------------------------------------- /test/utils/safe-transfer-lib-mocks/weird-tokens/RevertingToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | contract RevertingToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "RevertingToken"; 18 | 19 | string public constant symbol = "RT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address, uint256) public virtual { 47 | revert(); 48 | } 49 | 50 | function transfer(address, uint256) public virtual { 51 | revert(); 52 | } 53 | 54 | function transferFrom( 55 | address, 56 | address, 57 | uint256 58 | ) public virtual { 59 | revert(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/utils/safe-transfer-lib-mocks/weird-tokens/ReturnsTwoToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | contract ReturnsTwoToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsFalseToken"; 18 | 19 | string public constant symbol = "RTT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address, uint256) public virtual returns (uint256) { 47 | return 2; 48 | } 49 | 50 | function transfer(address, uint256) public virtual returns (uint256) { 51 | return 2; 52 | } 53 | 54 | function transferFrom( 55 | address, 56 | address, 57 | uint256 58 | ) public virtual returns (uint256) { 59 | return 2; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/utils/safe-transfer-lib-mocks/weird-tokens/ReturnsFalseToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | contract ReturnsFalseToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsFalseToken"; 18 | 19 | string public constant symbol = "RFT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address, uint256) public virtual returns (bool) { 47 | return false; 48 | } 49 | 50 | function transfer(address, uint256) public virtual returns (bool) { 51 | return false; 52 | } 53 | 54 | function transferFrom( 55 | address, 56 | address, 57 | uint256 58 | ) public virtual returns (bool) { 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/math/mocks/SafeMathWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | // Wrapper methods (for testing) 3 | #define macro SAFE_ADD_WRAPPER() = takes (0) returns (0) { 4 | 0x04 calldataload // [num1] 5 | 0x24 calldataload // [num2, num1] 6 | SAFE_ADD() // [result] 7 | 0x00 mstore // [] 8 | 0x20 0x00 return // [] 9 | } 10 | 11 | #define macro SAFE_SUB_WRAPPER() = takes (0) returns (0) { 12 | 0x04 calldataload // [num1] 13 | 0x24 calldataload // [num2, num1] 14 | swap1 // [num1, num2] 15 | SAFE_SUB() // [result] 16 | 0x00 mstore // [] 17 | 0x20 0x00 return // [] 18 | } 19 | 20 | #define macro SAFE_MUL_WRAPPER() = takes (0) returns (0) { 21 | 0x04 calldataload // [num1] 22 | 0x24 calldataload // [num2, num1] 23 | SAFE_MUL() // [result] 24 | 0x00 mstore // [] 25 | 0x20 0x00 return // [] 26 | } 27 | 28 | #define macro SAFE_DIV_WRAPPER() = takes (0) returns (0) { 29 | 0x04 calldataload // [num1] 30 | 0x24 calldataload // [num2, num1] 31 | swap1 // [num1, num2] 32 | SAFE_DIV() // [result] 33 | 0x00 mstore // [] 34 | 0x20 0x00 return // [] 35 | } 36 | 37 | #define macro SAFE_MOD_WRAPPER() = takes (0) returns (0) { 38 | 0x04 calldataload // [num1] 39 | 0x24 calldataload // [num2, num1] 40 | swap1 // [num1, num2] 41 | SAFE_MOD() // [result] 42 | 0x00 mstore // [] 43 | 0x20 0x00 return // [] 44 | } 45 | 46 | // Main 47 | #define macro MAIN() = takes (0) returns (0) { 48 | 0x00 calldataload 0xE0 shr 49 | dup1 __FUNC_SIG(safeAdd) eq safe_add jumpi 50 | dup1 __FUNC_SIG(safeSub) eq safe_sub jumpi 51 | dup1 __FUNC_SIG(safeMul) eq safe_mul jumpi 52 | dup1 __FUNC_SIG(safeDiv) eq safe_div jumpi 53 | dup1 __FUNC_SIG(safeMod) eq safe_mod jumpi 54 | 55 | 0x00 0x00 revert 56 | 57 | safe_add: 58 | SAFE_ADD_WRAPPER() 59 | safe_sub: 60 | SAFE_SUB_WRAPPER() 61 | safe_mul: 62 | SAFE_MUL_WRAPPER() 63 | safe_div: 64 | SAFE_DIV_WRAPPER() 65 | safe_mod: 66 | SAFE_MOD_WRAPPER() 67 | } -------------------------------------------------------------------------------- /src/mechanisms/huff-vrgda/LogisticVRGDA.huff: -------------------------------------------------------------------------------- 1 | /// @title Logistic Variable Rate Gradual Dutch Auction 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author transmissions11 4 | /// @author FrankieIsLost 5 | /// @author Maddiaa 6 | /// @notice VRGDA with a logistic issuance curve. 7 | /// @notice Original Implementation by Maddiaa (https://github.com/cheethas/huff-vrgda/blob/main/src/LogisticVRGDA.huff) 8 | 9 | #include "./VRGDA.huff" 10 | 11 | #define constant LOGISTIC_LIMIT = 0x00 // ( int256 ) 12 | #define constant LOGISTIC_LIMIT_DOUBLED = 0x00 // ( int256 ) 13 | #define constant TIME_SCALE = 0x00 // ( int256 ) 14 | 15 | /// @dev Given a number of tokens sold, return the target time that number of tokens should be sold by. 16 | /// @param {Stack} sold A number of tokens sold, scaled by 1e18, to get the corresponding target sale time for. 17 | /// @return The target time the tokens should be sold by, scaled by 1e18, where the time is 18 | /// relative, such that 0 means the tokens should be sold immediately when the VRGDA begins. 19 | #define macro GET_TARGET_SALE_TIME(fail) = takes (1) returns (0) { 20 | // init state = [sold] 21 | [TIME_SCALE] // [timeScale, sold] 22 | swap1 // [sold, perTimeUnit] 23 | 24 | [WAD] // [1e18, sold, timescale] 25 | swap1 // [sold, 1e18, timescale] 26 | [LOGISTIC_LIMIT] // [logisticLimit, sold, 1e18, timeScale] 27 | add // [sold + logisticLimit, 1e18, timeScale] 28 | [LOGISTIC_LIMIT_DOUBLED] // [logisticLimitDoubled, (sold+logisticLimit), 1e18, timeScale] 29 | UNSAFE_WAD_DIV() // [(logisticLimitDoubled / (sold + logisticLimit)), 1e18, timeScale] 30 | sub // [((logisticLimitDoubled / (sold + logisticLimit)) - 1e18), timeScale] 31 | 32 | LN_WAD(fail) // [wadLn((logisticLimitDoubled / (sold + logisticLimit)) - 1e18), timeScale] 33 | UNSAFE_WAD_DIV() // [wadLn((logisticLimitDoubled / (sold + logisticLimit)) - 1e18) / timeScale] 34 | 0x00 sub // [-wadLn((logisticLimitDoubled / (sold + logisticLimit)) - 1e18) / timeScale] 35 | } 36 | -------------------------------------------------------------------------------- /test/auth/Owned.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import {HuffConfig} from "foundry-huff/HuffConfig.sol"; 6 | import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; 7 | import {NonMatchingSelectorsHelper} from "../test-utils/NonMatchingSelectorHelper.sol"; 8 | 9 | 10 | interface Owned { 11 | function setOwner(address) external; 12 | function owner() external returns (address); 13 | } 14 | 15 | contract OwnedTest is Test, NonMatchingSelectorsHelper { 16 | Owned owner; 17 | address constant OWNER = address(0x420); 18 | 19 | event OwnerUpdated(address indexed user, address indexed newOwner); 20 | 21 | function setUp() public { 22 | // Create Owner 23 | string memory wrapper_code = vm.readFile("test/auth/mocks/OwnedWrappers.huff"); 24 | HuffConfig config = HuffDeployer.config().with_code(wrapper_code).with_args(abi.encode(OWNER)); 25 | vm.expectEmit(true, true, true, true); 26 | emit OwnerUpdated(address(config), OWNER); 27 | owner = Owned(config.deploy("auth/Owned")); 28 | } 29 | 30 | /// @notice Test that a non-matching selector reverts 31 | function testNonMatchingSelector(bytes32 callData) public { 32 | bytes4[] memory func_selectors = new bytes4[](2); 33 | func_selectors[0] = Owned.setOwner.selector; 34 | func_selectors[1] = Owned.owner.selector; 35 | 36 | 37 | bool success = nonMatchingSelectorHelper( 38 | func_selectors, 39 | callData, 40 | address(owner) 41 | ); 42 | assert(!success); 43 | } 44 | 45 | function testGetOwner() public { 46 | assertEq(OWNER, owner.owner()); 47 | } 48 | 49 | function testSetOwner(address new_owner) public { 50 | if (new_owner == OWNER) return; 51 | vm.startPrank(new_owner); 52 | vm.expectRevert(); 53 | owner.setOwner(new_owner); 54 | vm.stopPrank(); 55 | assertEq(OWNER, owner.owner()); 56 | } 57 | 58 | function testOwnerCanSetOwner() public { 59 | address new_owner = address(0x50ca1); 60 | vm.startPrank(OWNER); 61 | vm.expectEmit(true, true, true, true); 62 | emit OwnerUpdated(OWNER, new_owner); 63 | owner.setOwner(new_owner); 64 | vm.stopPrank(); 65 | assertEq(new_owner, owner.owner()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/utils/mocks/CallWrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define function callFunc() payable returns () 3 | #define function staticcallFunc() payable returns () 4 | #define function callcodeFunc() payable returns () 5 | 6 | #define macro CALL_WRAPPER() = takes (0) returns (0) { 7 | // Store 0xba5ed in memory 8 | 0xba5ed dup1 0x00 mstore // [0xba5ed] 9 | 10 | // Static Call 11 | CALL(0x01, 0x00, 0x01, 0x00, 0x00, 0x04, 0xFFFFFFFF) // [success, 0xba5ed] 12 | 13 | // Revert if call is unsuccessful 14 | cont jumpi 15 | 0x00 dup1 revert 16 | cont: 17 | 18 | // Load the result 19 | 0x00 mload // [res, 0xba5ed] 20 | 21 | // Compare the results and revert if unequal 22 | eq success jumpi // [] 23 | 0x00 dup1 revert 24 | success: 25 | 26 | stop 27 | } 28 | 29 | #define macro STATICCALL_WRAPPER() = takes (0) returns (0) { 30 | // Store 0xba5ed in memory 31 | 0xba5ed dup1 0x00 mstore // [0xba5ed] 32 | 33 | // Static Call 34 | STATICCALL(0x01, 0x00, 0x01, 0x00, 0x04, 0xFFFFFFFF) // [success, 0xba5ed] 35 | 36 | // Revert if call is unsuccessful 37 | cont jumpi 38 | 0x00 dup1 revert 39 | cont: 40 | 41 | // Load the result 42 | 0x00 mload // [res, 0xba5ed] 43 | 44 | // Compare the results and revert if unequal 45 | eq success jumpi // [] 46 | 0x00 dup1 revert 47 | success: 48 | 49 | stop 50 | } 51 | 52 | #define macro CALLCODE_WRAPPER() = takes (0) returns (0) { 53 | // TODO 54 | 55 | stop 56 | } 57 | 58 | #define macro MAIN() = takes (0) returns (0) { 59 | // Load the function selector 60 | pc calldataload 0xE0 shr // [sig] 61 | 62 | // Match on the function selector 63 | dup1 __FUNC_SIG(callFunc) eq call_jump jumpi // [sig] 64 | dup1 __FUNC_SIG(staticcallFunc) eq staticcall_jump jumpi // [sig] 65 | dup1 __FUNC_SIG(callcodeFunc) eq callcode_jump jumpi // [sig] 66 | 67 | 0x00 dup1 revert 68 | 69 | call_jump: 70 | CALL_WRAPPER() 71 | staticcall_jump: 72 | STATICCALL_WRAPPER() 73 | callcode_jump: 74 | CALLCODE_WRAPPER() 75 | } 76 | -------------------------------------------------------------------------------- /src/proxies/Proxy.huff: -------------------------------------------------------------------------------- 1 | /// @title Proxy 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author Maddiaa 4 | /// @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM 5 | /// instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to 6 | /// be specified by overriding the virtual {_implementation} function. 7 | /// 8 | /// Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a 9 | /// different contract through the {_delegate} function. 10 | /// 11 | /// The success and return data of the delegated call will be returned back to the caller of the proxy. 12 | /// @notice Adapted from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Proxy.sol) 13 | 14 | /// @notice Delegates a call to implementation 15 | /// @param {Stack} implementation - The address of the implementation 16 | #define macro DELEGATE() = takes (1) returns (0) { 17 | // Takes: [implementation] 18 | 19 | calldatasize // [cds, implementation] 20 | 0x00 dup1 // [0, 0, cds, implementation] 21 | calldatacopy // [implementation] 22 | 23 | // Call the implementation 24 | 0x00 // [0, implementation] 25 | dup1 // [0, 0, implementation] 26 | calldatasize // [cds, 0, 0, implementation] 27 | dup2 // [0, cds, 0, 0, implementation] 28 | dup5 // [implementation, 0, cds, 0, 0, implementation] 29 | gas // [gas, implementation, 0, cds, 0, 0, implementation] 30 | delegatecall // [success, implementation] 31 | 32 | // Copy the returned data 33 | returndatasize // [rds, success, implementation] 34 | 0x00 // [0, rds, success, implementation] 35 | dup1 // [0, 0, rds, success, implementation] 36 | returndatacopy // [success, implementation] 37 | 38 | // Return the returned data 39 | iszero proxy_call_failed jumpi // [implementation] 40 | 41 | returndatasize 0x00 return 42 | 43 | proxy_call_failed: 44 | returndatasize 0x00 revert 45 | } -------------------------------------------------------------------------------- /test/utils/TSOwnable.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffConfig.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | interface TSOwnable { 8 | function owner() external view returns (address); 9 | function pendingOwner() external view returns (address); 10 | function setPendingOwner(address) external; 11 | function acceptOwnership() external; 12 | } 13 | 14 | contract TSOwnableTest is Test { 15 | TSOwnable tsOwnable; 16 | address huffConfig; 17 | 18 | function setUp() public { 19 | // Deploy TSOwnable 20 | string memory wrapper_code = vm.readFile("test/utils/mocks/TSOwnableWrappers.huff"); 21 | huffConfig = address(new HuffConfig()); 22 | tsOwnable = TSOwnable( 23 | HuffConfig(huffConfig) 24 | .with_code(wrapper_code) 25 | .deploy("utils/TSOwnable") 26 | ); 27 | } 28 | 29 | function testTsOwnableGetOwner() public { 30 | assertEq(tsOwnable.owner(), huffConfig); 31 | } 32 | 33 | function testTsOwnableSetPendingOwner(address rando) public { 34 | vm.assume(rando != huffConfig); 35 | 36 | vm.startPrank(rando); 37 | vm.expectRevert("ONLY_OWNER"); 38 | tsOwnable.setPendingOwner(address(0xBEEF)); 39 | vm.stopPrank(); 40 | 41 | vm.prank(huffConfig); 42 | tsOwnable.setPendingOwner(address(0xBEEF)); 43 | 44 | assertEq(tsOwnable.pendingOwner(), address(0xBEEF)); 45 | } 46 | 47 | function testTsOwnableSetAndAcceptOwner(address rando) public { 48 | vm.assume(rando != address(0xBEEF)); 49 | 50 | vm.prank(huffConfig); 51 | tsOwnable.setPendingOwner(address(0xBEEF)); 52 | assertEq(tsOwnable.pendingOwner(), address(0xBEEF)); 53 | 54 | // Random person shouldn't be able to accept ownership 55 | vm.startPrank(rando); 56 | vm.expectRevert("ONLY_PENDING_OWNER"); 57 | tsOwnable.acceptOwnership(); 58 | vm.stopPrank(); 59 | 60 | vm.prank(address(0xBEEF)); 61 | tsOwnable.acceptOwnership(); 62 | assertEq(tsOwnable.owner(), address(0xBEEF)); 63 | assertEq(tsOwnable.pendingOwner(), address(0)); 64 | 65 | vm.startPrank(address(0xBEEF)); 66 | vm.expectRevert("ALREADY_OWNER"); 67 | tsOwnable.setPendingOwner(address(0xBEEF)); 68 | vm.stopPrank(); 69 | 70 | } 71 | } -------------------------------------------------------------------------------- /test/utils/mocks/BitPackLibWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function packValue(bytes32, uint256, uint256, uint256) pure returns (bytes32) 2 | #define function unpackValueFromRight(bytes32, uint256) pure returns (uint256) 3 | #define function unpackValueFromLeft(bytes32, uint256) pure returns (uint256) 4 | #define function unpackValueFromCenter(bytes32, uint256, uint256) pure returns (uint256) 5 | 6 | #define macro PACK_VALUE_WRAPPER() = takes (0) returns (0) { 7 | 0x04 calldataload // [word] 8 | 0x24 calldataload // [value, word] 9 | 0x44 calldataload // [index, value, word] 10 | 0x64 calldataload // [length, index, value, word] 11 | PACK_VALUE() // [new_word] 12 | 0x00 mstore // [] 13 | 0x20 0x00 return 14 | } 15 | 16 | #define macro UNPACK_FROM_RIGHT_WRAPPER() = takes (0) returns (0) { 17 | 0x04 calldataload // [word] 18 | 0x24 calldataload // [length, word] 19 | UNPACK_FROM_RIGHT() // [value] 20 | 0x00 mstore // [] 21 | 0x20 0x00 return 22 | } 23 | 24 | #define macro UNPACK_FROM_LEFT_WRAPPER() = takes (0) returns (0) { 25 | 0x04 calldataload // [word] 26 | 0x24 calldataload // [length, word] 27 | UNPACK_FROM_LEFT() // [value] 28 | 0x00 mstore // [] 29 | 0x20 0x00 return 30 | } 31 | 32 | #define macro UNPACK_FROM_CENTER_WRAPPER() = takes (0) returns (0) { 33 | 0x04 calldataload // [word] 34 | 0x24 calldataload // [length, word] 35 | 0x44 calldataload // [length, index, word] 36 | UNPACK_FROM_CENTER() // [value] 37 | 0x00 mstore // [] 38 | 0x20 0x00 return 39 | } 40 | 41 | #define macro MAIN() = takes (0) returns (0) { 42 | // Identify which function is being called using the 4 byte function signature 43 | pc calldataload 0xE0 shr 44 | 45 | dup1 __FUNC_SIG(packValue) eq packValue jumpi 46 | dup1 __FUNC_SIG(unpackValueFromRight) eq unpackValueFromRight jumpi 47 | dup1 __FUNC_SIG(unpackValueFromLeft) eq unpackValueFromLeft jumpi 48 | dup1 __FUNC_SIG(unpackValueFromCenter) eq unpackValueFromCenter jumpi 49 | 50 | 0x00 0x00 revert 51 | 52 | packValue: 53 | PACK_VALUE_WRAPPER() 54 | unpackValueFromRight: 55 | UNPACK_FROM_RIGHT_WRAPPER() 56 | unpackValueFromLeft: 57 | UNPACK_FROM_LEFT_WRAPPER() 58 | unpackValueFromCenter: 59 | UNPACK_FROM_CENTER_WRAPPER() 60 | } -------------------------------------------------------------------------------- /src/utils/Shuffling.huff: -------------------------------------------------------------------------------- 1 | /// @title Shuffling 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author Philogy 4 | /// @author asnared 5 | /// @notice Refactored algorithms for shuffling and other bitwise algorithms. 6 | /// @notice Adapted from Ethereum Consensus Specs (https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#compute_shuffled_index) 7 | 8 | #include "./Address.huff" 9 | #include "./Ternary.huff" 10 | 11 | #include "../math/Math.huff" 12 | 13 | // Constants 14 | #define constant POS_MASK = 0xffffffff00 15 | 16 | /// @notice Shuffling Algorithm 17 | #define macro MECHS__ONE_WAY_SHUFFLE(mem1, mem2) = takes (4) returns (1) { 18 | // Input Stack: [seed, index, index_count, iters] 19 | // Output Stack: [index'] 20 | 21 | __Mechs__shuffleContinue: // [seed, index, index_count, iters] 22 | mstore // [index, index_count, iters] 23 | 0x20 sha3 // [seed', index, index_count, iters] 24 | dup3 dup1 // [index_count, index_count, seed' index, index_count, iters] 25 | dup3 mod // [pivot, index_count, seed', index, index_count, iters] 26 | dup4 dup3 // [index_count, index, pivot, index_count, seed', index, index_count, iters] 27 | sub add mod // [flip, seed', index, index_count, iters] 28 | dup3 dup2 MAX() // [position, flip, seed', index, index_count, iters] 29 | dup1 [POS_MASK] and // [masked_position, position, flip, seed', index, index_count, iters] 30 | mstore // [position, flip, seed', index, index_count, iters] 31 | 0x40 sha3 // [rand2, position, flip, seed', index, index_count, iters] 32 | swap1 0xff and shr // [rand_bit_unmasked, flip, seed', index, index_count, iters] 33 | 0x1 and // [rand_bit, flip, seed', index, index_count, iters] 34 | swap2 swap3 swap2 // [rand_bit, flip, index, seed', index_count, iters] 35 | NOT_TERNARY() // [index', seed', index_count, iters] 36 | swap1 swap3 // [iters, index', index_count, seed'] 37 | UNSAFE_SUB() swap3 // [seed', index', index_count, iters'] 38 | 39 | // Continue if iters > 0 40 | dup4 __Mechs__shuffleContinue jumpi 41 | 42 | // Return the index 43 | pop swap2 pop pop // [index'] 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/utils/ReentrancyGuard.huff: -------------------------------------------------------------------------------- 1 | /// @title Reentrancy Guard 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author rayquaza7 4 | /// @notice Gas optimized reentrancy guard for smart contracts. 5 | /// @notice Adapted from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol) 6 | 7 | #include "./CommonErrors.huff" 8 | 9 | // Interface 10 | #define function lock() nonpayable returns () 11 | #define function unlock() nonpayable returns () 12 | 13 | // Constants 14 | #define constant LOCKED_SLOT = FREE_STORAGE_POINTER() 15 | #define constant _UNLOCKED = 0x01 16 | #define constant _LOCKED = 0x02 17 | 18 | /// @title Lock 19 | /// @notice Locks the contract to prevent reentrancy 20 | #define fn LOCK() = takes (0) returns (0) { 21 | [_LOCKED] // [0x02] 22 | dup1 // [0x02, 0x02] 23 | [LOCKED_SLOT] // [locked_slot, 0x02, 0x02] 24 | sload // [locked_slot_value, 0x02, 0x02] 25 | lt // [locked_slot_value < 0x02, 0x02] 26 | lock jumpi 27 | 28 | // Otherwise revert with re-entrancy 29 | REENTRANCY(0x00) 30 | 31 | lock: 32 | [LOCKED_SLOT] sstore 33 | } 34 | 35 | /// @title Unlock 36 | /// @notice Unlocks the contract 37 | #define fn UNLOCK() = takes (0) returns (0) { 38 | [_UNLOCKED] [LOCKED_SLOT] sstore 39 | } 40 | 41 | #define macro REENTRANCY_GUARD_MAIN() = takes (0) returns (0) { 42 | pc calldataload 0xE0 shr 43 | 44 | dup1 __FUNC_SIG(lock) eq lock_jump jumpi 45 | dup1 __FUNC_SIG(unlock) eq unlock_jump jumpi 46 | 47 | reentrancy_sig_no_match_found jump 48 | 49 | lock_jump: 50 | LOCK() 51 | unlock_jump: 52 | UNLOCK() 53 | 54 | reentrancy_sig_no_match_found: 55 | } 56 | 57 | /// @notice Test Unlocking 58 | #define test TEST_LOCK() = takes (0) returns (0) { 59 | // Make sure our slot is set to the UNLOCKED state 60 | UNLOCK() 61 | 62 | // Lock 63 | LOCK() 64 | [LOCKED_SLOT] sload 65 | 66 | // We expect the locked slot to be set to 2 - the LOCKED state 67 | 0x02 eq succeed jumpi 68 | 0x00 dup1 revert 69 | 70 | succeed: 71 | } 72 | 73 | /// @notice Test Unlocking 74 | #define test TEST_UNLOCK() = takes (0) returns (0) { 75 | // Make sure our slot is set to the LOCKED state 76 | LOCK() 77 | 78 | // Unlock 79 | UNLOCK() 80 | [LOCKED_SLOT] sload 81 | 82 | // We expect the locked slot to be set to 1 - the UNLOCKED state 83 | 0x01 eq succeed jumpi 84 | 0x00 dup1 revert 85 | 86 | succeed: 87 | } -------------------------------------------------------------------------------- /test/utils/safe-transfer-lib-mocks/weird-tokens/ReturnsTooLittleToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | contract ReturnsTooLittleToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsTooLittleToken"; 18 | 19 | string public constant symbol = "RTLT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address, uint256) public virtual { 47 | assembly { 48 | mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) 49 | return(0, 8) 50 | } 51 | } 52 | 53 | function transfer(address, uint256) public virtual { 54 | assembly { 55 | mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) 56 | return(0, 8) 57 | } 58 | } 59 | 60 | function transferFrom( 61 | address, 62 | address, 63 | uint256 64 | ) public virtual { 65 | assembly { 66 | mstore(0, 0x0100000000000000000000000000000000000000000000000000000000000000) 67 | return(0, 8) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/mechanisms/huff-clones/ExampleClone.huff: -------------------------------------------------------------------------------- 1 | /// @title ExampleClone 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author wighawag 4 | /// @author zefram 5 | /// @author hari 6 | /// @author z0r0z 7 | /// @author clabby 8 | /// @notice Clones with Immutable Args Library 9 | /// @notice Adapted from wighawag (https://github.com/wighawag/clones-with-immutable-args/blob/master/src/ExampleClone.sol) 10 | 11 | #include "./HuffClone.huff" 12 | 13 | #define function param1() view returns (address) 14 | #define function param2() view returns (uint256) 15 | #define function param3() view returns (uint64) 16 | #define function param4() view returns (uint8) 17 | #define function param5(uint256) view returns (uint256[] memory) 18 | 19 | #define macro PARAM_1() = takes (0) returns (0) { 20 | 0x00 GET_ARG_ADDRESS() // [arg_addr] 21 | 0x00 mstore // [] 22 | 0x20 0x00 return 23 | } 24 | 25 | #define macro PARAM_2() = takes (0) returns (0) { 26 | 0x14 GET_ARG_UINT_256() // [arg_uint] 27 | 0x00 mstore // [] 28 | 0x20 0x00 return 29 | } 30 | 31 | #define macro PARAM_3() = takes (0) returns (0) { 32 | 0x34 GET_ARG_UINT_64() // [arg_uint] 33 | 0x00 mstore // [] 34 | 0x20 0x00 return 35 | } 36 | 37 | #define macro PARAM_4() = takes (0) returns (0) { 38 | 0x3C GET_ARG_UINT_8() // [arg_uint] 39 | 0x00 mstore // [] 40 | 0x20 0x00 return 41 | } 42 | 43 | #define macro PARAM_5() = takes (0) returns (0) { 44 | 0x04 calldataload // [arr_len] 45 | 46 | // Store pointer in word before array 47 | 0x20 0x00 mstore // [arr_len] 48 | 49 | dup1 0x00 // [0x00, arr_len, arr_len] 50 | GET_ARG_UINT_256_ARR(0x20) // [ptr, arr_len] 51 | swap1 // [arr_len, ptr] 52 | 0x05 shl // [arr_len * 0x20, ptr] 53 | 0x40 add // [arr_len * 0x20 + 0x40, ptr] 54 | 0x00 return // [ptr] 55 | } 56 | 57 | #define macro MAIN() = takes (0) returns (0) { 58 | pc calldataload 0xE0 shr 59 | dup1 __FUNC_SIG(param1) eq param1 jumpi 60 | dup1 __FUNC_SIG(param2) eq param2 jumpi 61 | dup1 __FUNC_SIG(param3) eq param3 jumpi 62 | dup1 __FUNC_SIG(param4) eq param4 jumpi 63 | dup1 __FUNC_SIG(param5) eq param5 jumpi 64 | 65 | param1: 66 | PARAM_1() 67 | param2: 68 | PARAM_2() 69 | param3: 70 | PARAM_3() 71 | param4: 72 | PARAM_4() 73 | param5: 74 | PARAM_5() 75 | } 76 | -------------------------------------------------------------------------------- /src/utils/ERC20Transfer.huff: -------------------------------------------------------------------------------- 1 | /// @title ERC-20 Helper 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author Ben Leimberger 4 | /// @notice Util macros for ERC-20 interactions 5 | 6 | // Interface 7 | #define function transfer(address,uint256) nonpayable returns (bool) 8 | 9 | /// @notice Transfers an ERC-20 token from the caller to the given account 10 | #define macro ERC20_TRANSFER() = takes (3) returns (0) { 11 | // input stack: [acct_addr, amount, getter_addr] 12 | __FUNC_SIG(transfer) // [sig, acct_addr, amount, getter_addr] 13 | 0x00 mstore // [acct_addr, amount, getter_addr] 14 | 15 | // store args in memory 16 | // address, uint256 17 | dup1 0x20 mstore // [acct_addr, amount, getter_addr] 18 | dup2 0x40 mstore // [acct_addr, amount, getter_addr] 19 | 20 | // make call. return size is zero because we can fetch it after the call 21 | 0x00 // [ret_size, acct_addr, amount, getter_addr] 22 | dup1 // [ret_offset, ret_size, acct_addr, amount, getter_addr] 23 | 0x44 // [args_size, ret_offset, ret_size, acct_addr, amount, getter_addr] 24 | 0x1c // [args_offset, args_size, ret_offset, ret_size, acct_addr, amount, getter_addr] 25 | dup3 // [value, args_offset, args_size, ret_offset, ret_size, acct_addr, amount, getter_addr] 26 | dup8 // [to, value, args_offset, args_size, ret_offset, ret_size, acct_addr, amount, getter_addr] 27 | gas // [gas, to, value, args_offset, args_size, ret_offset, ret_size, acct_addr, amount, getter_addr] 28 | call // [success, acct_addr, amount, getter_addr] 29 | 30 | transfer_success jumpi // [acct_addr, amount, getter_addr] 31 | 0x00 dup1 revert // [] - Call failed, revert 32 | 33 | // Resume execution in invoking macro 34 | transfer_success: 35 | } 36 | 37 | /// @notice Helper to copy return data into memory 38 | #define macro COPY_RETURN_DATA(mem_offset, dest_offset) = takes (0) returns (0) { 39 | returndatasize // [returndatasize] 40 | // [mem_offset, returndatasize] 41 | // [destOffset, mem_offset, returndatasize] 42 | returndatacopy // [] - This stores the return data in memory 43 | } 44 | 45 | /// @notice Helper to return data with unknown size from memory 46 | #define macro RETURN_DATA(mem_offset) = takes (0) returns (0) { 47 | returndatasize // [returndatasize] 48 | // [mem_offset, returndatasize] 49 | return // [] 50 | } -------------------------------------------------------------------------------- /test/utils/mocks/ECDSAWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function recoverCd(bytes32, bytes calldata) view returns (address) 2 | #define function recoverShortSig(bytes32, bytes32, bytes32) view returns (address) 3 | #define function recoverVRSSig(bytes32, bytes32, bytes32, bytes32) view returns (address) 4 | #define function toEthSignedMessageHash(bytes32) view returns (bytes32) 5 | #define function toEthSignedMessageHashDyn(bytes) view returns (bytes32) 6 | 7 | #define macro RECOVER_CD_WRAPPER() = { 8 | RECOVER_CD_SIG(0x04, 0x64) // [result] 9 | 0x00 mstore // [] 10 | 0x20 0x00 return 11 | } 12 | 13 | #define macro RECOVER_SHORT_SIG_WRAPPER() = { 14 | 0x04 calldataload // [hash] 15 | 0x24 calldataload // [r, hash] 16 | 0x44 calldataload // [vs, r, hash] 17 | RECOVER_SHORT_SIG() // [result] 18 | 0x00 mstore // [] 19 | 0x20 0x00 return 20 | } 21 | 22 | #define macro RECOVER_VRS_SIG_WRAPPER() = { 23 | 0x64 calldataload // [s] 24 | 0x44 calldataload // [r, s] 25 | 0x24 calldataload // [v, r, s] 26 | 0x04 calldataload // [hash, v, r, s] 27 | RECOVER_VRS_SIG() // [result] 28 | 0x00 mstore // [] 29 | 0x20 0x00 return 30 | } 31 | 32 | #define macro TO_ETH_SIGNED_MSG_HASH_WRAPPER() = { 33 | 0x04 calldataload // [hash] 34 | TO_ETH_SIGNED_MSG_HASH() // [result] 35 | 0x00 mstore // [] 36 | 0x20 0x00 return 37 | } 38 | 39 | #define macro TO_ETH_SIGNED_MSG_HASH_DYN_WRAPPER() = { 40 | 0x24 calldataload // [len(s)] 41 | 0x20 add // [len(s) + 0x20] 42 | 0x24 0x60 calldatacopy // [] 43 | TO_ETH_SIGNED_MSG_HASH_DYN(0x60) // [result] 44 | 0x00 mstore // [] 45 | 0x20 0x00 return 46 | } 47 | 48 | #define macro MAIN() = { 49 | pc calldataload 0xE0 shr 50 | dup1 __FUNC_SIG(recoverCd) eq recover_cd jumpi 51 | dup1 __FUNC_SIG(recoverShortSig) eq recover_short_sig jumpi 52 | dup1 __FUNC_SIG(recoverVRSSig) eq recover_vrs_sig jumpi 53 | dup1 __FUNC_SIG(toEthSignedMessageHash) eq to_eth_signed_msg_hash jumpi 54 | dup1 __FUNC_SIG(toEthSignedMessageHashDyn) eq to_eth_signed_msg_hash_dyn jumpi 55 | 56 | // Revert if no function selectors match 57 | 0x00 dup1 revert 58 | 59 | // Fn dispatch 60 | recover_cd: 61 | RECOVER_CD_WRAPPER() 62 | recover_short_sig: 63 | RECOVER_SHORT_SIG_WRAPPER() 64 | recover_vrs_sig: 65 | RECOVER_VRS_SIG_WRAPPER() 66 | to_eth_signed_msg_hash: 67 | TO_ETH_SIGNED_MSG_HASH_WRAPPER() 68 | to_eth_signed_msg_hash_dyn: 69 | TO_ETH_SIGNED_MSG_HASH_DYN_WRAPPER() 70 | } 71 | -------------------------------------------------------------------------------- /test/utils/MerkleProofLib.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | interface IMerkleProofLib { 8 | function verifyProof(bytes32, bytes32, bytes32[] calldata) external pure returns (bool); 9 | } 10 | 11 | contract MerkleProofLibTest is Test { 12 | IMerkleProofLib merkleLib; 13 | 14 | function setUp() public { 15 | /// @notice deploy a new instance of IMerkleProofLib by 16 | /// passing in the address of the deployed Huff contract 17 | string memory wrapper_code = vm.readFile("test/utils/mocks/MerkleProofLibWrappers.huff"); 18 | merkleLib = IMerkleProofLib(HuffDeployer.deploy_with_code("utils/MerkleProofLib", wrapper_code)); 19 | } 20 | 21 | function testVerifyEmptyMerkleProofSuppliedLeafAndRootSame() public { 22 | bytes32[] memory proof; 23 | assertEq(this.verify(proof, 0x00, 0x00), true); 24 | } 25 | 26 | function testVerifyEmptyMerkleProofSuppliedLeafAndRootDifferent() public { 27 | bytes32[] memory proof; 28 | bytes32 leaf = "a"; 29 | assertEq(this.verify(proof, 0x00, leaf), false); 30 | } 31 | 32 | function testValidProofSupplied() public { 33 | // Merkle tree created from leaves ['a', 'b', 'c']. 34 | // Leaf is 'a'. 35 | bytes32[] memory proof = new bytes32[](2); 36 | proof[0] = 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510; 37 | proof[1] = 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2; 38 | bytes32 root = 0x5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7; 39 | bytes32 leaf = 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb; 40 | assertEq(this.verify(proof, root, leaf), true); 41 | } 42 | 43 | function testVerifyInvalidProofSupplied() public { 44 | // Merkle tree created from leaves ['a', 'b', 'c']. 45 | // Leaf is 'a'. 46 | // Proof is same as testValidProofSupplied but last byte of first element is modified. 47 | bytes32[] memory proof = new bytes32[](2); 48 | proof[0] = 0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5511; 49 | proof[1] = 0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2; 50 | bytes32 root = 0x5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7; 51 | bytes32 leaf = 0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb; 52 | 53 | assertEq(this.verify(proof, root, leaf), false); 54 | } 55 | 56 | function verify( 57 | bytes32[] calldata proof, 58 | bytes32 root, 59 | bytes32 leaf 60 | ) external view returns (bool) { 61 | return merkleLib.verifyProof(root, leaf, proof); 62 | } 63 | } -------------------------------------------------------------------------------- /test/mechanisms/huff-clones/HuffCloneFactory.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD 2 | pragma solidity ^0.8.15; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; 6 | import {IExampleClone, IExampleCloneFactory} from "./Interfaces.sol"; 7 | 8 | contract HuffCloneFactoryTest is Test { 9 | IExampleCloneFactory internal factory; 10 | 11 | function setUp() public { 12 | IExampleClone impl = IExampleClone(HuffDeployer.deploy("mechanisms/huff-clones/ExampleClone")); 13 | factory = IExampleCloneFactory( 14 | HuffDeployer.deploy_with_args("mechanisms/huff-clones/ExampleCloneFactory", abi.encode(address(impl))) 15 | ); 16 | } 17 | 18 | /// ----------------------------------------------------------------------- 19 | /// Gas benchmarking 20 | /// ----------------------------------------------------------------------- 21 | 22 | function testGas_clone( 23 | address param1, 24 | uint256 param2, 25 | uint64 param3, 26 | uint8 param4 27 | ) public { 28 | factory.createClone(param1, param2, param3, param4); 29 | } 30 | 31 | function testGas_arrClone( 32 | uint256 a, 33 | uint256 b, 34 | uint256 c, 35 | uint256 d, 36 | uint256 e 37 | ) public { 38 | uint256[] memory arr = new uint256[](5); 39 | arr[0] = a; 40 | arr[1] = b; 41 | arr[2] = c; 42 | arr[3] = d; 43 | arr[4] = e; 44 | 45 | factory.createArrClone(arr); 46 | } 47 | 48 | /// ----------------------------------------------------------------------- 49 | /// Correctness tests 50 | /// ----------------------------------------------------------------------- 51 | 52 | function testCorrectness_clone( 53 | address param1, 54 | uint256 param2, 55 | uint64 param3, 56 | uint8 param4 57 | ) public { 58 | IExampleClone clone = IExampleClone(factory.createClone( 59 | param1, 60 | param2, 61 | param3, 62 | param4 63 | )); 64 | assertEq(clone.param1(), param1); 65 | assertEq(clone.param2(), param2); 66 | assertEq(clone.param3(), param3); 67 | assertEq(clone.param4(), param4); 68 | } 69 | 70 | function testCorrectness_arrClone( 71 | uint256 a, 72 | uint256 b, 73 | uint256 c, 74 | uint256 d, 75 | uint256 e 76 | ) public { 77 | uint256[] memory arr = new uint256[](5); 78 | arr[0] = a; 79 | arr[1] = b; 80 | arr[2] = c; 81 | arr[3] = d; 82 | arr[4] = e; 83 | 84 | IExampleClone clone = IExampleClone(factory.createArrClone(arr)); 85 | assertEq(clone.param5(arr.length), arr); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /test/proxies/mocks/ClonesWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function clone(address) nonpayable returns (address) 2 | #define function cloneDeterministic(address,bytes32) nonpayable returns (address) 3 | #define function predictDeterministicAddress(address,bytes32) view returns (address) 4 | // #define function predictDeterministicAddress(address,bytes32,address) view returns (address) 5 | 6 | #define macro CLONE_WRAPPER() = { 7 | 0x04 calldataload // [implementation] 8 | CLONE() // [address] 9 | 0x00 mstore // [] 10 | 0x20 0x00 return // return address 11 | } 12 | 13 | #define macro CLONE_DETERMINISTIC_WRAPPER() = { 14 | 0x24 calldataload // [salt] 15 | 0x04 calldataload // [implementation, salt] 16 | CLONE_DETERMINISTIC() // [address] 17 | 0x00 mstore // [] 18 | 0x20 0x00 return // return address 19 | } 20 | 21 | #define macro PREDICT_DETERMINISTIC_ADDRESS_WRAPPER() = { 22 | address // [address(this)] 23 | 0x24 calldataload // [salt, address(this)] 24 | 0x04 calldataload // [implementation, salt, address(this)] 25 | PREDICT_DETERMINISTIC_ADDRESS(0x40) // [address] 26 | 0x00 mstore // [] 27 | 0x20 0x00 return // return address 28 | } 29 | 30 | #define macro PREDICT_DETERMINISTIC_ADDRESS_DEPLOYER_WRAPPER() = { 31 | 0x44 calldataload // [deployer] 32 | 0x24 calldataload // [salt, deployer] 33 | 0x04 calldataload // [implementation, salt, deployer] 34 | PREDICT_DETERMINISTIC_ADDRESS(0x40) // [address] 35 | 0x00 mstore // [] 36 | 0x20 0x00 return // return address 37 | } 38 | 39 | #define macro MAIN() = { 40 | pc calldataload 0xe0 shr 41 | 42 | dup1 __FUNC_SIG(clone) eq clone_jump jumpi 43 | dup1 __FUNC_SIG(cloneDeterministic) eq clone_deterministic_jump jumpi 44 | dup1 __FUNC_SIG(predictDeterministicAddress) eq predict_deterministic_address_jump jumpi 45 | dup1 __FUNC_SIG("predictDeterministicAddress(address,bytes32,address)") eq predict_deterministic_address_deployer_jump jumpi 46 | 47 | // Exit is selector does not match 48 | 0x00 dup1 revert 49 | 50 | clone_jump: 51 | CLONE_WRAPPER() 52 | clone_deterministic_jump: 53 | CLONE_DETERMINISTIC_WRAPPER() 54 | predict_deterministic_address_jump: 55 | PREDICT_DETERMINISTIC_ADDRESS_WRAPPER() 56 | predict_deterministic_address_deployer_jump: 57 | PREDICT_DETERMINISTIC_ADDRESS_DEPLOYER_WRAPPER() 58 | } -------------------------------------------------------------------------------- /test/proxies/Clones.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.15; 3 | 4 | 5 | import "foundry-huff/HuffDeployer.sol"; 6 | import "forge-std/Test.sol"; 7 | 8 | import { MockERC1155 } from "solmate/test/utils/mocks/MockERC1155.sol"; 9 | import { Bytes32AddressLib } from "solmate/utils/Bytes32AddressLib.sol"; 10 | 11 | interface Clones { 12 | function clone(address implementation) external returns (address instance); 13 | function cloneDeterministic(address implementation, bytes32 salt) external returns (address instance); 14 | function predictDeterministicAddress(address implementation, bytes32 salt) external view returns (address predicted); 15 | function predictDeterministicAddress(address implementation, bytes32 salt, address deployer) external pure returns (address predicted); 16 | } 17 | 18 | contract ClonesTest is Test { 19 | using Bytes32AddressLib for bytes32; 20 | 21 | Clones clones; 22 | MockERC1155 implementation; 23 | 24 | function setUp() public { 25 | string memory wrapper_code = vm.readFile("test/proxies/mocks/ClonesWrappers.huff"); 26 | clones = Clones(HuffDeployer.deploy_with_code("proxies/Clones", wrapper_code)); 27 | 28 | // Deploy a mock contract to use as the implementation 29 | implementation = new MockERC1155(); 30 | } 31 | 32 | function testClone() public { 33 | address instance = clones.clone(address(implementation)); 34 | assertTrue(instance != address(0)); 35 | } 36 | 37 | function testCloneDeterministic() public { 38 | bytes32 salt = keccak256(bytes("A salt!")); 39 | address instance = clones.cloneDeterministic(address(implementation), salt); 40 | address predicted = clones.predictDeterministicAddress(address(implementation), salt); 41 | assertEq(instance, predicted); 42 | } 43 | 44 | function testClonesPredictDeterministicAddress() public { 45 | bytes32 salt = keccak256(bytes("A salt!")); 46 | 47 | // Build creation code 48 | bytes memory creationCode = abi.encodePacked( 49 | hex"3d602d80600a3d3981f3363d3d373d3d3d363d73", 50 | address(implementation), 51 | hex"5af43d82803e903d91602b57fd5bf3" 52 | ); 53 | 54 | bytes32 creationCodeHash = keccak256(creationCode); 55 | 56 | bytes memory packed = abi.encodePacked(bytes1(0xff), address(clones), salt, creationCodeHash); 57 | 58 | // Constructed the expected address 59 | address expected = keccak256(packed).fromLast20Bytes(); 60 | 61 | // Validate expected address 62 | assertEq( 63 | clones.predictDeterministicAddress(address(implementation), salt), 64 | expected 65 | ); 66 | assertEq( 67 | clones.predictDeterministicAddress(address(implementation), salt, address(clones)), 68 | expected 69 | ); 70 | } 71 | } -------------------------------------------------------------------------------- /test/utils/LibBit.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | interface ILibBit { 8 | function ffs(uint256) external pure returns (uint256); 9 | function fls(uint256) external pure returns (uint256); 10 | function popCount(uint256) external pure returns (uint256); 11 | function isPowOf2(uint256) external pure returns (uint256); 12 | } 13 | 14 | contract LibBitTest is Test { 15 | ILibBit public lib; 16 | 17 | function setUp() public { 18 | /// @notice deploy a new instance of IJumpTableUtil by 19 | /// passing in the address of the deployed Huff contract 20 | string memory wrapper_code = vm.readFile("test/utils/mocks/LibBitWrappers.huff"); 21 | lib = ILibBit(HuffDeployer.deploy_with_code("utils/LibBit", wrapper_code)); 22 | } 23 | 24 | function testFuzzFLS() public { 25 | for (uint256 i = 1; i < 255; i++) { 26 | assertEq(lib.fls((1 << i) - 1), i - 1); 27 | assertEq(lib.fls((1 << i)), i); 28 | assertEq(lib.fls((1 << i) + 1), i); 29 | } 30 | assertEq(lib.fls(0), 256); 31 | } 32 | 33 | function testFLS() public { 34 | assertEq(lib.fls(0xff << 3), 10); 35 | } 36 | 37 | function testFuzzFFS() public { 38 | uint256 brutalizer = uint256(keccak256(abi.encode(address(this), block.timestamp))); 39 | for (uint256 i = 0; i < 256; i++) { 40 | assertEq(lib.ffs(1 << i), i); 41 | assertEq(lib.ffs(type(uint256).max << i), i); 42 | assertEq(lib.ffs((brutalizer | 1) << i), i); 43 | } 44 | assertEq(lib.ffs(0), 256); 45 | } 46 | 47 | function testFFS() public { 48 | assertEq(lib.ffs(0xff << 3), 3); 49 | } 50 | 51 | function testFuzzPopCount(uint256 x) public { 52 | uint256 c; 53 | unchecked { 54 | for (uint256 t = x; t != 0; c++) { 55 | t &= t - 1; 56 | } 57 | } 58 | assertEq(lib.popCount(x), c); 59 | } 60 | 61 | function testPopCount() public { 62 | assertEq(lib.popCount((1 << 255) | 1), 2); 63 | } 64 | 65 | function testIsPowOf2() public { 66 | assertEq(lib.isPowOf2(0), 0); 67 | assertEq(lib.isPowOf2(type(uint256).max), 0); 68 | unchecked { 69 | for (uint256 i; i < 256; ++i) { 70 | uint256 x = 1 << i; 71 | assertEq(lib.isPowOf2(x), 1); 72 | assertEq(lib.isPowOf2(~x), 0); 73 | } 74 | } 75 | } 76 | 77 | function testFuzzIsPowOf2(uint256 x) public { 78 | vm.assume(x > 0 && x < type(uint256).max); 79 | uint256 result; 80 | assembly { 81 | result := iszero(add(and(x, sub(x, 1)), iszero(x))) 82 | } 83 | assertEq(lib.isPowOf2(x), result); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/JumpTableUtil.huff: -------------------------------------------------------------------------------- 1 | 2 | /// @title JumpTableUtil 3 | /// @notice SPDX-License-Identifier: MIT 4 | /// @author clabby 5 | /// @notice Utility macros for retrieving jumpdest pcs from jump tables 6 | 7 | /// @notice Loads a jumpdest stored in a jumptable into memory at `mem_ptr` 8 | /// 9 | /// @param mem_ptr The memory location to load the 2 byte jumpdest into 10 | /// @param index The index of the jumpdest within the jumptable 11 | /// @param table_start The offset of the jumptable in the contract's bytecode 12 | #define macro LOAD_FROM_JT(mem_ptr) = takes (2) returns (1) { 13 | // Input stack: [index, table_start] 14 | 15 | 0x05 shl add // [table_start + index * 0x20] 16 | 0x20 swap1 // [table_start + index * 0x20, 0x20] 17 | // [mem_ptr, table_start + index * 0x20, 0x20] 18 | codecopy // [] 19 | 20 | // Return stack: [] 21 | } 22 | 23 | /// @notice Retrieves a jumpdest stored in a jumptable and puts it on the stack 24 | /// 25 | /// @param index The index of the jumpdest within the jumptable 26 | /// @param table_start The offset of the jumptable in the contract's bytecode 27 | #define macro RETRIEVE_FROM_JT() = takes (2) returns (1) { 28 | // Input stack: [index, table_start] 29 | 30 | LOAD_FROM_JT(0x00) // [] 31 | 0x00 mload // [res] 32 | 33 | // Return stack: [res] 34 | } 35 | 36 | /// @notice Loads a jumpdest stored in a packed jumptable into memory at `mem_ptr` 37 | /// @dev This macro only loads 2 bytes from the contract code, so make sure to account 38 | /// for this when passing a `mem_ptr`. I.e., if we want to store the jumpdest pc 39 | /// at offset `x`, we would pass in `x + 0x1e` as the `mem_ptr` argument. 40 | /// 41 | /// @param mem_ptr The memory location to load the 2 byte jumpdest into 42 | /// @param index The index of the jumpdest within the packed jumptable 43 | /// @param table_start The offset of the packed jumptable in the contract's bytecode 44 | #define macro LOAD_FROM_PACKED_JT(mem_ptr) = takes (2) returns (1) { 45 | // Input stack: [index, table_start] 46 | 47 | 0x01 shl add // [table_start + index * 0x02] 48 | 0x02 swap1 // [table_start + index * 0x02, 0x02] 49 | // [mem_ptr, table_start + index * 0x02, 0x02] 50 | codecopy // [] 51 | 52 | // Return stack: [] 53 | } 54 | 55 | /// @notice Retrieves a jumpdest stored in a packed jumptable and puts it on the stack 56 | /// 57 | /// @param index The index of the jumpdest within the packed jumptable 58 | /// @param table_start The offset of the packed jumptable in the contract's bytecode 59 | #define macro RETRIEVE_FROM_PACKED_JT() = takes (2) returns (1) { 60 | // Input stack: [index, table_start] 61 | 62 | LOAD_FROM_PACKED_JT(0x1e) 63 | // [] 64 | 0x00 mload // [res] 65 | 66 | // Return stack: [res] 67 | } 68 | -------------------------------------------------------------------------------- /test/utils/BitPackLib.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import "foundry-huff/HuffDeployer.sol"; 6 | 7 | interface IBitPackLib { 8 | function packValue(bytes32, uint256, uint256, uint256) external pure returns (bytes32); 9 | function unpackValueFromRight(bytes32, uint256) external pure returns (uint256); 10 | function unpackValueFromLeft(bytes32, uint256) external pure returns (uint256); 11 | function unpackValueFromCenter(bytes32, uint256, uint256) external pure returns (uint256); 12 | } 13 | 14 | contract BitPackLibTest is Test { 15 | IBitPackLib bitPackLib; 16 | 17 | function setUp() public { 18 | /// @notice deploy a new instance of IBitPackLib by 19 | /// passing in the address of the deployed Huff contract 20 | string memory wrapper_code = vm.readFile("test/utils/mocks/BitPackLibWrappers.huff"); 21 | bitPackLib = IBitPackLib(HuffDeployer.deploy_with_code("utils/BitPackLib", wrapper_code)); 22 | } 23 | 24 | function testPackValue() public { 25 | bytes32 newWord = bitPackLib.packValue(bytes32(0), 0x696969, 8, 24); 26 | assertEq(newWord, bytes32(0x0069696900000000000000000000000000000000000000000000000000000000)); 27 | } 28 | 29 | function testPackLargeValue() public { 30 | // Adds two 0's as right padding 31 | // 256 - 248 = 8 bits = 1 byte = 2 hex characters 32 | bytes32 newWord = bitPackLib.packValue(bytes32(0), 0x69696969696969696969696969696969696969696969696969696969696969, 0, 248); 33 | assertEq(newWord, bytes32(0x6969696969696969696969696969696969696969696969696969696969696900)); 34 | } 35 | 36 | function testPackHighIndex() public { 37 | bytes32 newWord = bitPackLib.packValue(bytes32(0), 0x69, 256, 0); 38 | assertEq(newWord, bytes32(0x0000000000000000000000000000000000000000000000000000000000000069)); 39 | } 40 | 41 | 42 | function testPackHighSize() public { 43 | bytes32 newWord = bitPackLib.packValue(bytes32(0), 0x69, 0, 256); 44 | assertEq(newWord, bytes32(0x0000000000000000000000000000000000000000000000000000000000000069)); 45 | } 46 | 47 | function testUnpackValueFromRight() public { 48 | bytes32 newWord = bitPackLib.packValue(bytes32(0), 0x2323696969, 232, 24); 49 | uint256 value = bitPackLib.unpackValueFromRight(newWord, 24); 50 | assertEq(value, 0x696969); 51 | } 52 | 53 | function testUnpackValueFromLeft() public { 54 | bytes32 newWord = bitPackLib.packValue(bytes32(0), 0x696969, 0, 24); 55 | uint256 value = bitPackLib.unpackValueFromLeft(newWord, 24); 56 | assertEq(value, 0x696969); 57 | } 58 | 59 | function testUnpackValueFromCenter() public { 60 | bytes32 newWord = bitPackLib.packValue(bytes32(0), 0x696969, 64, 24); 61 | uint256 value = bitPackLib.unpackValueFromCenter(newWord, 64, 24); 62 | assertEq(value, 0x696969); 63 | } 64 | } -------------------------------------------------------------------------------- /src/tokens/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | interface TSOwnable { 5 | function owner() external returns (address); 6 | function pendingOwner() external returns (address); 7 | function setPendingOwner(address) external; 8 | function acceptOwnership() external; 9 | 10 | event NewOwner(address indexed,address indexed); 11 | event NewPendingOwner(address indexed,address indexed); 12 | } 13 | 14 | /// @notice Interface of the ERC20 standard as defined in the EIP. 15 | /// @author Adapted from OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol) 16 | interface IERC20 { 17 | /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`) 18 | event Transfer(address indexed from, address indexed to, uint256 value); 19 | 20 | /// @dev Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve} 21 | event Approval(address indexed owner, address indexed spender, uint256 value); 22 | 23 | /// @dev Returns the name of token 24 | function name() external view returns (string memory); 25 | 26 | /// @dev Returns the symbol of token 27 | function symbol() external view returns (string memory); 28 | 29 | /// @dev Returns the symbol of token 30 | function decimals() external view returns (uint256); 31 | 32 | /// @dev Returns the domain separator 33 | function DOMAIN_SEPARATOR() external view returns (bytes32); 34 | 35 | /// @dev Permit 36 | function permit(address, address, uint256, uint256, uint8, bytes32, bytes32) external; 37 | 38 | /// @dev Permit Nonces 39 | function nonces(address) external returns (uint256); 40 | 41 | /// @dev Returns the amount of tokens in existence 42 | function totalSupply() external view returns (uint256); 43 | 44 | /// @dev Returns the amount of tokens owned by `account` 45 | function balanceOf(address account) external view returns (uint256); 46 | 47 | /// @dev Creates new token. 48 | function transfer(address to, uint256 amount) external returns (bool); 49 | 50 | /// @dev Moves `amount` tokens from `from` to `to` using the allowance mechanism 51 | function transferFrom(address from, address to, uint256 amount) external returns (bool); 52 | 53 | /// @dev Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` 54 | function allowance(address owner, address spender) external view returns (uint256); 55 | 56 | /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 57 | function approve(address spender, uint256 amount) external returns (bool); 58 | } 59 | 60 | interface IMintableERC20 is IERC20 { 61 | /// @dev Moves `amount` tokens from the caller's account to `to`. 62 | function mint(address to, uint256 amount) external; 63 | 64 | /// @dev Removes `amount` tokens from the `from` account. 65 | function burn(address from, uint256 amount) external; 66 | } 67 | 68 | interface IERC20Ownable is TSOwnable, IERC20 {} 69 | -------------------------------------------------------------------------------- /test/utils/safe-transfer-lib-mocks/weird-tokens/MissingReturnToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | contract MissingReturnToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "MissingReturnToken"; 18 | 19 | string public constant symbol = "MRT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address spender, uint256 amount) public virtual { 47 | allowance[msg.sender][spender] = amount; 48 | 49 | emit Approval(msg.sender, spender, amount); 50 | } 51 | 52 | function transfer(address to, uint256 amount) public virtual { 53 | balanceOf[msg.sender] -= amount; 54 | 55 | // Cannot overflow because the sum of all user 56 | // balances can't exceed the max uint256 value. 57 | unchecked { 58 | balanceOf[to] += amount; 59 | } 60 | 61 | emit Transfer(msg.sender, to, amount); 62 | } 63 | 64 | function transferFrom( 65 | address from, 66 | address to, 67 | uint256 amount 68 | ) public virtual { 69 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 70 | 71 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 72 | 73 | balanceOf[from] -= amount; 74 | 75 | // Cannot overflow because the sum of all user 76 | // balances can't exceed the max uint256 value. 77 | unchecked { 78 | balanceOf[to] += amount; 79 | } 80 | 81 | emit Transfer(from, to, amount); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/auth/Owned.huff: -------------------------------------------------------------------------------- 1 | /// @title Owned 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice An single owner authorization module 5 | /// @notice Adapted from 6 | 7 | // Interface 8 | #define function setOwner(address) nonpayable returns () 9 | #define function owner() view returns (address) 10 | 11 | // Events 12 | #define event OwnerUpdated(address indexed user, address indexed newOwner) 13 | 14 | // Storage Slots 15 | #define constant OWNER = FREE_STORAGE_POINTER() 16 | 17 | // CONSTRUCTOR 18 | #define macro OWNED_CONSTRUCTOR() = takes (0) returns (0) { 19 | // Copy the owner into memory 20 | 0x20 // [size] - byte size to copy 21 | 0x20 codesize sub // [offset, size] - offset in the code to copy from 22 | 0x00 // [mem, offset, size] - offset in memory to copy to 23 | codecopy // [] 24 | 25 | // Set the new owner 26 | 0x00 mload // [owner] 27 | dup1 // [owner, owner] 28 | [OWNER] // [OWNER, owner, owner] 29 | sstore // [owner] 30 | 31 | // Emit the owner updated event 32 | caller // [from, owner] 33 | __EVENT_HASH(OwnerUpdated) // [sig, from, owner] 34 | 0x00 0x00 // [0, 0, sig, from, owner] 35 | log3 // [] 36 | } 37 | 38 | /// @notice Only Owner Modifier 39 | #define macro IS_OWNER() = takes (0) returns (0) { 40 | caller // [msg.sender] 41 | [OWNER] sload // [owner, msg.sender] 42 | eq authed jumpi // [authed] 43 | 44 | // Revert otherwise 45 | 0x00 0x00 revert 46 | 47 | authed: 48 | } 49 | 50 | /// @notice Set the Owner 51 | /// @param {owner} [address] - The new owner 52 | #define macro SET_OWNER() = takes (0) returns (0) { 53 | // Check that the caller is authorized 54 | IS_OWNER() 55 | 56 | // Set the new owner 57 | 0x04 calldataload // [newOwner] 58 | dup1 // [newOwner, newOwner] 59 | [OWNER] sstore // [newOwner] 60 | 61 | // Emit the owner updated event 62 | caller // [from, newOwner] 63 | __EVENT_HASH(OwnerUpdated) // [sig, from, newOwner] 64 | 0x00 0x00 // [0, 32, sig, from, newOwner] 65 | log3 // [] 66 | 67 | stop 68 | } 69 | 70 | /// @notice Get the owner of the contract 71 | /// @return {owner} [address] - The owner of the contract 72 | #define macro OWNER() = takes (0) returns (0) { 73 | [OWNER] sload // [owner] 74 | 0x00 mstore // [] 75 | 0x20 0x00 return 76 | } 77 | 78 | /// @notice Main Function Dispatcher 79 | #define macro OWNED_MAIN() = takes (1) returns (1) { 80 | // Input Stack: [function_selector] 81 | 82 | dup1 __FUNC_SIG(setOwner) eq set_owner jumpi 83 | dup1 __FUNC_SIG(owner) eq owner jumpi 84 | 85 | // Bubble up to parent macro 86 | no_match jump 87 | 88 | set_owner: 89 | SET_OWNER() 90 | owner: 91 | OWNER() 92 | 93 | no_match: 94 | } -------------------------------------------------------------------------------- /src/utils/Refunded.huff: -------------------------------------------------------------------------------- 1 | /// @title Refunded 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice Efficient gas refunds distributed through a modifier 5 | /// @notice Adapted from Zolidity (https://github.com/z0r0z/zolidity/blob/main/src/utils/Refunded.sol) 6 | 7 | #include "./Errors.huff" 8 | #include "./ReentrancyGuard.huff" 9 | 10 | /// @notice The base cost of refunding 11 | #define constant BASE_COST = 0x6359 // 25433 12 | 13 | /// @notice The maximum amount of gas that can be refunded 14 | #define constant GAS_PRICE_MAX = 0x9502F9000 // 4e10 15 | 16 | // Refunded custom errors 17 | #define constant MAX_GAS_ERROR = 0x4d41585f47415300000000000000000000000000000000000000000000000000 18 | #define constant MAX_GAS_LENGTH = 0x07 19 | 20 | /// @notice Refunds contract calls up to a maximum of 4e10 gas 21 | /// @notice Modified functions over 21k gas benefit most from a refund 22 | #define macro REFUNDED(dest) = takes (0) returns (0) { 23 | // Get the starting amount of gas 24 | gas // [gasLeft] 25 | 26 | // Prevent Reentrancy 27 | LOCK() // [gasLeft] 28 | 29 | basefee [GAS_PRICE_MAX] add // [currMaxGas, gasLeft] 30 | gasprice gt iszero // [!(gasPrice > currMaxGas), gasLeft] 31 | __Safe_Gas_Refund__j jumpi // [gasLeft] 32 | MAX_GAS(0x00) 33 | 34 | __Safe_Gas_Refund__j: 35 | 36 | // The below attempts to mimic `_;` using a jump 37 | // NOTE: This must jump back to `__Refund_Return_Dest` to complete the refund 38 | jump 39 | __Refund_Return_Dest: 40 | 41 | // Calculate refund amount 42 | gas swap1 sub // [gasUsed] 43 | [BASE_COST] add // [gasUsed + BASE_COST] 44 | gasprice mul // [(gasUsed + BASE_COST) * gasPrice] 45 | 46 | // Refund the gas to origin 47 | 0x00 // [retOffset, value] 48 | 0x00 // [argSize, retOffset, value] 49 | 0x00 // [argOffset, argSize, retOffset, value] 50 | 0x00 // [retSize, argOffset, argSize, retOffset, value] 51 | swap4 // [value, argOffset, argSize, retOffset, retSize] 52 | origin // [to, value, argOffset, argSize, retOffset, retSize] 53 | gas // [gas, to, value, argOffset, argSize, retOffset, retSize] 54 | call // [success] 55 | __Refund_Successful__j jumpi 56 | 0x00 dup1 revert 57 | 58 | // The refund was successful! 59 | __Refund_Successful__j: 60 | 61 | // Finally, unlock the guard 62 | UNLOCK() 63 | } 64 | 65 | /// @notice Reverts with an "MAX_GAS" message if the condition is false 66 | #define macro MAX_GAS(condition) = takes (0) returns (0) { 67 | [MAX_GAS_ERROR] // ["MAX_GAS"] 68 | [MAX_GAS_LENGTH] // [7 (length), "MAX_GAS"] 69 | // [condition, 7 (length), "MAX_GAS"] 70 | REQUIRE() // [] 71 | } -------------------------------------------------------------------------------- /test/utils/mocks/JumpTableUtilWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function getJumpdestMem(uint256) view returns (uint256) 2 | #define function getJumpdestStack(uint256) view returns (uint256) 3 | #define function getJumpdestMemPacked(uint256) view returns (uint256) 4 | #define function getJumpdestStackPacked(uint256) view returns (uint256) 5 | 6 | #define jumptable TEST_TABLE { 7 | test_label_a test_label_b test_label_c test_label_d 8 | } 9 | 10 | #define jumptable__packed TEST_TABLE_PACKED { 11 | test_label_a test_label_b test_label_c test_label_d 12 | } 13 | 14 | #define macro GET_JUMPDEST_MEM_WRAPPER() = takes (0) returns (0) { 15 | __tablestart(TEST_TABLE) // [table_start] 16 | 0x04 calldataload // [n, table_start] 17 | 18 | // Load a jumpdest inside of `TEST_TABLE` at index `n` into 19 | // memory at 0x00. 20 | LOAD_FROM_JT(0x00) // [] 21 | 0x20 0x00 return 22 | } 23 | 24 | #define macro GET_JUMPDEST_STACK_WRAPPER() = takes (0) returns (0) { 25 | __tablestart(TEST_TABLE) // [table_start] 26 | 0x04 calldataload // [n, table_start] 27 | RETRIEVE_FROM_JT() // [jumpdest_pc] 28 | 29 | // Store our jumpdest_pc in memory & return it 30 | 0x00 mstore // [] 31 | 0x20 0x00 return 32 | } 33 | 34 | #define macro GET_JUMPDEST_MEM_PACKED_WRAPPER() = takes (0) returns (0) { 35 | __tablestart(TEST_TABLE_PACKED) // [table_start] 36 | 0x04 calldataload // [n, table_start] 37 | 38 | // Store the retrieved jumpdest at 0x00 in memory. 39 | // Since `LOAD_FROM_PACKED_JT` only retrieves 2 bytes 40 | // from the contract's code in the `codecopy` op, we 41 | // need to pass our desired memory pointer + 0x1e (30 bytes) 42 | LOAD_FROM_PACKED_JT(0x1e) // [] 43 | 0x20 0x00 return 44 | } 45 | 46 | #define macro GET_JUMPDEST_STACK_PACKED_WRAPPER() = takes (0) returns (0) { 47 | __tablestart(TEST_TABLE_PACKED) // [table_start] 48 | 0x04 calldataload // [n, table_start] 49 | RETRIEVE_FROM_PACKED_JT() // [jumpdest_pc] 50 | 51 | // Store our jumpdest_pc in memory & return it 52 | 0x00 mstore // [] 53 | 0x20 0x00 return 54 | } 55 | 56 | #define macro MAIN() = takes (0) returns (0) { 57 | pc calldataload 0xE0 shr 58 | dup1 __FUNC_SIG(getJumpdestMem) eq get_jumpdest_mem jumpi 59 | dup1 __FUNC_SIG(getJumpdestStack) eq get_jumpdest_stack jumpi 60 | dup1 __FUNC_SIG(getJumpdestMemPacked) eq get_jumpdest_mem_packed jumpi 61 | dup1 __FUNC_SIG(getJumpdestStackPacked) eq get_jumpdest_stack_packed jumpi 62 | 63 | // Revert if no function signature matched 64 | test_label_d jump 65 | 66 | get_jumpdest_mem: 67 | GET_JUMPDEST_MEM_WRAPPER() 68 | get_jumpdest_stack: 69 | GET_JUMPDEST_MEM_WRAPPER() 70 | get_jumpdest_mem_packed: 71 | GET_JUMPDEST_MEM_PACKED_WRAPPER() 72 | get_jumpdest_stack_packed: 73 | GET_JUMPDEST_STACK_PACKED_WRAPPER() 74 | 75 | // Test labels included in `TEST_TABLE` 76 | test_label_a: 77 | test_label_b: 78 | test_label_c: 79 | test_label_d: 80 | 0x00 dup1 revert 81 | } -------------------------------------------------------------------------------- /src/utils/Pausable.huff: -------------------------------------------------------------------------------- 1 | /// @title Pausable 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author zarf.eth 4 | /// @notice A Pausable implementation 5 | 6 | 7 | // Interface 8 | 9 | /// @notice returns whether the contract is paused 10 | #define function isPaused() view returns (bool) 11 | 12 | /// @notice Pauses the contract 13 | /// @dev Only callable when the contract is unpaused. 14 | #define function pause() nonpayable returns () 15 | 16 | /// @notice Unpauses the contract 17 | /// @dev Only callable when the contract is paused. 18 | #define function unpause() nonpayable returns () 19 | 20 | /// @notice Emitted when contract is paused. 21 | #define event Paused(address) 22 | 23 | /// @notice Emitted when contract is unpaused. 24 | #define event Unpaused(address) 25 | 26 | 27 | // Storage 28 | 29 | /// @notice Paused Storage Slot 30 | #define constant PAUSED_SLOT = FREE_STORAGE_POINTER() 31 | 32 | /// @notice Unpaused representation 33 | #define constant NOT_PAUSED = 0x01 34 | 35 | /// @notice Paused representation 36 | #define constant PAUSED = 0x02 37 | 38 | 39 | /// @notice Pausable constructor 40 | #define macro PAUSABLE_CONSTRUCTOR() = takes (0) returns (0) { 41 | [NOT_PAUSED] [PAUSED_SLOT] sstore // [] 42 | } 43 | 44 | /// @notice whenNotPaused modifier 45 | #define macro WHEN_NOT_PAUSED_MODIFIER() = takes (0) returns (0) { 46 | [PAUSED_SLOT] sload // [isPaused] 47 | [NOT_PAUSED] eq when_not_paused jumpi // [] 48 | 0x00 dup1 revert // [] 49 | when_not_paused: // [] 50 | } 51 | 52 | /// @notice whenPaused modifier 53 | #define macro WHEN_PAUSED_MODIFIER() = takes (0) returns (0) { 54 | [PAUSED_SLOT] sload // [isPaused] 55 | [PAUSED] eq when_paused jumpi // [] 56 | 0x00 dup1 revert // [] 57 | when_paused: // [] 58 | } 59 | 60 | /// @notice return whether contract is paused 61 | #define macro PAUSABLE_IS_PAUSED() = takes (0) returns (0) { 62 | 0x01 // [1] 63 | [PAUSED_SLOT] sload // [isPaused, 1] 64 | sub // [bool] 65 | 0x00 mstore // [] 66 | 0x20 0x00 return // [] 67 | } 68 | 69 | /// @notice Pause the contract 70 | #define macro PAUSABLE_PAUSE() = takes (0) returns (0) { 71 | WHEN_NOT_PAUSED_MODIFIER() // [] 72 | 73 | //emit Paused(address) 74 | caller __EVENT_HASH(Paused) 0x00 dup1 // [0, 0, EVENT_PAUSED, msg.sender] 75 | log2 // [] 76 | 77 | [PAUSED] [PAUSED_SLOT] sstore // [] 78 | stop 79 | } 80 | 81 | /// @notice Unpause the contract 82 | #define macro PAUSABLE_UNPAUSE() = takes (0) returns (0) { 83 | WHEN_PAUSED_MODIFIER() // [] 84 | 85 | //emit Unpaused(address) 86 | caller __EVENT_HASH(Unpaused) 0x00 dup1 // [0, 0, EVENT_UNPAUSED, msg.sender] 87 | log2 // [] 88 | 89 | [NOT_PAUSED] [PAUSED_SLOT] sstore // [] 90 | stop 91 | } 92 | -------------------------------------------------------------------------------- /test/utils/CREATE3.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | 7 | import { WETH } from "solmate/tokens/WETH.sol"; 8 | import { MockERC20 } from "solmate/test/utils/mocks/MockERC20.sol"; 9 | import { MockAuthChild } from "solmate/test/utils/mocks/MockAuthChild.sol"; 10 | 11 | interface CREATE3 { 12 | function deploy(bytes32 salt, bytes memory creationCode, uint256 value) external payable returns (address deployed); 13 | function getDeployed(bytes32 salt) external view returns (address deployed); 14 | } 15 | 16 | contract CREATE3Test is Test { 17 | CREATE3 create3; 18 | 19 | function setUp() public { 20 | string memory wrapper_code = vm.readFile("test/utils/mocks/Create3Wrappers.huff"); 21 | create3 = CREATE3(HuffDeployer.deploy_with_code("utils/CREATE3", wrapper_code)); 22 | } 23 | 24 | function testCreate3ERC20() public { 25 | bytes32 salt = keccak256(bytes("A salt!")); 26 | 27 | bytes memory packedCode = abi.encodePacked(type(MockERC20).creationCode, abi.encode("Mock Token", "MOCK", 18)); 28 | 29 | MockERC20 deployed = MockERC20( 30 | create3.deploy( 31 | salt, 32 | packedCode, 33 | 0 34 | ) 35 | ); 36 | 37 | assertEq(address(deployed), create3.getDeployed(salt)); 38 | 39 | assertEq(deployed.name(), "Mock Token"); 40 | assertEq(deployed.symbol(), "MOCK"); 41 | assertEq(deployed.decimals(), 18); 42 | } 43 | 44 | function testFailDoubleDeploySameBytecode() public { 45 | bytes32 salt = keccak256(bytes("Salty...")); 46 | 47 | create3.deploy(salt, type(MockAuthChild).creationCode, 0); 48 | create3.deploy(salt, type(MockAuthChild).creationCode, 0); 49 | } 50 | 51 | function testFailDoubleDeployDifferentBytecode() public { 52 | bytes32 salt = keccak256(bytes("and sweet!")); 53 | 54 | create3.deploy(salt, type(WETH).creationCode, 0); 55 | create3.deploy(salt, type(MockAuthChild).creationCode, 0); 56 | } 57 | 58 | function testDeployERC20( 59 | bytes32 salt, 60 | string calldata name, 61 | string calldata symbol, 62 | uint8 decimals 63 | ) public { 64 | MockERC20 deployed = MockERC20( 65 | create3.deploy(salt, abi.encodePacked(type(MockERC20).creationCode, abi.encode(name, symbol, decimals)), 0) 66 | ); 67 | 68 | assertEq(address(deployed), create3.getDeployed(salt)); 69 | 70 | assertEq(deployed.name(), name); 71 | assertEq(deployed.symbol(), symbol); 72 | assertEq(deployed.decimals(), decimals); 73 | } 74 | 75 | function testFailDoubleDeploySameBytecode(bytes32 salt, bytes calldata bytecode) public { 76 | create3.deploy(salt, bytecode, 0); 77 | create3.deploy(salt, bytecode, 0); 78 | } 79 | 80 | function testFailDoubleDeployDifferentBytecode( 81 | bytes32 salt, 82 | bytes calldata bytecode1, 83 | bytes calldata bytecode2 84 | ) public { 85 | create3.deploy(salt, bytecode1, 0); 86 | create3.deploy(salt, bytecode2, 0); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/utils/safe-transfer-lib-mocks/weird-tokens/ReturnsTooMuchToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | contract ReturnsTooMuchToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsTooMuchToken"; 18 | 19 | string public constant symbol = "RTMT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | CONSTRUCTOR 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | constructor() { 38 | totalSupply = type(uint256).max; 39 | balanceOf[msg.sender] = type(uint256).max; 40 | } 41 | 42 | /*/////////////////////////////////////////////////////////////// 43 | ERC20 LOGIC 44 | //////////////////////////////////////////////////////////////*/ 45 | 46 | function approve(address spender, uint256 amount) public virtual { 47 | allowance[msg.sender][spender] = amount; 48 | 49 | emit Approval(msg.sender, spender, amount); 50 | 51 | assembly { 52 | mstore(0, 1) 53 | return(0, 4096) 54 | } 55 | } 56 | 57 | function transfer(address to, uint256 amount) public virtual { 58 | balanceOf[msg.sender] -= amount; 59 | 60 | // Cannot overflow because the sum of all user 61 | // balances can't exceed the max uint256 value. 62 | unchecked { 63 | balanceOf[to] += amount; 64 | } 65 | 66 | emit Transfer(msg.sender, to, amount); 67 | 68 | assembly { 69 | mstore(0, 1) 70 | return(0, 4096) 71 | } 72 | } 73 | 74 | function transferFrom( 75 | address from, 76 | address to, 77 | uint256 amount 78 | ) public virtual { 79 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 80 | 81 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 82 | 83 | balanceOf[from] -= amount; 84 | 85 | // Cannot overflow because the sum of all user 86 | // balances can't exceed the max uint256 value. 87 | unchecked { 88 | balanceOf[to] += amount; 89 | } 90 | 91 | emit Transfer(from, to, amount); 92 | 93 | assembly { 94 | mstore(0, 1) 95 | return(0, 4096) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/mechanisms/huff-vrgda/VRGDA.huff: -------------------------------------------------------------------------------- 1 | /// @title Variable Rate Gradual Dutch Auction 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author transmissions11 4 | /// @author FrankieIsLost 5 | /// @author Maddiaa 6 | /// @notice Sell tokens roughly according to an issuance schedule. 7 | /// @notice Original Implementation by Maddiaa (https://github.com/cheethas/huff-vrgda/blob/main/src/VRGDA.huff) 8 | 9 | #include "./SignedWadMath.huff" 10 | 11 | // These will be overriden with the constructor flag 12 | // The constructor logic will need to be copied within deploy scripts 13 | // in order to inline the correct constants 14 | #define constant TARGET_PRICE = 0x00 // ( int256 ) 15 | #define constant DECAY_CONSTANT = 0x00 // ( int256 ) 16 | 17 | /// @notice Calculate the price of a token according to the VRGDA formula. 18 | /// @param {Stack} timeSinceStart Time passed since the VRGDA began, scaled by 1e18. 19 | /// @param {Stack} sold The total number of tokens that have been sold so far. 20 | /// @return The price of a token according to VRGDA, scaled by 1e18. 21 | #define macro GET_VRGDA_PRICE(fail) = takes(2) returns(1) { 22 | // Inputs : [timeSinceStart, Sold] 23 | 24 | swap1 // [sold, timeSinceStart] 25 | 0x01 // [1, sold, timeSinceStart] 26 | add // [1 + sold, timeSinceStart] 27 | TO_WAD_UNSAFE() // [wad(1 + sold), timeSinceStart] 28 | GET_TARGET_SALE_TIME(fail) // [targetSaleTime, timeSinceStart] 29 | 30 | swap1 // [(timeSinceStart, targetSaleTime] 31 | sub // [(timeSinceStart - targetSaleTime)) 32 | 33 | [DECAY_CONSTANT] // [decayConst, (timeSinceStart - targetSaleTime)] 34 | UNSAFE_WAD_MUL() // [decayConst * (timeSinceStart - targetSaleTime)] 35 | EXP_WAD(fail) // [wadExp(decayConst * (timeSinceStart - targetSaleTime))] 36 | 37 | [TARGET_PRICE] // [targetPrice, wadExp(decayConst * (timeSinceStart - targetSaleTime))] 38 | WAD_MUL(fail) // [targetPrice * wadExp(decayConst * (timeSinceStart - targetSaleTime))] 39 | } 40 | 41 | /// Return Consant 42 | /// @notice Returns a constant provided as an arg 43 | /// @param _constant The constant to return 44 | #define macro RETURN_CONSTANT(constant) = takes(1) returns(0) { 45 | __RETURN_STACK_ITEM_ONE() 46 | } 47 | 48 | /// Return Stack Item One 49 | /// @notice Returns the item currently at the top of the stack 50 | /// @dev 32 bytes only 51 | #define macro __RETURN_STACK_ITEM_ONE() = takes(1) returns(0) { 52 | 0x00 mstore 53 | 0x20 0x00 return 54 | } 55 | 56 | /// @notice Get VRGDA Price External 57 | /// @notice External wrapper to return the current vrgda price 58 | #define macro GET_VRGDA_PRICE_EXTERNAL(fail) = { 59 | 0x24 calldataload // [sold] 60 | 0x04 calldataload // [timesinceStart, sold] 61 | 62 | GET_VRGDA_PRICE(fail) // [price] 63 | __RETURN_STACK_ITEM_ONE() 64 | } 65 | 66 | /// @notice Get Target Sale Time External 67 | /// @notice External wrapper to return the target sale time 68 | #define macro GET_TARGET_SALE_TIME_EXTERNAL(fail) = { 69 | 0x04 calldataload // [sold] 70 | GET_TARGET_SALE_TIME(fail) // [targetSaleTime] 71 | __RETURN_STACK_ITEM_ONE() // *return targetSaleTime 72 | } -------------------------------------------------------------------------------- /test/data-structures/Hashmap.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import {HuffConfig} from "foundry-huff/HuffConfig.sol"; 6 | import {HuffDeployer} from "foundry-huff/HuffDeployer.sol"; 7 | 8 | interface Hashmap { 9 | function loadElement(bytes32) external view returns (bytes32); 10 | function loadElementFromKeys(bytes32, bytes32) external returns (bytes32); 11 | function loadElementFromKeys2D(bytes32, bytes32, bytes32) external returns (bytes32); 12 | function loadElementFromKeys3D(bytes32, bytes32, bytes32, bytes32) external returns (bytes32); 13 | function storeElement(bytes32 key, bytes32 value) external; 14 | function storeElementFromKeys(bytes32 key1, bytes32 key2, bytes32 value) external; 15 | function storeElementFromKeys2D( 16 | bytes32 slot, 17 | bytes32 key1, 18 | bytes32 key2, 19 | bytes32 value 20 | ) external; 21 | function storeElementFromKeys3D( 22 | bytes32 slot, 23 | bytes32 key1, 24 | bytes32 key2, 25 | bytes32 key3, 26 | bytes32 value 27 | ) external; 28 | } 29 | 30 | contract HashmapTest is Test { 31 | Hashmap hmap; 32 | 33 | function setUp() public { 34 | // Read instantiable hashmap from file 35 | string memory instantiable_code = vm.readFile("test/data-structures/mocks/HashmapWrappers.huff"); 36 | 37 | // Create an Instantiable Hashmap 38 | HuffConfig config = HuffDeployer.config().with_code(instantiable_code); 39 | hmap = Hashmap(config.deploy("data-structures/Hashmap")); 40 | } 41 | 42 | /// @notice Test getting a value for a key 43 | function testGetKey(bytes32 key) public { 44 | bytes32 element = hmap.loadElement(key); 45 | assertEq(element, bytes32(0)); 46 | } 47 | 48 | /// @notice Test set a key 49 | function testSetKey(bytes32 key, bytes32 value) public { 50 | assertEq(hmap.loadElement(key), bytes32(0)); 51 | hmap.storeElement(key, value); 52 | assertEq(hmap.loadElement(key), value); 53 | } 54 | 55 | /// @notice Test get with keys 56 | function testGetKeys(bytes32 key_one, bytes32 key_two) public { 57 | bytes32 element = hmap.loadElementFromKeys(key_one, key_two); 58 | assertEq(element, bytes32(0)); 59 | } 60 | 61 | /// @notice Test set with keys 62 | function testSetKeys(bytes32 key_one, bytes32 key_two, bytes32 value) public { 63 | assertEq(hmap.loadElementFromKeys(key_one, key_two), bytes32(0)); 64 | hmap.storeElementFromKeys(key_one, key_two, value); 65 | assertEq(hmap.loadElementFromKeys(key_one, key_two), value); 66 | } 67 | 68 | /// @notice Test set with slot and 2 keys 69 | function testSetKeys2D( 70 | bytes32 slot, 71 | bytes32 key_one, 72 | bytes32 key_two, 73 | bytes32 value 74 | ) public { 75 | assertEq(hmap.loadElementFromKeys2D(slot, key_one, key_two), bytes32(0)); 76 | hmap.storeElementFromKeys2D(slot, key_one, key_two, value); 77 | assertEq(hmap.loadElementFromKeys2D(slot, key_one, key_two), value); 78 | } 79 | 80 | /// @notice Test set with slot and 3 keys 81 | function testSetKeys3D( 82 | bytes32 slot, 83 | bytes32 key_one, 84 | bytes32 key_two, 85 | bytes32 key_three, 86 | bytes32 value 87 | ) public { 88 | assertEq(hmap.loadElementFromKeys3D(slot, key_one, key_two, key_three), bytes32(0)); 89 | hmap.storeElementFromKeys3D(slot, key_one, key_two, key_three, value); 90 | assertEq(hmap.loadElementFromKeys3D(slot, key_one, key_two, key_three), value); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/tokens/mocks/ERC4626Wrappers.huff: -------------------------------------------------------------------------------- 1 | 2 | #define function beforeWithdrawHookCalledCounter() nonpayable returns (uint256) 3 | #define function afterDepositHookCalledCounter() nonpayable returns (uint256) 4 | 5 | #define constant BEFORE_HOOK_COUNTER = FREE_STORAGE_POINTER() 6 | #define constant AFTER_HOOK_COUNTER = FREE_STORAGE_POINTER() 7 | 8 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 9 | ERC4626_CONSTRUCTOR() // [] 10 | } 11 | 12 | #define macro BEFORE_WITHDRAW() = takes (2) returns (0) { 13 | // Input Stack: [assets, shares] 14 | // Output Stack: [] 15 | pop pop // [] 16 | [BEFORE_HOOK_COUNTER] sload // [counter] 17 | 0x01 add // [counter + 1] 18 | [BEFORE_HOOK_COUNTER] sstore // [] 19 | } 20 | 21 | #define macro AFTER_DEPOSIT() = takes (2) returns (0) { 22 | // Input Stack: [assets, shares] 23 | // Output Stack: [] 24 | pop pop // [] 25 | [AFTER_HOOK_COUNTER] sload // [counter] 26 | 0x01 add // [counter + 1] 27 | [AFTER_HOOK_COUNTER] sstore // [] 28 | } 29 | 30 | #define macro READ_BEFORE_HOOK_COUNTER() = takes (0) returns (0) { 31 | [BEFORE_HOOK_COUNTER] sload // [counter] 32 | 0x00 mstore // [] 33 | 0x20 0x00 return // [] 34 | } 35 | 36 | #define macro READ_AFTER_HOOK_COUNTER() = takes (0) returns (0) { 37 | [AFTER_HOOK_COUNTER] sload // [counter] 38 | 0x00 mstore // [] 39 | 0x20 0x00 return // [] 40 | } 41 | 42 | #define macro TOTAL_ASSETS_INNER() = takes (0) returns (1) { 43 | // Input Stack: [] 44 | // Output Stack: [total_assets] 45 | 46 | // Store the asset.balanceOf(address(this)) args in memory 47 | __FUNC_SIG(balanceOf) 48 | 0xE0 shl 49 | 0x20 mstore 50 | address 0x24 mstore 51 | 52 | // Get the asset variable 53 | _GET_IMMUTABLE(ASSET_OFFSET, 0x80) // [asset] 54 | 55 | // Construct the call 56 | 0x20 // [retSize, asset] 57 | 0x00 // [retOffset, retSize, asset] 58 | 0x24 // [argSize, retOffset, retSize, asset] 59 | 0x20 // [argOffset, argSize, retOffset, retSize, asset] 60 | 0x00 // [value, argOffset, argSize, retOffset, retSize, asset] 61 | dup6 // [to, value, argOffset, argSize, retOffset, retSize, asset] 62 | gas // [gas, to, value, argOffset, argSize, retOffset, retSize, asset] 63 | call // [success, asset] 64 | 65 | // Verify the call succeeded 66 | success jumpi // [asset] 67 | 0x00 dup1 revert // [] 68 | success: 69 | 70 | // Since the returndata is copied to [0x00:0x20], we can just mload 71 | pop 0x00 mload // [total_assets] 72 | } 73 | 74 | 75 | #define macro MAIN() = takes (0) returns (0) { 76 | pc calldataload 0xE0 shr // [sig] 77 | 78 | dup1 __FUNC_SIG(beforeWithdrawHookCalledCounter) eq before_jump jumpi // [sig] 79 | dup1 __FUNC_SIG(afterDepositHookCalledCounter) eq after_jump jumpi // [sig] 80 | 81 | ERC4626_MAIN() // [sig] 82 | 83 | 0x00 dup1 revert 84 | 85 | before_jump: 86 | READ_BEFORE_HOOK_COUNTER() 87 | after_jump: 88 | READ_AFTER_HOOK_COUNTER() 89 | } 90 | -------------------------------------------------------------------------------- /src/utils/InsertionSort.huff: -------------------------------------------------------------------------------- 1 | /// @title Sort 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author tanim0la 4 | /// @notice Insertion sort implementation 5 | 6 | /* MACRO */ 7 | /// @notice Returns two items `offSet` and `arrSize` 8 | #define macro SORT() = takes (0) returns (2) { 9 | 10 | 0x04 calldatasize sub // [arrSize] 11 | dup1 // [arrSize, arrSize] 12 | 0x04 // [offset, arrSize, arrSize] 13 | 0x40 // [mem, offset, arrSize, arrSize] 14 | calldatacopy // [arrSize] 15 | 16 | 0x01 returndatasize mstore // i = 1 17 | 0x60 mload // [len, arrSize] 18 | 19 | // For loop 20 | start: 21 | // End if i == len 22 | returndatasize mload // [i, len, arrSize] 23 | dup2 dup2 // [i, len, i, len, arrSize] 24 | eq end jumpi 25 | 26 | // Assign i to j 27 | 0x20 mstore // [len, arrSize] 28 | 29 | // While loop 30 | while: 31 | returndatasize // [0, len, arrSize] 32 | 0x20 mload dup2 dup2 // [j, 0, j, 0, len, arrSize] 33 | gt // [cndt1, j, 0, len, arrSize] 34 | 35 | 36 | // 0x80 + (0x20 * j) 37 | dup2 0x20 mul 0x80 add // [mem[arr[j]], cndt1, j, 0, len, arrSize] 38 | 39 | // 0x80 + (0x20 * (j - 0x01)) 40 | 0x01 dup4 sub 0x20 mul 0x80 add // [mem[arr[j - i]], mem[arr[j]], cndt1, j, 0, len, arrSize] 41 | dup2 // [mem[arr[j]], mem[arr[j - 1]], mem[arr[j]], cndt1, j, 0, len, arrSize] 42 | 43 | mload // [arr[j], mem[arr[j - 1]], mem[arr[j]], cndt1, j, 0, len, arrSize] 44 | dup2 mload // [arr[j - 1], arr[j], mem[arr[j - 1]], mem[arr[j]], cndt1, j, 0, len, arrSize] 45 | 46 | gt dup4 // [cndt1, cndt2, mem[arr[j - 1]], mem[arr[j]], cndt1, j, 0, len, arrSize] 47 | and continueWhile jumpi // [mem[arr[j - 1]], mem[arr[j]], cndt1, j, 0, len, arrSize] 48 | 49 | // Go to continue 50 | pop pop pop pop pop // [len, arrSize] 51 | 52 | // Increment i++ 53 | returndatasize mload 0x01 add // [i + 1, len, arrSize] 54 | returndatasize mstore start jump // [len, arrSize] 55 | 56 | // While block 57 | continueWhile: 58 | // arr[j - 1] = arr[j] 59 | dup1 mload dup3 mload // [arr[j], arr[j - i], mem[arr[j - 1]], mem[arr[j]], cndt1, j, 0, len, arrSize] 60 | dup3 mstore // [arr[j - 1], mem[arr[j - 1]], mem[arr[j]], cndt1, j, 0, len, arrSize] 61 | 62 | // arr[j] = arr[j - 1] 63 | dup3 mstore // [mem[arr[j - 1], mem[arr[j]], cndt1, j, 0, len, arrSize] 64 | 65 | pop pop pop // [j, 0, len, arrSize] 66 | 67 | // Decrement j-- 68 | 0x01 dup2 sub // [j - 1 ,j, 0, len, arrSize] 69 | 0x20 mstore pop pop // [len, arrSize] 70 | while jump 71 | end: 72 | pop pop 0x40 // [offset, arrSize] 73 | } 74 | -------------------------------------------------------------------------------- /src/utils/SSTORE2.huff: -------------------------------------------------------------------------------- 1 | /// @title SSTORE2 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author asnared 4 | /// @notice Faster & cheaper contract key-value storage for Ethereum Contracts 5 | /// @notice Adapted from 0xsequence/sstore2 (https://github.com/0xsequence/sstore2) 6 | 7 | #include "./Bytecode.huff" 8 | #include "./CommonErrors.huff" 9 | 10 | #define constant TYPE_UINT_256_MAX = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 11 | 12 | /// @notice Stores `_data` and returns `pointer` as key for later retrieval 13 | /// @dev The pointer is a contract address with `_data` as code 14 | /// @param {_data} [bytes memory] to be written 15 | /// @return pointer Pointer to the written `_data` 16 | #define macro SSTORE2_WRITE() = takes (0) returns (1) { 17 | // Load the data memory pointer 18 | 0x04 calldataload // [&_data] 19 | dup1 0x04 add calldataload swap1 // [&_data, _data.length] 20 | 21 | // Create the contract creation code 22 | CREATION_CODE_FOR() // [&_data, _data.length] 23 | 24 | // Deploy the contract 25 | swap1 // [_data.length, &_data] 26 | 0x0f add // [size, &_data] 27 | 0x00 // [offset, size, &_data] 28 | 0x00 // [value, offset, size, &_data] 29 | create // [address, &_data] 30 | 31 | // Check that the address is non-zero 32 | dup1 success jumpi // [address, &_data] 33 | CREATE_FAILED(0x00) 34 | success: 35 | 36 | // Clean the stack and return the address 37 | swap1 pop // [address] 38 | } 39 | 40 | /// @notice Reads the contents of the `_pointer` code as data, skips the first byte 41 | /// @dev The function is intended for reading pointers generated by `write` 42 | /// @param _pointer to be read 43 | /// @return data read from `_pointer` contract 44 | #define macro SSTORE2_READ() = takes (1) returns (1) { 45 | // Input Stack: [_pointer] 46 | // We start at 1 because the first byte are zeros to prevent the contract from being called 47 | 0x01 // [_start, _pointer] 48 | [TYPE_UINT_256_MAX] // [_end, _start, _pointer] 49 | swap2 // [_pointer, _start, _end] 50 | CODE_AT() // [code] 51 | } 52 | 53 | /// @notice Reads the contents of the `_pointer` code as data, skips the first byte 54 | /// @dev The function is intended for reading pointers generated by `write` 55 | /// @param _pointer to be read 56 | /// @param _start number of bytes to skip 57 | /// @return data read from `_pointer` contract 58 | #define macro SSTORE2_READ_AT() = takes (2) returns (1) { 59 | // Input Stack: [_pointer, _start] 60 | swap1 // [_start, _pointer] 61 | [TYPE_UINT_256_MAX] // [_end, _start, _pointer] 62 | swap2 // [_pointer, _start, _end] 63 | CODE_AT() // [code] 64 | } 65 | 66 | /// @notice Reads the contents of the `_pointer` code as data, skips the first byte 67 | /// @dev The function is intended for reading pointers generated by `write` 68 | /// @param _pointer to be read 69 | /// @param _start number of bytes to skip 70 | /// @param _end index before which to end extraction 71 | /// @return data read from `_pointer` contract 72 | #define macro SSTORE2_READ_BETWEEN() = takes (3) returns (1) { 73 | // Input Stack: [_pointer, _start, _end] 74 | CODE_AT() // [code] 75 | } 76 | 77 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | out = 'out' 3 | libs = ['lib'] 4 | ffi = true 5 | fs_permissions = [ 6 | { access = "read", path = "./test/auth/mocks/AuthWrappers.huff" }, 7 | { access = "read", path = "./test/auth/mocks/OwnedWrappers.huff" }, 8 | { access = "read", path = "./test/auth/mocks/RolesAuthorityWrappers.huff" }, 9 | 10 | { access = "read", path = "./test/data-structures/mocks/ArrayWrappers.huff" }, 11 | { access = "read", path = "./test/data-structures/mocks/HashmapWrappers.huff" }, 12 | { access = "read", path = "./test/data-structures/mocks/BytesWrappers.huff" }, 13 | 14 | { access = "read", path = "./test/math/mocks/FixedPointMathWrappers.huff" }, 15 | { access = "read", path = "./test/math/mocks/SafeMathWrappers.huff" }, 16 | { access = "read", path = "./test/math/mocks/MathWrappers.huff" }, 17 | { access = "read", path = "./test/math/mocks/TrigonometryWrappers.huff" }, 18 | 19 | { access = "read", path = "./test/mechanisms/huff-vrgda/mocks/LinearVRGDAWrappers.huff" }, 20 | { access = "read", path = "./test/mechanisms/huff-vrgda/mocks/LogisticVRGDAWrappers.huff" }, 21 | 22 | { access = "read", path = "./test/proxies/mocks/ClonesWrappers.huff" }, 23 | { access = "read", path = "./test/proxies/mocks/ProxyWrappers.huff" }, 24 | { access = "read", path = "./test/proxies/mocks/ProxyFactoryWrappers.huff" }, 25 | 26 | { access = "read", path = "./test/tokens/mocks/ERC20MintableWrappers.huff" }, 27 | { access = "read", path = "./test/tokens/mocks/ERC20Wrappers.huff" }, 28 | { access = "read", path = "./test/tokens/mocks/ERC721Wrappers.huff" }, 29 | { access = "read", path = "./test/tokens/mocks/ERC1155Wrappers.huff" }, 30 | { access = "read", path = "./test/tokens/mocks/ERC4626Wrappers.huff" }, 31 | 32 | { access = "read", path = "./test/utils/mocks/BitPackLibWrappers.huff" }, 33 | { access = "read", path = "./test/utils/mocks/CallWrappers.huff" }, 34 | { access = "read", path = "./test/utils/mocks/ErrorWrappers.huff" }, 35 | { access = "read", path = "./test/utils/mocks/EthersWrappers.huff" }, 36 | { access = "read", path = "./test/utils/mocks/JumpTableUtilWrappers.huff" }, 37 | { access = "read", path = "./test/utils/mocks/LibBitWrappers.huff" }, 38 | { access = "read", path = "./test/utils/mocks/MerkleDistributorWrappers.huff" }, 39 | { access = "read", path = "./test/utils/mocks/MerkleProofLibWrappers.huff" }, 40 | { access = "read", path = "./test/utils/mocks/MulticallableWrappers.huff" }, 41 | { access = "read", path = "./test/utils/mocks/Create3Wrappers.huff" }, 42 | { access = "read", path = "./test/utils/mocks/PausableWrappers.huff" }, 43 | { access = "read", path = "./test/utils/mocks/ReentrancyGuardWrappers.huff" }, 44 | { access = "read", path = "./test/utils/mocks/RefundedWrappers.huff" }, 45 | { access = "read", path = "./test/utils/mocks/SafeTransferLibWrappers.huff" }, 46 | { access = "read", path = "./test/utils/mocks/ShufflingWrappers.huff" }, 47 | { access = "read", path = "./test/utils/mocks/InsertionSortWrappers.huff" }, 48 | { access = "read", path = "./test/utils/mocks/SSTORE2Wrappers.huff" }, 49 | { access = "read", path = "./test/utils/mocks/TSOwnableWrappers.huff" }, 50 | { access = "read", path = "./test/utils/mocks/ECDSAWrappers.huff" }, 51 | { access = "read", path = "./test/utils/mocks/ConstantsWrappers.huff" }, 52 | { access = "read", path = "./test/utils/mocks/DateTimeLibWrappers.huff" } 53 | ] 54 | remappings = [ 55 | "forge-std/=lib/forge-std/src/", 56 | "foundry-huff/=lib/foundry-huff/src/", 57 | "solmate/=lib/solmate/src/" 58 | ] 59 | 60 | [profile.default.fuzz] 61 | runs = 1000 -------------------------------------------------------------------------------- /test/mechanisms/huff-vrgda/LinearVRGDA.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import { 7 | wadLn, 8 | toWadUnsafe, 9 | fromDaysWadUnsafe, 10 | toDaysWadUnsafe 11 | } from "./utils/SignedWadMath.sol"; 12 | 13 | import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; 14 | 15 | 16 | import "foundry-huff/HuffDeployer.sol"; 17 | 18 | uint256 constant ONE_THOUSAND_YEARS = 356 days * 1000; 19 | uint256 constant MAX_SELLABLE = 6392; 20 | 21 | interface MockLinearVRGDA { 22 | function getTargetSaleTime(int256 sold) external view returns (int256); 23 | function targetPrice() external view returns (int256); 24 | function perTimeUnit() external view returns (int256); 25 | 26 | // needed to test - TODO: remove 27 | function decayConstant() external view returns (int256); 28 | 29 | function getVRGDAPrice(int256 timeSinceStart, uint256 sold) external view returns (uint256); 30 | } 31 | 32 | contract LinearVRGDATest is Test { 33 | MockLinearVRGDA vrgda; 34 | 35 | // Required to sanity check huff deployer 36 | int256 targetPrice; 37 | int256 decayConstant; 38 | int256 perTimeUnit; 39 | 40 | function setUp() public { 41 | // setup parameters 42 | targetPrice = 69.42e18; 43 | int256 priceDecayPercent = 0.31e18; 44 | perTimeUnit = 2e18; 45 | 46 | // calculate the decay constant 47 | decayConstant = wadLn(1e18 - priceDecayPercent); 48 | require(decayConstant < 0, "NON_NEGATIVE_DECAY_CONSTANT"); 49 | 50 | // Overwrite inlined constants using the huff compiler - essentially equivalent to an immutable 51 | string memory vrgda_wrapper = vm.readFile("test/mechanisms/huff-vrgda/mocks/LinearVRGDAWrappers.huff"); 52 | vrgda = MockLinearVRGDA(HuffDeployer 53 | .config() 54 | .with_code(vrgda_wrapper) 55 | .with_bytes32_constant("TARGET_PRICE", bytes32(abi.encodePacked(targetPrice))) 56 | .with_bytes32_constant("DECAY_CONSTANT", bytes32(abi.encodePacked(decayConstant))) 57 | .with_bytes32_constant("PER_TIME_UNIT", bytes32(abi.encodePacked(perTimeUnit))) 58 | .deploy("mechanisms/huff-vrgda/LinearVRGDA") 59 | ); 60 | } 61 | 62 | // Assert that the huff deployer has overriden constants correctly 63 | function testConstantsOverride() public { 64 | assertEq(vrgda.targetPrice(), targetPrice); 65 | assertEq(vrgda.decayConstant(), decayConstant); 66 | assertEq(vrgda.perTimeUnit(), perTimeUnit); 67 | } 68 | 69 | function testTargetPrice() public { 70 | // Warp to the target sale time so that the VRGDA price equals the target price. 71 | vm.warp(block.timestamp + fromDaysWadUnsafe(vrgda.getTargetSaleTime(1e18))); 72 | 73 | uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), 0); 74 | assertEq(cost / 0.00001e18, uint256(vrgda.targetPrice()) / 0.00001e18); 75 | } 76 | 77 | function testPricingBasic() public { 78 | // Our VRGDA targets this number of mints at given time. 79 | uint256 timeDelta = 120 days; 80 | uint256 numMint = 239; 81 | 82 | vm.warp(block.timestamp + timeDelta); 83 | 84 | uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint); 85 | assertEq(cost / 0.00001e18, uint256(vrgda.targetPrice()) / 0.00001e18); 86 | } 87 | 88 | function testAlwaysTargetPriceInRightConditions(uint256 sold) public { 89 | sold = bound(sold, 0, type(uint128).max); 90 | 91 | assertEq( 92 | vrgda.getVRGDAPrice(vrgda.getTargetSaleTime(toWadUnsafe(sold + 1)), sold) / 0.00001e18, 93 | uint256(vrgda.targetPrice()) / 0.00001e18 94 | ); 95 | } 96 | } -------------------------------------------------------------------------------- /src/mechanisms/huff-clones/ExampleCloneFactory.huff: -------------------------------------------------------------------------------- 1 | /// @title ExampleCloneFactory 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author wighawag 4 | /// @author zefram 5 | /// @author hari 6 | /// @author z0r0z 7 | /// @author clabby 8 | /// @notice Clones with Immutable Args Library 9 | /// @notice Adapted from wighawag (https://github.com/wighawag/clones-with-immutable-args/blob/master/src/ExampleCloneFactory.sol) 10 | 11 | #include "./HuffCloneLib.huff" 12 | 13 | #define function createClone(address,uint256,uint64,uint8) nonpayable returns (address) 14 | #define function createArrClone(uint256[] calldata) nonpayable returns (address) 15 | 16 | #define constant IMPL_SLOT = FREE_STORAGE_POINTER() 17 | 18 | /// @notice Creates an `ExampleClone` contract 19 | #define macro CREATE_CLONE() = takes (0) returns (0) { 20 | 0x64 calldataload // [uint8] 21 | 0x44 calldataload // [uint64, uint8] 22 | 0x24 calldataload // [uint256, uint64, uint8] 23 | 0x04 calldataload // [address, uint256, uint64, uint8] 24 | 25 | // data len = 61 (0x3D) 26 | 0x3D 0x40 mstore // [address, uint256, uint64, uint8] 27 | 28 | // Store address << 0x60 @ 0x60 29 | 0x60 shl // [address << 0x60, uint256, uint64, uint8] 30 | 0x60 mstore // [uint256, uint64, uint8] 31 | 32 | // Store uint256 @ 0x74 33 | 0x74 mstore // [uint64, uint8] 34 | 35 | // Store uint64 << 0xC0 @ 0x94 36 | 0xC0 shl // [uint64 << 0xC0, uint8] 37 | 0x94 mstore // [uint8] 38 | 39 | // Store uint8 << 0xF8 @ 0x9C 40 | 0xF8 shl // [uint8 << 0xF8] 41 | 0x9C mstore // [] 42 | 43 | 0x40 // [data_ptr] 44 | [IMPL_SLOT] sload // [impl_addr, data_ptr] 45 | 46 | CLONE(err, 0x00) // [instance] 47 | 0x00 mstore // [] 48 | 0x20 0x00 return 49 | 50 | err: 51 | 0x00 0x00 revert 52 | } 53 | 54 | /// @notice Creates an `ExampleClone` contract that has an immutable 55 | /// uint256 array 56 | #define macro CREATE_ARRAY_CLONE() = takes (0) returns (0) { 57 | 0x24 calldataload // [arr_len] 58 | 0x05 shl // [arr_len * 0x20] 59 | 0x20 add // [arr_len * 0x20 + 0x20] 60 | 0x24 // [0x24, arr_len * 0x20 + 0x20] 61 | 0x40 // [0x40, 0x24, arr_len * 0x20 + 0x20] 62 | calldatacopy // [] 63 | 64 | // Set data length in bytes 65 | 0x40 dup1 // [0x40, 0x40] 66 | mload // [arr_len, 0x40] 67 | 0x05 shl // [arr_len * 0x20, 0x40] 68 | 0x40 mstore // [0x40 (data_ptr)] 69 | 70 | [IMPL_SLOT] sload // [impl_addr, data_ptr] 71 | 72 | CLONE(err, 0x00) // [instance] 73 | 0x00 mstore // [] 74 | 0x20 0x00 return 75 | 76 | err: 77 | 0x00 0x00 revert 78 | } 79 | 80 | #define macro MAIN() = takes (0) returns (0) { 81 | pc calldataload 0xE0 shr 82 | dup1 __FUNC_SIG(createClone) eq clone jumpi 83 | dup1 __FUNC_SIG(createArrClone) eq arr_clone jumpi 84 | 85 | clone: 86 | CREATE_CLONE() 87 | arr_clone: 88 | CREATE_ARRAY_CLONE() 89 | } 90 | 91 | #define macro CONSTRUCTOR() = takes (0) returns (0) { 92 | 0x20 // [size] - byte size to copy 93 | 0x20 codesize sub // [offset, size] - offset in the code to copy from 94 | 0x00 // [mem, offset, size] - offset in memory to copy to 95 | codecopy // [] 96 | 97 | 0x00 mload 98 | [IMPL_SLOT] // [impl_ptr, impl_addr] 99 | sstore // [] 100 | } 101 | -------------------------------------------------------------------------------- /test/utils/mocks/MulticallableWrappers.huff: -------------------------------------------------------------------------------- 1 | #define function multicall(bytes[] calldata) payable returns (bytes[] memory) 2 | #define function call1() view returns (uint256) 3 | #define function call2() view returns (uint256) 4 | #define function call3() view returns (uint256) 5 | #define function returnsTuple(uint256, uint256) view returns (uint256, uint256) 6 | #define function returnsStr(string) view returns (string) 7 | #define function returnsSender() view returns (address) 8 | #define function pay() payable returns (uint256) 9 | #define function paid() view returns (uint256) 10 | #define function revertsNoMsg() view returns () 11 | #define function revertsMsg() view returns () 12 | 13 | #define constant PAID_SLOT = FREE_STORAGE_POINTER() 14 | 15 | #define macro CALL_1() = takes (0) returns (0) { 16 | 0x11 0x00 mstore 17 | 0x20 0x00 return 18 | } 19 | 20 | #define macro CALL_2() = takes (0) returns (0) { 21 | 0x22 0x00 mstore 22 | 0x20 0x00 return 23 | } 24 | 25 | #define macro CALL_3() = takes (0) returns (0) { 26 | 0x33 0x00 mstore 27 | 0x20 0x00 return 28 | } 29 | 30 | #define macro RETURNS_TUPLE() = takes (0) returns (0) { 31 | 0x04 calldataload // [x] 32 | 0x00 mstore // [] 33 | 0x24 calldataload // [y] 34 | 0x20 mstore // [] 35 | 0x40 0x00 return 36 | } 37 | 38 | #define macro RETURNS_STR() = takes (0) returns (0) { 39 | 0x24 calldataload // [str_len] 40 | 0x40 add // [str_len + 0x40] 41 | dup1 // [str_len + 0x40, str_len + 0x40] 42 | 0x04 // [0x04, str_len + 0x40, str_len + 0x40] 43 | 0x00 // [0x00, 0x04, str_len + 0x40, str_len + 0x40] 44 | calldatacopy // [str_len + 0x40] 45 | 0x00 return 46 | } 47 | 48 | #define macro RETURNS_SENDER() = takes (0) returns (0) { 49 | caller // [msg.sender] 50 | 0x00 mstore // [] 51 | 0x20 0x00 return 52 | } 53 | 54 | #define macro PAY() = takes (0) returns (0) { 55 | [PAID_SLOT] sload // [paid] 56 | callvalue add // [paid + callvalue] 57 | [PAID_SLOT] sstore // [] 58 | 0x00 dup1 return 59 | } 60 | 61 | #define macro PAID() = takes (0) returns (0) { 62 | [PAID_SLOT] sload // [paid] 63 | 0x00 mstore // [] 64 | 0x20 0x00 return 65 | } 66 | 67 | #define macro REVERTS_NO_MSG() = takes (0) returns (0) { 68 | 0x00 dup1 revert 69 | } 70 | 71 | #define macro REVERTS_MSG() = takes (0) returns (0) { 72 | 0x5465737420526576657274000000000000000000000000000000000000000000 73 | 0x00 mstore 74 | 0x0B 0x00 revert 75 | } 76 | 77 | #define macro MAIN() = takes (0) returns (0) { 78 | pc calldataload 0xE0 shr 79 | dup1 __FUNC_SIG(multicall) eq multicall jumpi 80 | dup1 __FUNC_SIG(call1) eq call_one jumpi 81 | dup1 __FUNC_SIG(call2) eq call_two jumpi 82 | dup1 __FUNC_SIG(call3) eq call_three jumpi 83 | dup1 __FUNC_SIG(returnsTuple) eq returns_tuple jumpi 84 | dup1 __FUNC_SIG(returnsStr) eq returns_str jumpi 85 | dup1 __FUNC_SIG(returnsSender) eq returns_sender jumpi 86 | dup1 __FUNC_SIG(pay) eq pay jumpi 87 | dup1 __FUNC_SIG(paid) eq paid jumpi 88 | dup1 __FUNC_SIG(revertsNoMsg) eq revert_no_msg jumpi 89 | dup1 __FUNC_SIG(revertsMsg) eq revert_msg jumpi 90 | 91 | 0x00 dup1 revert 92 | 93 | multicall: 94 | MULTICALL() 95 | call_one: 96 | CALL_1() 97 | call_two: 98 | CALL_2() 99 | call_three: 100 | CALL_3() 101 | returns_tuple: 102 | RETURNS_TUPLE() 103 | returns_str: 104 | RETURNS_STR() 105 | returns_sender: 106 | RETURNS_SENDER() 107 | pay: 108 | PAY() 109 | paid: 110 | PAID() 111 | revert_no_msg: 112 | REVERTS_NO_MSG() 113 | revert_msg: 114 | REVERTS_MSG() 115 | } -------------------------------------------------------------------------------- /src/math/SafeMath.huff: -------------------------------------------------------------------------------- 1 | /// @title SafeMath 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author kadenzipfel 4 | /// @notice Math module over Solidity's arithmetic operations with safety checks 5 | /// @notice Adapted from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol) 6 | 7 | #include "../utils/Errors.huff" 8 | 9 | // Interface 10 | #define function safeAdd(uint256,uint256) pure returns (uint256) 11 | #define function safeSub(uint256,uint256) pure returns (uint256) 12 | #define function safeMul(uint256,uint256) pure returns (uint256) 13 | #define function safeDiv(uint256,uint256) pure returns (uint256) 14 | #define function safeMod(uint256,uint256) pure returns (uint256) 15 | 16 | /// @notice Adds two numbers and reverts on overflow 17 | #define macro SAFE_ADD() = takes (2) returns (1) { 18 | // input stack // [num1, num2] 19 | dup2 // [num2, num1, num2] 20 | add // [result, num2] 21 | dup1 // [result, result, num2] 22 | swap2 // [num2, result, result] 23 | gt // [is_overflow, result] 24 | iszero // [is_not_overflow, result] 25 | is_not_overflow jumpi // [result] 26 | [ARITHMETIC_OVERFLOW] PANIC() 27 | is_not_overflow: // [result] 28 | } 29 | 30 | /// @notice Subtracts two numbers and reverts on underflow 31 | #define macro SAFE_SUB() = takes (2) returns (1) { 32 | // input stack // [num1, num2] 33 | dup1 // [num1, num1, num2] 34 | dup3 // [num2, num1, num1, num2] 35 | gt // [is_underflow, num1, num2] 36 | iszero // [is_not_underflow, num1, num2] 37 | is_not_underflow jumpi // [num1, num2] 38 | [ARITHMETIC_OVERFLOW] PANIC() 39 | is_not_underflow: // [num1, num2] 40 | sub // [result] 41 | } 42 | 43 | /// @notice Multiplies two numbers and reverts on overflow 44 | #define macro SAFE_MUL() = takes (2) returns (1) { 45 | // input stack // [num1, num2] 46 | dup1 // [num1, num1, num2] 47 | is_not_zero jumpi // [num1, num2] 48 | mul // [result] 49 | 0x01 is_not_overflow jumpi 50 | is_not_zero: // [num1, num2] 51 | dup2 // [num2, num1, num2] 52 | dup2 // [num1, num2, num1, num2] 53 | mul // [result, num1, num2] 54 | swap1 // [num1, result, num2] 55 | dup2 // [result, num1, result, num2] 56 | div // [div_check, result, num2] 57 | swap1 // [result, div_check, num2] 58 | swap2 // [num2, div_check, result] 59 | eq // [is_not_overflow, result] 60 | is_not_overflow jumpi // [result] 61 | [ARITHMETIC_OVERFLOW] PANIC() 62 | is_not_overflow: 63 | } 64 | 65 | /// @notice Divides two numbers and reverts on division by zero 66 | #define macro SAFE_DIV() = takes (2) returns (1) { 67 | // input stack // [num1, num2] 68 | 0x00 dup3 // [num2, 0, num1, num2] 69 | gt // [is_not_div_zero, num1, num2] 70 | is_not_div_zero jumpi 71 | [DIVIDE_BY_ZERO] PANIC() 72 | is_not_div_zero: 73 | div // [result] 74 | } 75 | 76 | /// @notice Divides two numbers and reverts on division by zero or modulo zero 77 | #define macro SAFE_MOD() = takes (2) returns (1) { 78 | // input stack // [num1, num2] 79 | 0x00 dup3 // [num2, 0, num1, num2] 80 | gt // [is_not_mod_zero, num1, num2] 81 | is_not_mod_zero jumpi 82 | [ARITHMETIC_OVERFLOW] PANIC() 83 | is_not_mod_zero: 84 | mod // [result] 85 | } -------------------------------------------------------------------------------- /test/math/SafeMath.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "forge-std/Test.sol"; 5 | import "foundry-huff/HuffDeployer.sol"; 6 | 7 | interface SafeMath { 8 | function safeAdd(uint256,uint256) external pure returns (uint256); 9 | function safeSub(uint256,uint256) external pure returns (uint256); 10 | function safeMul(uint256,uint256) external pure returns (uint256); 11 | function safeDiv(uint256,uint256) external pure returns (uint256); 12 | function safeMod(uint256,uint256) external pure returns (uint256); 13 | } 14 | 15 | contract SafeMathTest is Test { 16 | SafeMath safeMath; 17 | 18 | function setUp() public { 19 | string memory wrappers = vm.readFile("test/math/mocks/SafeMathWrappers.huff"); 20 | safeMath = SafeMath(HuffDeployer.deploy_with_code("math/SafeMath", wrappers)); 21 | } 22 | 23 | function testSafeAdd() public { 24 | uint256 result = safeMath.safeAdd(420, 69); 25 | assertEq(result, 489); 26 | } 27 | 28 | function testSafeAdd(uint256 a, uint256 b) public { 29 | unchecked { 30 | uint256 c = a + b; 31 | 32 | if (a > c) { 33 | vm.expectRevert(stdError.arithmeticError); 34 | safeMath.safeAdd(a, b); 35 | return; 36 | } 37 | 38 | uint256 result = safeMath.safeAdd(a, b); 39 | assertEq(result, a + b); 40 | } 41 | } 42 | 43 | function testSafeSub() public { 44 | uint256 result = safeMath.safeSub(420, 69); 45 | assertEq(result, 351); 46 | } 47 | 48 | function testSafeSub(uint256 a, uint256 b) public { 49 | unchecked { 50 | if (b > a) { 51 | vm.expectRevert(stdError.arithmeticError); 52 | safeMath.safeSub(a, b); 53 | return; 54 | } 55 | 56 | uint256 result = safeMath.safeSub(a, b); 57 | assertEq(result, a - b); 58 | } 59 | } 60 | 61 | function testSafeMul() public { 62 | uint256 result = safeMath.safeMul(420, 69); 63 | assertEq(result, 28980); 64 | } 65 | 66 | function testSafeMul(uint256 a, uint256 b) public { 67 | unchecked { 68 | uint256 result; 69 | if (a == 0 || b == 0) { 70 | result = safeMath.safeMul(a, b); 71 | assertEq(result, 0); 72 | return; 73 | } 74 | 75 | uint256 c = a * b; 76 | if (c / a != b) { 77 | vm.expectRevert(stdError.arithmeticError); 78 | safeMath.safeMul(a, b); 79 | return; 80 | } 81 | 82 | result = safeMath.safeMul(a, b); 83 | assertEq(result, c); 84 | } 85 | } 86 | 87 | function testSafeDiv() public { 88 | uint256 result = safeMath.safeDiv(420, 69); 89 | assertEq(result, 6); 90 | } 91 | 92 | function testSafeDiv(uint256 a, uint256 b) public { 93 | unchecked { 94 | if (b == 0) { 95 | vm.expectRevert(stdError.divisionError); 96 | safeMath.safeDiv(a, b); 97 | return; 98 | } 99 | 100 | uint256 result = safeMath.safeDiv(a, b); 101 | assertEq(result, a / b); 102 | } 103 | } 104 | 105 | function testSafeMod() public { 106 | uint256 result = safeMath.safeMod(420, 69); 107 | assertEq(result, 6); 108 | } 109 | 110 | function testSafeMod(uint256 a, uint256 b) public { 111 | unchecked { 112 | if (b == 0) { 113 | vm.expectRevert(stdError.arithmeticError); 114 | safeMath.safeMod(a, b); 115 | return; 116 | } 117 | 118 | uint256 result = safeMath.safeMod(a, b); 119 | assertEq(result, a % b); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /src/mechanisms/huff-clones/HuffClone.huff: -------------------------------------------------------------------------------- 1 | /// @title HuffClone 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author wighawag 4 | /// @author zefram 5 | /// @author hari 6 | /// @author z0r0z 7 | /// @author clabby 8 | /// @notice Clones with Immutable Args Library 9 | /// @notice Adapted from wighawag (https://github.com/wighawag/clones-with-immutable-args/blob/master/src/Clone.sol) 10 | 11 | /// @notice Reads an immutable arg with type address 12 | /// @param argOffset The offset of the arg in the packed data 13 | /// @return arg The arg value 14 | #define macro GET_ARG_ADDRESS() = takes (1) returns (1) { 15 | // Initial Stack: // [argOffset] 16 | GET_IMMUTABLE_ARGS_OFFSET() // [offset, argOffset] 17 | add // [offset + argOffset] 18 | calldataload // [cd] 19 | 0x60 // [0x60, cd] 20 | shr // [cd >> 0x60] 21 | } 22 | 23 | /// @notice Reads an immutable arg with type uint256 24 | /// @param argOffset The offset of the arg in the packed data 25 | /// @return arg The arg value 26 | #define macro GET_ARG_UINT_256() = takes (1) returns (1) { 27 | // Initial Stack: // [argOffset] 28 | GET_IMMUTABLE_ARGS_OFFSET() // [offset, argOffset] 29 | add // [offset + argOffset] 30 | calldataload // [cd] 31 | } 32 | 33 | /// @notice Reads a uint256 array stored in the immutable args. 34 | /// @param argOffset The offset of the arg in the packed data 35 | /// @param arrLen Number of elements in the array 36 | /// @return arr The beginning location of the array in memory 37 | #define macro GET_ARG_UINT_256_ARR(mem_ptr) = takes (2) returns (1) { 38 | // Initial Stack: [argOffset, arrLen] 39 | GET_IMMUTABLE_ARGS_OFFSET() // [offset, argOffset, arrLen] 40 | add // [offset + argOffset, arrLen] 41 | 42 | dup2 // [mem_ptr, arrLen, offset + argOffset, arrLen] 43 | mstore // [offset + argOffset, arrLen] 44 | swap1 0x05 shl swap1 // [offset + argOffset, arrLen * 0x20] 45 | 0x20 add // [mem_ptr + 0x20, offset + argOffset, arrLen * 0x20] 46 | calldatacopy // [] 47 | 48 | // Return the memory pointer of the array 49 | // [mem_ptr] 50 | } 51 | 52 | /// @notice Reads an immutable arg with type uint64 53 | /// @param argOffset The offset of the arg in the packed data 54 | /// @return arg The arg value 55 | #define macro GET_ARG_UINT_64() = takes (1) returns (1) { 56 | // Initial Stack: // [argOffset] 57 | GET_IMMUTABLE_ARGS_OFFSET() // [offset, argOffset] 58 | add // [offset + argOffset] 59 | calldataload // [cd] 60 | 0xC0 // [0xC0, cd] 61 | shr // [cd >> 0xC0] 62 | } 63 | 64 | /// @notice Reads an immutable arg with type uint8 65 | /// @param argOffset The offset of the arg in the packed data 66 | /// @return The arg value 67 | #define macro GET_ARG_UINT_8() = takes (1) returns (1) { 68 | // Initial Stack: // [argOffset] 69 | GET_IMMUTABLE_ARGS_OFFSET() // [offset, argOffset] 70 | add // [offset + argOffset] 71 | calldataload // [cd] 72 | 0xF8 // [0xF8, cd] 73 | shr // [cd >> 0xF8] 74 | } 75 | 76 | /// @return The offset of the packed immutable args in calldata 77 | #define macro GET_IMMUTABLE_ARGS_OFFSET() = takes (0) returns (1) { 78 | 0x02 // [0x02] 79 | calldatasize // [calldatasize, 0x02] 80 | sub // [calldatasize - 0x02] 81 | 82 | calldataload // [cd] 83 | 0xF0 // [0xF0, cd] 84 | shr // [cd >> 0xF0] 85 | 86 | 0x02 // [0x02, cd >> 0xF0] 87 | add // [0x02 + cd >> 0xF0] 88 | 89 | calldatasize // [calldatasize, 0x02 + cd >> 0xF0] 90 | sub // [calldatasize - (0x02 + cd >> 0xF0)] 91 | } 92 | -------------------------------------------------------------------------------- /src/data-structures/Arrays.huff: -------------------------------------------------------------------------------- 1 | /// @title Arrays 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author exp-table 4 | /// @notice Array utility library for Solidity contracts 5 | 6 | /// @notice Sets an array in storage from calldata. 7 | /// Note that since no assumptions is made regarding the context in which 8 | /// this function is called, the position of the encoded array in the calldata 9 | /// has to be specified. 10 | #define macro SET_ARRAY_FROM_CALLDATA() = takes(2) returns (0) { 11 | // Input stack: [calldata_start, slot] 12 | // skip size of one individual element 13 | 0x20 add // [calldata_start+0x20, slot] 14 | dup1 0x20 add swap1 // [calldata_start+0x20, calldata_start+0x40, slot] 15 | // load length 16 | calldataload // [length, calldata_offset, slot] 17 | // store length at slot 18 | dup1 dup4 // [slot, length, length, calldata_offset, slot] 19 | sstore // [length, calldata_offset, slot] 20 | 21 | // store slot in memory scratch space and compute hash 22 | dup3 0x00 mstore // [length, calldata_offset, slot] 23 | 0x20 0x00 sha3 // [sha3(slot), length, ,calldata_offset slot] 24 | 25 | // loop and store every element in slot sha3(slot)+n 26 | 0x00 // [index(0), sha3(slot), length, calldata_offset, slot] 27 | start jump 28 | continue: 29 | // if index == length -> it's over 30 | eq end jumpi // [index(i), sha3(slot), length, calldata_offset, slot] 31 | start: 32 | // load from calldata 33 | dup1 0x20 mul dup5 add calldataload // [array(i), index(i), sha3(slot), length, calldata_offset, slot] 34 | // store at slot sha3(slot)+index 35 | dup3 dup3 add sstore // [index(i), sha3(slot), length, calldata_offset, slot] 36 | // inc index 37 | 0x01 add // [index(i+1), sha3(slot), length, calldata_offset, slot] 38 | dup3 dup2 // [index(i+1), length, index(i+1), sha3(slot), length, calldata_offset, slot] 39 | continue jump 40 | 41 | end: 42 | } 43 | 44 | /// @notice Returns an array in memory specified at {mem_ptr} 45 | #define macro RETURN_ARRAY(mem_ptr) = takes(1) returns (0) { 46 | // Input stack: [slot] 47 | 48 | // store the size of each element in memory 49 | 0x20 mstore // [slot] 50 | // [mem_ptr, slot] 51 | // load length from storage 52 | dup2 sload dup1 // [length, length, curr_mem_ptr, slot] 53 | // store length in memory 54 | swap2 0x20 add // [curr_mem_ptr+0x20, length, length, slot] 55 | swap1 dup2 mstore // [curr_mem_ptr, length, slot] 56 | 57 | // store slot in memory scratch space and compute hash 58 | swap2 0x00 mstore // [length, curr_mem_ptr] 59 | 0x20 0x00 sha3 swap2 // [curr_mem_ptr, length, sha3(slot)] 60 | 61 | // loop and load every element in slot sha3(slot)+n 62 | 0x00 start jump // [index(0), curr_mem_ptr, length, sha3(slot)] 63 | continue: 64 | // if index == length -> it's over 65 | eq end jumpi // [index(i), curr_mem_ptr, length, sha3(slot)] 66 | start: 67 | // load from storage ; add index to sha3(slot) 68 | dup1 dup5 add sload // [array(i), index(i), curr_mem_ptr, length, sha3(slot)] 69 | // store in memory 70 | swap1 swap2 0x20 add // [curr_mem_ptr+0x20, array(i), index(i), length, sha3(slot)] 71 | dup1 swap2 swap1 mstore // [curr_mem_ptr+0x20, index(i), length, sha3(slot)] 72 | // update index 73 | swap1 0x01 add // [index(i+1), curr_mem_ptr, length, sha3(slot)] 74 | dup1 dup4 75 | continue jump 76 | 77 | end: 78 | // size of data to return = size of individual element + array length + encoded elements 79 | swap2 0x02 add 0x05 shl // [size, curr_mem_ptr, index(i), sha3(slot)] 80 | return 81 | } -------------------------------------------------------------------------------- /src/utils/MerkleProofLib.huff: -------------------------------------------------------------------------------- 1 | /// @title MerkleProofLib 2 | /// @notice SPDX-License-Identifier: MIT 3 | /// @author clabby 4 | /// @notice Gas optimized merkle proof verification library 5 | /// @notice Adapted from Solmate (https://github.com/transmissions11/solmate/blob/v7/src/utils/MerkleProofLib.sol) 6 | /// @dev The `proof_cd_ptr` passed via the stack to this macro should point to the offset 7 | /// of the proof array's length in the calldata. This macro assumes that the proof 8 | /// array contains 32 byte values. 9 | 10 | /// @notice Verifies a merkle proof. 11 | /// @param proof_cd_ptr Pointer to the length of the proof array. 12 | /// @param leaf Leaf to prove inclusion of 13 | /// @param root Root of the merkle tree 14 | /// @return is_valid True if the inclusion of `leaf` in the merkle tree represented by 15 | /// `root` was able to be proven, false if not. 16 | #define macro VERIFY_PROOF() = takes (3) returns (1) { 17 | // Input Stack: [proof_cd_ptr, leaf, root] 18 | 19 | // Get ending offset (ptr + 1 + proof_len * 0x20) of proof array 20 | // and its starting offset (ptr + 0x20) 21 | dup1 22 | 0x20 add 23 | swap1 // [proof_cd_ptr, proof_cd_ptr + 0x20, leaf, root] 24 | calldataload // [proof_arr_len, proof_cd_ptr + 0x20, leaf, root] 25 | 0x05 shl // [proof_arr_len << 5, proof_cd_ptr + 0x20, leaf, root] 26 | dup2 add // [proof_arr_len << 5 + proof_cd_ptr + 0x20, proof_cd_ptr + 0x20, leaf, root] 27 | 28 | // Stack description changed to reflect the vars' respective purposes in the loop 29 | swap1 // [loop_offset, proof_arr_end, computed_hash, root] 30 | 31 | loop: 32 | dup2 dup2 // [loop_offset, proof_arr_end, loop_offset, proof_arr_end, computed_hash, root] 33 | lt // [loop_offset < proof_arr_end, loop_offset, proof_arr_end, computed_hash, root] 34 | // If loop index is >= the proof arr end offset, finish the loop 35 | iszero finish jumpi 36 | 37 | // Load data at proof_arr[loop_offset] 38 | dup1 // [loop_offset, loop_offset, proof_arr_end, computed_hash, root] 39 | calldataload // [proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 40 | 41 | dup1 // [proof_arr[loop_offset], proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 42 | dup5 // [computed_hash, proof_arr[loop_offset], proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 43 | gt // [computed_hash > proof_arr[loop_offset], proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 44 | 0x05 shl // [(computed_hash > proof_arr[loop_offset]) << 5, proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 45 | 46 | dup5 // [computed_hash, (computed_hash > proof_arr[loop_offset]) << 5, proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 47 | dup2 // [(computed_hash > proof_arr[loop_offset]) << 5, computed_hash, (computed_hash > proof_arr[loop_offset]) << 5, proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 48 | mstore // [(computed_hash > proof_arr[loop_offset]) << 5, proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 49 | 50 | 0x20 xor // [((computed_hash > proof_arr[loop_offset]) << 5) ^ 0x20, proof_arr[loop_offset], loop_offset, proof_arr_end, computed_hash, root] 51 | mstore // [loop_offset, proof_arr_end, computed_hash, root] 52 | 53 | // Compute new hash 54 | 0x40 0x00 sha3 // [computed_hash_new, loop_offset, proof_arr_end, computed_hash, root] 55 | swap3 pop // [loop_offset, proof_arr_end, computed_hash, root] 56 | 57 | // Increment loop offset by 0x20 58 | 0x20 add // [loop_offset + 0x20, proof_arr_end, computed_hash, root] 59 | 60 | loop jump 61 | finish: 62 | pop pop // [root, computed_hash] 63 | eq // [root == computed_hash] 64 | } -------------------------------------------------------------------------------- /test/utils/safe-transfer-lib-mocks/weird-tokens/ReturnsGarbageToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | contract ReturnsGarbageToken { 5 | /*/////////////////////////////////////////////////////////////// 6 | EVENTS 7 | //////////////////////////////////////////////////////////////*/ 8 | 9 | event Transfer(address indexed from, address indexed to, uint256 amount); 10 | 11 | event Approval(address indexed owner, address indexed spender, uint256 amount); 12 | 13 | /*/////////////////////////////////////////////////////////////// 14 | METADATA STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | string public constant name = "ReturnsGarbageToken"; 18 | 19 | string public constant symbol = "RGT"; 20 | 21 | uint8 public constant decimals = 18; 22 | 23 | /*/////////////////////////////////////////////////////////////// 24 | ERC20 STORAGE 25 | //////////////////////////////////////////////////////////////*/ 26 | 27 | uint256 public totalSupply; 28 | 29 | mapping(address => uint256) public balanceOf; 30 | 31 | mapping(address => mapping(address => uint256)) public allowance; 32 | 33 | /*/////////////////////////////////////////////////////////////// 34 | MOCK STORAGE 35 | //////////////////////////////////////////////////////////////*/ 36 | 37 | bytes garbage; 38 | 39 | /*/////////////////////////////////////////////////////////////// 40 | CONSTRUCTOR 41 | //////////////////////////////////////////////////////////////*/ 42 | 43 | constructor() { 44 | totalSupply = type(uint256).max; 45 | balanceOf[msg.sender] = type(uint256).max; 46 | } 47 | 48 | /*/////////////////////////////////////////////////////////////// 49 | ERC20 LOGIC 50 | //////////////////////////////////////////////////////////////*/ 51 | 52 | function approve(address spender, uint256 amount) public virtual { 53 | allowance[msg.sender][spender] = amount; 54 | 55 | emit Approval(msg.sender, spender, amount); 56 | 57 | bytes memory _garbage = garbage; 58 | 59 | assembly { 60 | return(add(_garbage, 32), mload(_garbage)) 61 | } 62 | } 63 | 64 | function transfer(address to, uint256 amount) public virtual { 65 | balanceOf[msg.sender] -= amount; 66 | 67 | // Cannot overflow because the sum of all user 68 | // balances can't exceed the max uint256 value. 69 | unchecked { 70 | balanceOf[to] += amount; 71 | } 72 | 73 | emit Transfer(msg.sender, to, amount); 74 | 75 | bytes memory _garbage = garbage; 76 | 77 | assembly { 78 | return(add(_garbage, 32), mload(_garbage)) 79 | } 80 | } 81 | 82 | function transferFrom( 83 | address from, 84 | address to, 85 | uint256 amount 86 | ) public virtual { 87 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 88 | 89 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 90 | 91 | balanceOf[from] -= amount; 92 | 93 | // Cannot overflow because the sum of all user 94 | // balances can't exceed the max uint256 value. 95 | unchecked { 96 | balanceOf[to] += amount; 97 | } 98 | 99 | emit Transfer(from, to, amount); 100 | 101 | bytes memory _garbage = garbage; 102 | 103 | assembly { 104 | return(add(_garbage, 32), mload(_garbage)) 105 | } 106 | } 107 | 108 | /*/////////////////////////////////////////////////////////////// 109 | MOCK LOGIC 110 | //////////////////////////////////////////////////////////////*/ 111 | 112 | function setGarbage(bytes memory _garbage) public virtual { 113 | garbage = _garbage; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /test/proxies/mocks/ProxyWrappers.huff: -------------------------------------------------------------------------------- 1 | // Functions 2 | // Implementation 3 | #define function implementation() nonpayable returns (address) 4 | #define function upgradeTo(address) nonpayable returns () 5 | #define function upgradeToAndCall(address,bytes,bool) nonpayable returns () 6 | #define function upgradeToAndCallUUPS(address,bytes,bool) nonpayable returns () 7 | 8 | // Admin 9 | #define function admin() view returns (address) 10 | #define function changeAdmin(address) nonpayable returns () 11 | 12 | // Beacon 13 | #define function beacon() view returns (address) 14 | #define function setBeacon(address) nonpayable returns () 15 | #define function upgradeBeaconAndCall(address,bytes,bool) nonpayable returns () 16 | 17 | 18 | // Implementations 19 | #define macro IMPLEMENTATION_WRAPPER() = { 20 | IMPLEMENTATION() 21 | 0x00 mstore 22 | 0x20 0x00 return 23 | } 24 | 25 | #define macro UPGRADE_TO_WRAPPER() = { 26 | 0x04 calldataload 27 | UPGRADE_TO() 28 | stop 29 | } 30 | 31 | #define macro UPGRADE_TO_AND_CALL_WRAPPER() = { 32 | 0x44 calldataload // [forceCall] 33 | 0x24 calldataload // [data, forceCall] 34 | 0x04 calldataload // [implementation, data, forceCall] 35 | UPGRADE_TO_AND_CALL() 36 | stop 37 | } 38 | 39 | #define macro UPGRADE_TO_AND_CALL_UUPS_WRAPPER() = { 40 | 0x44 calldataload // [forceCall] 41 | 0x24 calldataload // [data, forceCall] 42 | 0x04 calldataload // [implementation, data, forceCall] 43 | UPGRADE_TO_AND_CALL_UUPS() 44 | stop 45 | } 46 | 47 | // Admin 48 | #define macro GET_ADMIN_WRAPPER() = { 49 | GET_ADMIN() 50 | 0x00 mstore 51 | 0x20 0x00 return 52 | } 53 | 54 | #define macro CHANGE_ADMIN_WRAPPER() = { 55 | 0x04 calldataload 56 | CHANGE_ADMIN() 57 | stop 58 | } 59 | 60 | 61 | // Beacon 62 | #define macro GET_BEACON_WRAPPER() = { 63 | GET_BEACON() 64 | 0x00 mstore 65 | 0x20 0x00 return 66 | } 67 | 68 | #define macro SET_BEACON_WRAPPER() = { 69 | 0x04 calldataload 70 | SET_BEACON() 71 | stop 72 | } 73 | 74 | #define macro UPGRADE_BEACON_TO_AND_CALL_WRAPPER() = { 75 | 0x44 calldataload // [forceCall] 76 | 0x24 calldataload // [data, forceCall] 77 | 0x04 calldataload // [implementation, data, forceCall] 78 | UPGRADE_TO_BEACON_AND_CALL() 79 | stop 80 | } 81 | 82 | 83 | // For the sake of this example, the owner will be passed in 84 | #define macro CONSTRUCTOR() = { 85 | // ERC1967_PROXY_CONSTRUCTOR() 86 | 87 | 0x20 88 | 0x20 codesize sub 89 | 0x00 90 | codecopy 91 | 0x00 mload 92 | 93 | SET_ADMIN() 94 | } 95 | 96 | #define macro MAIN() = { 97 | pc calldataload 0xe0 shr 98 | 99 | dup1 __FUNC_SIG(implementation) eq implementation jumpi 100 | dup1 __FUNC_SIG(upgradeTo) eq upgradeTo jumpi 101 | dup1 __FUNC_SIG(upgradeToAndCall) eq upgradeToAndCall jumpi 102 | dup1 __FUNC_SIG(upgradeToAndCallUUPS) eq upgradeToAndCallUUPS jumpi 103 | 104 | dup1 __FUNC_SIG(admin) eq getAdmin jumpi 105 | dup1 __FUNC_SIG(changeAdmin) eq changeAdmin jumpi 106 | 107 | dup1 __FUNC_SIG(beacon) eq getBeacon jumpi 108 | dup1 __FUNC_SIG(setBeacon) eq setBeacon jumpi 109 | dup1 __FUNC_SIG(upgradeBeaconAndCall) eq upgradeBeaconAndCall jumpi 110 | 111 | GET_IMPLEMENTATION() // [implementation] 112 | DELEGATE() 113 | 114 | implementation: 115 | IMPLEMENTATION_WRAPPER() 116 | upgradeTo: 117 | UPGRADE_TO_WRAPPER() 118 | upgradeToAndCall: 119 | UPGRADE_TO_AND_CALL_WRAPPER() 120 | upgradeToAndCallUUPS: 121 | UPGRADE_TO_AND_CALL_UUPS_WRAPPER() 122 | 123 | getAdmin: 124 | GET_ADMIN_WRAPPER() 125 | changeAdmin: 126 | CHANGE_ADMIN_WRAPPER() 127 | 128 | getBeacon: 129 | GET_BEACON_WRAPPER() 130 | setBeacon: 131 | SET_BEACON_WRAPPER() 132 | upgradeBeaconAndCall: 133 | UPGRADE_BEACON_TO_AND_CALL_WRAPPER() 134 | 135 | } -------------------------------------------------------------------------------- /test/utils/MerkleDistributor.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | import "forge-std/console2.sol"; 7 | 8 | import { ERC20 } from "solmate/tokens/ERC20.sol"; 9 | 10 | contract MerkleDistributorTest is Test { 11 | ERC20 token; 12 | bytes32 constant merkleRoot = bytes32(uint256(0x4a9dd8c7af51100e0f04f128c61c526a9380cfde11c830800f9e670429440c1f)); 13 | 14 | // Test User 1 15 | address user1 = 0xD08c8e6d78a1f64B1796d6DC3137B19665cb6F1F; 16 | uint amount1 = 10; 17 | uint index1 = 0; 18 | 19 | // Test User 2 20 | address user2 = 0xb7D15753D3F76e7C892B63db6b4729f700C01298; 21 | uint amount2 = 15; 22 | uint index2 = 1; 23 | 24 | // Test User 3 25 | address user3 = 0xf69Ca530Cd4849e3d1329FBEC06787a96a3f9A68; 26 | uint amount3 = 20; 27 | uint index3 = 2; 28 | 29 | // Test User 4 30 | address user4 = 0xa8532aAa27E9f7c3a96d754674c99F1E2f824800; 31 | uint amount4 = 30; 32 | uint index4 = 3; 33 | 34 | /// @dev Address of the SimpleStore contract. 35 | MerkleDistributor public merkleDistributor; 36 | 37 | /// @dev Setup the testing environment. 38 | function setUp() public { 39 | // Deploy test erc20 token 40 | token = new TestToken(10_000 * 10**18); 41 | 42 | // Deploy MerkleDistributor 43 | string memory wrapper_code = vm.readFile("test/utils/mocks/MerkleDistributorWrappers.huff"); 44 | merkleDistributor = MerkleDistributor( 45 | HuffDeployer 46 | .config() 47 | .with_code(wrapper_code) 48 | .with_args(bytes.concat(abi.encode(address(token)), abi.encode(merkleRoot))) 49 | .deploy("utils/MerkleDistributor") 50 | ); 51 | 52 | // Transfer to merkledistributor 53 | uint currBalance = token.balanceOf(address(this)); 54 | token.transfer(address(merkleDistributor), currBalance); 55 | 56 | // Confirm constructor variables set properly 57 | assertEq(merkleDistributor.getTokenAddress(), address(token)); 58 | assertEq(merkleDistributor.getMerkleRoot(), merkleRoot); 59 | } 60 | 61 | /// @dev Ensure revert for claimed index 62 | function testRevert() public { 63 | bytes32[] memory proof4 = new bytes32[](2); 64 | proof4[0] = 0x39245471a38c683d6559bbc50b3c9ed8ec3b6d79c0e63c466e68a261bd4394fa; 65 | proof4[1] = 0x491dec713e1401118c9b1a82bb2fc861fe3500d34ee49e5ab56f514fded22de3; 66 | 67 | merkleDistributor.claim(index4, user4, amount4, proof4); 68 | assert(merkleDistributor.isClaimed(index4)); 69 | } 70 | 71 | /// @dev Ensure tokens transfer 72 | function testClaimTransfer() public { 73 | bytes32[] memory proof3 = new bytes32[](2); 74 | proof3[0] = 0x85b9749ddb06ff3ff63b0a0333c8b19b29a7ffb4d9a6891c6f1351a2b670d5bb; 75 | proof3[1] = 0x97ca9eb557c807d4223d28a6b6e2581aaeb2cd26a7d24c4195db6dcbf3eafc45; 76 | 77 | uint balanceBeforeUser3 = token.balanceOf(user3); 78 | merkleDistributor.claim(index3, user3, amount3, proof3); 79 | uint balanceAfterUser3 = token.balanceOf(user3); 80 | 81 | assert(balanceBeforeUser3 + amount3 == balanceAfterUser3); 82 | } 83 | 84 | /// @dev Ensure claim mechanism works 85 | function testClaimOnce() public { 86 | bytes32[] memory proof1 = new bytes32[](2); 87 | proof1[0] = 0x3fdbb9ff8c87c843885788bfb3023d1351a05efb9a5a4dfd5332081fe8be4c31; 88 | proof1[1] = 0x491dec713e1401118c9b1a82bb2fc861fe3500d34ee49e5ab56f514fded22de3; 89 | merkleDistributor.claim(index1, user1, amount1, proof1); 90 | } 91 | } 92 | 93 | contract TestToken is ERC20 { 94 | constructor(uint256 initialSupply) ERC20("MDT", "Merk", 18) { 95 | _mint(msg.sender, initialSupply); 96 | } 97 | } 98 | 99 | interface MerkleDistributor { 100 | function getMerkleRoot() external view returns (bytes32); 101 | function getTokenAddress() external view returns (address); 102 | function isClaimed(uint256) external view returns (bool); 103 | function claim(uint256, address, uint256, bytes32[] calldata) external; 104 | } -------------------------------------------------------------------------------- /test/data-structures/Bytes.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import "foundry-huff/HuffDeployer.sol"; 5 | import "forge-std/Test.sol"; 6 | import "forge-std/console2.sol"; 7 | 8 | interface IBytes { 9 | function concatMemoryAndSet1() external; 10 | function concatMemoryAndSet2() external; 11 | function concatMemoryAndSet3() external; 12 | function concatMemoryAndSet4() external; 13 | function concatMemoryAndSet5() external; 14 | function concatMemoryAndSet6() external; 15 | function sliceMemoryAndSet1() external; 16 | function sliceMemoryAndSet2() external; 17 | function sliceMemoryAndSet3() external; 18 | } 19 | 20 | contract BytesTest is Test { 21 | IBytes b; 22 | 23 | function setUp() public { 24 | string memory instantiable_code = vm.readFile( 25 | "test/data-structures/mocks/BytesWrappers.huff" 26 | ); 27 | 28 | // Create an Instantiable Arrays 29 | HuffConfig config = HuffDeployer.config().with_code(instantiable_code); 30 | b = IBytes(config.deploy("data-structures/Bytes")); 31 | } 32 | 33 | function testConcat1() public { 34 | b.concatMemoryAndSet1(); 35 | 36 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(64))); 37 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0xbabe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe)); 38 | assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0xbabe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe)); 39 | } 40 | 41 | function testConcat2() public { 42 | b.concatMemoryAndSet2(); 43 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(96))); 44 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0xbabe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe)); 45 | assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0xbabe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe)); 46 | assertEq(vm.load(address(b), bytes32(uint256(96))), bytes32(0xbabe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe2babe)); 47 | } 48 | 49 | function testConcat3() public { 50 | b.concatMemoryAndSet3(); 51 | 52 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(32))); 53 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0xbabe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe)); 54 | assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0)); 55 | } 56 | 57 | function testConcat4() public { 58 | b.concatMemoryAndSet4(); 59 | 60 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(37))); 61 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0xbabe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe)); 62 | assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(bytes5(0xbabe2babe2))); 63 | } 64 | 65 | function testConcat5() public { 66 | b.concatMemoryAndSet5(); 67 | 68 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(15))); 69 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(bytes15(0xbabe1babe1babe1babe1babe2babe2))); 70 | assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0)); 71 | } 72 | 73 | function testConcat6() public { 74 | b.concatMemoryAndSet6(); 75 | 76 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(15))); 77 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(bytes15(0xbabe1babe1babe1babe1babe2babe2))); 78 | assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(0)); 79 | } 80 | 81 | function testSlice1() public { 82 | b.sliceMemoryAndSet1(); 83 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(16))); 84 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(bytes16(0xbabe1babe1babe1babe1babe1babe1ba))); 85 | } 86 | 87 | function testSlice2() public { 88 | b.sliceMemoryAndSet2(); 89 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(36))); 90 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(0x1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babe1babebabe)); 91 | assertEq(vm.load(address(b), bytes32(uint256(64))), bytes32(bytes4(0x2babe2ba))); 92 | } 93 | 94 | function testSlice3() public { 95 | b.sliceMemoryAndSet3(); 96 | assertEq(vm.load(address(b), bytes32(0)), bytes32(uint256(4))); 97 | assertEq(vm.load(address(b), bytes32(uint256(32))), bytes32(bytes4(0xbabe2bab))); 98 | } 99 | } --------------------------------------------------------------------------------