mirror of
https://github.com/status-im/EIPs.git
synced 2025-02-22 11:48:19 +00:00
parent
4d9b090c14
commit
59516ea7ad
@ -237,7 +237,7 @@ This proposal is fully backward compatible. Functionalities of existing standard
|
||||
|
||||
## Implementation
|
||||
|
||||
Please visit [here](https://github.com/HAOYUatHZ/fair-atomic-swap/blob/master/src/atomicswap/eip2266/) to find our example implementation.
|
||||
Please visit [here](../assets/eip-2266/Example.sol) to find our example implementation.
|
||||
|
||||
## Copyright
|
||||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
|
454
assets/eip-2266/Example.sol
Normal file
454
assets/eip-2266/Example.sol
Normal file
@ -0,0 +1,454 @@
|
||||
// Copyright (c) 2019 Chris Haoyu LIN, Runchao HAN, Jiangshan YU
|
||||
// ERC2266 is compatible with ERC20 standard: https://theethereum.wiki/w/index.php/ERC20_Token_Standard
|
||||
// naming style follows the guide: https://solidity.readthedocs.io/en/v0.5.11/style-guide.html#naming-styles
|
||||
|
||||
pragma solidity ^0.5.11;
|
||||
|
||||
contract ERC20 {
|
||||
function totalSupply() public view returns (uint);
|
||||
function balanceOf(address tokenOwner) public view returns (uint balance);
|
||||
function allowance(address tokenOwner, address spender) public view returns (uint remaining);
|
||||
function transfer(address to, uint tokens) public returns (bool success);
|
||||
function approve(address spender, uint tokens) public returns (bool success);
|
||||
function transferFrom(address from, address to, uint tokens) public returns (bool success);
|
||||
event Transfer(address indexed from, address indexed to, uint tokens);
|
||||
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
|
||||
}
|
||||
|
||||
contract Example
|
||||
{
|
||||
enum AssetState { Empty, Filled, Redeemed, Refunded }
|
||||
|
||||
struct Swap {
|
||||
bytes32 secretHash;
|
||||
bytes32 secret;
|
||||
address payable initiator;
|
||||
address payable participant;
|
||||
address tokenA;
|
||||
address tokenB;
|
||||
}
|
||||
|
||||
struct InitiatorAsset {
|
||||
uint256 amount;
|
||||
uint256 refundTimestamp;
|
||||
AssetState state;
|
||||
}
|
||||
|
||||
struct ParticipantAsset {
|
||||
uint256 amount;
|
||||
uint256 refundTimestamp;
|
||||
AssetState state;
|
||||
}
|
||||
|
||||
struct Premium {
|
||||
uint256 amount;
|
||||
uint256 refundTimestamp;
|
||||
AssetState state;
|
||||
}
|
||||
|
||||
mapping(bytes32 => Swap) public swap;
|
||||
mapping(bytes32 => InitiatorAsset) public initiatorAsset;
|
||||
mapping(bytes32 => ParticipantAsset) public participantAsset;
|
||||
mapping(bytes32 => Premium) public premium;
|
||||
|
||||
event SetUp(
|
||||
bytes32 secretHash,
|
||||
address initiator,
|
||||
address participant,
|
||||
address tokenA,
|
||||
address tokenB,
|
||||
uint256 initiatorAssetAmount,
|
||||
uint256 participantAssetAmount,
|
||||
uint256 premiumAmount
|
||||
);
|
||||
|
||||
event Initiated(
|
||||
uint256 initiateTimestamp,
|
||||
bytes32 secretHash,
|
||||
address initiator,
|
||||
address participant,
|
||||
address initiatorAssetToken,
|
||||
uint256 initiatorAssetAmount,
|
||||
uint256 initiatorAssetRefundTimestamp
|
||||
);
|
||||
|
||||
event PremiumFilled(
|
||||
uint256 fillPremiumTimestamp,
|
||||
bytes32 secretHash,
|
||||
address initiator,
|
||||
address participant,
|
||||
address premiumToken,
|
||||
uint256 premiumAmount,
|
||||
uint256 premiumRefundTimestamp
|
||||
);
|
||||
|
||||
event Participated(
|
||||
uint256 participateTimestamp,
|
||||
bytes32 secretHash,
|
||||
address initiator,
|
||||
address participant,
|
||||
address participantAssetToken,
|
||||
uint256 participantAssetAmount,
|
||||
uint256 participantAssetRefundTimestamp
|
||||
);
|
||||
|
||||
event InitiatorAssetRedeemed(
|
||||
uint256 redeemTimestamp,
|
||||
bytes32 secretHash,
|
||||
bytes32 secret,
|
||||
address redeemer,
|
||||
address assetToken,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
event ParticipantAssetRedeemed(
|
||||
uint256 redeemTimestamp,
|
||||
bytes32 secretHash,
|
||||
bytes32 secret,
|
||||
address redeemer,
|
||||
address assetToken,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
event InitiatorAssetRefunded(
|
||||
uint256 refundTimestamp,
|
||||
bytes32 secretHash,
|
||||
address refunder,
|
||||
address assetToken,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
event ParticipantAssetRefunded(
|
||||
uint256 refundTimestamp,
|
||||
bytes32 secretHash,
|
||||
address refunder,
|
||||
address assetToken,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
event PremiumRedeemed(
|
||||
uint256 redeemTimestamp,
|
||||
bytes32 secretHash,
|
||||
address redeemer,
|
||||
address token,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
event PremiumRefunded(
|
||||
uint256 refundTimestamp,
|
||||
bytes32 secretHash,
|
||||
address refunder,
|
||||
address token,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
constructor() public {}
|
||||
|
||||
modifier isInitiatorAssetEmptyState(bytes32 secretHash) {
|
||||
require(initiatorAsset[secretHash].state == AssetState.Empty);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier isParticipantAssetEmptyState(bytes32 secretHash) {
|
||||
require(participantAsset[secretHash].state == AssetState.Empty);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier isPremiumEmptyState(bytes32 secretHash) {
|
||||
require(premium[secretHash].state == AssetState.Empty);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier canSetup(bytes32 secretHash) {
|
||||
require(initiatorAsset[secretHash].state == AssetState.Empty);
|
||||
require(participantAsset[secretHash].state == AssetState.Empty);
|
||||
require(premium[secretHash].state == AssetState.Empty);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier canInitiate(bytes32 secretHash) {
|
||||
require(swap[secretHash].initiator == msg.sender);
|
||||
require(initiatorAsset[secretHash].state == AssetState.Empty);
|
||||
require(ERC20(swap[secretHash].tokenA).balanceOf(msg.sender) >= initiatorAsset[secretHash].amount);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier canFillPremium(bytes32 secretHash) {
|
||||
require(swap[secretHash].initiator == msg.sender);
|
||||
require(premium[secretHash].state == AssetState.Empty);
|
||||
require(ERC20(swap[secretHash].tokenB).balanceOf(msg.sender) >= premium[secretHash].amount);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier canParticipate(bytes32 secretHash) {
|
||||
require(swap[secretHash].participant == msg.sender);
|
||||
require(participantAsset[secretHash].state == AssetState.Empty);
|
||||
require(premium[secretHash].state == AssetState.Filled);
|
||||
require(ERC20(swap[secretHash].tokenB).balanceOf(msg.sender) >= participantAsset[secretHash].amount);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier checkRefundTimestampOverflow(uint256 refundTime) {
|
||||
uint256 refundTimestamp = block.timestamp + refundTime;
|
||||
require(refundTimestamp > block.timestamp, "calc refundTimestamp overflow");
|
||||
require(refundTimestamp > refundTime, "calc refundTimestamp overflow");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier isAssetRedeemable(bytes32 secretHash, bytes32 secret) {
|
||||
if (swap[secretHash].initiator == msg.sender) {
|
||||
require(initiatorAsset[secretHash].state == AssetState.Filled);
|
||||
require(block.timestamp <= initiatorAsset[secretHash].refundTimestamp);
|
||||
} else {
|
||||
require(swap[secretHash].participant == msg.sender);
|
||||
require(participantAsset[secretHash].state == AssetState.Filled);
|
||||
require(block.timestamp <= participantAsset[secretHash].refundTimestamp);
|
||||
}
|
||||
require(sha256(abi.encodePacked(secret)) == secretHash);
|
||||
_;
|
||||
}
|
||||
|
||||
modifier isAssetRefundable(bytes32 secretHash) {
|
||||
if (swap[secretHash].initiator == msg.sender) {
|
||||
require(initiatorAsset[secretHash].state == AssetState.Filled);
|
||||
require(block.timestamp > initiatorAsset[secretHash].refundTimestamp);
|
||||
} else {
|
||||
require(swap[secretHash].participant == msg.sender);
|
||||
require(participantAsset[secretHash].state == AssetState.Filled);
|
||||
require(block.timestamp > participantAsset[secretHash].refundTimestamp);
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
modifier isPremiumFilledState(bytes32 secretHash) {
|
||||
require(premium[secretHash].state == AssetState.Filled);
|
||||
_;
|
||||
}
|
||||
|
||||
// Premium is redeemable for Bob if Bob participates and redeem
|
||||
// before premium's timelock expires
|
||||
modifier isPremiumRedeemable(bytes32 secretHash) {
|
||||
// the participant invokes this method to redeem the premium
|
||||
require(swap[secretHash].participant == msg.sender);
|
||||
// the premium should be deposited
|
||||
require(premium[secretHash].state == AssetState.Filled);
|
||||
// if Bob participates, which means participantAsset will be: Filled -> (Redeemed/Refunded)
|
||||
require(participantAsset[secretHash].state == AssetState.Refunded || participantAsset[secretHash].state == AssetState.Redeemed);
|
||||
// the premium timelock should not be expired
|
||||
require(block.timestamp <= premium[secretHash].refundTimestamp);
|
||||
_;
|
||||
}
|
||||
|
||||
// Premium is refundable for Alice only when Alice initiates
|
||||
// but Bob does not participate after premium's timelock expires
|
||||
modifier isPremiumRefundable(bytes32 secretHash) {
|
||||
// the initiator invokes this method to refund the premium
|
||||
require(swap[secretHash].initiator == msg.sender);
|
||||
// the premium should be deposited
|
||||
require(premium[secretHash].state == AssetState.Filled);
|
||||
// asset2 should be empty
|
||||
// which means Bob does not participate
|
||||
require(participantAsset[secretHash].state == AssetState.Empty);
|
||||
require(block.timestamp > premium[secretHash].refundTimestamp);
|
||||
_;
|
||||
}
|
||||
|
||||
function setup(bytes32 secretHash,
|
||||
address payable initiator,
|
||||
address tokenA,
|
||||
address tokenB,
|
||||
uint256 initiatorAssetAmount,
|
||||
address payable participant,
|
||||
uint256 participantAssetAmount,
|
||||
uint256 premiumAmount)
|
||||
public
|
||||
payable
|
||||
canSetup(secretHash)
|
||||
{
|
||||
swap[secretHash].secretHash = secretHash;
|
||||
swap[secretHash].initiator = initiator;
|
||||
swap[secretHash].participant = participant;
|
||||
swap[secretHash].tokenA = tokenA;
|
||||
swap[secretHash].tokenB = tokenB;
|
||||
initiatorAsset[secretHash].amount = initiatorAssetAmount;
|
||||
initiatorAsset[secretHash].state = AssetState.Empty;
|
||||
participantAsset[secretHash].amount = participantAssetAmount;
|
||||
participantAsset[secretHash].state = AssetState.Empty;
|
||||
premium[secretHash].amount = premiumAmount;
|
||||
premium[secretHash].state = AssetState.Empty;
|
||||
|
||||
emit SetUp(
|
||||
secretHash,
|
||||
initiator,
|
||||
participant,
|
||||
tokenA,
|
||||
tokenB,
|
||||
initiatorAssetAmount,
|
||||
participantAssetAmount,
|
||||
premiumAmount
|
||||
);
|
||||
}
|
||||
|
||||
// Initiator needs to pay for the initiatorAsset(tokenA) with initiatorAssetAmount
|
||||
// Initiator will also need to call tokenA.approve(this_contract_address, initiatorAssetAmount) in advance
|
||||
function initiate(bytes32 secretHash, uint256 assetRefundTime)
|
||||
public
|
||||
payable
|
||||
canInitiate(secretHash)
|
||||
checkRefundTimestampOverflow(assetRefundTime)
|
||||
{
|
||||
ERC20(swap[secretHash].tokenA).transferFrom(swap[secretHash].initiator, address(this), initiatorAsset[secretHash].amount);
|
||||
initiatorAsset[secretHash].state = AssetState.Filled;
|
||||
initiatorAsset[secretHash].refundTimestamp = block.timestamp + assetRefundTime;
|
||||
|
||||
emit Initiated(
|
||||
block.timestamp,
|
||||
secretHash,
|
||||
msg.sender,
|
||||
swap[secretHash].participant,
|
||||
swap[secretHash].tokenA,
|
||||
initiatorAsset[secretHash].amount,
|
||||
initiatorAsset[secretHash].refundTimestamp
|
||||
);
|
||||
}
|
||||
|
||||
// Initiator needs to pay for the premium(tokenB) with premiumAmount
|
||||
// Initiator will also need to call tokenB.approve(this_contract_address, premiumAmount) in advance
|
||||
function fillPremium(bytes32 secretHash, uint256 premiumRefundTime)
|
||||
public
|
||||
payable
|
||||
canFillPremium(secretHash)
|
||||
checkRefundTimestampOverflow(premiumRefundTime)
|
||||
{
|
||||
ERC20(swap[secretHash].tokenB).transferFrom(swap[secretHash].initiator, address(this), premium[secretHash].amount);
|
||||
premium[secretHash].state = AssetState.Filled;
|
||||
premium[secretHash].refundTimestamp = block.timestamp + premiumRefundTime;
|
||||
|
||||
emit PremiumFilled(
|
||||
block.timestamp,
|
||||
secretHash,
|
||||
msg.sender,
|
||||
swap[secretHash].participant,
|
||||
swap[secretHash].tokenB,
|
||||
premium[secretHash].amount,
|
||||
premium[secretHash].refundTimestamp
|
||||
);
|
||||
}
|
||||
|
||||
// Participant needs to pay for the participantAsset(tokenB) with participantAssetAmount
|
||||
// Participant will also need to call tokenB.approve(this_contract_address, participantAssetAmount) in advance
|
||||
function participate(bytes32 secretHash, uint256 assetRefundTime)
|
||||
public
|
||||
payable
|
||||
canParticipate(secretHash)
|
||||
checkRefundTimestampOverflow(assetRefundTime)
|
||||
{
|
||||
ERC20(swap[secretHash].tokenB).transferFrom(swap[secretHash].participant, address(this), participantAsset[secretHash].amount);
|
||||
participantAsset[secretHash].state = AssetState.Filled;
|
||||
participantAsset[secretHash].refundTimestamp = block.timestamp + assetRefundTime;
|
||||
|
||||
emit Participated(
|
||||
block.timestamp,
|
||||
secretHash,
|
||||
swap[secretHash].initiator,
|
||||
msg.sender,
|
||||
swap[secretHash].tokenB,
|
||||
participantAsset[secretHash].amount,
|
||||
participantAsset[secretHash].refundTimestamp
|
||||
);
|
||||
}
|
||||
|
||||
function redeemAsset(bytes32 secret, bytes32 secretHash)
|
||||
public
|
||||
isAssetRedeemable(secretHash, secret)
|
||||
{
|
||||
swap[secretHash].secret = secret;
|
||||
if (swap[secretHash].initiator == msg.sender) {
|
||||
ERC20(swap[secretHash].tokenB).transfer(msg.sender, participantAsset[secretHash].amount);
|
||||
participantAsset[secretHash].state = AssetState.Redeemed;
|
||||
|
||||
emit ParticipantAssetRedeemed(
|
||||
block.timestamp,
|
||||
secretHash,
|
||||
secret,
|
||||
msg.sender,
|
||||
swap[secretHash].tokenB,
|
||||
participantAsset[secretHash].amount
|
||||
);
|
||||
} else {
|
||||
ERC20(swap[secretHash].tokenA).transfer(msg.sender, initiatorAsset[secretHash].amount);
|
||||
initiatorAsset[secretHash].state = AssetState.Redeemed;
|
||||
|
||||
emit InitiatorAssetRedeemed(
|
||||
block.timestamp,
|
||||
secretHash,
|
||||
secret,
|
||||
msg.sender,
|
||||
swap[secretHash].tokenA,
|
||||
initiatorAsset[secretHash].amount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function refundAsset(bytes32 secretHash)
|
||||
public
|
||||
isPremiumFilledState(secretHash)
|
||||
isAssetRefundable(secretHash)
|
||||
{
|
||||
if (swap[secretHash].initiator == msg.sender) {
|
||||
ERC20(swap[secretHash].tokenA).transfer(msg.sender, initiatorAsset[secretHash].amount);
|
||||
initiatorAsset[secretHash].state = AssetState.Refunded;
|
||||
|
||||
emit InitiatorAssetRefunded(
|
||||
block.timestamp,
|
||||
secretHash,
|
||||
msg.sender,
|
||||
swap[secretHash].tokenA,
|
||||
initiatorAsset[secretHash].amount
|
||||
);
|
||||
} else {
|
||||
ERC20(swap[secretHash].tokenB).transfer(msg.sender, participantAsset[secretHash].amount);
|
||||
participantAsset[secretHash].state = AssetState.Refunded;
|
||||
|
||||
emit ParticipantAssetRefunded(
|
||||
block.timestamp,
|
||||
secretHash,
|
||||
msg.sender,
|
||||
swap[secretHash].tokenB,
|
||||
participantAsset[secretHash].amount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function redeemPremium(bytes32 secretHash)
|
||||
public
|
||||
isPremiumRedeemable(secretHash)
|
||||
{
|
||||
ERC20(swap[secretHash].tokenB).transfer(msg.sender, premium[secretHash].amount);
|
||||
premium[secretHash].state = AssetState.Redeemed;
|
||||
|
||||
emit PremiumRefunded(
|
||||
block.timestamp,
|
||||
swap[secretHash].secretHash,
|
||||
msg.sender,
|
||||
swap[secretHash].tokenB,
|
||||
premium[secretHash].amount
|
||||
);
|
||||
}
|
||||
|
||||
function refundPremium(bytes32 secretHash)
|
||||
public
|
||||
isPremiumRefundable(secretHash)
|
||||
{
|
||||
ERC20(swap[secretHash].tokenB).transfer(msg.sender, premium[secretHash].amount);
|
||||
premium[secretHash].state = AssetState.Refunded;
|
||||
|
||||
emit PremiumRefunded(
|
||||
block.timestamp,
|
||||
swap[secretHash].secretHash,
|
||||
msg.sender,
|
||||
swap[secretHash].tokenB,
|
||||
premium[secretHash].amount
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user