本文最后更新于 2024-03-25,本文发布时间距今超过 90 天, 文章内容可能已经过时。最新内容请以官方内容为准

P2PSwap contract prototype

P2PSwap logic overview

P2PSwap 是一个基于以太坊的跨链代币交换合约,允许用户创建一个代币交换合约,存入特定数量的一种代币 A,并在将来某个时间点用另一种代币 B 进行交换。

以下是 P2PSwap 合约从编写、部署到调用的完整流程,包括流程图和文字描述:

编写合约

  1. 定义需求:确定合约的功能,即允许用户创建一个代币交换合约,存入特定数量的一种代币 A,并在将来某个时间点用另一种代币 B 进行交换。
  2. 编写代码:根据需求编写 P2PSwap 合约代码,包括定义状态变量事件构造函数函数
  3. 添加依赖:在合约中导入所需的 OpenZeppelin 接口和 Ownable 合约。

部署合约

  1. 编译合约:使用 Solidity 编译器编译 P2PSwap 合约代码。
  2. 设置环境:配置 Hardhat 或 Truffle 等开发环境,包括网络配置和账户信息。
  3. 部署合约:通过开发环境的命令行工具(如 npx hardhat deploy)将合约部署到以太坊网络(可以是测试网络或主网络)。
  4. 获取构造函数参数:在部署时,用户提供 _token0_token1(两种 ERC20 代币的地址),_amountIn_amountOut(交换数量)作为构造函数的参数。

调用合约

  1. 合约激活:部署完成后,合约的 isActive 状态为 true,表示交换合约已经激活。
  2. 存入代币:创建者(或其他用户)可以通过调用 token0TransferIn 函数将 token0 存入合约。
  3. 等待交换:其他用户可以通过发送 token1 到合约地址来完成交换,但需要合约中的 token1 数量达到 amountOut
  4. 完成交换:一旦合约收到足够的 token1,任何用户都可以通过调用 token1TransferOut 函数来获取对应的 token0
  5. 取消交换:如果合约未被使用,创建者可以通过调用 cancelSwap 函数来取消交换并取回 token0

流程图

+----------------+       +----------------+       +----------------+
|  编写合约代码   | --->  |  编译合约代码  | --->  |  部署合约到网络 |
+----------------+       +----------------+       +----------------+
           |                       |                          |
           V                       V                          V
+----------------+       +----------------+       +----------------+
|  设置开发环境  | --->   | 配置网络和账户  | --->  |构造函数参数设置 |
+----------------+       +----------------+       +----------------+
           |                       |                          |
           V                       V                          V
+----------------+       +----------------+       +----------------+
|  等待代币存入  | --->   |用户发送代币到合约| ---> |  更新合约状态   |
+----------------+       +----------------+       +----------------+
           |                       |                          |
           V                       V                          V
+----------------+       +----------------+       +----------------+
|  取消交换      | --->  | 调用 cancelSwap | --->  |  合约状态更新   |
+----------------+       +----------------+       +----------------+

状态描述

  • 编写完成:合约代码包含所有必要的逻辑和安全特性。
  • 部署时:合约被部署到区块链上,构造函数执行并初始化状态变量。
  • 初始化后token0token1 被设置为用户指定的 ERC20 代币地址,amountInamountOut 被设置为用户指定的交换数量,isActive 被设置为 true
  • 调用时:用户通过调用合约函数来存入代币、请求交换或取消交换。每次调用都会根据其逻辑更新合约状态。

在整个流程中,合约的状态是由用户与合约的交互来改变的。每次交互都需要通过区块链网络进行,并且会消耗 Gas 费用。合约的状态存储在区块链上,确保了其透明性和不可篡改性。

P2PSwap sample

以下是一个基于 ERC20 代币的简单 P2P 交换合约的示例,它允许用户存入一种代币并指定希望交换的另一种代币数量。

其他用户可以向该合约发送指定数量的第二种代币来获取第一种代币。

这个示例没有实现复杂的交易匹配逻辑,也没有考虑前端界面,它仅仅是一个基本的智能合约实现,用于说明如何使用 Solidity 来创建这样的功能。

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract P2PSwap is Ownable {
    IERC20 public token0;
    IERC20 public token1;
    uint public amountIn;
    uint public amountOut;
    address public creator;
    bool public isActive;

    event SwapCreated(address indexed creator, uint amountIn, uint amountOut);
    event Token0Received(address indexed sender, uint amountIn);
    event Token1Received(address indexed sender, uint amountOut);

    constructor(IERC20 _token0, IERC20 _token1, uint _amountIn, uint _amountOut) {
        token0 = _token0;
        token1 = _token1;
        amountIn = _amountIn;
        amountOut = _amountOut;
        creator = _msgSender();
        isActive = true;
        emit SwapCreated(creator, amountIn, amountOut);
    }

    function receive() external payable {
        require(isActive, "Swap is not active");
        require(msg.sender == creator, "Only the creator can receive Ether");
    }

    function token0TransferIn() public {
        require(isActive, "Swap is not active");
        require(token0.balanceOf(msg.sender) >= amountIn, "Insufficient token0 balance");
        require(token0.allowance(msg.sender, address(this)) >= amountIn, "Allowance not set");

        // Transfer token0 from the sender to this contract
        token0.transferFrom(msg.sender, address(this), amountIn);
        emit Token0Received(msg.sender, amountIn);
    }

    function token1TransferOut() public {
        require(isActive, "Swap is not active");
        // Check if the sender has sent the correct amount of token1
        require(token1.balanceOf(address(this)) >= amountOut, "Not enough token1 in the contract");
        
        // Transfer token1 from the contract to the sender
        token1.transfer(msg.sender, amountOut);
        emit Token1Received(msg.sender, amountOut);
    }

    function cancelSwap() public onlyOwner {
        isActive = false;
        // Transfer remaining token0 back to the creator
        token0.transfer(creator, token0.balanceOf(address(this)));
    }
}

代码注释和功能说明

  • P2PSwap 合约继承自 Ownable,这意味着它有一个所有者(creator),并且只有所有者可以调用某些函数(如 cancelSwap)。
  • token0token1 是 ERC20 代币合约的接口,分别代表用户想要存入和取出的代币。
  • amountInamountOut 表示用户希望存入的第一种代币数量和希望取出的第二种代币数量。
  • isActive 是一个布尔值,表示交换是否仍然有效。
  • creator 是创建这个交换的用户的地址。
  • receive() 函数允许合约接收 Ether。这里没有实现 Ether 的逻辑,因为它是基于 ERC20 代币的交换。
  • token0TransferIn() 函数允许用户将第一种代币(token0)转入合约。它检查用户是否有足够的代币余额,并且是否已经为合约设置了足够的授权。
  • token1TransferOut() 函数允许用户从合约中提取第二种代币(token1)。它检查合约是否有足够的代币余额。
  • cancelSwap() 函数允许所有者取消交换,并将剩余的第一种代币退还给创建者。
  • SwapCreatedToken0ReceivedToken1Received 是事件,用于记录交换的创建和代币的转入和转出。

Ownable

Ownable 是 OpenZeppelin Contracts 提供的一个基础合约,它提供了一个简单的所有权模型,允许您指定一个地址作为合约的所有者,并给予该所有者一些特殊权限。这个合约通常被用作其他合约的基类,以便在这些合约中实现所有权控制。

以下是 Ownable 合约的核心功能和特点:

核心变量

  • _owner: 这是一个存储所有者地址的变量。在 Ownable 合约构造函数中初始化,通常设置为部署合约的地址。

修饰符

  • onlyOwner: 这是一个修饰符,用于保护那些只有所有者才能调用的函数。如果您在一个函数声明前加上 onlyOwner,那么只有合约所有者才能执行这个函数。

函数

  • owner(): 返回当前所有者的地址。
  • isOwner(): 一个内部函数,检查一个给定的地址是否是所有者。
  • transferOwnership(address newOwner): 允许当前所有者将所有权转移给另一个地址。

使用示例

import "@openzeppelin/contracts/access/Ownable.sol";

contract MyContract is Ownable {
    // 只有所有者可以调用这个函数
    function onlyForOwner() public onlyOwner {
        // 所有者特有的逻辑
    }

    constructor() {
        // 调用 Ownable 构造函数来初始化所有者
        Ownable.__Ownable_init();
    }
}

为什么使用 Ownable

  • 权限控制Ownable 提供了一种简单的方式来控制对合约功能的访问权限,确保只有可信的地址可以执行关键操作。
  • 安全性:通过限制对敏感函数的访问,Ownable 有助于防止未授权的修改和潜在的安全风险。
  • 易于集成:作为一个基类,Ownable 可以轻松地集成到其他合约中,无需重复编写所有权逻辑。

注意事项

  • 所有权转移:在转移所有权时,应谨慎行事,因为新所有者将获得所有相关权限。
  • 合约升级:如果您需要升级合约,您可能需要考虑如何在转移所有权后进行升级,或者使用其他设计模式来实现升级。
  • 修饰符位置onlyOwner 修饰符应该放在所有者相关的函数声明之前,以确保它们的正确执行。

在使用 Ownable 时,建议仔细阅读 OpenZeppelin Contracts 的文档和源代码。此外,您还可以通过阅读其他开源合约和项目来获取更多关于 Ownable 的信息。