Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Contract] Add function that open and close withdrawal of settlement #139

Merged
merged 1 commit into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/contracts/contracts/Ledger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ contract Ledger {
bytes32 dataHash = keccak256(abi.encode(_phone, _account, nonce[_account]));
require(ECDSA.recover(ECDSA.toEthSignedMessageHash(dataHash), _signature) == _account, "Invalid signature");
address userAddress = linkCollection.toAddress(_phone);
require(userAddress != address(0x00), "Unregistered email-address");
require(userAddress != address(0x00), "Unregistered phone-address");
require(userAddress == _account, "Invalid address");
require(unPayablePointBalances[_phone] > 0, "Insufficient balance");

Expand All @@ -327,7 +327,7 @@ contract Ledger {
);

uint256 purchaseAmount = convertCurrencyToPoint(data.amount, data.currency);
uint256 feeAmount = convertCurrencyToPoint(data.amount * fee / 100, data.currency);
uint256 feeAmount = convertCurrencyToPoint((data.amount * fee) / 100, data.currency);
uint256 feeToken = convertPointToToken(feeAmount);

require(pointBalances[data.account] >= purchaseAmount + feeAmount, "Insufficient balance");
Expand Down Expand Up @@ -388,7 +388,7 @@ contract Ledger {
);

uint256 purchaseAmount = convertCurrencyToPoint(data.amount, data.currency);
uint256 feeAmount = convertCurrencyToPoint(data.amount * fee / 100, data.currency);
uint256 feeAmount = convertCurrencyToPoint((data.amount * fee) / 100, data.currency);
uint256 amountToken = convertPointToToken(purchaseAmount);
uint256 feeToken = convertPointToToken(feeAmount);

Expand Down
77 changes: 75 additions & 2 deletions packages/contracts/contracts/ShopCollection.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@

pragma solidity ^0.8.0;

import "del-osx-artifacts/contracts/PhoneLinkCollection.sol";
import "./ValidatorCollection.sol";

/// @notice 상점컬랙션
contract ShopCollection {
/// @notice Hash value of a blank string
bytes32 public constant NULL = 0x32105b1d0b88ada155176b58ee08b45c31e4f2f7337475831982c313533b880c;

/// @notice 검증자의 상태코드
enum WithdrawStatus {
CLOSE,
OPEN
}

struct WithdrawData {
uint256 amount;
address account;
WithdrawStatus status;
}

/// @notice 검증자의 상태코드
enum ShopStatus {
INVALID,
Expand All @@ -23,7 +37,9 @@ contract ShopCollection {
uint256 providedPoint; // 제공된 포인트 총량
uint256 usedPoint; // 사용된 포인트 총량
uint256 settledPoint; // 정산된 포인트 총량
uint256 withdrawnPoint; // 정산된 포인트 총량
ShopStatus status;
WithdrawData withdrawData;
}

mapping(string => ShopData) private shops;
Expand All @@ -32,7 +48,10 @@ contract ShopCollection {
string[] private items;

address public validatorAddress;
address public linkCollectionAddress;

ValidatorCollection private validatorCollection;
PhoneLinkCollection private linkCollection;

/// @notice 상점이 추가될 때 발생되는 이벤트
event AddedShop(string shopId, uint256 provideWaitTime, uint256 providePercent, bytes32 phone);
Expand All @@ -45,15 +64,21 @@ contract ShopCollection {
/// @notice 정산된 마일리가 증가할 때 발생되는 이벤트
event IncreasedSettledPoint(string shopId, uint256 increase, uint256 total, string purchaseId);

event OpenedWithdrawal(string shopId, uint256 amount, address account);
event ClosedWithdrawal(string shopId, uint256 amount, address account);

address public ledgerAddress;
address public deployer;

/// @notice 생성자
/// @param _validatorAddress 검증자컬랙션의 주소
constructor(address _validatorAddress) {
constructor(address _validatorAddress, address _linkCollectionAddress) {
validatorAddress = _validatorAddress;
linkCollectionAddress = _linkCollectionAddress;

validatorCollection = ValidatorCollection(_validatorAddress);
linkCollection = PhoneLinkCollection(_linkCollectionAddress);

ledgerAddress = address(0x00);
deployer = msg.sender;
}
Expand Down Expand Up @@ -102,7 +127,9 @@ contract ShopCollection {
providedPoint: 0,
usedPoint: 0,
settledPoint: 0,
status: ShopStatus.ACTIVE
withdrawnPoint: 0,
status: ShopStatus.ACTIVE,
withdrawData: WithdrawData({ amount: 0, account: address(0x0), status: WithdrawStatus.CLOSE })
});
items.push(_shopId);
shops[_shopId] = data;
Expand Down Expand Up @@ -182,4 +209,50 @@ contract ShopCollection {
function shopsLength() public view returns (uint256) {
return items.length;
}

/// @notice 인출가능한 정산금액을 리턴한다.
/// @param _shopId 상점의 아이디
function withdrawableOf(string memory _shopId) public view returns (uint256) {
ShopData memory shop = shops[_shopId];
return shop.settledPoint - shop.withdrawnPoint;
}

/// @notice 정산금의 인출을 요청한다. 상점주인만이 실행가능
/// @param _shopId 상점아이디
/// @param _amount 인출금
function openWithdrawal(string calldata _shopId, uint256 _amount) public {
ShopData memory shop = shops[_shopId];

bytes32 phone = linkCollection.toPhone(msg.sender);
require(phone != bytes32(0x00), "Unregistered phone-address");
require(shop.phone == phone, "Invalid address");

require(_amount <= shop.settledPoint - shop.withdrawnPoint, "Insufficient withdrawal amount");
require(shop.withdrawData.status == WithdrawStatus.CLOSE, "Already opened");

shops[_shopId].withdrawData.account = msg.sender;
shops[_shopId].withdrawData.amount = _amount;
shops[_shopId].withdrawData.status = WithdrawStatus.OPEN;

emit OpenedWithdrawal(_shopId, _amount, msg.sender);
}

/// @notice 정산금의 인출을 마감한다. 상점주인만이 실행가능
/// @param _shopId 상점아이디
/// @param _amount 인출금
function closeWithdrawal(string calldata _shopId, uint256 _amount) public {
ShopData memory shop = shops[_shopId];

bytes32 phone = linkCollection.toPhone(msg.sender);
require(phone != bytes32(0x00), "Unregistered phone-address");
require(shop.phone == phone, "Invalid address");

require(shop.withdrawData.status == WithdrawStatus.OPEN, "Not opened");
require(shop.withdrawData.amount == _amount, "Inconsistent amount");

shops[_shopId].withdrawData.status = WithdrawStatus.CLOSE;
shops[_shopId].withdrawnPoint += shop.withdrawData.amount;

emit ClosedWithdrawal(_shopId, _amount, msg.sender);
}
}
13 changes: 11 additions & 2 deletions packages/contracts/test/02-ShopCollection.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Amount } from "../src/utils/Amount";
import { ContractUtils } from "../src/utils/ContractUtils";
import { ShopCollection, Token, ValidatorCollection } from "../typechain-types";
import { PhoneLinkCollection, ShopCollection, Token, ValidatorCollection } from "../typechain-types";

import "@nomiclabs/hardhat-ethers";
import "@nomiclabs/hardhat-waffle";
Expand All @@ -19,14 +19,23 @@ describe("Test for ShopCollection", () => {
provider.getWallets();

const validators = [validator1, validator2, validator3];
const linkValidators = [validator1];
const shopWallets = [shop1, shop2, shop3, shop4, shop5];
let linkCollectionContract: PhoneLinkCollection;
let validatorContract: ValidatorCollection;
let tokenContract: Token;
let shopCollection: ShopCollection;

const amount = Amount.make(20_000, 18);

before(async () => {
const linkCollectionFactory = await hre.ethers.getContractFactory("PhoneLinkCollection");
linkCollectionContract = (await linkCollectionFactory
.connect(deployer)
.deploy(linkValidators.map((m) => m.address))) as PhoneLinkCollection;
await linkCollectionContract.deployed();
await linkCollectionContract.deployTransaction.wait();

const tokenFactory = await hre.ethers.getContractFactory("Token");
tokenContract = (await tokenFactory.connect(deployer).deploy(deployer.address, "Sample", "SAM")) as Token;
await tokenContract.deployed();
Expand Down Expand Up @@ -57,7 +66,7 @@ describe("Test for ShopCollection", () => {
const shopCollectionFactory = await hre.ethers.getContractFactory("ShopCollection");
shopCollection = (await shopCollectionFactory
.connect(deployer)
.deploy(validatorContract.address)) as ShopCollection;
.deploy(validatorContract.address, linkCollectionContract.address)) as ShopCollection;
await shopCollection.deployed();
await shopCollection.deployTransaction.wait();
});
Expand Down
60 changes: 59 additions & 1 deletion packages/contracts/test/03-Ledger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ describe("Test for Ledger", () => {
const shopCollectionFactory = await hre.ethers.getContractFactory("ShopCollection");
shopCollection = (await shopCollectionFactory
.connect(deployer)
.deploy(validatorContract.address)) as ShopCollection;
.deploy(validatorContract.address, linkCollectionContract.address)) as ShopCollection;
await shopCollection.deployed();
await shopCollection.deployTransaction.wait();
};
Expand Down Expand Up @@ -1697,6 +1697,64 @@ describe("Test for Ledger", () => {
);
});
});

context("Withdrawal of settlement", () => {
const shopIndex = 2;
const shop = shopData[shopIndex];
const amount2 = Amount.make(400, 18).value;
it("Check Settlement", async () => {
const withdrawalAmount = await shopCollection.withdrawableOf(shop.shopId);
expect(withdrawalAmount).to.equal(amount2);
});

it("Link phone-wallet of the shop", async () => {
const nonce = await linkCollectionContract.nonceOf(shopWallets[shopIndex].address);
const phoneHash = ContractUtils.getPhoneHash(shop.phone);
const signature = await ContractUtils.signRequestHash(shopWallets[shopIndex], phoneHash, nonce);
requestId = ContractUtils.getRequestId(phoneHash, shopWallets[shopIndex].address, nonce);
await expect(
linkCollectionContract
.connect(relay)
.addRequest(requestId, phoneHash, shopWallets[shopIndex].address, signature)
)
.to.emit(linkCollectionContract, "AddedRequestItem")
.withArgs(requestId, phoneHash, shopWallets[shopIndex].address);
await linkCollectionContract.connect(validator1).voteRequest(requestId);
await linkCollectionContract.connect(validator1).countVote(requestId);
});

it("Open Withdrawal", async () => {
await expect(
shopCollection
.connect(shopWallets[shopIndex].connect(hre.waffle.provider))
.openWithdrawal(shop.shopId, amount2)
)
.to.emit(shopCollection, "OpenedWithdrawal")
.withNamedArgs({
shopId: shop.shopId,
amount: amount2,
account: shopWallets[shopIndex].address,
});
const withdrawalAmount = await shopCollection.withdrawableOf(shop.shopId);
expect(withdrawalAmount).to.equal(amount2);
});

it("Close Withdrawal", async () => {
await expect(
shopCollection
.connect(shopWallets[shopIndex].connect(hre.waffle.provider))
.closeWithdrawal(shop.shopId, amount2)
)
.to.emit(shopCollection, "ClosedWithdrawal")
.withNamedArgs({
shopId: shop.shopId,
amount: amount2,
account: shopWallets[shopIndex].address,
});
const withdrawalAmount = await shopCollection.withdrawableOf(shop.shopId);
expect(withdrawalAmount).to.equal(0);
});
});
});

context("Multi Currency", () => {
Expand Down