# [Paradigm-CTF 2023] Dropper Write Up After reading the source contract code, our key is to optimize our gas usage when calling the contract following functions: ```solidity= interface AirdropLike { function airdropETH(address[] calldata, uint256[] calldata) external payable; function airdropERC20(address, address[] calldata, uint256[] calldata, uint256) external; function airdropERC721(address, address[] calldata, uint256[] calldata) external; } ``` I can use huff to optimize the function but the best solution to write some hardcode addresses and transfer value when you launchs your instance and get your target addresses. That's the skill I think most of the players who master huff would use. But there are still other tricks we can play to optimize the gas usage. 1. Use `calldatasize` as a function selector. Usually we would use `push4 selector eq` or something like that to judge method calling. But in this case we would find out that the methods our contract would implemented would be so limited. So we don't check the selector but instead we use the `calldatasize` to decide which branch we should jump. 2. We can reuse the `call` result -- `success` to pass as a parameter for the next call. like ```huff= #define macro AIRDROP_NATIVE() = takes (0) returns (0) { 0x00 // last arg 0x00 0x00 0x00 value callee gas call // the stack would be call result -- success 0x00 0x00 0x00 value2 callee2 0x8000 call stop } ``` 3. Use opcodes like `callvalue` / `caller` / `...` as many as possible to optimize gas usages. Let's take Fuzzland Write-up as an example. Their implementation is very close to ultimate version of minimal gas optimization and I really learned a lot from the implementation. But there is still room for improvement. Like: Fuzzland's implementation: ```huff= #define macro MAIN() = takes (0) returns (0) { calldatasize 0x484 eq sig_airdrop_eth jumpi calldatasize 0x4c4 eq sig_airdrop_erc20 jumpi AIRDROP_ERC721() sig_airdrop_eth: AIRDROP_NATIVE() sig_airdrop_erc20: AIRDROP_ERC20() } ``` My optimized implementation: ```huff= #define macro MAIN() = takes (0) returns (0) { callvalue sig_airdrop_eth jumpi calldatasize 0x4c4 eq sig_airdrop_erc20 jumpi AIRDROP_ERC721() sig_airdrop_eth: AIRDROP_NATIVE() sig_airdrop_erc20: AIRDROP_ERC20() } ``` 4. Use some more tricky ways to find optimizations like ChainLight did. Like use `selfdestruct` at the end of the final eth transfer. And Even more aggressive. By setting up the base fee and the gas price and use opcodes `basefee` and `gasprice` to optimize `push1 / pushx...` opcodes. Here is how ChainLight did and it's a really really really impressive solution. *since we are operating in a private fork network, we can carefully set up the base fee and transaction gas price to replace the usage of the constants 0x24 and 0x44, saving 1 gas for each use of those.* ```huff= [ERC20_RECIPIENT0] basefee mstore [ERC20_AMOUNT0] gasprice mstore AIRDROP_ERC20_INNER() ``` The gas control script and the genius idea was from [Tim Becker](https://x.com/tjbecker_) from ChainLight. Each block has a target size of 15 million gas, but the size of blocks will increase or decrease in accordance with network demand, up until the block limit of 30 million gas (2x the target block size). The base fee is calculated by a formula that compares the size of the previous block (the amount of gas used for all the transactions) with the target size. The base fee will increase by a maximum of 12.5%(1/8) per block if the target block size is exceeded. This exponential growth makes it economically non-viable for block size to remain high indefinitely. ```javascript= import { ethers } from "ethers" let provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL || "[set RPC_URL]") let w = new ethers.Wallet(process.env.PRIV_KEY || "[set PRIV_KEY]", provider) let chal = new ethers.Contract( process.env.CHAL_ADDR || "[set CHALL_ADDR]", [ "function submit(address) returns ()" ], w ) const calcNextBlockBaseFee = (curBlock: ethers.providers.Block) => { const baseFee = curBlock.baseFeePerGas! const gasUsed = curBlock.gasUsed! const targetGasUsed = curBlock.gasLimit.div(2) const delta = gasUsed.sub(targetGasUsed) const newBaseFee = baseFee.add( baseFee.mul(delta).div(targetGasUsed).div(8) )//.add(1) return newBaseFee }; async function getBaseFee(): Promise<ethers.BigNumber> { let block = await provider.getBlock("latest") return calcNextBlockBaseFee(block) } async function main() { let nonce = await provider.getTransactionCount(w.address); for (let baseFee = await getBaseFee(); !baseFee.eq(0x24); baseFee = await getBaseFee()) { console.log("Current base fee: ", baseFee.toString()); let tx; if (baseFee.lt(0x24)) { console.log("raising..."); tx = w.sendTransaction({to: w.address, value: 0, gasLimit:29000000, gasPrice: baseFee.mul(2), nonce, data: "0x" + "F".repeat(0x1e0000)}) } else { console.log("lowering..."); tx = w.sendTransaction({to: w.address, value: 0, gasLimit: 21000, gasPrice: baseFee.mul(2), nonce}); } nonce++; await (await tx).wait(); } } main() ``` ### Reference - [Fuzzland Paradigm CTF write-up](https://github.com/fuzzland/writeup/blob/master/paradigm.md#dropper) - [Huff Dispatching](https://docs.huff.sh/tutorial/function-dispatching/) - [ChainLight Paradigm CTF write-up](https://github.com/chainlight-io/publications/blob/main/ctf-writeups/paradigm2023/dropper/AirdropGas.huff) - [How are gas fees calculated](https://ethereum.org/en/developers/docs/gas/#how-are-gas-fees-calculated)