Skip to content

Commit

Permalink
update batch transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
AmazingAng committed Oct 4, 2023
1 parent e1bf8a9 commit fc529ba
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 1 deletion.
170 changes: 170 additions & 0 deletions src/BatchTransfer.huff
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/* Interface */
#define function batchTransferERC20(address token, uint256 total, address[] recipients, uint256[] amounts) nonpayable returns ()
#define function batchTransferETH(address[] recipients, uint256[] amounts) payable returns ()
#define function transfer(address,uint256) nonpayable returns (bool)
#define function transferFrom(address,address,uint256) nonpayable returns ()

/* Method */
#define macro BATCH_TRANSFER_ERC20() = takes (0) returns (0) {
// calldata:
// 0x00: sig | 0x04: token | 0x24: total | 0x44: recipients[] | 0x64: amounts[]

// 1. safeTransfer total amount of token from sender to this contract
0x04 calldataload // [token]
0x24 calldataload // [total, token]
address // [this, total, token]
// store args in memory
__FUNC_SIG(transferFrom) 0x00 mstore // [this, total, token] memory: [0x00: sig]
caller 0x20 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this]
dup1 0x40 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this]
dup2 0x60 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this, 0x60: total]
// call transferFrom
// return size is 0x20, return offset is 0x60
// arg size is 0x64, arg offset if 0x1c (where sig start)
0x20 // [ret_size, this, total, token]
0x60 // [ret_offset, ret_size, this, total, token]
0x64 // [args_size, ret_offset, ret_size, this, total, token]
0x1c // [args_offset, args_size, ret_offset, ret_size, this, total, token]
0x00 // [value, args_offset, args_size, ret_offset, ret_size, this, total, token]
dup8 // [to, value, args_offset, args_size, ret_offset, ret_size, this, total, token]
gas // [gas, to, value, args_offset, args_size, ret_offset, ret_size, this, total, token]
call // [success, this, total, token]
// safeTransferFrom ERC20 token
returndatasize iszero // [returndatasize == 0, success, this, total, token]
0x60 // [ret_offset, returndatasize == 0, success, this, total, token]
mload // [ret_data, returndatasize == 0, success, this, total, token]
0x01 eq // [ret_data == true, returndatasize == 0, success]
or // [ret_data == true | returndatasize == 0, success, this, total, token]
and // [success & (data == 0x01 | returndatasize == 0), this, total, token]
transferFrom_success jumpi // [this, total, token]
// revert if call failed
0x00 dup1 revert //
// transferFrom sucess
transferFrom_success: // [this, total, token]

// 2. batch transfer from this contract to recipients in a loop
// read recipients[] offset and length
0x44 calldataload 0x04 add // [rcp_offset, this, total, token]
dup1 calldataload // [rcp_len, rcp_offset, this, total, token]
// read amounts[] offset and length
0x64 calldataload 0x04 add // [amt_offset, rcp_len, rcp_offset, this, total, token]
dup1 calldataload // [amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
// check if length are equal
dup3 dup2 eq // [amt_len==rcp_len?, amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
arr_len_success jumpi // [amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
// revert if call failed
0x00 dup1 revert
// if amt_len==rcp_len
arr_len_success:
0x01 // [index(1), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
// store transfer sig in memory
__FUNC_SIG(transfer) 0x00 mstore // memory: [0x00: sig]

// 循环
loop:
// chech (index>len), if true, jump to end.
dup2 dup2 gt end jumpi // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
// read recipients[i-1], store to memory 0x20
dup5 dup2 0x20 mul add calldataload 0x20 mstore // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
// read amounts[i-1], store to memory 0x40
dup3 dup2 0x20 mul add calldataload 0x40 mstore // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]

// call transfer
// return size is 0x20, return offset is 0x60
// arg size is 0x64, arg offset if 0x1c (where sig start)
0x20 // [ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
0x60 // [ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
0x44 // [args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
0x1c // [args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
0x00 // [value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
dup13 // [to, value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
gas // [gas, to, value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
call // [success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]

// safeTransfer ERC20 token
returndatasize iszero // [returndatasize == 0, success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
0x60 // [ret_offset, returndatasize == 0, success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
mload // [ret_data, returndatasize == 0, success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
0x01 eq // [ret_data == true, returndatasize == 0, successindex(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
or // [ret_data == true | returndatasize == 0, success, index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
and // [success & (data == 0x01 | returndatasize == 0), index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
transfer_success jumpi // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
// revert if call failed
0x00 dup1 revert //
// transferF sucess
transfer_success: // [index(i), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
// update i
0x01 add // [index(i+1), amt_len, amt_offset, rcp_len, rcp_offset, this, total, token]
// continue loop
loop jump

// end of the loop, stop
end:
stop
}

#define macro BATCH_TRANSFER_ETH() = takes (0) returns (0) {
// calldata:
// 0x00: sig | 0x04: recipients[] | 0x24: amounts[]

// 1. batch transfer ETH from this contract to recipients in a loop
// read recipients[] offset and length
0x04 calldataload 0x04 add // [rcp_offset]
dup1 calldataload // [rcp_len, rcp_offset]
// read amounts[] offset and length
0x24 calldataload 0x04 add // [amt_offset, rcp_len, rcp_offset]
dup1 calldataload // [amt_len, amt_offset, rcp_len, rcp_offset]
// check if length are equal
dup3 dup2 eq // [amt_len==rcp_len?, amt_len, amt_offset, rcp_len, rcp_offset]
arr_len_success jumpi // [amt_len, amt_offset, rcp_len, rcp_offset]
// revert if call failed
0x00 dup1 revert
// if amt_len==rcp_len
arr_len_success:
0x01 // [index(1), amt_len, amt_offset, rcp_len, rcp_offset]

// loop
loop:
// chech (index>len), if true, jump to end.
dup2 dup2 gt end jumpi // [index(i), amt_len, amt_offset, rcp_len, rcp_offset]

// call transfer
0x00 // [ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset]
dup1 // [ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset]
dup1 // [args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset]
dup1 // [args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset]
dup7 dup6 0x20 mul add calldataload // [value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset]
dup10 dup7 0x20 mul add calldataload // [to, value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset]
gas // [gas, to, value, args_offset, args_size, ret_offset, ret_size, index(i), amt_len, amt_offset, rcp_len, rcp_offset]
call // [success, index(i), amt_len, amt_offset, rcp_len, rcp_offset]

// safeTransfer ERC20 token
transfer_success jumpi // [index(i), amt_len, amt_offset, rcp_len, rcp_offset]
// revert if call failed
0x00 dup1 revert //
// transferF sucess
transfer_success: // [index(i), amt_len, amt_offset, rcp_len, rcp_offset]
// update i
0x01 add // [index(i+1), amt_len, amt_offset, rcp_len, rcp_offset]
// continue loop
loop jump

// end of the loop, stop
end:
stop
}

#define macro MAIN() = takes (0) returns (0) {
// 通过selector判断要调用哪个函数
0x00 calldataload 0xE0 shr
dup1 __FUNC_SIG(batchTransferERC20) eq batch_transfer_erc20 jumpi
dup1 __FUNC_SIG(batchTransferETH) eq batch_transfer_eth jumpi

// 如果没有匹配的函数,就revert
0x00 0x00 revert

batch_transfer_erc20:
BATCH_TRANSFER_ERC20()
batch_transfer_eth:
BATCH_TRANSFER_ETH()
}
59 changes: 59 additions & 0 deletions src/TestBatch.huff
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* Interface */
#define function batchTransferERC20(address token, uint256 total, address[] recipients, uint256[] amounts) nonpayable returns ()
#define function transfer(address,uint256) nonpayable returns (bool)
#define function transferFrom(address,address,uint256) nonpayable returns ()

/* Method */
#define macro BATCH_TRANSFER_ERC20() = takes (0) returns (0) {
// calldata:
// 0x00: sig | 0x04: token | 0x24: total | 0x44: recipients[] | 0x64: amounts[]

// 1. safeTransfer total amount of token from sender to this contract
0x04 calldataload // [token]
0x24 calldataload // [total, token]
address // [this, total, token]
// store args in memory
__FUNC_SIG(transferFrom) 0x00 mstore // [this, total, token] memory: [0x00: sig]
caller 0x20 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this]
dup1 0x40 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this]
dup2 0x60 mstore // [this, total, token] memory: [0x00: sig, 0x20: sender, 0x40: this, 0x60: total]
// call transferFrom
// return size is 0x20, return offset is 0x60
// arg size is 0x64, arg offset if 0x1c (where sig start)
0x20 // [ret_size, this, total, token]
0x60 // [ret_offset, ret_size, this, total, token]
0x64 // [args_size, ret_offset, ret_size, this, total, token]
0x1c // [args_offset, args_size, ret_offset, ret_size, this, total, token]
0x00 // [value, args_offset, args_size, ret_offset, ret_size, this, total, token]
dup8 // [to, value, args_offset, args_size, ret_offset, ret_size, this, total, token]
gas // [gas, to, value, args_offset, args_size, ret_offset, ret_size, this, total, token]
call // [success, this, total, token]
// safeTransferFrom ERC20 token
returndatasize iszero // [returndatasize == 0, success, this, total, token]
0x60 // [ret_offset, returndatasize == 0, success, this, total, token]
mload // [ret_data, returndatasize == 0, success, this, total, token]
0x01 eq // [ret_data == true, returndatasize == 0, success]
or // [ret_data == true | returndatasize == 0, success, this, total, token]
and // [success & (data == 0x01 | returndatasize == 0), this, total, token]
transferFrom_success jumpi // [this, total, token]
// revert if call failed
0x00 dup1 revert //
// transferFrom sucess
transferFrom_success: // [this, total, token]

// end of the loop, stop
end:
stop
}

#define macro MAIN() = takes (0) returns (0) {
// 通过selector判断要调用哪个函数
0x00 calldataload 0xE0 shr
dup1 __FUNC_SIG(batchTransferERC20) eq batch_transfer_erc20 jumpi

// 如果没有匹配的函数,就revert
0x00 0x00 revert

batch_transfer_erc20:
BATCH_TRANSFER_ERC20()
}
55 changes: 55 additions & 0 deletions test/BatchTransferTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "foundry-huff/HuffDeployer.sol";
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "@openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";

contract ArrayTest is Test {
IBatchTransfer iBatch;
MyToken token;
address address0 = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
address[] recipients;
uint[] amounts;
uint total;

/// @dev Setup the testing environment.
function setUp() public {
token = new MyToken("Token","TKN");
iBatch = IBatchTransfer(HuffDeployer.deploy("BatchTransfer"));
for(uint i; i < 500; i++){
recipients.push(address(uint(address0)+i));
amounts.push(i);
total += i;
}
token.mint(address0, total);
vm.prank(address0);
token.approve(address(iBatch), total);
}

function testBatchTransferERC20() public {
vm.prank(address0);
IBatchTransfer.batchTransferETH(recipients, amounts);
}

}

interface IBatchTransfer {
function batchTransferERC20(address token, uint256 total, address[] memory recipients, uint256[] memory amounts) external;
function batchTransferETH(address[] memory recipients, uint256[] memory amounts) external payable;
}

contract MyToken is ERC20{

constructor (string memory _name, string memory _symbol) ERC20 (_name,_symbol){
}

function mint(address to, uint256 amount) public virtual {
_mint(to,amount);
}

function burn(address form, uint amount) public virtual {
_burn(form, amount);
}
}
2 changes: 1 addition & 1 deletion tutorials/06_ControlFlow/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ tags:

## 控制流

EVM底层主要是使用跳转指令`JUMPI``JUMPDEST`完成代码的流程控制。如果你对它们不了解,建议阅读[WTF EVM Opcodes教程第9讲](https://github.com/WTFAcademy/WTF-EVM-Opcodes/tree/main/09_FlowOp)
EVM底层主要是使用跳转指令`JUMP``JUMPI``JUMPDEST`进行代码的流程控制。如果你对它们不了解,建议阅读[WTF EVM Opcodes教程第9讲](https://github.com/WTFAcademy/WTF-EVM-Opcodes/tree/main/09_FlowOp)

为了方便开发者使用跳转指令,Huff提供了跳转标签,可以在宏或函数哪定义,由冒号后跟一个单词表示。注意,虽然看起来标签是由于缩进而作为代码块的作用域,但它们实际上只是字节码中的跳转目的地。如果标签下面存在操作,除非程序计数器被更改或执行由`revert``return``stop``selfdestruct`操作码中断,否则它们将被执行。

Expand Down

0 comments on commit fc529ba

Please sign in to comment.