源代码: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合约
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";
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;
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)"
);
function domain() internal pure returns (EIP712Domain memory) {
uint256 chainId;
assembly {
chainId := chainid()
}
return EIP712Domain({
name: "Chequebook",
version: "1.0",
chainId: 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
));
}
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;
mapping (address => HardDeposit) public hardDeposits;
uint public totalHardDeposit;
address public issuer;
bool public bounced;
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;
}
function balance() public view returns(uint) {
return token.balanceOf(address(this));
}
function liquidBalance() public view returns(uint) {
return balance().sub(totalHardDeposit);
}
function liquidBalanceFor(address beneficiary) public view returns(uint) {
return liquidBalance().add(hardDeposits[beneficiary].amount);
}
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");
}
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");
if (hardDepositUsage != 0) {
hardDeposits[beneficiary].amount = hardDeposits[beneficiary].amount.sub(hardDepositUsage);
totalHardDeposit = totalHardDeposit.sub(hardDepositUsage);
}
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);
}
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);
}
function cashChequeBeneficiary(address recipient, uint cumulativePayout, bytes memory issuerSig) public {
_cashChequeInternal(msg.sender, recipient, cumulativePayout, 0, issuerSig);
}
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");
uint timeout = hardDeposit.timeout == 0 ? defaultHardDepositTimeout : hardDeposit.timeout;
hardDeposit.canBeDecreasedAt = block.timestamp + timeout;
hardDeposit.decreaseAmount = decreaseAmount;
emit HardDepositDecreasePrepared(beneficiary, decreaseAmount);
}
function decreaseHardDeposit(address beneficiary) public {
HardDeposit storage hardDeposit = hardDeposits[beneficiary];
require(block.timestamp >= hardDeposit.canBeDecreasedAt && hardDeposit.canBeDecreasedAt != 0, "deposit not yet timed out");
hardDeposit.amount = hardDeposit.amount.sub(hardDeposit.decreaseAmount);
hardDeposit.canBeDecreasedAt = 0;
totalHardDeposit = totalHardDeposit.sub(hardDeposit.decreaseAmount);
emit HardDepositAmountChanged(beneficiary, hardDeposit.amount);
}
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);
totalHardDeposit = totalHardDeposit.add(amount);
hardDeposit.canBeDecreasedAt = 0;
emit HardDepositAmountChanged(beneficiary, hardDeposit.amount);
}
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);
}
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