您正在查看: OtherChain-新手教程 分类下的文章

Delivery层初始化配置

Delivery层初始化配置

首先需要和bttc层一样准备一个genesis配置,如果是同步现有测试网或者主网,可以官方仓库拿到

主网:https://github.com/bttcprotocol/launch/blob/master/mainnet-v1/without-sentry/delivery/config/genesis.json

测试网:https://github.com/bttcprotocol/launch/blob/master/testnet-1029/without-sentry/delivery/config/genesis.json

如果是启动私链也可以自己创建,deliveryd程序也提供了初始化测试链的功能,比如需要创建一个私链,3个验证者2个同步节点

./deliveryd create-testnet --v 3 --n 2 --output-dir ./output --chain-id delivery-9528

执行完成后,会自动创建5个节点对应的配置文件,每份主要的配置如下

genesis.json

初始化链配置,主要关心以下字段

  • chain_id 链id

  • app_state->accounts 初始化账户资金等信息

  • bor->spans->validator_set->validators 初始化验证者信息

  • bor->spans->validator_set->proposer 初始化提案者信息

  • bor->spans->selected_producers 初始化当前生产者信息

  • bor->spans->bor_chain_id bttc层链id

  • chainmanager->chain_params 链合约相关地址

  • checkpoint 配置相关参数

  • gov 治理相关参数

  • slashing 惩罚相关参数

  • staking 初始化相关地址抵押量

对于私链,在初始化数据基础上,主要关注chain_id和chain_params,其余地址相应数据使用默认即可

config.toml

链基础配置,主要关心参数如下

  • fast_sync:是否开始快速同步

  • db_dir: 数据的存放位置

  • genesis_file:genesis.json存放位置

  • priv_validator_key_file:验证者私钥文件位置

  • priv_validator_state_file:验证者状态文件位置

  • node_key_file:节点密钥存放位置

  • persistent_peers:持久链接的peer地址,类同于eth的staticnode

  • private_peer_ids: 私有的节点id, 用于隐私接入,比如哨兵节点

    • 登录验证人节点.
    • 运行 deliveryd tendermint show-node-id. 示例: private_peer_ids = "e2c6a611e449b61f2266f0054a315fad6ce607ba"
delivery-config.toml

RPC和 REST配置,主要关心参数如下

  • eth_rpc_url:eth链RPC地址

  • bsc_rpc_url:bsc链RPC地址

  • bttc_rpc_url:bttc层RPC地址

  • tron_rpc_url:tron链RPC地址

  • 测试网:47.252.19.181:50051

  • tron_grid_url:tron链grid地址

  • 测试网:https://test-tronevent.bt.io

  • amqp_url:AMQP地址,在bridge过程中会用于任务的消息队列

node_key.json

节点的密钥信息,可以使用工具自动生成

priv_validator_key.json

节点验证者的密钥信息,可以使用工具自动生成

对于单节点测试,也可以直接执行初始化

./deliveryd init --chain-id delivery-9528 --home ./

plasma与侧链区别

侧链

优点

实现上相对简单易用

缺点

但需要在信任侧链的基础之上,如果侧链失败(意味着共识机制受到损害),您可能会损失所有资金。

相对解决

通常是只跨小额临时使用的代币,将风险人为控制降低。

plasma

优点

plasma在设计上比侧链更安全。(用户可以使用区块根来表明他们在 Plasma 链上收到了资金,如果 Plasma 链共识机制停止创建区块,用户可以使用区块根向以太坊提出索赔)

缺点

不能真正进行与侧链相同的复杂操作。不做信任假设来保证资金安全,所以我们总是不得不假设 Plasma 链共识机制随时可能失败,需要围绕它进行设计。这对 Plasma 链上可能发生的事情增加了额外的限制。主要缺点是你不能真正进行与侧链相同的复杂操作

参考

https://docs.plasma.group/en/latest/src/plasma/sidechains.html

kaleido chain 测试链部署

由于在调研Algorand共识,所以最近阅读和测试相关的链实现,此篇将主要讲解kaleido的私链部署测试。
由于相关项目基本已经很久不更新,文档社区匮乏,只能阅读代码,反向推出运行参数和步骤。

编译源代码

参考《kaleido 编译与测试》,执行编译相关子程序

make all

最终kaleido\build\bin会生成genesis程序,下面会用到

genesis.json创建

启动私链,首先需要准备bios节点配置
阅读相关代码(github
可以使用genesis程序进行生成,首次直接运行genesis

Hint: You must make a minerkey before making genesis.

提示需要先生成minerkey,继续跟进
阅读相关代码(github

    makeMinerKeyCommand = cli.Command{
        Action:    utils.MigrateFlags(makeMinerKey),
        Name:      "makeminerkey",
        Usage:     "Generates a miner key for mining",
        ArgsUsage: " ",
        Flags: []cli.Flag{
            utils.DataDirFlag,
            utils.MinerStakeOwnerFlag,
            utils.MinerKeyCoinbaseFlag,
            utils.MinerKeyStartFlag,
            utils.MinerKeyLifespanFlag,
        },
        Category: "MINER COMMANDS",
        Description: `
The makeMinerKey command generates a miner key for mining.
If the key already exists, just return it.

可以使用kalgo加上参数makeminerkey进行生成,参考有限文档例子如下

mkdir $PWD/data
export KALEIDO_HOME=$PWD/data
echo $KALEIDO_HOME

./kalgo makeminerkey \
--miner.stakeowner 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0 \
--minerkey.coinbase 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0 \
--minerkey.start 1

0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0修改为自己的地址
执行生成后,返回

MinerKey: 0x39fb25e90000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006400000000000000000000000048f155527f25eb1d4cb2aa32b7e84692aa0025c003ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f534566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645
Details:
    miner = 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0
    coinbase = 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0
    start = 1
    end = 1000000
    lifespan = 100
    vrfVerifier = 0x03ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f53
    voteVerfier = 0x4566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645
GenesisStorage:
    0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2fefd = 0x48f155527f25eb1d4cb2aa32b7e84692aa0025c0000000640000000000000001
    0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2fefe = 0x03ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f53
    0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2feff = 0x4566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645

以上仅为测试数据,对于自己相关私有数据请注意保管

由于cmd/genesis/main.goconsensus/clique/clique.go已写死了0x0e09768B2B2e7aa534243f8bf9AFdC145DdA8EDa为测试数据,所以我们可以修改对应代码,也可以拿测试地址生成minerkey,然后生成genesis.json然后再手动修改对应的地址和数据。
切记替换地址时,(github

contracts.MinerAddress: {
        Balance: common.Big0,
        Code:    common.FromHex(contracts.MinerBinRuntime),
        Storage: map[common.Hash]common.Hash{
            common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8a"): common.HexToHash("0x45ec182edc6774c9a2926172f1fd996e59b58ced000000640000000000000001"),
            common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8b"): common.HexToHash("0xf88a8d844c217531a38d6019ea671652340fe0d899996250bccce13af99933de"),
            common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8c"): common.HexToHash("0x6e8f4a7c7651766722dd7fb9d7a97cd28678a1cefb12631580a7ffe90a910b8f"),
        },
    },

Storage中的数据,要替换成上面执行kalgo makeminerkey返回的数据中的GenesisStorage对应的数据,不然检查不通过(github)会报bad signature

此时按照常规eth启动步骤执行即可,细节不再单独讲解

./kalgo --datadir ./data init genesis.json
./kalgo --config geth.toml  --nodiscover

./kalgo attach ipc:./data/geth.ipc

添加新节点

同上为新节点创建minerkey

mkdir $PWD/data
export KALEIDO_HOME=$PWD/data
echo $KALEIDO_HOME

./kalgo makeminerkey \
--miner.stakeowner 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa \
--minerkey.coinbase 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa \
--minerkey.start 1

返回

MinerKey: 0x39fb25e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000064000000000000000000000000bb93fcc2bb90d46255c43dd398a1a372e12be6aa474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8
Details:
    miner = 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa
    coinbase = 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa
    start = 1
    end = 1000000
    lifespan = 100
    vrfVerifier = 0x474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e
    voteVerfier = 0x35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8
GenesisStorage:
    0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569857 = 0xbb93fcc2bb90d46255c43dd398a1a372e12be6aa000000640000000000000001
    0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569858 = 0x474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e
    0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569859 = 0x35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8
mkdir -p ./data/kalgo/minerkeys
cp ~/.kaleido/kalgo/minerkeys/0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa-0-1000000.bin ./data/kalgo/minerkeys
./kalgo makeminerkey \
--miner.stakeowner 0x10210572d6b4924Af7Ef946136295e9b209E1FA0 \
--minerkey.coinbase 0x10210572d6b4924Af7Ef946136295e9b209E1FA0 \
--minerkey.start 9500


mkdir -p ./data/kalgo/minerkeys
cp ~/.kaleido/kalgo/minerkeys/0x10210572d6b4924Af7Ef946136295e9b209E1FA0-0-1000000.bin ./data/kalgo/minerkeys


myaddr = '0x10210572d6b4924Af7Ef946136295e9b209E1FA0';
hash = eth.sendTransaction({
    from: myaddr, 
    to: '0x1000000000000000000000000000000000000002', 
    gas: 200000, 
    gasPrice: 20000000000, 
    data: 
'0x39fb25e9000000000000000000000000000000000000000000000000000000000000251c000000000000000000000000000000000000000000000000000000000000006400000000000000000000000010210572d6b4924af7ef946136295e9b209e1fa04141f8e905b628d3657b151a62c1414c4cc4f554d24bc16971c8233b761879d09b8f95e47a6f9662b78198c45ff986153c1e3316ede5cbfae09c6f927d374bd2'
});
admin.sleepBlocks(2);
eth.getTransactionReceipt(hash);


personal.importRawKey("8d91c6c7a494a4373...0715","j7OseEMVQ3Trna6gWdkp")
personal.unlockAccount("0x10210572d6b4924Af7Ef946136295e9b209E1FA0","j7OseEMVQ3Trna6gWdkp",0)



nohup ./kalgo --config geth.toml --networkid 1001 --unlock "0x10210572d6b4924Af7Ef946136295e9b209E1FA0" --password ./password --mine --miner.stakeowner "0x10210572d6b4924Af7Ef946136295e9b209E1FA0" --nodiscover --etherbase 0 --verbosity 4 2>> ./geth.log &

personal.unlockAccount("0xf8329ea42489c2164f44a81deb3be22c2c456109","j7OseEMVQ3Trna6gWdkp",0)
eth.sendTransaction({from: "0xf8329ea42489c2164f44a81deb3be22c2c456109", to: "0x10210572d6b4924Af7Ef946136295e9b209E1FA0", value: web3.toWei(25000000, 'ether')})


admin.addPeer("enode://e20d5cb1c726bb5f8bcc5c3b91255e7fcf459add7584cf6becbb99715f2f84334f14233843bb50ce68c71c4f1b6cfeaff3294cec85a488f363428476010651b1@172.31.204.223:32668?discport=0")
admin.addPeer("enode://95b41257c7da4bd0a98e81cb636b833c36693a3ab76ff55987663455b186dac96437d785b7bb6c5d5335bae91402f227d99a395c1dfe31663a891eb6e5d90140@172.31.204.224:32668?discport=0")



./kalgo --datadir ./data init genesis.json
./kalgo --config geth.toml  --nodiscover

./kalgo attach ipc:./data/geth.ipc

参考

https://docs.kaleidochain.io

ZKP(Zero-Knowledge Proofs)技术调研

在之前《隐私/匿名币》中说过Zcash 首次运用 zk-SNARKs 零知识证明技术验证交易有效性,原来的计划是先把基于以太坊的侧链开发完成,再做ZKP(Zero-Knowledge Proofs)的技术跟进,以解决匿名交易以及增强TPS扩容方案(交易指纹上链,交易数据链下存储)。

今天先做一些技术调研和储备,方便后续技术评估时的一些技术方向的把控

技术相关

什么是 zk-SNARK?
零知识证明:STARKs 与 SNARKs
zk-SNARKs 介绍(零知识证明概述以及如何将 zk-SNARK 集成到以太坊中)
https://zokrates.github.io/
https://docs.ethhub.io/ethereum-roadmap/privacy/
https://starkware.co/
https://docs.ethhub.io/built-on-ethereum/infrastructure/aztec-protocol/
https://scrt.network/blog/introducing-secret-network
https://ethresear.ch/t/zether-the-first-privacy-mechanism-designed-for-ethereum/5029
https://ethereum.stackexchange.com/questions/49781/can-zksnarks-be-implemented-on-the-ethereum-ecosystem
https://github.com/starkware-libs/
https://hackmd.io/@zkteam/gnark
https://docs.gnark.consensys.net/en/latest/

开源代码

https://github.com/zkcrypto/bellman/
https://github.com/matter-labs/bellman
https://github.com/zcash/librustzcash (Groth16)
https://github.com/zcash/mpc
https://github.com/scipr-lab/libsnark
https://github.com/Zokrates/ZoKrates
https://github.com/AztecProtocol
https://github.com/arnaucube/go-snark-study
https://github.com/vocdoni/go-snark
https://github.com/ConsenSys/gnark (Groth16, PlonK)
https://github.com/ConsenSys/gnark-crypto
https://github.com/nikkolasg/playsnark (Groth16, PHGR13)
https://github.com/ConsenSys/quorum

Swarm激励模型合约

源代码:https://github.com/ethersphere/swap-swear-and-swindle

Swarm目前只跑在Goerli测试网,核心的合约为ERC20SimpleSwap,源代码在master分支,对于后续功能合约 full Swap, Swear and Swindle相关代码在experimental分支

逻辑简述

目前Swarm的Goerli测试网部署的合约逻辑为
用户通过执行SimpleSwapFactory合约,部署ERC20SimpleSwap合约,并与发行的gBZZ代币交互

用户与Swarm交互

测试用户

0x10210572d6b4924Af7Ef946136295e9b209E1FA0

部署ERC20SimpleSwap合约

用户地址通过执行SimpleSwapFactory合约的deploySimpleSwap方法,完成部署ERC20SimpleSwap合约,通过查看交易的eventlog,部署的Chequebook合约地址为0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212

deploySimpleSwap合约代码

function deploySimpleSwap(address issuer, uint defaultHardDepositTimeoutDuration)
  public returns (address) {
    address contractAddress = address(new ERC20SimpleSwap(issuer, ERC20Address, defaultHardDepositTimeoutDuration));
    deployedContracts[contractAddress] = true;
    emit SimpleSwapDeployed(contractAddress);
    return contractAddress;
  }

deploySimpleSwap执行同时完成了ERC20SimpleSwap的构造初始化

constructor(address _issuer, address _token, uint _defaultHardDepositTimeout) public {
    issuer = _issuer;
    token = ERC20(_token);
    defaultHardDepositTimeout = _defaultHardDepositTimeout;
  }

同时初始化了交互Token gBZZ代币

订金(为支票薄提供资金)

通过执行API进行为支票薄提供资金
http://localhost:1635/chequebook/deposit?amount=1000000000
执行的逻辑为用户地址0x10210572d6b4924af7ef946136295e9b209e1fa0向自己的Chequebook合约地址0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212转对应数量的gBZZ,也可以理解成充值抵押

测试交易

https://goerli.etherscan.io/tx/0x81080bdc2ee4ede8898f003c4b74c9dcabdd6467e443f9ab8669f0c62434aaee

提现

将自己Chequebook合约中的gBZZ体现到用户地址
执行逻辑为执行自己的Chequebook合约地址0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212中的withdraw方法。

合约代码

function withdraw(uint amount) public {
    /* 只有发行人可以做到这一点 */
    require(msg.sender == issuer, "not issuer");
    /* 确保我们不从保证金中提取任何东西 */
    require(amount <= liquidBalance(), "liquidBalance not sufficient");
    require(token.transfer(issuer, amount), "transfer failed");
  }

测试交易

https://goerli.etherscan.io/tx/0x967fabac2da76d868a9f80d8a64baacbe2cb585519388a4494593705ef050421

分析ERC20SimpleSwap合约

// SPDX-License-Identifier: BSD-3-Clause
pragma solidity =0.7.6;
pragma abicoder v2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/presets/ERC20PresetMinterPauser.sol";


/**
@title支票簿合同,无豁免
@author The Swarm作者
@notice支票簿合同允许支票簿的发行者将支票发送到无限数量的交易对手。
此外,可以通过hardDeposits保证偿付能力
@dev作为发行人,如果发送的支票的累积价值高于所有存款的累积价值,则不应该发送任何支票
作为受益人,我们应始终考虑支票弹跳的可能性(当未分配hardDeposits时)
*/
contract ERC20SimpleSwap {
  using SafeMath for uint;

  event ChequeCashed(
    address indexed beneficiary,
    address indexed recipient,
    address indexed caller,
    uint totalPayout,
    uint cumulativePayout,
    uint callerPayout
  );
  event ChequeBounced();
  event HardDepositAmountChanged(address indexed beneficiary, uint amount);
  event HardDepositDecreasePrepared(address indexed beneficiary, uint decreaseAmount);
  event HardDepositTimeoutChanged(address indexed beneficiary, uint timeout);
  event Withdraw(uint amount);

  uint public defaultHardDepositTimeout;
  /* 跟踪每个受益人的硬存款(偿付能力的链上保证)的结构*/
  struct HardDeposit {
    uint amount; /* 分配的硬存款金额 */
    uint decreaseAmount; /* 减少量从请求减少时的金额中减去 */
    uint timeout; /* 发行者必须等待超时秒数才能减少hardDeposit,0表示应用defaultHardDepositTimeout */
    uint canBeDecreasedAt; /* 可以减少硬质沉积的时间点 */
  }

  struct EIP712Domain {
    string name;
    string version;
    uint256 chainId;
  }

  bytes32 public constant EIP712DOMAIN_TYPEHASH = keccak256(
    "EIP712Domain(string name,string version,uint256 chainId)"
  );
  bytes32 public constant CHEQUE_TYPEHASH = keccak256(
    "Cheque(address chequebook,address beneficiary,uint256 cumulativePayout)"
  );
  bytes32 public constant CASHOUT_TYPEHASH = keccak256(
    "Cashout(address chequebook,address sender,uint256 requestPayout,address recipient,uint256 callerPayout)"
  );
  bytes32 public constant CUSTOMDECREASETIMEOUT_TYPEHASH = keccak256(
    "CustomDecreaseTimeout(address chequebook,address beneficiary,uint256 decreaseTimeout)"
  );

  // 该合同使用的EIP712域
  function domain() internal pure returns (EIP712Domain memory) {
    uint256 chainId;
    assembly {
      chainId := chainid()
    }
    return EIP712Domain({
      name: "Chequebook",
      version: "1.0",
      chainId: chainId
    });
  }

  // 计算EIP712域分隔符。 这不能恒定,因为它取决于chainId
  function domainSeparator(EIP712Domain memory eip712Domain) internal pure returns (bytes32) {
    return keccak256(abi.encode(
        EIP712DOMAIN_TYPEHASH,
        keccak256(bytes(eip712Domain.name)),
        keccak256(bytes(eip712Domain.version)),
        eip712Domain.chainId
    ));
  }

  // 使用EIP712签名方案恢复签名
  function recoverEIP712(bytes32 hash, bytes memory sig) internal pure returns (address) {
    bytes32 digest = keccak256(abi.encodePacked(
        "\x19\x01",
        domainSeparator(domain()),
        hash
    ));
    return ECDSA.recover(digest, sig);
  }

  /* 此支票簿针对其写支票的令牌 */
  ERC20 public token;
  /* 将每个受益人与已支付给他们的款项相关联 */
  mapping (address => uint) public paidOut;
  /* 支付总额 */
  uint public totalPaidOut;
  /* 将每个受益人与他们的HardDeposit相关联 */
  mapping (address => HardDeposit) public hardDeposits;
  /* 所有硬存款的总和 */
  uint public totalHardDeposit;
  /* 合同的签发人,在施工中 */
  address public issuer;
  /* 指示支票是否在过去退回 */
  bool public bounced;

  /**
  @notice设置颁发者,令牌和defaultHardDepositTimeout。 只能被调用一次。
   @param _issuer此支票簿的支票的发行者(需要作为“将支票簿设置为付款”的参数)。
   _issuer必须是外部拥有的帐户,或者它必须支持调用函数cashCheque
   @param _token此支票簿使用的令牌
   @param _defaultHardDepositTimeout持续时间(以秒为单位),默认情况下将用于减少hardDeposit分配
  */
  function init(address _issuer, address _token, uint _defaultHardDepositTimeout) public {
    require(_issuer != address(0), "invalid issuer");
    require(issuer == address(0), "already initialized");
    issuer = _issuer;
    token = ERC20(_token);
    defaultHardDepositTimeout = _defaultHardDepositTimeout;
  }

  /// @return 支票簿的余额
  function balance() public view returns(uint) {
    return token.balanceOf(address(this));
  }
  /// @return 余额中没有硬存款支付的部分
  function liquidBalance() public view returns(uint) {
    return balance().sub(totalHardDeposit);
  }

  /// @return 余额中可用于特定受益人的部分
  function liquidBalanceFor(address beneficiary) public view returns(uint) {
    return liquidBalance().add(hardDeposits[beneficiary].amount);
  }
  /**
  @dev内部函数负责检查issuerSignature,更新hardDeposit余额并进行转账。
   由CashCheque和CashChequeBeneficary调用
   @param受益人支票分配给的受益人。 受益人必须是外部拥有的帐户
   @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额
   @paramcumulativePayout分配给受益人的累计支票数量
   @param issuerSig如果发行人不是发件人,则发行人必须已在累计付款上明确授予受益人
  */
  function _cashChequeInternal(
    address beneficiary,
    address recipient,
    uint cumulativePayout,
    uint callerPayout,
    bytes memory issuerSig
  ) internal {
    /* 发行人必须通过作为主叫方或通过签名明确批准累计支付 */
    if (msg.sender != issuer) {
      require(issuer == recoverEIP712(chequeHash(address(this), beneficiary, cumulativePayout), issuerSig),
      "invalid issuer signature");
    }
    /* requestPayout是请求进行付款处理的金额 */
    uint requestPayout = cumulativePayout.sub(paidOut[beneficiary]);
    /* 计算实际支出 */
    uint totalPayout = Math.min(requestPayout, liquidBalanceFor(beneficiary));
    /* 计算硬存款使用量 */
    uint hardDepositUsage = Math.min(totalPayout, hardDeposits[beneficiary].amount);
    require(totalPayout >= callerPayout, "SimpleSwap: cannot pay caller");
    /* 如果有一些使用的硬存款,请更新hardDeposits */
    if (hardDepositUsage != 0) {
      hardDeposits[beneficiary].amount = hardDeposits[beneficiary].amount.sub(hardDepositUsage);

      totalHardDeposit = totalHardDeposit.sub(hardDepositUsage);
    }
    /* 增加存储的payedOut金额以避免双倍支付 */
    paidOut[beneficiary] = paidOut[beneficiary].add(totalPayout);
    totalPaidOut = totalPaidOut.add(totalPayout);

    /* 让全世界知道发行人对未兑现支票的承诺过高 */
    if (requestPayout != totalPayout) {
      bounced = true;
      emit ChequeBounced();
    }

    if (callerPayout != 0) {
    /* 如果指定,则转接到呼叫者 */
      require(token.transfer(msg.sender, callerPayout), "transfer failed");
      /* 进行实际付款 */
      require(token.transfer(recipient, totalPayout.sub(callerPayout)), "transfer failed");
    } else {
      /* 进行实际付款 */
      require(token.transfer(recipient, totalPayout), "transfer failed");
    }

    emit ChequeCashed(beneficiary, recipient, msg.sender, totalPayout, cumulativePayout, callerPayout);
  }
  /**
  @notice兑现非受益人的受益人支票,并通过发送方与发件人奖励这样做
  @dev受益人必须能够生成签名(作为外部拥有的帐户)才能使用此功能
  @param受益人支票分配给的受益人。受益人必须是外部拥有的帐户
  @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额
  @paramcumulativePayout分配给受益人的累计支票数量
  @param受益人Sig受益人必须已经明确批准才能由发件人兑现累计付款并发送给callerPayout
  @param issuerSig如果发行人不是发件人,则发行人必须已在累计付款上明确授予受益人
  @param callerPayout当受益人还没有以太币时,他可以在callerPayout的帮助下激励其他人兑现支票
  @param issuerSig如果发行人不是发件人,则发行人必须已在累计付款上明确授予受益人
  */
  function cashCheque(
    address beneficiary,
    address recipient,
    uint cumulativePayout,
    bytes memory beneficiarySig,
    uint256 callerPayout,
    bytes memory issuerSig
  ) public {
    require(
      beneficiary == recoverEIP712(
        cashOutHash(
          address(this),
          msg.sender,
          cumulativePayout,
          recipient,
          callerPayout
        ), beneficiarySig
      ), "invalid beneficiary signature");
    _cashChequeInternal(beneficiary, recipient, cumulativePayout, callerPayout, issuerSig);
  }

  /**
  @注意兑现支票作为受益人
   @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额
   @param的累计付款要求付款
   @param issuerSig发行者必须已对收款人的累计付款给予明确批准
  */
  function cashChequeBeneficiary(address recipient, uint cumulativePayout, bytes memory issuerSig) public {
    _cashChequeInternal(msg.sender, recipient, cumulativePayout, 0, issuerSig);
  }

  /**
  @注意准备减少硬沉积
   @dev减少hardDeposit必须分两步完成,以使受益人兑现任何未兑现的支票(并利用相关的硬存款)
   @param受益人,应减少其硬存款
   @param reduction存款应减少的金额
  */
  function prepareDecreaseHardDeposit(address beneficiary, uint decreaseAmount) public {
    require(msg.sender == issuer, "SimpleSwap: not issuer");
    HardDeposit storage hardDeposit = hardDeposits[beneficiary];
    /* 如果减少的幅度超过存款,则不能减少 */
    require(decreaseAmount <= hardDeposit.amount, "hard deposit not sufficient");
    // 如果从未设置hardDeposit.timeout,则应用defaultHardDepositTimeout
    uint timeout = hardDeposit.timeout == 0 ? defaultHardDepositTimeout : hardDeposit.timeout;
    hardDeposit.canBeDecreasedAt = block.timestamp + timeout;
    hardDeposit.decreaseAmount = decreaseAmount;
    emit HardDepositDecreasePrepared(beneficiary, decreaseAmount);
  }

  /**
  @notice自调用prepareDecreaseHard存款以来等待了必要的时间后,减少了硬存款
  @param受益人,应减少其硬存款
  */
  function decreaseHardDeposit(address beneficiary) public {
    HardDeposit storage hardDeposit = hardDeposits[beneficiary];
    require(block.timestamp >= hardDeposit.canBeDecreasedAt && hardDeposit.canBeDecreasedAt != 0, "deposit not yet timed out");
    /* this throws if decreaseAmount > amount */
    //TODO: 如果prepareDecreaseHardDeposit和reducedHardDeposit之间有现金兑现,那么reducingHardDeposit将抛出并且无法减少硬存款。
    hardDeposit.amount = hardDeposit.amount.sub(hardDeposit.decreaseAmount);
    /* 重置canBeDecreasedAt以避免两次减少 */
    hardDeposit.canBeDecreasedAt = 0;
    /* 保持totalDeposit同步 */
    totalHardDeposit = totalHardDeposit.sub(hardDeposit.decreaseAmount);
    emit HardDepositAmountChanged(beneficiary, hardDeposit.amount);
  }

  /**
  @注意增加硬保证金
   @param受益人,应减少其硬存款
   @param金额新的硬存款
  */
  function increaseHardDeposit(address beneficiary, uint amount) public {
    require(msg.sender == issuer, "SimpleSwap: not issuer");
    /* 确保保证金不超过全球余额 */
    require(totalHardDeposit.add(amount) <= balance(), "hard deposit exceeds balance");

    HardDeposit storage hardDeposit = hardDeposits[beneficiary];
    hardDeposit.amount = hardDeposit.amount.add(amount);
    // 我们没有明确设置hardDepositTimout,因为零意味着使用默认的HardDeposit超时
    totalHardDeposit = totalHardDeposit.add(amount);
    /* 禁用任何未决的减少 */
    hardDeposit.canBeDecreasedAt = 0;
    emit HardDepositAmountChanged(beneficiary, hardDeposit.amount);
  }

  /**
 @notice允许为每个受益人设置自定义的hardDepositDecreaseTimeout
   @dev,当必须保证偿付能力的时间长于defaultHardDepositDecreaseTimeout时,这是必需的
   @param收款人的硬存款减少的受益人必须更改超时
   @param hardDepositTimeout受益人新的hardDeposit.timeout
   @param受益人Sig受益人必须通过在新的reduceTimeout上签名来明确批准
  */
  function setCustomHardDepositTimeout(
    address beneficiary,
    uint hardDepositTimeout,
    bytes memory beneficiarySig
  ) public {
    require(msg.sender == issuer, "not issuer");
    require(
      beneficiary == recoverEIP712(customDecreaseTimeoutHash(address(this), beneficiary, hardDepositTimeout), beneficiarySig),
      "invalid beneficiary signature"
    );
    hardDeposits[beneficiary].timeout = hardDepositTimeout;
    emit HardDepositTimeoutChanged(beneficiary, hardDepositTimeout);
  }

  /// @notice提取以太币
   /// @param要提取的金额
   // solhint-disable-next-line no-simple-event-func-name
  function withdraw(uint amount) public {
    /* 只有发行人可以做到这一点 */
    require(msg.sender == issuer, "not issuer");
    /* 确保我们不从保证金中提取任何东西 */
    require(amount <= liquidBalance(), "liquidBalance not sufficient");
    require(token.transfer(issuer, amount), "transfer failed");
  }

  function chequeHash(address chequebook, address beneficiary, uint cumulativePayout)
  internal pure returns (bytes32) {
    return keccak256(abi.encode(
      CHEQUE_TYPEHASH,
      chequebook,
      beneficiary,
      cumulativePayout
    ));
  }  

  function cashOutHash(address chequebook, address sender, uint requestPayout, address recipient, uint callerPayout)
  internal pure returns (bytes32) {
    return keccak256(abi.encode(
      CASHOUT_TYPEHASH,
      chequebook,
      sender,
      requestPayout,
      recipient,
      callerPayout
    ));
  }

  function customDecreaseTimeoutHash(address chequebook, address beneficiary, uint decreaseTimeout)
  internal pure returns (bytes32) {
    return keccak256(abi.encode(
      CUSTOMDECREASETIMEOUT_TYPEHASH,
      chequebook,
      beneficiary,
      decreaseTimeout
    ));
  }
}

Cash Cheque Beneficiary

https://goerli.etherscan.io/tx/0x6e255bcd2eb2a84d2a80e25368413f805a036d3cea18d43c81e96f0158a3084c