Skip to content

Commit

Permalink
[Contract] Add function that open and close withdrawal of settlement
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelKim20 committed Oct 5, 2023
1 parent a4a87de commit 3b210bd
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 8 deletions.
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

0 comments on commit 3b210bd

Please sign in to comment.