github.com/ethersphere/bee/v2@v2.2.0/pkg/api/staking_test.go (about) 1 // Copyright 2022 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package api_test 6 7 import ( 8 "context" 9 "fmt" 10 "math/big" 11 "net/http" 12 "testing" 13 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethersphere/bee/v2/pkg/bigint" 16 17 "github.com/ethersphere/bee/v2/pkg/api" 18 "github.com/ethersphere/bee/v2/pkg/jsonhttp" 19 "github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest" 20 "github.com/ethersphere/bee/v2/pkg/sctx" 21 "github.com/ethersphere/bee/v2/pkg/storageincentives/staking" 22 stakingContractMock "github.com/ethersphere/bee/v2/pkg/storageincentives/staking/mock" 23 ) 24 25 func TestDepositStake(t *testing.T) { 26 t.Parallel() 27 28 txHash := common.HexToHash("0x1234") 29 minStake := big.NewInt(100000000000000000).String() 30 depositStake := func(amount string) string { 31 return fmt.Sprintf("/stake/%s", amount) 32 } 33 34 t.Run("ok", func(t *testing.T) { 35 t.Parallel() 36 37 contract := stakingContractMock.New( 38 stakingContractMock.WithDepositStake(func(ctx context.Context, stakedAmount *big.Int) (common.Hash, error) { 39 return txHash, nil 40 }), 41 ) 42 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 43 jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusOK) 44 }) 45 46 t.Run("with invalid stake amount", func(t *testing.T) { 47 t.Parallel() 48 49 invalidMinStake := big.NewInt(0).String() 50 contract := stakingContractMock.New( 51 stakingContractMock.WithDepositStake(func(ctx context.Context, stakedAmount *big.Int) (common.Hash, error) { 52 return common.Hash{}, staking.ErrInsufficientStakeAmount 53 }), 54 ) 55 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 56 jsonhttptest.Request(t, ts, http.MethodPost, depositStake(invalidMinStake), http.StatusBadRequest, 57 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "insufficient stake amount"})) 58 }) 59 60 t.Run("out of funds", func(t *testing.T) { 61 t.Parallel() 62 63 contract := stakingContractMock.New( 64 stakingContractMock.WithDepositStake(func(ctx context.Context, stakedAmount *big.Int) (common.Hash, error) { 65 return common.Hash{}, staking.ErrInsufficientFunds 66 }), 67 ) 68 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 69 jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusBadRequest) 70 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "out of funds"}) 71 }) 72 73 t.Run("internal error", func(t *testing.T) { 74 t.Parallel() 75 76 contract := stakingContractMock.New( 77 stakingContractMock.WithDepositStake(func(ctx context.Context, stakedAmount *big.Int) (common.Hash, error) { 78 return common.Hash{}, fmt.Errorf("some error") 79 }), 80 ) 81 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 82 jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusInternalServerError) 83 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "cannot stake"}) 84 }) 85 86 t.Run("gas limit header", func(t *testing.T) { 87 t.Parallel() 88 89 contract := stakingContractMock.New( 90 stakingContractMock.WithDepositStake(func(ctx context.Context, stakedAmount *big.Int) (common.Hash, error) { 91 gasLimit := sctx.GetGasLimit(ctx) 92 if gasLimit != 2000000 { 93 t.Fatalf("want 2000000, got %d", gasLimit) 94 } 95 return txHash, nil 96 }), 97 ) 98 ts, _, _, _ := newTestServer(t, testServerOptions{ 99 StakingContract: contract, 100 }) 101 102 jsonhttptest.Request(t, ts, http.MethodPost, depositStake(minStake), http.StatusOK, 103 jsonhttptest.WithRequestHeader(api.GasLimitHeader, "2000000"), 104 ) 105 }) 106 } 107 108 func TestGetStakeCommitted(t *testing.T) { 109 t.Parallel() 110 111 t.Run("ok", func(t *testing.T) { 112 t.Parallel() 113 114 contract := stakingContractMock.New( 115 stakingContractMock.WithGetStake(func(ctx context.Context) (*big.Int, error) { 116 return big.NewInt(1), nil 117 }), 118 ) 119 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 120 jsonhttptest.Request(t, ts, http.MethodGet, "/stake", http.StatusOK, 121 jsonhttptest.WithExpectedJSONResponse(&api.GetStakeResponse{StakedAmount: bigint.Wrap(big.NewInt(1))})) 122 }) 123 124 t.Run("with error", func(t *testing.T) { 125 t.Parallel() 126 127 contractWithError := stakingContractMock.New( 128 stakingContractMock.WithGetStake(func(ctx context.Context) (*big.Int, error) { 129 return big.NewInt(0), fmt.Errorf("get stake failed") 130 }), 131 ) 132 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError}) 133 jsonhttptest.Request(t, ts, http.MethodGet, "/stake", http.StatusInternalServerError, 134 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "get staked amount failed"})) 135 }) 136 } 137 138 func TestGetStakeWithdrawable(t *testing.T) { 139 t.Parallel() 140 141 t.Run("ok", func(t *testing.T) { 142 t.Parallel() 143 144 contract := stakingContractMock.New( 145 stakingContractMock.WithGetStake(func(ctx context.Context) (*big.Int, error) { 146 return big.NewInt(1), nil 147 }), 148 ) 149 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 150 jsonhttptest.Request(t, ts, http.MethodGet, "/stake/withdrawable", http.StatusOK, 151 jsonhttptest.WithExpectedJSONResponse(&api.GetWithdrawableResponse{WithdrawableAmount: bigint.Wrap(big.NewInt(1))})) 152 }) 153 154 t.Run("with error", func(t *testing.T) { 155 t.Parallel() 156 157 contractWithError := stakingContractMock.New( 158 stakingContractMock.WithGetStake(func(ctx context.Context) (*big.Int, error) { 159 return big.NewInt(0), fmt.Errorf("get stake failed") 160 }), 161 ) 162 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contractWithError}) 163 jsonhttptest.Request(t, ts, http.MethodGet, "/stake/withdrawable", http.StatusInternalServerError, 164 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "get staked amount failed"})) 165 }) 166 } 167 168 func Test_stakingDepositHandler_invalidInputs(t *testing.T) { 169 t.Parallel() 170 171 client, _, _, _ := newTestServer(t, testServerOptions{}) 172 173 tests := []struct { 174 name string 175 amount string 176 want jsonhttp.StatusResponse 177 }{{ 178 name: "amount - invalid value", 179 amount: "a", 180 want: jsonhttp.StatusResponse{ 181 Code: http.StatusBadRequest, 182 Message: "invalid path params", 183 Reasons: []jsonhttp.Reason{ 184 { 185 Field: "amount", 186 Error: "invalid value", 187 }, 188 }, 189 }, 190 }} 191 192 for _, tc := range tests { 193 tc := tc 194 t.Run(tc.name, func(t *testing.T) { 195 t.Parallel() 196 197 jsonhttptest.Request(t, client, http.MethodPost, "/stake/"+tc.amount, tc.want.Code, 198 jsonhttptest.WithExpectedJSONResponse(tc.want), 199 ) 200 }) 201 } 202 } 203 204 func TestWithdrawStake(t *testing.T) { 205 t.Parallel() 206 207 txHash := common.HexToHash("0x1234") 208 209 t.Run("ok", func(t *testing.T) { 210 t.Parallel() 211 212 contract := stakingContractMock.New( 213 stakingContractMock.WithWithdrawStake(func(ctx context.Context) (common.Hash, error) { 214 return txHash, nil 215 }), 216 ) 217 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 218 jsonhttptest.Request(t, ts, http.MethodDelete, "/stake/withdrawable", http.StatusOK, jsonhttptest.WithExpectedJSONResponse( 219 &api.StakeTransactionReponse{TxHash: txHash.String()})) 220 }) 221 222 t.Run("with invalid stake amount", func(t *testing.T) { 223 t.Parallel() 224 225 contract := stakingContractMock.New( 226 stakingContractMock.WithWithdrawStake(func(ctx context.Context) (common.Hash, error) { 227 return common.Hash{}, staking.ErrInsufficientStake 228 }), 229 ) 230 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 231 jsonhttptest.Request(t, ts, http.MethodDelete, "/stake/withdrawable", http.StatusBadRequest, 232 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "insufficient stake to withdraw"})) 233 }) 234 235 t.Run("internal error", func(t *testing.T) { 236 t.Parallel() 237 238 contract := stakingContractMock.New( 239 stakingContractMock.WithWithdrawStake(func(ctx context.Context) (common.Hash, error) { 240 return common.Hash{}, fmt.Errorf("some error") 241 }), 242 ) 243 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 244 jsonhttptest.Request(t, ts, http.MethodDelete, "/stake/withdrawable", http.StatusInternalServerError) 245 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "cannot withdraw stake"}) 246 }) 247 248 t.Run("gas limit header", func(t *testing.T) { 249 t.Parallel() 250 251 contract := stakingContractMock.New( 252 stakingContractMock.WithWithdrawStake(func(ctx context.Context) (common.Hash, error) { 253 gasLimit := sctx.GetGasLimit(ctx) 254 if gasLimit != 2000000 { 255 t.Fatalf("want 2000000, got %d", gasLimit) 256 } 257 return txHash, nil 258 }), 259 ) 260 ts, _, _, _ := newTestServer(t, testServerOptions{ 261 StakingContract: contract, 262 }) 263 264 jsonhttptest.Request(t, ts, http.MethodDelete, "/stake/withdrawable", http.StatusOK, 265 jsonhttptest.WithRequestHeader(api.GasLimitHeader, "2000000"), 266 ) 267 }) 268 } 269 270 func TestMigrateStake(t *testing.T) { 271 t.Parallel() 272 273 txHash := common.HexToHash("0x1234") 274 275 t.Run("ok", func(t *testing.T) { 276 t.Parallel() 277 278 contract := stakingContractMock.New( 279 stakingContractMock.WithMigrateStake(func(ctx context.Context) (common.Hash, error) { 280 return txHash, nil 281 }), 282 ) 283 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 284 jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusOK, jsonhttptest.WithExpectedJSONResponse( 285 &api.StakeTransactionReponse{TxHash: txHash.String()})) 286 }) 287 288 t.Run("with invalid stake amount", func(t *testing.T) { 289 t.Parallel() 290 291 contract := stakingContractMock.New( 292 stakingContractMock.WithMigrateStake(func(ctx context.Context) (common.Hash, error) { 293 return common.Hash{}, staking.ErrInsufficientStake 294 }), 295 ) 296 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 297 jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusBadRequest, 298 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusBadRequest, Message: "insufficient stake to migrate"})) 299 }) 300 301 t.Run("internal error", func(t *testing.T) { 302 t.Parallel() 303 304 contract := stakingContractMock.New( 305 stakingContractMock.WithMigrateStake(func(ctx context.Context) (common.Hash, error) { 306 return common.Hash{}, fmt.Errorf("some error") 307 }), 308 ) 309 ts, _, _, _ := newTestServer(t, testServerOptions{StakingContract: contract}) 310 jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusInternalServerError) 311 jsonhttptest.WithExpectedJSONResponse(&jsonhttp.StatusResponse{Code: http.StatusInternalServerError, Message: "cannot withdraw stake"}) 312 }) 313 314 t.Run("gas limit header", func(t *testing.T) { 315 t.Parallel() 316 317 contract := stakingContractMock.New( 318 stakingContractMock.WithMigrateStake(func(ctx context.Context) (common.Hash, error) { 319 gasLimit := sctx.GetGasLimit(ctx) 320 if gasLimit != 2000000 { 321 t.Fatalf("want 2000000, got %d", gasLimit) 322 } 323 return txHash, nil 324 }), 325 ) 326 ts, _, _, _ := newTestServer(t, testServerOptions{ 327 StakingContract: contract, 328 }) 329 330 jsonhttptest.Request(t, ts, http.MethodDelete, "/stake", http.StatusOK, 331 jsonhttptest.WithRequestHeader(api.GasLimitHeader, "2000000"), 332 ) 333 }) 334 }