Skip to content

Commit

Permalink
feat: improve stake payment by isolate funds to StakeVault
Browse files Browse the repository at this point in the history
  • Loading branch information
thongxuan committed Dec 19, 2024
1 parent 2be3dbf commit 72351de
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 182 deletions.
243 changes: 94 additions & 149 deletions contracts/payment/stake/LemonadeStakePayment.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,28 @@
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "hardhat/console.sol";

import "../../utils/Data.sol";
import "../PaymentConfigRegistry.sol";
import "./StakeVault.sol";

bytes32 constant STAKE_REFUND = keccak256(abi.encode("STAKE_REFUND"));
bytes32 constant STAKE_SLASH = keccak256(abi.encode("STAKE_SLASH"));

contract LemonadeStakePayment is OwnableUpgradeable {
error NotAvailable();
error InvalidData();
error AlreadyStaked();
error CannotPayFee();
error CannotRelease();
error CannotStake();

event VaultRegistered(uint256 id);

struct StakeConfig {
address owner;
address vault;
uint256 refundPPM;
}

struct Staking {
uint256 configId;
address guest;
address currency;
uint256 amount;
uint256 stakeAmount;
uint256 refundAmount;
bool slashed;
bool refunded;
}

contract LemonadeStakePayment is OwnableUpgradeable, Transferable {
//-- STORAGE
address public configRegistry;
uint256 public counter;
address[] currencies; //-- all the currency ever staked
uint256[5] _gap;

mapping(address => uint256) currencyIndex;
mapping(bytes32 => Staking) stakings;
mapping(uint256 => StakeConfig) public configs;
mapping(bytes32 => address) stakings; //-- key is payment id
uint256[5] __gap;

//-- ERRORS
error InvalidData();

//-- EVENTS
event VaultRegistered(address vault);

function initialize(address registry) public initializer {
__Ownable_init();
configRegistry = registry;
Expand All @@ -55,33 +34,33 @@ contract LemonadeStakePayment is OwnableUpgradeable {
configRegistry = registry;
}

function register(address vault, uint256 refundPPM) external {
if (refundPPM > 1000000 || refundPPM == 0) {
revert InvalidData();
}

function register(address payout, uint256 refundPPM) external {
address owner = _msgSender();
StakeConfig memory config = StakeConfig(owner, vault, refundPPM);

counter += 1;
configs[counter] = config;
StakeVault vault = new StakeVault(
owner,
payout,
address(this),
refundPPM
);

emit VaultRegistered(counter);
emit VaultRegistered(address(vault));
}

function stake(
uint256 configId,
string memory eventId,
string memory paymentId,
address vault,
string calldata eventId,
string calldata paymentId,
address currency,
uint256 amount
) external payable {
if (amount == 0) {
revert InvalidData();
}
bytes32 id = _toId(paymentId);

if (stakings[id].amount > 0) {
bytes32 stakeId = stringToId(paymentId);

if (stakings[stakeId] != address(0)) {
revert AlreadyStaked();
}

Expand All @@ -95,86 +74,107 @@ contract LemonadeStakePayment is OwnableUpgradeable {
payable(configRegistry)
);

StakeConfig storage config = configs[configId];

uint256 stakeAmount = (amount * 1000000) /
(registry.feePPM() + 1000000);
uint256 feeAmount = amount - stakeAmount;
uint256 refundAmount = (stakeAmount * config.refundPPM) / 1000000;

//-- transfer fee and notify
_transfer(configRegistry, currency, feeAmount);
registry.notifyFee(eventId, currency, feeAmount);

address guest = _msgSender();

if (isNative) {
(bool success, ) = payable(configRegistry).call{value: feeAmount}(
""
);
_stake(stakeId, guest, vault, stakeAmount, currency);
}

if (!success) revert CannotPayFee();
} else {
bool success = IERC20(currency).transferFrom(
guest,
configRegistry,
feeAmount
);
function _stake(
bytes32 stakeId,
address guest,
address vault,
uint256 stakeAmount,
address currency
) internal {
bool isNative = currency == address(0);

if (!success) revert CannotPayFee();
StakeVault stakeVault = StakeVault(payable(vault));

success = IERC20(currency).transferFrom(
if (!isNative) {
//-- first transfer the ERC20 to the contract
bool success = IERC20(currency).transferFrom(
guest,
address(this),
stakeAmount
);

if (!success) revert CannotStake();
}
if (!success) {
revert CannotTransfer();
}

if (currencyIndex[currency] == 0) {
uint256 index = currencies.length + 1;
currencies.push(currency);
currencyIndex[currency] = index;
//-- then allow the vault to transfer the amount to itself
IERC20(currency).approve(vault, stakeAmount);
}

stakings[id] = Staking(
configId,
guest,
stakeVault.stake{value: isNative ? stakeAmount : 0}(
stakeId,
currency,
amount,
stakeAmount,
refundAmount,
false,
false
guest
);

registry.notifyFee(eventId, currency, feeAmount);
stakings[stakeId] = vault;
}

function getStakings(
string[] memory ids
string[] calldata ids
) public view returns (Staking[] memory result) {
uint256 length = ids.length;

result = new Staking[](length);

for (uint256 i = 0; i < length; ) {
bytes32 id = _toId(ids[i]);
result[i] = stakings[id];
bytes32 stakeId = stringToId(ids[i]);
address vault = stakings[stakeId];

if (vault == address(0)) continue;

StakeVault stakeVault = StakeVault(payable(vault));

(
address guest,
address currency,
uint256 stakeAmount,
uint256 refundAmount,
bool slashed,
bool refunded
) = stakeVault.stakings(stakeId);

result[i] = Staking(
guest,
currency,
stakeAmount,
refundAmount,
slashed,
refunded
);

unchecked {
++i;
}
}

return result;
}

function refund(
string calldata paymentId,
bytes calldata signature
) external {
bytes32 id = _toId(paymentId);
bytes32 stakeId = stringToId(paymentId);

Staking storage staking = stakings[id];
address vault = stakings[stakeId];

if (staking.slashed || staking.refunded) {
revert NotAvailable();
if (vault == address(0)) {
revert NoStaking();
}

//-- verify signature
Expand All @@ -185,102 +185,47 @@ contract LemonadeStakePayment is OwnableUpgradeable {
bytes32[] memory data = new bytes32[](2);

data[0] = STAKE_REFUND;
data[1] = id;
data[1] = stakeId;

registry.assertSignature(data, signature);

//-- let's refund
staking.refunded = true;
StakeVault stakeVault = StakeVault(payable(vault));

_release(staking.guest, staking.currency, staking.refundAmount);
stakeVault.refund(stakeId);
}

function slash(
uint256 configId,
address vault,
string[] memory paymentIds,
bytes memory signature
) external {
uint256 idsLength = paymentIds.length;

StakeConfig storage config = configs[configId];

PaymentConfigRegistry registry = PaymentConfigRegistry(
payable(configRegistry)
);

uint256 currenciesLength = currencies.length;
uint256[] memory slashes = new uint256[](currenciesLength);

//-- collect data to verify the signature
bytes32[] memory data = new bytes32[](paymentIds.length + 1);
bytes32[] memory stakeIds = new bytes32[](paymentIds.length);
data[0] = STAKE_SLASH;

for (uint256 i = 0; i < idsLength; ) {
bytes32 id = _toId(paymentIds[i]);
bytes32 stakeId = stringToId(paymentIds[i]);

Staking storage staking = stakings[id];

if (staking.configId != configId) {
revert InvalidData();
}

if (staking.slashed || staking.refunded) {
revert NotAvailable();
}

//-- add to slash sum
uint256 index = currencyIndex[staking.currency] - 1;
slashes[index] += staking.stakeAmount;

//-- update the staking
staking.slashed = true;
stakeIds[i] = stakeId;

unchecked {
++i;
}

data[i] = id;
data[i] = stakeId;
}

registry.assertSignature(data, signature);

//-- release

for (uint256 i = 0; i < currenciesLength; ) {
address currency = currencies[i];
uint256 amount = slashes[currencyIndex[currency] - 1];

if (amount > 0) {
_release(config.vault, currency, amount);
}

unchecked {
++i;
}
}
}

function _release(
address destination,
address currency,
uint256 amount
) internal {
if (currency == address(0)) {
(bool success, ) = payable(destination).call{value: amount}("");

if (!success) revert CannotRelease();
} else {
bool success = IERC20(currency).transferFrom(
address(this),
destination,
amount
);

if (!success) revert CannotRelease();
}
}
StakeVault stakeVault = StakeVault(payable(vault));

function _toId(string memory id) internal pure returns (bytes32) {
return keccak256(abi.encode(id));
stakeVault.slash(stakeIds);
}
}
Loading

0 comments on commit 72351de

Please sign in to comment.