From 88c329ea35515e919ccf4af0122c6ee371d8db07 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 26 Oct 2023 17:56:07 +0300 Subject: [PATCH] Add pay function --- contracts/DateTimeUtils.sol | 17 ++++++ contracts/Paymaster.sol | 92 +++++++++++++++++++++++++++-- contracts/errors/Parameters.sol | 27 +++++++++ contracts/errors/Replenishment.sol | 31 ++++++++++ contracts/interfaces/IPaymaster.sol | 12 ++++ contracts/types/Skl.sol | 33 +++++++++++ contracts/types/Usd.sol | 33 +++++++++++ 7 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 contracts/errors/Parameters.sol create mode 100644 contracts/errors/Replenishment.sol create mode 100644 contracts/types/Skl.sol create mode 100644 contracts/types/Usd.sol diff --git a/contracts/DateTimeUtils.sol b/contracts/DateTimeUtils.sol index cf7c424..d53ec0f 100644 --- a/contracts/DateTimeUtils.sol +++ b/contracts/DateTimeUtils.sol @@ -33,6 +33,23 @@ type Year is uint256; type Timestamp is uint256; using DateTimeUtils for Timestamp global; +using { + monthsLess as <, + monthsEqual as ==, + monthsGreater as > +} for Months global; + +function monthsLess(Months left, Months right) pure returns (bool result) { + return Months.unwrap(left) < Months.unwrap(right); +} + +function monthsEqual(Months a, Months b) pure returns (bool result) { + return !(a < b) && !(b < a); +} + +function monthsGreater(Months left, Months right) pure returns (bool result) { + return !(left < right) && !(left == right); +} library DateTimeUtils { function timestamp() internal view returns (Timestamp timestampValue) { diff --git a/contracts/Paymaster.sol b/contracts/Paymaster.sol index 931ee49..831167d 100644 --- a/contracts/Paymaster.sol +++ b/contracts/Paymaster.sol @@ -19,21 +19,39 @@ along with Paymaster. If not, see . */ -pragma solidity ^0.8.18; +pragma solidity ^0.8.19; -// cspell:words structs +// cspell:words structs IERC20 +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; +import { + SchainPriceIsNotSet, + SkaleTokenIsNotSet, + SklPriceIsNotSet +} from "./errors/Parameters.sol"; +import { + ReplenishmentPeriodIsTooBig, + TooSmallAllowance, + TransferFailure +} from "./errors/Replenishment.sol"; import {SchainNotFound, SchainAddingError, SchainDeletionError} from "./errors/Schain.sol"; import {ValidatorNotFound, ValidatorAddingError, ValidatorDeletionError} from "./errors/Validator.sol"; -import {IPaymaster, SchainHash, ValidatorId} from "./interfaces/IPaymaster.sol"; +import { + IPaymaster, + SchainHash, + USD, + ValidatorId +} from "./interfaces/IPaymaster.sol"; +import {SKL} from "./types/Skl.sol"; import { DateTimeUtils, - Timestamp + Timestamp, + Months } from "./DateTimeUtils.sol"; @@ -59,6 +77,11 @@ contract Paymaster is AccessManagedUpgradeable, IPaymaster { mapping(ValidatorId => Validator) public validators; EnumerableSet.UintSet private _validatorIds; + Months public maxReplenishmentPeriod; + USD public schainPricePerMonth; + USD public oneSklPrice; + IERC20 public skaleToken; + function addSchain(string calldata name) external override restricted { SchainHash schainHash = SchainHash.wrap(keccak256(abi.encodePacked(name))); Schain memory schain = Schain({ @@ -97,9 +120,50 @@ contract Paymaster is AccessManagedUpgradeable, IPaymaster { validator.activeNodesAmount = Math.min(amount, validator.nodesAmount); } - // function pay(SchainHash schainHash, Months duration) external { + function setMaxReplenishmentPeriod(Months months) external override restricted { + maxReplenishmentPeriod = months; + } + + function setSchainPrice(USD price) external override restricted { + schainPricePerMonth = price; + } + + function setSklPrice(USD price) external override restricted { + oneSklPrice = price; + } + + function setSkaleToken(IERC20 token) external override restricted { + skaleToken = token; + } + + function pay(SchainHash schainHash, Months duration) external override { + if (duration > maxReplenishmentPeriod) { + revert ReplenishmentPeriodIsTooBig(); + } + + Schain storage schain = _getSchain(schainHash); + SKL cost = _toSKL(_getCost(duration)); + + if (address(skaleToken) == address(0)) { + revert SkaleTokenIsNotSet(); + } + SKL allowance = SKL.wrap( + skaleToken.allowance(_msgSender(), address(this)) + ); + if (allowance < cost) { + revert TooSmallAllowance({ + spender: address(this), + required: SKL.unwrap(cost), + allowed: SKL.unwrap(allowance) + }); + } - // } + if (!skaleToken.transferFrom(_msgSender(), address(this), SKL.unwrap(cost))) { + revert TransferFailure(); + } + + schain.paidUntil = schain.paidUntil.add(duration); + } // Private @@ -146,4 +210,20 @@ contract Paymaster is AccessManagedUpgradeable, IPaymaster { revert ValidatorNotFound(id); } } + + function _toSKL(USD amount) private view returns (SKL result) { + if (oneSklPrice == USD.wrap(0)) { + revert SklPriceIsNotSet(); + } + result = SKL.wrap( + USD.unwrap(amount) * 1e18 / USD.unwrap(oneSklPrice) + ); + } + + function _getCost(Months period) private view returns (USD cost) { + if (schainPricePerMonth == USD.wrap(0)) { + revert SchainPriceIsNotSet(); + } + cost = USD.wrap(Months.unwrap(period) * USD.unwrap(schainPricePerMonth)); + } } diff --git a/contracts/errors/Parameters.sol b/contracts/errors/Parameters.sol new file mode 100644 index 0000000..3aa52d1 --- /dev/null +++ b/contracts/errors/Parameters.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/* + Parameters.sol - Paymaster + Copyright (C) 2023-Present SKALE Labs + @author Dmytro Stebaiev + + Paymaster is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Paymaster is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Paymaster. If not, see . +*/ + +pragma solidity ^0.8.18; + + +error SchainPriceIsNotSet(); +error SkaleTokenIsNotSet(); +error SklPriceIsNotSet(); diff --git a/contracts/errors/Replenishment.sol b/contracts/errors/Replenishment.sol new file mode 100644 index 0000000..1266d61 --- /dev/null +++ b/contracts/errors/Replenishment.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/* + Replenishment.sol - Paymaster + Copyright (C) 2023-Present SKALE Labs + @author Dmytro Stebaiev + + Paymaster is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Paymaster is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Paymaster. If not, see . +*/ + +pragma solidity ^0.8.18; + + +error ReplenishmentPeriodIsTooBig(); +error TooSmallAllowance( + address spender, + uint256 required, + uint256 allowed +); +error TransferFailure(); diff --git a/contracts/interfaces/IPaymaster.sol b/contracts/interfaces/IPaymaster.sol index f5160a2..eadacf3 100644 --- a/contracts/interfaces/IPaymaster.sol +++ b/contracts/interfaces/IPaymaster.sol @@ -21,6 +21,13 @@ pragma solidity ^0.8.18; +// cspell:words IERC20 + +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; + +import {Months} from "../DateTimeUtils.sol"; +import {USD} from "../types/Usd.sol"; + type SchainHash is bytes32; type ValidatorId is uint256; @@ -32,4 +39,9 @@ interface IPaymaster { function removeValidator(ValidatorId id) external; function setNodesAmount(ValidatorId id, uint256 amount) external; function setActiveNodes(ValidatorId id, uint256 amount) external; + function setMaxReplenishmentPeriod(Months months) external; + function setSchainPrice(USD price) external; + function setSklPrice(USD price) external; + function setSkaleToken(IERC20 token) external; + function pay(SchainHash schainHash, Months duration) external; } diff --git a/contracts/types/Skl.sol b/contracts/types/Skl.sol new file mode 100644 index 0000000..4d04701 --- /dev/null +++ b/contracts/types/Skl.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/* + Skl.sol - Paymaster + Copyright (C) 2023-Present SKALE Labs + @author Dmytro Stebaiev + + Paymaster is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Paymaster is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Paymaster. If not, see . +*/ + +pragma solidity ^0.8.18; + + +type SKL is uint256; + +using { + sklLess as < +} for SKL global; + +function sklLess(SKL left, SKL right) pure returns (bool result) { + return SKL.unwrap(left) < SKL.unwrap(right); +} diff --git a/contracts/types/Usd.sol b/contracts/types/Usd.sol new file mode 100644 index 0000000..7f98a76 --- /dev/null +++ b/contracts/types/Usd.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +/* + Usd.sol - Paymaster + Copyright (C) 2023-Present SKALE Labs + @author Dmytro Stebaiev + + Paymaster is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Paymaster is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Paymaster. If not, see . +*/ + +pragma solidity ^0.8.18; + + +type USD is uint256; + +function usdEquals(USD a, USD b) pure returns (bool result) { + return USD.unwrap(a) == USD.unwrap(b); +} + +using { + usdEquals as == +} for USD global;