Gas Mechanism and Transfer Design
Learning Objectives
- Understand the economic model and incentive mechanism of blockchain
- Master the concepts and relationships of Gas, Gas Price, and Gas Fee
- Understand Ether units and conversions
- Master contract transfer design (receive / fallback / payable)
Blockchain Economic System
Why is an Economic Model Needed?
- Computing and storage resources are scarce: Every node must execute and store all transactions
- Consensus and trustlessness require miners' work: Miners (validators) need incentives to continue participating
- Transaction execution has a cost (gas): Gas fees become a direct incentive for miners
- Ether is the native currency of the ecosystem: Used for paying gas fees, transfers, and contract interactions
Ether Units
1 ETH = 10^18 Wei (Wei is the smallest unit)
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract EtherUnits {
uint public oneWei = 1 wei;
bool public isOneWei = (1 wei == 1);
uint public oneEther = 1 ether;
bool public isOneEther = (1 ether == 1e18);
}
Common unit conversions:
| Unit | Wei Value |
|---|---|
| 1 Wei | 1 |
| 1 Gwei | 10^9 |
| 1 Ether | 10^18 |
Gas, Gas Fee, Gas Price
Relationship Among the Three
Gas Fee = Gas × Gas Price
- Gas: Determined entirely by the execution logic. For functions with fixed logic, gas consumption is constant (e.g., a single SSTORE operation consistently consumes 20000 gas)
- Gas Price: Set by the transaction initiator in the transaction, with Gwei as the unit. Gas Price is determined by market supply and demand—prices rise during network congestion and fall when idle
- Gas Fee: The final fee paid, equal to the actual gas consumed multiplied by the gas price
Gas Limit
Gas Limit exists at three levels:
- Transaction Level: The transaction initiator sets the gas limit, indicating the maximum amount of gas they are willing to consume
- Contract Call Level: When calling between contracts, a gas limit can be specified to control the gas consumption of sub-calls
- Block Level: Each block has an upper gas limit, restricting the total computation for a single block
gasleft() Function
gasleft() returns the currently available gas, which can be used for:
- Monitoring gas consumption
- Exiting early before gas runs out
- Limiting gas consumption of sub-calls
Refund Rules
- Unused gas remaining will be refunded to the transaction initiator
- If a transaction fails (revert / out of gas), consumed gas is not refunded
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract Gas {
uint public i = 0;
uint public remained;
function forever() public {
while (true) {
if (i > 100) return;
if (i == 10) {
remained = gasleft(); // 记录剩余 gas
}
i += 1;
}
}
}
contract GasCaller {
Gas private gas;
constructor(Gas _gas) {
gas = _gas;
}
function callForever() public {
gas.forever{gas: 200000}(); // 限制子调用最多消耗 200000 gas
}
}
Contracts Holding Ether
- Contracts can hold Ether! Contract addresses, like EOA addresses, have balances
- Contracts can freely transfer between other contracts or EOAs
- Typical applications: Multisig Wallets, DeFi protocols, ICO contracts
Transfer Design Philosophy
Core Understanding
Transfers do not have a separate function; they occur together with function calls.
When calling a contract function, Ether can be sent simultaneously by attaching the option {value: amount}:
// 通过 call 转账
_to.call{value: 1 ether}("");
// 通过具名函数转账
contract.deposit{value: 1 ether}();
Called Function Resolution Logic (Important!)
When a contract receives a call, the EVM decides which function to execute based on the following logic:
Send Ether (Function Call)
│
Is msg.data empty?
/ \
No Yes
/ \
Function name matches? receive() exists?
/ \ / \
Yes No Yes No
/ \ / \
Matching function fallback() receive() fallback()
payable Check
- If
msg.value > 0and the target function does not have thepayablemodifier → Call fails (automatic revert) - The sole purpose of the
payablemodifier is to allow a function to receive Ether
receive() Function
receive() is a function specifically for handling pure Ether transfers (when calldata is empty), making the responsibilities of fallback clearer:
receive(): Handles pure Ether transfersfallback(): Handles calls to non-existent functions
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract ReceiveEther {
string public caller;
receive() external payable {
caller = "receive";
}
fallback() external payable {
caller = "fallback";
}
function deposit() public payable {
// 接收 Ether 的普通函数
}
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
Comparison of Transfer Methods
| Method | Gas Limit | Recommendation Level | Description |
|---|---|---|---|
transfer |
Fixed 2300 | Not Recommended | Old design, insufficient gas for complex logic |
send |
Fixed 2300 | Not Recommended | Old design, returns bool but often ignored |
call |
Customizable | Recommended | Flexible, secure (requires return value check) |
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract SendEther {
// 推荐方式:通过 call 转账
function sendViaCall(address payable _to) public payable {
(bool sent, ) = _to.call{value: msg.value}("");
require(sent, "Failed to send Ether");
}
// 通过具名函数转账
function sendViaFoo(address payable _to) public payable {
ReceiveEther re = ReceiveEther(_to);
re.deposit{value: msg.value}();
}
}
Comprehensive Example: ERC20 Token + ICO
Below is a simplified ERC20 token contract, combining ICO (Initial Coin Offering) functionality, demonstrating the practical application of transfer design:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract MyErc20Token {
string public name = "MyDollar";
string public symbol = "$";
uint public decimals = 4;
address public owner;
mapping(address => uint) public balanceOf;
constructor() {
owner = msg.sender;
}
// 铸币:用户发送 ETH 购买代币
function mint() external payable {
balanceOf[msg.sender] += msg.value;
}
event TransferEvent(uint oldv, uint newv);
modifier isOwner() {
require(msg.sender == owner, "not owner!");
_;
}
// 转账:从调用者账户转给目标地址
function transfer(address to, uint amount) public {
address from = msg.sender;
uint current = balanceOf[from];
require(current >= amount, "not enough balance!");
uint toc = balanceOf[to];
current -= amount;
toc += amount;
balanceOf[from] = current;
balanceOf[to] = toc;
}
// 提现:仅 owner 可以将合约中的 ETH 提取到自己的地址
function withdraw() external isOwner {
(bool suc, ) = owner.call{value: address(this).balance}("");
require(suc, "failed!");
}
}
Contract Function Analysis:
- mint(): Users send ETH to call this function, and the contract records the corresponding token balance (payable allows receiving ETH)
- transfer(): Transfers between token holders (note that this is a token-level transfer, not an ETH transfer)
- withdraw(): The contract owner withdraws all ETH held by the contract to their address, using the
call{value: ...}method for transfer - isOwner modifier: Permission control, ensuring only the contract deployer can withdraw
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/7496
