@juicedollar/jusd 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@
2
2
  pragma solidity ^0.8.0;
3
3
 
4
4
  import {IJuiceDollar} from "./interface/IJuiceDollar.sol";
5
+ import {IReserve} from "./interface/IReserve.sol";
5
6
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
7
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
8
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
@@ -13,6 +14,11 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER
13
14
  contract StablecoinBridge {
14
15
  using SafeERC20 for IERC20;
15
16
 
17
+ /**
18
+ * @notice Emergency quorum in basis points. 1000 = 10%.
19
+ */
20
+ uint32 private constant EMERGENCY_QUORUM = 1000;
21
+
16
22
  IERC20 public immutable usd; // the source stablecoin
17
23
  IJuiceDollar public immutable JUSD; // the JUSD
18
24
  uint8 private immutable usdDecimals;
@@ -29,9 +35,20 @@ contract StablecoinBridge {
29
35
  uint256 public immutable limit;
30
36
  uint256 public minted;
31
37
 
38
+ /**
39
+ * @notice Whether this bridge has been permanently stopped via emergency stop.
40
+ */
41
+ bool public stopped;
42
+
43
+ event EmergencyStopped(address indexed caller, string message);
44
+
32
45
  error Limit(uint256 amount, uint256 limit);
33
46
  error Expired(uint256 time, uint256 expiration);
34
47
  error UnsupportedToken(address token);
48
+ error Stopped();
49
+ error AlreadyStopped();
50
+ error NotQualified();
51
+ error NoGovernance();
35
52
 
36
53
  constructor(address other, address JUSDAddress, uint256 limit_, uint256 weeks_) {
37
54
  usd = IERC20(other);
@@ -56,19 +73,47 @@ contract StablecoinBridge {
56
73
  * @param amount The amount of the source stablecoin to bridge (convert).
57
74
  */
58
75
  function mintTo(address target, uint256 amount) public {
76
+ if (stopped) revert Stopped();
77
+
59
78
  usd.safeTransferFrom(msg.sender, address(this), amount);
60
-
79
+
61
80
  uint256 targetAmount = _convertAmount(amount, usdDecimals, JUSDDecimals);
62
81
  _mint(target, targetAmount);
63
82
  }
64
83
 
65
84
  function _mint(address target, uint256 amount) internal {
85
+ if (stopped) revert Stopped();
66
86
  if (block.timestamp > horizon) revert Expired(block.timestamp, horizon);
67
87
  JUSD.mint(target, amount);
68
88
  minted += amount;
69
89
  if (minted > limit) revert Limit(amount, limit);
70
90
  }
71
91
 
92
+ /**
93
+ * @notice Permanently stop this bridge in case of emergency.
94
+ * @dev Requires 10% governance power. Once stopped, cannot be reactivated.
95
+ * Users can still burn JUSD to retrieve their stablecoins.
96
+ * @param _helpers Addresses that delegated their votes to the caller (must be sorted ascending, no duplicates)
97
+ * @param _message Reason for the emergency stop
98
+ */
99
+ function emergencyStop(address[] calldata _helpers, string calldata _message) external {
100
+ if (stopped) revert AlreadyStopped();
101
+
102
+ IReserve reserve = JUSD.reserve();
103
+ uint256 _totalVotes = reserve.totalVotes();
104
+ if (_totalVotes == 0) revert NoGovernance();
105
+
106
+ // Use votesDelegated from Equity which validates:
107
+ // - helpers are sorted and unique
108
+ // - helpers have delegated to sender
109
+ // - sender is not in helpers list
110
+ uint256 _votes = reserve.votesDelegated(msg.sender, _helpers);
111
+ if (_votes * 10_000 < EMERGENCY_QUORUM * _totalVotes) revert NotQualified();
112
+
113
+ stopped = true;
114
+ emit EmergencyStopped(msg.sender, _message);
115
+ }
116
+
72
117
  /**
73
118
  * @notice Convenience method for burnAndSend(msg.sender, amount)
74
119
  * @param amount The amount of JUSD to burn.
@@ -99,11 +144,11 @@ contract StablecoinBridge {
99
144
  */
100
145
  function _convertAmount(uint256 amount, uint8 fromDecimals, uint8 toDecimals) internal pure returns (uint256) {
101
146
  if (fromDecimals < toDecimals) {
102
- return amount * 10**(toDecimals - fromDecimals);
147
+ return amount * 10 ** (toDecimals - fromDecimals);
103
148
  } else if (fromDecimals > toDecimals) {
104
- return amount / 10**(fromDecimals - toDecimals);
149
+ return amount / 10 ** (fromDecimals - toDecimals);
105
150
  } else {
106
151
  return amount;
107
152
  }
108
153
  }
109
- }
154
+ }
@@ -6,4 +6,6 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
6
  interface IReserve is IERC20 {
7
7
  function invest(uint256 amount, uint256 expected) external returns (uint256);
8
8
  function checkQualified(address sender, address[] calldata helpers) external view;
9
+ function votesDelegated(address sender, address[] calldata helpers) external view returns (uint256);
10
+ function totalVotes() external view returns (uint256);
9
11
  }
@@ -32,7 +32,7 @@ contract PositionExpirationTest {
32
32
  10,
33
33
  100 /* collateral */,
34
34
  1000000 * 10 ** 18,
35
- 7 days,
35
+ 14 days,
36
36
  30 days,
37
37
  1 days,
38
38
  50000,
@@ -46,7 +46,7 @@ contract PositionExpirationTest {
46
46
  10,
47
47
  100 /* collateral */,
48
48
  1000000 * 10 ** 18,
49
- 7 days,
49
+ 14 days,
50
50
  30 days,
51
51
  1 days,
52
52
  50000,
@@ -29,9 +29,9 @@ contract PositionRollingTest {
29
29
 
30
30
  function openTwoPositions() public {
31
31
  jusd.approve(address(hub), hub.OPENING_FEE());
32
- p1 = IPosition(openPosition(100, uint40(3 days)));
32
+ p1 = IPosition(openPosition(100, uint40(14 days)));
33
33
  jusd.approve(address(hub), hub.OPENING_FEE());
34
- p2 = IPosition(openPosition(10, uint40(7 days)));
34
+ p2 = IPosition(openPosition(10, uint40(21 days)));
35
35
  }
36
36
 
37
37
  function mintFromFirstPosition(uint256 amount) public {
package/dist/index.d.mts CHANGED
@@ -7,8 +7,16 @@ interface ChainAddress {
7
7
  savingsGateway: Address;
8
8
  savingsVaultJUSD: Address;
9
9
  mintingHubGateway: Address;
10
+ /** Bridge contract addresses */
10
11
  bridgeStartUSD: Address;
12
+ bridgeUSDC?: Address;
13
+ bridgeUSDT?: Address;
14
+ bridgeCTUSD?: Address;
15
+ /** Underlying stablecoin token addresses */
11
16
  startUSD: Address;
17
+ USDC?: Address;
18
+ USDT?: Address;
19
+ CTUSD?: Address;
12
20
  roller: Address;
13
21
  positionFactoryV2: Address;
14
22
  genesisPosition: Address;
@@ -2788,6 +2796,10 @@ declare const MintingHubGatewayABI: readonly [{
2788
2796
  readonly inputs: readonly [];
2789
2797
  readonly name: "ChallengeTimeTooShort";
2790
2798
  readonly type: "error";
2799
+ }, {
2800
+ readonly inputs: readonly [];
2801
+ readonly name: "EmptyMessage";
2802
+ readonly type: "error";
2791
2803
  }, {
2792
2804
  readonly inputs: readonly [];
2793
2805
  readonly name: "IncompatibleCollateral";
@@ -2824,6 +2836,18 @@ declare const MintingHubGatewayABI: readonly [{
2824
2836
  }];
2825
2837
  readonly name: "LeaveNoDust";
2826
2838
  readonly type: "error";
2839
+ }, {
2840
+ readonly inputs: readonly [{
2841
+ readonly internalType: "uint256";
2842
+ readonly name: "length";
2843
+ readonly type: "uint256";
2844
+ }, {
2845
+ readonly internalType: "uint256";
2846
+ readonly name: "maxLength";
2847
+ readonly type: "uint256";
2848
+ }];
2849
+ readonly name: "MessageTooLong";
2850
+ readonly type: "error";
2827
2851
  }, {
2828
2852
  readonly inputs: readonly [];
2829
2853
  readonly name: "NativeOnlyForWCBTC";
@@ -2935,6 +2959,26 @@ declare const MintingHubGatewayABI: readonly [{
2935
2959
  }];
2936
2960
  readonly name: "ForcedSale";
2937
2961
  readonly type: "event";
2962
+ }, {
2963
+ readonly anonymous: false;
2964
+ readonly inputs: readonly [{
2965
+ readonly indexed: true;
2966
+ readonly internalType: "address";
2967
+ readonly name: "position";
2968
+ readonly type: "address";
2969
+ }, {
2970
+ readonly indexed: true;
2971
+ readonly internalType: "address";
2972
+ readonly name: "denier";
2973
+ readonly type: "address";
2974
+ }, {
2975
+ readonly indexed: false;
2976
+ readonly internalType: "string";
2977
+ readonly name: "message";
2978
+ readonly type: "string";
2979
+ }];
2980
+ readonly name: "PositionDeniedByGovernance";
2981
+ readonly type: "event";
2938
2982
  }, {
2939
2983
  readonly anonymous: false;
2940
2984
  readonly inputs: readonly [{
@@ -2960,6 +3004,31 @@ declare const MintingHubGatewayABI: readonly [{
2960
3004
  }];
2961
3005
  readonly name: "PositionOpened";
2962
3006
  readonly type: "event";
3007
+ }, {
3008
+ readonly anonymous: false;
3009
+ readonly inputs: readonly [{
3010
+ readonly indexed: true;
3011
+ readonly internalType: "address";
3012
+ readonly name: "position";
3013
+ readonly type: "address";
3014
+ }, {
3015
+ readonly indexed: false;
3016
+ readonly internalType: "uint256";
3017
+ readonly name: "collateral";
3018
+ readonly type: "uint256";
3019
+ }, {
3020
+ readonly indexed: false;
3021
+ readonly internalType: "uint256";
3022
+ readonly name: "price";
3023
+ readonly type: "uint256";
3024
+ }, {
3025
+ readonly indexed: false;
3026
+ readonly internalType: "uint256";
3027
+ readonly name: "principal";
3028
+ readonly type: "uint256";
3029
+ }];
3030
+ readonly name: "PositionUpdate";
3031
+ readonly type: "event";
2963
3032
  }, {
2964
3033
  readonly anonymous: false;
2965
3034
  readonly inputs: readonly [{
@@ -3260,6 +3329,38 @@ declare const MintingHubGatewayABI: readonly [{
3260
3329
  }];
3261
3330
  readonly stateMutability: "payable";
3262
3331
  readonly type: "function";
3332
+ }, {
3333
+ readonly inputs: readonly [{
3334
+ readonly internalType: "address";
3335
+ readonly name: "denier";
3336
+ readonly type: "address";
3337
+ }, {
3338
+ readonly internalType: "string";
3339
+ readonly name: "message";
3340
+ readonly type: "string";
3341
+ }];
3342
+ readonly name: "emitPositionDenied";
3343
+ readonly outputs: readonly [];
3344
+ readonly stateMutability: "nonpayable";
3345
+ readonly type: "function";
3346
+ }, {
3347
+ readonly inputs: readonly [{
3348
+ readonly internalType: "uint256";
3349
+ readonly name: "_collateral";
3350
+ readonly type: "uint256";
3351
+ }, {
3352
+ readonly internalType: "uint256";
3353
+ readonly name: "_price";
3354
+ readonly type: "uint256";
3355
+ }, {
3356
+ readonly internalType: "uint256";
3357
+ readonly name: "_principal";
3358
+ readonly type: "uint256";
3359
+ }];
3360
+ readonly name: "emitPositionUpdate";
3361
+ readonly outputs: readonly [];
3362
+ readonly stateMutability: "nonpayable";
3363
+ readonly type: "function";
3263
3364
  }, {
3264
3365
  readonly inputs: readonly [{
3265
3366
  readonly internalType: "contract IPosition";
@@ -4069,6 +4170,10 @@ declare const SavingsVaultJUSDABI: readonly [{
4069
4170
  }];
4070
4171
  readonly name: "SafeERC20FailedOperation";
4071
4172
  readonly type: "error";
4173
+ }, {
4174
+ readonly inputs: readonly [];
4175
+ readonly name: "ZeroShares";
4176
+ readonly type: "error";
4072
4177
  }, {
4073
4178
  readonly anonymous: false;
4074
4179
  readonly inputs: readonly [{
@@ -4946,6 +5051,10 @@ declare const PositionV2ABI: readonly [{
4946
5051
  readonly inputs: readonly [];
4947
5052
  readonly name: "Closed";
4948
5053
  readonly type: "error";
5054
+ }, {
5055
+ readonly inputs: readonly [];
5056
+ readonly name: "EmptyMessage";
5057
+ readonly type: "error";
4949
5058
  }, {
4950
5059
  readonly inputs: readonly [{
4951
5060
  readonly internalType: "uint40";
@@ -4994,6 +5103,18 @@ declare const PositionV2ABI: readonly [{
4994
5103
  }];
4995
5104
  readonly name: "LimitExceeded";
4996
5105
  readonly type: "error";
5106
+ }, {
5107
+ readonly inputs: readonly [{
5108
+ readonly internalType: "uint256";
5109
+ readonly name: "length";
5110
+ readonly type: "uint256";
5111
+ }, {
5112
+ readonly internalType: "uint256";
5113
+ readonly name: "maxLength";
5114
+ readonly type: "uint256";
5115
+ }];
5116
+ readonly name: "MessageTooLong";
5117
+ readonly type: "error";
4997
5118
  }, {
4998
5119
  readonly inputs: readonly [];
4999
5120
  readonly name: "NativeTransferFailed";
@@ -7596,6 +7717,10 @@ declare const StablecoinBridgeABI: readonly [{
7596
7717
  }];
7597
7718
  readonly stateMutability: "nonpayable";
7598
7719
  readonly type: "constructor";
7720
+ }, {
7721
+ readonly inputs: readonly [];
7722
+ readonly name: "AlreadyStopped";
7723
+ readonly type: "error";
7599
7724
  }, {
7600
7725
  readonly inputs: readonly [{
7601
7726
  readonly internalType: "uint256";
@@ -7620,6 +7745,14 @@ declare const StablecoinBridgeABI: readonly [{
7620
7745
  }];
7621
7746
  readonly name: "Limit";
7622
7747
  readonly type: "error";
7748
+ }, {
7749
+ readonly inputs: readonly [];
7750
+ readonly name: "NoGovernance";
7751
+ readonly type: "error";
7752
+ }, {
7753
+ readonly inputs: readonly [];
7754
+ readonly name: "NotQualified";
7755
+ readonly type: "error";
7623
7756
  }, {
7624
7757
  readonly inputs: readonly [{
7625
7758
  readonly internalType: "address";
@@ -7628,6 +7761,10 @@ declare const StablecoinBridgeABI: readonly [{
7628
7761
  }];
7629
7762
  readonly name: "SafeERC20FailedOperation";
7630
7763
  readonly type: "error";
7764
+ }, {
7765
+ readonly inputs: readonly [];
7766
+ readonly name: "Stopped";
7767
+ readonly type: "error";
7631
7768
  }, {
7632
7769
  readonly inputs: readonly [{
7633
7770
  readonly internalType: "address";
@@ -7636,6 +7773,21 @@ declare const StablecoinBridgeABI: readonly [{
7636
7773
  }];
7637
7774
  readonly name: "UnsupportedToken";
7638
7775
  readonly type: "error";
7776
+ }, {
7777
+ readonly anonymous: false;
7778
+ readonly inputs: readonly [{
7779
+ readonly indexed: true;
7780
+ readonly internalType: "address";
7781
+ readonly name: "caller";
7782
+ readonly type: "address";
7783
+ }, {
7784
+ readonly indexed: false;
7785
+ readonly internalType: "string";
7786
+ readonly name: "message";
7787
+ readonly type: "string";
7788
+ }];
7789
+ readonly name: "EmergencyStopped";
7790
+ readonly type: "event";
7639
7791
  }, {
7640
7792
  readonly inputs: readonly [];
7641
7793
  readonly name: "JUSD";
@@ -7670,6 +7822,20 @@ declare const StablecoinBridgeABI: readonly [{
7670
7822
  readonly outputs: readonly [];
7671
7823
  readonly stateMutability: "nonpayable";
7672
7824
  readonly type: "function";
7825
+ }, {
7826
+ readonly inputs: readonly [{
7827
+ readonly internalType: "address[]";
7828
+ readonly name: "_helpers";
7829
+ readonly type: "address[]";
7830
+ }, {
7831
+ readonly internalType: "string";
7832
+ readonly name: "_message";
7833
+ readonly type: "string";
7834
+ }];
7835
+ readonly name: "emergencyStop";
7836
+ readonly outputs: readonly [];
7837
+ readonly stateMutability: "nonpayable";
7838
+ readonly type: "function";
7673
7839
  }, {
7674
7840
  readonly inputs: readonly [];
7675
7841
  readonly name: "horizon";
@@ -7724,6 +7890,16 @@ declare const StablecoinBridgeABI: readonly [{
7724
7890
  }];
7725
7891
  readonly stateMutability: "view";
7726
7892
  readonly type: "function";
7893
+ }, {
7894
+ readonly inputs: readonly [];
7895
+ readonly name: "stopped";
7896
+ readonly outputs: readonly [{
7897
+ readonly internalType: "bool";
7898
+ readonly name: "";
7899
+ readonly type: "bool";
7900
+ }];
7901
+ readonly stateMutability: "view";
7902
+ readonly type: "function";
7727
7903
  }, {
7728
7904
  readonly inputs: readonly [];
7729
7905
  readonly name: "usd";
@@ -7764,6 +7940,10 @@ declare const MintingHubV2ABI: readonly [{
7764
7940
  readonly inputs: readonly [];
7765
7941
  readonly name: "ChallengeTimeTooShort";
7766
7942
  readonly type: "error";
7943
+ }, {
7944
+ readonly inputs: readonly [];
7945
+ readonly name: "EmptyMessage";
7946
+ readonly type: "error";
7767
7947
  }, {
7768
7948
  readonly inputs: readonly [];
7769
7949
  readonly name: "IncompatibleCollateral";
@@ -7800,6 +7980,18 @@ declare const MintingHubV2ABI: readonly [{
7800
7980
  }];
7801
7981
  readonly name: "LeaveNoDust";
7802
7982
  readonly type: "error";
7983
+ }, {
7984
+ readonly inputs: readonly [{
7985
+ readonly internalType: "uint256";
7986
+ readonly name: "length";
7987
+ readonly type: "uint256";
7988
+ }, {
7989
+ readonly internalType: "uint256";
7990
+ readonly name: "maxLength";
7991
+ readonly type: "uint256";
7992
+ }];
7993
+ readonly name: "MessageTooLong";
7994
+ readonly type: "error";
7803
7995
  }, {
7804
7996
  readonly inputs: readonly [];
7805
7997
  readonly name: "NativeOnlyForWCBTC";
@@ -7911,6 +8103,26 @@ declare const MintingHubV2ABI: readonly [{
7911
8103
  }];
7912
8104
  readonly name: "ForcedSale";
7913
8105
  readonly type: "event";
8106
+ }, {
8107
+ readonly anonymous: false;
8108
+ readonly inputs: readonly [{
8109
+ readonly indexed: true;
8110
+ readonly internalType: "address";
8111
+ readonly name: "position";
8112
+ readonly type: "address";
8113
+ }, {
8114
+ readonly indexed: true;
8115
+ readonly internalType: "address";
8116
+ readonly name: "denier";
8117
+ readonly type: "address";
8118
+ }, {
8119
+ readonly indexed: false;
8120
+ readonly internalType: "string";
8121
+ readonly name: "message";
8122
+ readonly type: "string";
8123
+ }];
8124
+ readonly name: "PositionDeniedByGovernance";
8125
+ readonly type: "event";
7914
8126
  }, {
7915
8127
  readonly anonymous: false;
7916
8128
  readonly inputs: readonly [{
@@ -7936,6 +8148,31 @@ declare const MintingHubV2ABI: readonly [{
7936
8148
  }];
7937
8149
  readonly name: "PositionOpened";
7938
8150
  readonly type: "event";
8151
+ }, {
8152
+ readonly anonymous: false;
8153
+ readonly inputs: readonly [{
8154
+ readonly indexed: true;
8155
+ readonly internalType: "address";
8156
+ readonly name: "position";
8157
+ readonly type: "address";
8158
+ }, {
8159
+ readonly indexed: false;
8160
+ readonly internalType: "uint256";
8161
+ readonly name: "collateral";
8162
+ readonly type: "uint256";
8163
+ }, {
8164
+ readonly indexed: false;
8165
+ readonly internalType: "uint256";
8166
+ readonly name: "price";
8167
+ readonly type: "uint256";
8168
+ }, {
8169
+ readonly indexed: false;
8170
+ readonly internalType: "uint256";
8171
+ readonly name: "principal";
8172
+ readonly type: "uint256";
8173
+ }];
8174
+ readonly name: "PositionUpdate";
8175
+ readonly type: "event";
7939
8176
  }, {
7940
8177
  readonly anonymous: false;
7941
8178
  readonly inputs: readonly [{
@@ -8188,6 +8425,38 @@ declare const MintingHubV2ABI: readonly [{
8188
8425
  }];
8189
8426
  readonly stateMutability: "payable";
8190
8427
  readonly type: "function";
8428
+ }, {
8429
+ readonly inputs: readonly [{
8430
+ readonly internalType: "address";
8431
+ readonly name: "denier";
8432
+ readonly type: "address";
8433
+ }, {
8434
+ readonly internalType: "string";
8435
+ readonly name: "message";
8436
+ readonly type: "string";
8437
+ }];
8438
+ readonly name: "emitPositionDenied";
8439
+ readonly outputs: readonly [];
8440
+ readonly stateMutability: "nonpayable";
8441
+ readonly type: "function";
8442
+ }, {
8443
+ readonly inputs: readonly [{
8444
+ readonly internalType: "uint256";
8445
+ readonly name: "_collateral";
8446
+ readonly type: "uint256";
8447
+ }, {
8448
+ readonly internalType: "uint256";
8449
+ readonly name: "_price";
8450
+ readonly type: "uint256";
8451
+ }, {
8452
+ readonly internalType: "uint256";
8453
+ readonly name: "_principal";
8454
+ readonly type: "uint256";
8455
+ }];
8456
+ readonly name: "emitPositionUpdate";
8457
+ readonly outputs: readonly [];
8458
+ readonly stateMutability: "nonpayable";
8459
+ readonly type: "function";
8191
8460
  }, {
8192
8461
  readonly inputs: readonly [{
8193
8462
  readonly internalType: "contract IPosition";