From 93b4211eb3c5f8b673b61c75c67e5985dc0adc7b Mon Sep 17 00:00:00 2001 From: StartfundInc <111301211+StartfundInc@users.noreply.github.com> Date: Tue, 25 Oct 2022 11:13:31 -0500 Subject: [PATCH] Update eip-5528.md --- EIPS/eip-5528.md | 133 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 15 deletions(-) diff --git a/EIPS/eip-5528.md b/EIPS/eip-5528.md index 6c12af09a90ca..714ae6d5635bb 100644 --- a/EIPS/eip-5528.md +++ b/EIPS/eip-5528.md @@ -32,14 +32,13 @@ In traditional finance, trusted escrow services solve this problem. In the world There are two types of contract for the escrow process: -- *Payable Contract*: The sellers and buyers use this token to fund the *Escrow Contract*. -- *Escrow Contract*: Defines the escrow policies and holds *Payable Contract*'s token for a certain period. - -This standard proposes interfaces on top of the [EIP-20](./eip-20.md) standard. +- *Payable Contract*: The sellers and buyers use this token to fund the *Escrow Contract*. This contract MUST override [EIP-20](./eip-20.md) interfaces. +- *Escrow Contract*: Defines the escrow policies and holds *Payable Contract*'s token for a certain period. This contract does not requires override [EIP-20](./eip-20.md) interfaces. ### Methods -#### constructor +#### `constructor` +The *Escrow Contract* demonstrates details of escrow policies as none-mutable matter in constructor implementation. The *Escrow Contract* MUST define the following policies: @@ -67,8 +66,7 @@ In the case of *Escrow Contract*: In the case of *Payable Contract*: - The address `_to` MUST be the *Escrow Contract* address. - - MUST call EIP-20's `transfer` function. - - Before calling `transfer` function, MUST call the same function of the *Escrow Contract* interface. The parameter `_to` MUST be `msg.sender` to recognize the user address in the *Escrow Contract*. + - MUST call the same function of the *Escrow Contract* interface. The parameter `_to` MUST be `msg.sender` to recognize the user address in the *Escrow Contract*. ```solidity function escrowFund(address _to, uint256 _value) public returns (bool) @@ -87,8 +85,7 @@ In the case of *Escrow Contract*: In the case of *Payable Contract*: - The address `_from` MUST be the *Escrow Contract* address. - - MUST call EIP-20's `_transfer` likely function. - - Before calling `_transfer` function, MUST call the same function of the *Escrow Contract* interface. The parameter `_from` MUST be `msg.sender` to recognize the user address in the *Escrow Contract*. + - MUST call the same function of the *Escrow Contract* interface. The parameter `_from` MUST be `msg.sender` to recognize the user address in the *Escrow Contract*. ```solidity function escrowRefund(address _from, uint256 _value) public returns (bool) @@ -110,36 +107,142 @@ function escrowWithdraw() public returns (bool) ### Example of interface +This example demonstrates simple exchange of one seller and one buyer in one-to-one exchange rates. ```solidity pragma solidity ^0.4.20; -interface IERC5528 is ERC20 { +interface IERC5528 { function escrowFund(address _to, uint256 _value) public returns (bool); - function escrowRefund(address to, uint256 amount) public returns (bool); + function escrowRefund(address _from, uint256 _value) public returns (bool); function escrowWithdraw() public returns (bool); } +contract PayableContract is IERC5528, IERC20 { + /* + General ERC20 implementations + */ + + function _transfer(address from, address to, uint256 amount) internal { + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + _balances[from] = fromBalance - amount; + _balances[to] += amount; + } + + function transfer(address to, uint256 amount) public returns (bool) { + address owner = msg.sender; + _transfer(owner, to, amount); + return true; + } + + function escrowFund(address _to, uint256 _value) public returns (bool){ + bool res = IERC5528(to).escrowFund(msg.sender, amount); + require(res, "Fund Failed"); + _transfer(msg.sender, to, amount); + return true; + } + + function escrowRefund(address _from, uint256 _value) public returns (bool){ + bool res = IERC5528(_from).escrowRefund(msg.sender, _value); + require(res, "Refund Failed"); + _transfer(_from, msg.sender, _value); + return true; + } +} + +contract EscrowContract is IERC5528 { + + enum State { Inited, Running, Success, Closed } + struct BalanceData { + address addr; + uint256 amount; + } + + address _addrSeller; + address _addrBuyer; + BalanceData _fundSeller; + BalanceData _fundBuyer; + EscrowStatus _status; + + constructor(address sellerContract, address buyerContract){ + _addrSeller = sellerContract; + _addrBuyer = buyerContract; + _status = State.Inited; + } + + function escrowFund(address _to, uint256 _value) public returns (bool){ + if(msg.sender == _addrSeller){ + require(_status.state == State.Running, "must be running state"); + _fundSeller.addr = _to; + _fundSeller.amount = _value; + _status = State.Success; + }else if(msg.sender == _addrBuyer){ + require(_status.state == State.Inited, "must be init state"); + _fundBuyer.addr = _to; + _fundBuyer.amount = _value; + _status = State.Running; + }else{ + require(false, "Invalid to address"); + } + return true; + } + + function escrowRefund(address _from, uint256 amount) public returns (bool){ + require(_status.state == State.Running, "refund is only available on running state"); + require(msg.sender == _addrBuyer, "invalid caller for refund"); + require(_fundBuyer.addr == _from, "only buyer can refund"); + require(_fundBuyer.amount >= amount, "buyer fund is not enough to refund"); + _fundBuyer.amount = _fundBuyer.amount - amount + return true; + } + + function escrowWithdraw() public returns (bool){ + require(_status.state == State.Success, "withdraw is only available on success state"); + uint256 common = MIN(_fundBuyer.amount, _fundSeller.amount); + + if(common > 0){ + _fundBuyer.amount = _fundBuyer.amount - common; + _fundSeller.amount = _fundSeller.amount - common; + + // Exchange + IERC5528(_addrSeller).transfer(_fundBuyer.addr, common); + IERC5528(_addrBuyer).transfer(_fundSeller.addr, common); + + // send back the remaining balances + if(_fundBuyer.amount > 0){ + IERC5528(_addrBuyer).transfer(_fundBuyer.addr, _fundBuyer.amount); + } + if(_fundSeller.amount > 0){ + IERC5528(_addrSeller).transfer(_fundSeller.addr, _fundSeller.amount); + } + } + + _status = State.Closed; + } + +} + ``` ## Rationale -The interfaces described in this EIP have been chosen to cover the refundable issue in the escrow operation. +The interfaces cover the escrow operation's refundable issue. The suggested 3 functions (`escrowFund`, `escrowRefund` and `escrowWithdraw`) are based on `transfer` function in EIP-20. `escrowFund` send tokens to the *Escrow Contract*. The *Escrow Contract* can hold the contract in the escrow process or reject tokens if the policy does not meet. -`escrowRefund` can be invoked in the middle of the escrow process or when the escrow process is failed. +`escrowRefund` can be invoked in the middle of the escrow process or when the escrow process fails. -`escrowWithdraw` allows users (sellers and buyers) to transfer tokens from the escrow account. When the escrow process is completed, the seller can get the buyer's token, and the buyers can get the seller's token. +`escrowWithdraw` allows users (sellers and buyers) to transfer tokens from the escrow account. When the escrow process completes, the seller can get the buyer's token, and the buyers can get the seller's token. ## Backwards Compatibility -This EIP is fully backward compatible with the [EIP-20](./eip-20.md) specification. +The *Payable Contract* which implements this EIP is fully backward compatible with the [EIP-20](./eip-20.md) specification. ## Test Cases