-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathNpmDistributor.sol
More file actions
262 lines (214 loc) · 9.18 KB
/
NpmDistributor.sol
File metadata and controls
262 lines (214 loc) · 9.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// Neptune Mutual Protocol (https://neptunemutual.com)
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.0;
import "openzeppelin-solidity/contracts/token/ERC20/utils/SafeERC20.sol";
import "openzeppelin-solidity/contracts/security/ReentrancyGuard.sol";
import "../interfaces/IPolicy.sol";
import "../interfaces/IVault.sol";
import "../interfaces/IClaimsProcessor.sol";
// @title NPM Store Interface
interface IStoreLike {
function getAddress(bytes32 k) external view returns (address);
}
/**
* @title Neptune Mutual Distributor contract
* @dev The distributor contract enables resellers to interact with
* the Neptune Mutual protocol and offer policies to their users.
*
* This contract demonstrates how a distributor may charge an extra fee
* and deposit the proceeds in their own treasury account.
*/
contract NpmDistributor is ReentrancyGuard {
using SafeERC20 for IERC20;
using SafeERC20 for IVault;
event PolicySold(
bytes32 indexed coverKey,
bytes32 indexed productKey,
address indexed cxToken,
address account,
uint256 duration,
uint256 protection,
bytes32 referralCode,
uint256 fee,
uint256 premium
);
event LiquidityAdded(bytes32 indexed coverKey, address indexed account, bytes32 indexed referralCode, uint256 amount, uint256 npmStake);
event LiquidityRemoved(bytes32 indexed coverKey, address indexed account, uint256 amount, uint256 npmStake, bool exit);
event Drained(IERC20 indexed token, address indexed to, uint256 amount);
bytes32 public constant NS_CONTRACTS = "ns:contracts";
bytes32 public constant CNS_CLAIM_PROCESSOR = "cns:claim:processor";
bytes32 public constant CNS_COVER_VAULT = "cns:cover:vault";
bytes32 public constant CNS_COVER_POLICY = "cns:cover:policy";
bytes32 public constant CNS_COVER_STABLECOIN = "cns:cover:sc";
bytes32 public constant CNS_NPM_INSTANCE = "cns:core:npm:instance";
uint256 public constant MULTIPLIER = 10_000;
uint256 public immutable feePercentage;
address public immutable treasury;
IStoreLike public immutable store;
/**
* @dev Constructs this contract
* @param _store Enter the address of NPM protocol store
* @param _treasury Enter your treasury wallet address
* @param _feePercentage Enter distributor fee percentage
*/
constructor(
IStoreLike _store,
address _treasury,
uint256 _feePercentage
) {
require(address(_store) != address(0), "Invalid store");
require(_treasury != address(0), "Invalid treasury");
require(_feePercentage > 0 && _feePercentage < MULTIPLIER, "Invalid fee percentage");
store = _store;
treasury = _treasury;
feePercentage = _feePercentage;
}
/**
* @dev Returns the stablecoin used by the protocol in this blockchain.
*/
function getStablecoin() public view returns (IERC20) {
return IERC20(store.getAddress(CNS_COVER_STABLECOIN));
}
/**
* @dev Returns NPM token instance in this blockchain.
*/
function getNpm() public view returns (IERC20) {
return IERC20(store.getAddress(CNS_NPM_INSTANCE));
}
/**
* @dev Returns the protocol policy contract instance.
*/
function getPolicyContract() public view returns (IPolicy) {
return IPolicy(store.getAddress(keccak256(abi.encodePacked(NS_CONTRACTS, CNS_COVER_POLICY))));
}
/**
* @dev Returns the vault contract instance by the given key.
*/
function getVaultContract(bytes32 coverKey) public view returns (IVault) {
return IVault(store.getAddress(keccak256(abi.encodePacked(NS_CONTRACTS, CNS_COVER_VAULT, coverKey))));
}
/**
* @dev Returns the protocol claims processor contract instance.
*/
function getClaimsProcessorContract() external view returns (IClaimsProcessor) {
return IClaimsProcessor(store.getAddress(keccak256(abi.encodePacked(NS_CONTRACTS, CNS_CLAIM_PROCESSOR))));
}
/**
* @dev Calculates the premium required to purchase policy.
* @param coverKey Enter the cover key for which you want to buy policy.
* @param duration Enter the period of the protection in months.
* @param protection Enter the stablecoin dollar amount you want to protect.
*/
function getPremium(
bytes32 coverKey,
bytes32 productKey,
uint256 duration,
uint256 protection
) public view returns (uint256 premium, uint256 fee) {
IPolicy policy = getPolicyContract();
require(address(policy) != address(0), "Fatal: Policy missing");
IPolicy.CoverFeeInfoType memory coverFeeInfo = policy.getCoverFeeInfo(coverKey, productKey, duration, protection);
premium = coverFeeInfo.fee;
// Add your fee in addition to the protocol premium
fee = (premium * feePercentage) / MULTIPLIER;
}
/**
* @dev Purchases a new policy on behalf of your users.
*
* Prior to using this method, you must first call the "getPremium" function
* and approve the policy fees that this contract would spend.
*
* In the event that this function succeeds, the recipient's wallet will be
* credited with "cxToken". Take note that the "claimPolicy" method may be
* used in the future to reclaim cxTokens and receive payouts
* after the resolution of an incident.
*
* @custom:suppress-acl This is a publicly accessible feature
* @custom:suppress-pausable
*
*/
function purchasePolicy(IPolicy.PurchaseCoverArgs memory args) external nonReentrant {
require(args.coverKey > 0, "Invalid key");
require(args.coverDuration > 0 && args.coverDuration < 4, "Invalid duration");
require(args.amountToCover > 0, "Invalid protection amount");
IPolicy policy = getPolicyContract();
require(address(policy) != address(0), "Fatal: Policy missing");
IERC20 stablecoin = getStablecoin();
require(address(stablecoin) != address(0), "Fatal: Stablecoin missing");
// Get fee info
(uint256 premium, uint256 fee) = getPremium(args.coverKey, args.productKey, args.coverDuration, args.amountToCover);
// Transfer stablecoin to this contract
stablecoin.safeTransferFrom(msg.sender, address(this), premium + fee);
// Approve protocol to pull the protocol fee
stablecoin.safeIncreaseAllowance(address(policy), premium);
args.onBehalfOf = msg.sender;
// Purchase protection for this user
(address cxTokenAt, ) = policy.purchaseCover(args);
// Send your fee (+ any remaining stablecoin balance) to your treasury address
stablecoin.safeTransfer(treasury, stablecoin.balanceOf(address(this)));
emit PolicySold(args.coverKey, args.productKey, cxTokenAt, msg.sender, args.coverDuration, args.amountToCover, args.referralCode, fee, premium);
}
function addLiquidity(IVault.AddLiquidityArgs calldata args) external nonReentrant {
require(args.coverKey > 0, "Invalid key");
require(args.amount > 0, "Invalid amount");
IVault pod = getVaultContract(args.coverKey);
IERC20 stablecoin = getStablecoin();
IERC20 npm = getNpm();
require(address(pod) != address(0), "Fatal: Vault missing");
require(address(stablecoin) != address(0), "Fatal: Stablecoin missing");
require(address(npm) != address(0), "Fatal: NPM missing");
// Before moving forward, first drain all balances of this contract
_drain(pod);
_drain(stablecoin);
_drain(npm);
// Transfer stablecoin from sender's wallet here
stablecoin.safeTransferFrom(msg.sender, address(this), args.amount);
// Approve the Vault (or pod) contract to spend stablecoin
stablecoin.safeIncreaseAllowance(address(pod), args.amount);
if (args.npmStakeToAdd > 0) {
// Transfer NPM from the sender's wallet here
npm.safeTransferFrom(msg.sender, address(this), args.npmStakeToAdd);
// Approve the Vault (or pod) contract to spend NPM
npm.safeIncreaseAllowance(address(pod), args.npmStakeToAdd);
}
pod.addLiquidity(args);
pod.safeTransfer(msg.sender, pod.balanceOf(address(this)));
emit LiquidityAdded(args.coverKey, msg.sender, args.referralCode, args.amount, args.npmStakeToAdd);
}
function removeLiquidity(
bytes32 coverKey,
uint256 amount,
uint256 npmStake,
bool exit
) external nonReentrant {
require(coverKey > 0, "Invalid key");
require(amount > 0, "Invalid amount");
IVault pod = getVaultContract(coverKey);
IERC20 stablecoin = getStablecoin();
IERC20 npm = getNpm();
require(address(pod) != address(0), "Fatal: Vault missing");
require(address(stablecoin) != address(0), "Fatal: Stablecoin missing");
require(address(npm) != address(0), "Fatal: NPM missing");
// Before moving forward, first drain all balances of this contract
_drain(pod);
_drain(stablecoin);
_drain(npm);
// Transfer pod from sender's wallet here
pod.safeTransferFrom(msg.sender, address(this), amount);
// Approve the Vault (or pod) contract to spend pod
pod.safeIncreaseAllowance(address(pod), amount);
pod.removeLiquidity(coverKey, amount, npmStake, exit);
stablecoin.safeTransfer(msg.sender, pod.balanceOf(address(this)));
emit LiquidityRemoved(coverKey, msg.sender, amount, npmStake, exit);
}
/**
* @dev Drains a given token to the treasury address
*/
function _drain(IERC20 token) private {
uint256 balance = token.balanceOf(address(this));
if (balance > 0) {
token.safeTransfer(treasury, balance);
emit Drained(token, treasury, balance);
}
}
}