Skip to content

Commit

Permalink
Implement Settlement Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelKim20 committed Oct 28, 2024
1 parent 0008344 commit 724d7fe
Show file tree
Hide file tree
Showing 8 changed files with 1,730 additions and 7 deletions.
20 changes: 20 additions & 0 deletions packages/contracts/contracts/interfaces/IShop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface IShop {
ACTIVE,
INACTIVE
}

struct ShopData {
bytes32 shopId; // 상점 아이디
string name; // 상점 이름
Expand All @@ -16,12 +17,29 @@ interface IShop {
address delegator; // 위임자의 지갑주소
uint256 providedAmount; // 제공된 결제통화의 총량
uint256 usedAmount; // 사용된 결제통화의 총량
uint256 collectedAmount; // 정산관리자에 의해 수집된 결제통화의 총량
uint256 refundedAmount; // 정산된 결제통화의 총량
ShopStatus status;
uint256 itemIndex;
uint256 accountIndex;
}

enum SettlementClientStates {
INVALID,
ACTIVE
}

struct SettlementClientData {
uint256 index;
SettlementClientStates states;
}

struct ShopSettlementData {
bytes32 manager;
bytes32[] clients;
mapping(bytes32 => SettlementClientData) clientValues;
}

function setLedger(address _contractAddress) external;

function isAvailableId(bytes32 _shopId) external view returns (bool);
Expand All @@ -41,4 +59,6 @@ interface IShop {
function refundableOf(bytes32 _shopId) external view returns (uint256 refundableAmount, uint256 refundableToken);

function nonceOf(address _account) external view returns (uint256);

function settlementManagerOf(bytes32 _shopId) external view returns (bytes32);
}
228 changes: 225 additions & 3 deletions packages/contracts/contracts/shop/Shop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ contract Shop is ShopStorage, Initializable, OwnableUpgradeable, UUPSUpgradeable
uint256 balanceToken
);

event SetSettlementManager(bytes32 shopId, bytes32 managerShopId);
event RemovedSettlementManager(bytes32 shopId, bytes32 managerShopId);
event CollectedSettlementAmount(
bytes32 clientId,
address clientAccount,
string clientCurrency,
uint256 clientAmount,
uint256 clientTotal,
bytes32 managerId,
address managerAccount,
string managerCurrency,
uint256 managerAmount,
uint256 managerTotal
);

/// @notice 생성자
function initialize(
address _currencyRate,
Expand Down Expand Up @@ -124,6 +139,7 @@ contract Shop is ShopStorage, Initializable, OwnableUpgradeable, UUPSUpgradeable
delegator: address(0x0),
providedAmount: 0,
usedAmount: 0,
collectedAmount: 0,
refundedAmount: 0,
status: ShopStatus.ACTIVE,
itemIndex: items.length,
Expand All @@ -133,6 +149,9 @@ contract Shop is ShopStorage, Initializable, OwnableUpgradeable, UUPSUpgradeable
shops[_shopId] = data;
shopIdByAddress[_account].push(_shopId);

ShopSettlementData storage settlementData = settlements[_shopId];
settlementData.manager = bytes32(0x0);

nonce[_account]++;

ShopData memory shop = shops[_shopId];
Expand Down Expand Up @@ -337,7 +356,9 @@ contract Shop is ShopStorage, Initializable, OwnableUpgradeable, UUPSUpgradeable
bytes32 _shopId
) external view override returns (uint256 refundableAmount, uint256 refundableToken) {
ShopData memory shop = shops[_shopId];
uint256 settlementAmount = (shop.usedAmount > shop.providedAmount) ? shop.usedAmount - shop.providedAmount : 0;
uint256 settlementAmount = (shop.collectedAmount + shop.usedAmount > shop.providedAmount)
? shop.collectedAmount + shop.usedAmount - shop.providedAmount
: 0;
refundableAmount = (settlementAmount > shop.refundedAmount) ? settlementAmount - shop.refundedAmount : 0;
refundableToken = currencyRate.convertCurrencyToToken(refundableAmount, shops[_shopId].currency);
}
Expand All @@ -352,9 +373,12 @@ contract Shop is ShopStorage, Initializable, OwnableUpgradeable, UUPSUpgradeable
require(ECDSA.recover(ECDSA.toEthSignedMessageHash(dataHash), _signature) == _account, "1501");
require(shops[_shopId].account == _account, "1050");
require(_amount % 1 gwei == 0, "1030");
require(settlements[_shopId].manager == bytes32(0x0), "1552");

ShopData memory shop = shops[_shopId];
uint256 settlementAmount = (shop.usedAmount > shop.providedAmount) ? shop.usedAmount - shop.providedAmount : 0;
uint256 settlementAmount = (shop.collectedAmount + shop.usedAmount > shop.providedAmount)
? shop.collectedAmount + shop.usedAmount - shop.providedAmount
: 0;
uint256 refundableAmount = (settlementAmount > shop.refundedAmount)
? settlementAmount - shop.refundedAmount
: 0;
Expand All @@ -373,9 +397,207 @@ contract Shop is ShopStorage, Initializable, OwnableUpgradeable, UUPSUpgradeable
emit Refunded(_shopId, _account, _amount, refundedTotal, currency, amountToken, balanceToken);
}

/// @notice nonce를 리턴한다
/// @notice nonce 를 리턴한다
/// @param _account 지갑주소
function nonceOf(address _account) external view override returns (uint256) {
return nonce[_account];
}

/// @notice 정산관리자를 지정한다
/// @param _managerShopId 정산관리자의 상점아이디
/// @param _shopId 클라이언트의 상점아이디
/// @param _signature 서명
/// @dev 중계서버를 통해서 호출됩니다.
function setSettlementManager(bytes32 _shopId, bytes32 _managerShopId, bytes calldata _signature) external {
require(_shopId != bytes32(0x0), "1223");
require(_managerShopId != bytes32(0x0), "1223");
require(_shopId != _managerShopId, "1224");
require(shops[_shopId].status != ShopStatus.INVALID, "1201");
require(shops[_managerShopId].status != ShopStatus.INVALID, "1201");
address account = shops[_shopId].account;
bytes32 dataHash = keccak256(
abi.encode("SetSettlementManager", _shopId, _managerShopId, block.chainid, nonce[account])
);
require(ECDSA.recover(ECDSA.toEthSignedMessageHash(dataHash), _signature) == account, "1501");

// 정산관리자에 클라이언트를 추가한다.
ShopSettlementData storage settlementData = settlements[_managerShopId];
if (settlementData.clientValues[_shopId].states == SettlementClientStates.INVALID) {
settlementData.clientValues[_shopId] = SettlementClientData({
index: settlementData.clients.length,
states: SettlementClientStates.ACTIVE
});
settlementData.clients.push(_shopId);
}
// 정산클라이언트의 정보에 정산관리자를 설정한다
ShopSettlementData storage clientSettlementData = settlements[_shopId];
clientSettlementData.manager = _managerShopId;

nonce[account]++;

emit SetSettlementManager(_shopId, _managerShopId);
}

/// @notice 정산관리자를 제거한다
/// @param _shopId 클라이언트의 상점아이디
/// @dev 중계서버를 통해서 호출됩니다.
function removeSettlementManager(bytes32 _shopId, bytes calldata _signature) external {
require(_shopId != bytes32(0x0), "1223");
require(shops[_shopId].status != ShopStatus.INVALID, "1201");
address account = shops[_shopId].account;
bytes32 dataHash = keccak256(
abi.encode("RemoveSettlementManager", _shopId, bytes32(0x0), block.chainid, nonce[account])
);
require(ECDSA.recover(ECDSA.toEthSignedMessageHash(dataHash), _signature) == account, "1501");

// 정산클라이언트의 정보에 정산관리자를 제거한다
ShopSettlementData storage clientSettlementData = settlements[_shopId];
bytes32 managerShopId = clientSettlementData.manager;
clientSettlementData.manager = bytes32(0x0);

// 정산관리자에서 클라이언트를 제거한다.
if (managerShopId != bytes32(0x0)) {
ShopSettlementData storage settlementData = settlements[managerShopId];
if (settlementData.clientValues[_shopId].states == SettlementClientStates.ACTIVE) {
uint256 idx = settlementData.clientValues[_shopId].index;
uint256 last = settlementData.clients.length - 1;
settlementData.clients[idx] = settlementData.clients[last];
settlementData.clientValues[settlementData.clients[idx]].index = idx;
settlementData.clientValues[_shopId].states = SettlementClientStates.INVALID;
settlementData.clients.pop();
}
}

nonce[account]++;

emit RemovedSettlementManager(_shopId, managerShopId);
}

/// @notice 정산관리자의 상점아이디를 리턴한다
function settlementManagerOf(bytes32 _shopId) external view override returns (bytes32) {
return settlements[_shopId].manager;
}

function getSettlementClientLength(bytes32 _managerShopId) external view returns (uint256) {
require(_managerShopId != bytes32(0x0), "1223");
require(shops[_managerShopId].status != ShopStatus.INVALID, "1201");
return settlements[_managerShopId].clients.length;
}

function getSettlementClientList(
bytes32 _managerShopId,
uint256 startIndex,
uint256 endIndex
) external view returns (bytes32[] memory) {
require(_managerShopId != bytes32(0x0), "1223");
require(shops[_managerShopId].status != ShopStatus.INVALID, "1201");
uint256 length = settlements[_managerShopId].clients.length;
uint256 first;
uint256 last;
if (startIndex <= endIndex) {
first = (startIndex <= length - 1) ? startIndex : length;
last = (endIndex <= length) ? endIndex : length;
} else {
first = (endIndex <= length - 1) ? endIndex : length;
last = (startIndex <= length) ? startIndex : length;
}
bytes32[] memory res = new bytes32[](last - first);
for (uint256 idx = first; idx < last; idx++) {
res[idx - first] = settlements[_managerShopId].clients[idx];
}
return res;
}

function collectSettlementAmount(
bytes32 _managerShopId,
bytes32 _clientShopId,
bytes calldata _signature
) external {
require(_managerShopId != bytes32(0x0), "1223");
require(_clientShopId != bytes32(0x0), "1223");
require(shops[_managerShopId].status != ShopStatus.INVALID, "1201");
require(shops[_clientShopId].status != ShopStatus.INVALID, "1201");
require(settlements[_clientShopId].manager == _managerShopId, "1553");
require(
settlements[_managerShopId].clientValues[_clientShopId].states == SettlementClientStates.ACTIVE,
"1554"
);

address account = shops[_managerShopId].account;
bytes32 dataHash = keccak256(
abi.encode("CollectSettlementAmount", _managerShopId, _clientShopId, block.chainid, nonce[account])
);
require(ECDSA.recover(ECDSA.toEthSignedMessageHash(dataHash), _signature) == account, "1501");

nonce[account]++;

_collectSettlementAmount(_managerShopId, _clientShopId);
}

function collectSettlementAmountMultiClient(
bytes32 _managerShopId,
bytes32[] calldata _clientShopIds,
bytes calldata _signature
) external {
require(_managerShopId != bytes32(0x0), "1223");
require(shops[_managerShopId].status != ShopStatus.INVALID, "1201");

address account = shops[_managerShopId].account;
bytes32 dataHash = keccak256(
abi.encode(
"CollectSettlementAmountMultiClient",
_managerShopId,
_clientShopIds,
block.chainid,
nonce[account]
)
);
require(ECDSA.recover(ECDSA.toEthSignedMessageHash(dataHash), _signature) == account, "1501");

nonce[account]++;

for (uint256 idx = 0; idx < _clientShopIds.length; idx++) {
bytes32 clientShopId = _clientShopIds[idx];
if (shops[clientShopId].status == ShopStatus.INVALID) continue;
if (settlements[clientShopId].manager != _managerShopId) continue;
if (settlements[_managerShopId].clientValues[clientShopId].states != SettlementClientStates.ACTIVE)
continue;
_collectSettlementAmount(_managerShopId, clientShopId);
}
}

function _collectSettlementAmount(bytes32 _managerShopId, bytes32 _clientShopId) internal {
ShopData storage managerShop = shops[_managerShopId];
ShopData storage clientShop = shops[_clientShopId];

uint256 settlementAmount = (clientShop.collectedAmount + clientShop.usedAmount > clientShop.providedAmount)
? clientShop.collectedAmount + clientShop.usedAmount - clientShop.providedAmount
: 0;
uint256 refundableAmount = (settlementAmount > clientShop.refundedAmount)
? settlementAmount - clientShop.refundedAmount
: 0;

if (refundableAmount > 0) {
clientShop.refundedAmount += refundableAmount;
uint256 managerAmount = currencyRate.convertCurrency(
refundableAmount,
clientShop.currency,
managerShop.currency
);
managerShop.collectedAmount += managerAmount;

emit CollectedSettlementAmount(
clientShop.shopId,
clientShop.account,
clientShop.currency,
refundableAmount,
clientShop.refundedAmount,
managerShop.shopId,
managerShop.account,
managerShop.currency,
managerAmount,
managerShop.collectedAmount
);
}
}
}
1 change: 1 addition & 0 deletions packages/contracts/contracts/shop/ShopStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "../interfaces/ILedger.sol";
contract ShopStorage {
mapping(bytes32 => IShop.ShopData) internal shops;
mapping(address => bytes32[]) internal shopIdByAddress;
mapping(bytes32 => IShop.ShopSettlementData) internal settlements;

bytes32[] internal items;

Expand Down
4 changes: 2 additions & 2 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "acc-contracts-v2",
"version": "2.8.0",
"version": "2.9.0",
"description": "Smart contracts that decentralized loyalty systems",
"files": [
"**/*.sol"
Expand Down Expand Up @@ -71,7 +71,7 @@
"@openzeppelin/contracts-upgradeable": "^4.9.5",
"@openzeppelin/hardhat-upgrades": "^1.28.0",
"acc-bridge-contracts-v2": "~2.5.0",
"loyalty-tokens": "~2.3.0",
"loyalty-tokens": "~2.1.1",
"multisig-wallet-contracts": "~2.0.0"
}
}
Loading

0 comments on commit 724d7fe

Please sign in to comment.