1 | package org.cardanofoundation.explorer.api.service.impl; | |
2 | ||
3 | import java.math.BigInteger; | |
4 | import java.time.LocalDate; | |
5 | import java.time.LocalDateTime; | |
6 | import java.util.ArrayList; | |
7 | import java.util.Collections; | |
8 | import java.util.List; | |
9 | import java.util.Map; | |
10 | import java.util.Optional; | |
11 | import java.util.Set; | |
12 | import java.util.function.Function; | |
13 | import java.util.stream.Collectors; | |
14 | ||
15 | import lombok.RequiredArgsConstructor; | |
16 | import lombok.extern.log4j.Log4j2; | |
17 | ||
18 | import org.springframework.beans.factory.annotation.Value; | |
19 | import org.springframework.data.domain.Pageable; | |
20 | import org.springframework.stereotype.Service; | |
21 | ||
22 | import org.cardanofoundation.conversions.CardanoConverters; | |
23 | import org.cardanofoundation.explorer.api.common.constant.CommonConstant; | |
24 | import org.cardanofoundation.explorer.api.common.constant.CommonConstant.NetworkType; | |
25 | import org.cardanofoundation.explorer.api.common.enumeration.AnalyticType; | |
26 | import org.cardanofoundation.explorer.api.exception.BusinessCode; | |
27 | import org.cardanofoundation.explorer.api.mapper.TokenMapper; | |
28 | import org.cardanofoundation.explorer.api.model.response.BaseFilterResponse; | |
29 | import org.cardanofoundation.explorer.api.model.response.address.AddressChartBalanceData; | |
30 | import org.cardanofoundation.explorer.api.model.response.address.AddressChartBalanceResponse; | |
31 | import org.cardanofoundation.explorer.api.model.response.address.AddressResponse; | |
32 | import org.cardanofoundation.explorer.api.model.response.token.TokenAddressResponse; | |
33 | import org.cardanofoundation.explorer.api.projection.AddressTokenProjection; | |
34 | import org.cardanofoundation.explorer.api.repository.ledgersync.MultiAssetRepository; | |
35 | import org.cardanofoundation.explorer.api.repository.ledgersync.ScriptRepository; | |
36 | import org.cardanofoundation.explorer.api.repository.ledgersyncagg.AddressRepository; | |
37 | import org.cardanofoundation.explorer.api.repository.ledgersyncagg.AddressTxAmountRepository; | |
38 | import org.cardanofoundation.explorer.api.repository.ledgersyncagg.AddressTxCountRepository; | |
39 | import org.cardanofoundation.explorer.api.repository.ledgersyncagg.AggregateAddressTxBalanceRepository; | |
40 | import org.cardanofoundation.explorer.api.repository.ledgersyncagg.LatestTokenBalanceRepository; | |
41 | import org.cardanofoundation.explorer.api.service.AddressService; | |
42 | import org.cardanofoundation.explorer.api.util.AddressUtils; | |
43 | import org.cardanofoundation.explorer.api.util.DataUtil; | |
44 | import org.cardanofoundation.explorer.api.util.DateUtils; | |
45 | import org.cardanofoundation.explorer.common.entity.enumeration.ScriptType; | |
46 | import org.cardanofoundation.explorer.common.entity.ledgersyncsagg.Address; | |
47 | import org.cardanofoundation.explorer.common.entity.ledgersyncsagg.AddressTxCount; | |
48 | import org.cardanofoundation.explorer.common.entity.ledgersyncsagg.AggregateAddressTxBalance; | |
49 | import org.cardanofoundation.explorer.common.entity.ledgersyncsagg.LatestTokenBalance; | |
50 | import org.cardanofoundation.explorer.common.exception.BusinessException; | |
51 | ||
52 | @Service | |
53 | @RequiredArgsConstructor | |
54 | @Log4j2 | |
55 | public class AddressServiceImpl implements AddressService { | |
56 | ||
57 | private final AddressRepository addressRepository; | |
58 | private final LatestTokenBalanceRepository latestTokenBalanceRepository; | |
59 | private final TokenMapper tokenMapper; | |
60 | private final ScriptRepository scriptRepository; | |
61 | private final AggregateAddressTxBalanceRepository aggregateAddressTxBalanceRepository; | |
62 | private final AddressTxCountRepository addressTxCountRepository; | |
63 | private final AddressTxAmountRepository addressTxAmountRepository; | |
64 | private final MultiAssetRepository multiAssetRepository; | |
65 | private final CardanoConverters cardanoConverters; | |
66 | ||
67 | @Value("${application.network}") | |
68 | private String network; | |
69 | ||
70 | @Override | |
71 | public AddressResponse getAddressDetail(String address) { | |
72 | final int ADDRESS_MIN_LENGTH = 56; | |
73 |
3
1. getAddressDetail : changed conditional boundary → SURVIVED 2. getAddressDetail : negated conditional → KILLED 3. getAddressDetail : negated conditional → KILLED |
if (!checkNetworkAddress(address) || address.length() < ADDRESS_MIN_LENGTH) { |
74 | throw new BusinessException(BusinessCode.ADDRESS_NOT_FOUND); | |
75 | } | |
76 | ||
77 | AddressResponse addressResponse = | |
78 | AddressResponse.fromProjection(addressRepository.getAddressDetail(address)); | |
79 | ||
80 |
1
1. getAddressDetail : negated conditional → KILLED |
if (addressResponse == null) { |
81 | addressResponse = | |
82 | AddressResponse.builder().address(address).txCount(0L).balance(BigInteger.ZERO).build(); | |
83 | } | |
84 | ||
85 |
1
1. getAddressDetail : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressResponse::setStakeAddress → KILLED |
addressResponse.setStakeAddress(AddressUtils.checkStakeAddress(address)); |
86 |
1
1. getAddressDetail : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressResponse::setScriptHash → KILLED |
addressResponse.setScriptHash(AddressUtils.getHexPaymentPart(address)); |
87 |
1
1. getAddressDetail : removed call to org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::setAssociatedScript → KILLED |
setAssociatedScript(addressResponse); |
88 |
1
1. getAddressDetail : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::getAddressDetail → KILLED |
return addressResponse; |
89 | } | |
90 | ||
91 | /** | |
92 | * Set associated script for address (native script or smart contract) | |
93 | * | |
94 | * @param addressResponse | |
95 | */ | |
96 | private void setAssociatedScript(AddressResponse addressResponse) { | |
97 |
1
1. setAssociatedScript : negated conditional → KILLED |
if (addressResponse.getScriptHash() != null) { |
98 | scriptRepository | |
99 | .findByHash(addressResponse.getScriptHash()) | |
100 |
1
1. setAssociatedScript : removed call to java/util/Optional::ifPresent → KILLED |
.ifPresent( |
101 | script -> { | |
102 |
1
1. lambda$setAssociatedScript$0 : negated conditional → KILLED |
if (ScriptType.PLUTUSV1.equals(script.getType()) |
103 |
1
1. lambda$setAssociatedScript$0 : negated conditional → KILLED |
|| ScriptType.PLUTUSV2.equals(script.getType()) |
104 |
1
1. lambda$setAssociatedScript$0 : negated conditional → KILLED |
|| ScriptType.PLUTUSV3.equals(script.getType())) { |
105 |
1
1. lambda$setAssociatedScript$0 : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressResponse::setAssociatedSmartContract → KILLED |
addressResponse.setAssociatedSmartContract(Boolean.TRUE); |
106 | } else { | |
107 |
1
1. lambda$setAssociatedScript$0 : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressResponse::setAssociatedNativeScript → KILLED |
addressResponse.setAssociatedNativeScript(Boolean.TRUE); |
108 | } | |
109 | }); | |
110 | } | |
111 | } | |
112 | ||
113 | /** | |
114 | * Check address is valid in this network | |
115 | * | |
116 | * @param address address view value | |
117 | * @return true if valid and false if not | |
118 | */ | |
119 | private boolean checkNetworkAddress(String address) { | |
120 |
1
1. checkNetworkAddress : negated conditional → KILLED |
if (address.startsWith(CommonConstant.TESTNET_ADDRESS_PREFIX)) { |
121 |
2
1. checkNetworkAddress : replaced boolean return with true for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::checkNetworkAddress → SURVIVED 2. checkNetworkAddress : negated conditional → KILLED |
return !network.equals(NetworkType.MAINNET); |
122 |
1
1. checkNetworkAddress : negated conditional → SURVIVED |
} else if (address.startsWith(CommonConstant.ADDRESS_PREFIX)) { |
123 |
2
1. checkNetworkAddress : replaced boolean return with true for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::checkNetworkAddress → SURVIVED 2. checkNetworkAddress : replaced boolean return with false for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::checkNetworkAddress → KILLED |
return network.equals(NetworkType.MAINNET); |
124 | } else { // Genesis address | |
125 |
1
1. checkNetworkAddress : replaced boolean return with false for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::checkNetworkAddress → NO_COVERAGE |
return true; |
126 | } | |
127 | } | |
128 | ||
129 | @Override | |
130 | public AddressChartBalanceResponse getAddressAnalytics(String address, AnalyticType type) { | |
131 | Address addr = | |
132 | addressRepository | |
133 | .findFirstByAddress(address) | |
134 |
1
1. lambda$getAddressAnalytics$1 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::lambda$getAddressAnalytics$1 → NO_COVERAGE |
.orElseThrow(() -> new BusinessException(BusinessCode.ADDRESS_NOT_FOUND)); |
135 | AddressChartBalanceResponse response = new AddressChartBalanceResponse(); | |
136 | ||
137 | AddressTxCount addressTxCount = | |
138 | addressTxCountRepository | |
139 | .findById(address) | |
140 | .orElse(AddressTxCount.builder().address(address).txCount(0L).build()); | |
141 | ||
142 |
1
1. getAddressAnalytics : negated conditional → KILLED |
if (Long.valueOf(0).equals(addressTxCount.getTxCount())) { |
143 |
1
1. getAddressAnalytics : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::getAddressAnalytics → KILLED |
return AddressChartBalanceResponse.builder() |
144 | .highestBalance(BigInteger.ZERO) | |
145 | .lowestBalance(BigInteger.ZERO) | |
146 | .data(Collections.emptyList()) | |
147 | .build(); | |
148 | } | |
149 | List<LocalDateTime> dates = DateUtils.getListDateAnalytic(type); | |
150 | ||
151 | List<AddressChartBalanceData> data = new ArrayList<>(); | |
152 |
1
1. getAddressAnalytics : negated conditional → SURVIVED |
if (AnalyticType.ONE_DAY.equals(type)) { |
153 | var fromBalance = | |
154 | addressTxAmountRepository | |
155 | .sumBalanceByAddress(addr.getAddress(), cardanoConverters.time().toSlot(dates.get(0))) | |
156 | .orElse(BigInteger.ZERO); | |
157 |
1
1. getAddressAnalytics : removed call to org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::getHighestAndLowestBalance → KILLED |
getHighestAndLowestBalance(addr, fromBalance, dates, response); |
158 | ||
159 | data.add(new AddressChartBalanceData(dates.get(0), fromBalance)); | |
160 |
2
1. getAddressAnalytics : negated conditional → SURVIVED 2. getAddressAnalytics : changed conditional boundary → KILLED |
for (int i = 1; i < dates.size(); i++) { |
161 |
1
1. getAddressAnalytics : Replaced integer subtraction with addition → KILLED |
long fromSlot = cardanoConverters.time().toSlot(dates.get(i - 1)); |
162 | long toSlot = cardanoConverters.time().toSlot(dates.get(i)); | |
163 | ||
164 | Optional<BigInteger> balance = | |
165 | addressTxAmountRepository.getBalanceByAddressAndSlotBetween( | |
166 | addr.getAddress(), fromSlot, toSlot); | |
167 | ||
168 |
1
1. getAddressAnalytics : negated conditional → KILLED |
if (balance.isPresent()) { |
169 | fromBalance = fromBalance.add(balance.get()); | |
170 | } | |
171 | data.add(new AddressChartBalanceData(dates.get(i), fromBalance)); | |
172 | } | |
173 |
1
1. getAddressAnalytics : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressChartBalanceResponse::setData → SURVIVED |
response.setData(data); |
174 | } else { | |
175 | // Remove last date because we will get data of today | |
176 | dates.remove(0); | |
177 | ||
178 | var fromBalance = | |
179 | addressTxAmountRepository | |
180 | .sumBalanceByAddress(addr.getAddress(), cardanoConverters.time().toSlot(dates.get(0))) | |
181 | .orElse(BigInteger.ZERO); | |
182 |
1
1. getAddressAnalytics : removed call to org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::getHighestAndLowestBalance → KILLED |
getHighestAndLowestBalance(addr, fromBalance, dates, response); |
183 | ||
184 | List<AggregateAddressTxBalance> aggregateAddressTxBalances = | |
185 | aggregateAddressTxBalanceRepository.findAllByAddressIdAndDayBetween( | |
186 | addr.getAddress(), | |
187 | dates.get(0).toLocalDate(), | |
188 |
1
1. getAddressAnalytics : Replaced integer subtraction with addition → KILLED |
dates.get(dates.size() - 1).toLocalDate()); |
189 | ||
190 | // Data in aggregate_address_tx_balance save at end of day, but we will display start of day | |
191 | // So we need to add 1 day to display correct data | |
192 | Map<LocalDate, BigInteger> mapBalance = | |
193 | aggregateAddressTxBalances.stream() | |
194 | .collect( | |
195 | Collectors.toMap( | |
196 |
1
1. lambda$getAddressAnalytics$2 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::lambda$getAddressAnalytics$2 → NO_COVERAGE |
balance -> balance.getDay().plusDays(1), |
197 | AggregateAddressTxBalance::getBalance)); | |
198 | for (LocalDateTime date : dates) { | |
199 |
1
1. getAddressAnalytics : negated conditional → KILLED |
if (mapBalance.containsKey(date.toLocalDate())) { |
200 | fromBalance = fromBalance.add(mapBalance.get(date.toLocalDate())); | |
201 | } | |
202 | data.add(new AddressChartBalanceData(date, fromBalance)); | |
203 | } | |
204 |
1
1. getAddressAnalytics : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressChartBalanceResponse::setData → SURVIVED |
response.setData(data); |
205 | } | |
206 |
1
1. getAddressAnalytics : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::getAddressAnalytics → KILLED |
return response; |
207 | } | |
208 | ||
209 | /** | |
210 | * Get highest and lowest balance of address | |
211 | * | |
212 | * @param addr address | |
213 | * @param fromBalance balance of address at start date | |
214 | * @param dates list date | |
215 | * @param response chart response | |
216 | */ | |
217 | private void getHighestAndLowestBalance( | |
218 | Address addr, | |
219 | BigInteger fromBalance, | |
220 | List<LocalDateTime> dates, | |
221 | AddressChartBalanceResponse response) { | |
222 | long fromSlot = cardanoConverters.time().toSlot(dates.get(0)); | |
223 |
1
1. getHighestAndLowestBalance : Replaced integer subtraction with addition → KILLED |
long toSlot = cardanoConverters.time().toSlot(dates.get(dates.size() - 1)); |
224 | ||
225 | var minMaxBalance = | |
226 | addressTxAmountRepository.findMinMaxBalanceByAddress( | |
227 | addr.getAddress(), fromBalance, fromSlot, toSlot); | |
228 | ||
229 |
2
1. getHighestAndLowestBalance : changed conditional boundary → SURVIVED 2. getHighestAndLowestBalance : negated conditional → SURVIVED |
if (minMaxBalance.getMaxVal().compareTo(fromBalance) > 0) { |
230 |
1
1. getHighestAndLowestBalance : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressChartBalanceResponse::setHighestBalance → NO_COVERAGE |
response.setHighestBalance(minMaxBalance.getMaxVal()); |
231 | } else { | |
232 |
1
1. getHighestAndLowestBalance : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressChartBalanceResponse::setHighestBalance → SURVIVED |
response.setHighestBalance(fromBalance); |
233 | } | |
234 |
2
1. getHighestAndLowestBalance : changed conditional boundary → SURVIVED 2. getHighestAndLowestBalance : negated conditional → SURVIVED |
if (minMaxBalance.getMinVal().compareTo(fromBalance) < 0) { |
235 |
1
1. getHighestAndLowestBalance : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressChartBalanceResponse::setLowestBalance → NO_COVERAGE |
response.setLowestBalance(minMaxBalance.getMinVal()); |
236 | } else { | |
237 |
1
1. getHighestAndLowestBalance : removed call to org/cardanofoundation/explorer/api/model/response/address/AddressChartBalanceResponse::setLowestBalance → SURVIVED |
response.setLowestBalance(fromBalance); |
238 | } | |
239 | } | |
240 | ||
241 | /** | |
242 | * Get list token by display name | |
243 | * | |
244 | * @param pageable page information | |
245 | * @param address wallet address | |
246 | * @param displayName display name of token | |
247 | * @return list token by display name | |
248 | */ | |
249 | @Override | |
250 | public BaseFilterResponse<TokenAddressResponse> getTokenByDisplayName( | |
251 | Pageable pageable, String address, String displayName) { | |
252 | ||
253 | List<TokenAddressResponse> tokenListResponse = new ArrayList<>(); | |
254 | List<LatestTokenBalance> latestTokenBalances = | |
255 | latestTokenBalanceRepository.findAllByAddress(address); | |
256 | ||
257 | Set<String> unitSet = | |
258 | latestTokenBalances.stream().map(LatestTokenBalance::getUnit).collect(Collectors.toSet()); | |
259 | ||
260 | Map<String, AddressTokenProjection> tokenMetadataMap = | |
261 | multiAssetRepository.findTokenMetadataByUnitIn(unitSet).stream() | |
262 | .collect(Collectors.toMap(AddressTokenProjection::getUnit, Function.identity())); | |
263 | ||
264 | boolean isSearchByDisplayName; | |
265 |
1
1. getTokenByDisplayName : negated conditional → KILLED |
if (!DataUtil.isNullOrEmpty(displayName)) { |
266 | isSearchByDisplayName = true; | |
267 | displayName = displayName.trim().toLowerCase(); | |
268 | } else { | |
269 | isSearchByDisplayName = false; | |
270 | } | |
271 | ||
272 | for (LatestTokenBalance latestTokenBalance : latestTokenBalances) { | |
273 | AddressTokenProjection projection = tokenMetadataMap.get(latestTokenBalance.getUnit()); | |
274 | TokenAddressResponse tokenAddressResponse = | |
275 | tokenMapper.fromAddressTokenProjection(projection); | |
276 |
1
1. getTokenByDisplayName : removed call to org/cardanofoundation/explorer/api/model/response/token/TokenAddressResponse::setQuantity → KILLED |
tokenAddressResponse.setQuantity(latestTokenBalance.getQuantity()); |
277 |
1
1. getTokenByDisplayName : removed call to org/cardanofoundation/explorer/api/model/response/token/TokenAddressResponse::setAddress → KILLED |
tokenAddressResponse.setAddress(address); |
278 | ||
279 |
1
1. getTokenByDisplayName : negated conditional → KILLED |
if (!isSearchByDisplayName) { |
280 | tokenListResponse.add(tokenAddressResponse); | |
281 |
1
1. getTokenByDisplayName : negated conditional → KILLED |
} else if (tokenAddressResponse.getDisplayName().toLowerCase().contains(displayName) |
282 |
1
1. getTokenByDisplayName : negated conditional → NO_COVERAGE |
|| tokenAddressResponse.getFingerprint().equals(displayName)) { |
283 | tokenListResponse.add(tokenAddressResponse); | |
284 | } | |
285 | } | |
286 | ||
287 |
1
1. getTokenByDisplayName : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/AddressServiceImpl::getTokenByDisplayName → KILLED |
return new BaseFilterResponse<>(BaseFilterResponse.getPageImpl(tokenListResponse, pageable)); |
288 | } | |
289 | } | |
Mutations | ||
73 |
1.1 2.2 3.3 |
|
80 |
1.1 |
|
85 |
1.1 |
|
86 |
1.1 |
|
87 |
1.1 |
|
88 |
1.1 |
|
97 |
1.1 |
|
100 |
1.1 |
|
102 |
1.1 |
|
103 |
1.1 |
|
104 |
1.1 |
|
105 |
1.1 |
|
107 |
1.1 |
|
120 |
1.1 |
|
121 |
1.1 2.2 |
|
122 |
1.1 |
|
123 |
1.1 2.2 |
|
125 |
1.1 |
|
134 |
1.1 |
|
142 |
1.1 |
|
143 |
1.1 |
|
152 |
1.1 |
|
157 |
1.1 |
|
160 |
1.1 2.2 |
|
161 |
1.1 |
|
168 |
1.1 |
|
173 |
1.1 |
|
182 |
1.1 |
|
188 |
1.1 |
|
196 |
1.1 |
|
199 |
1.1 |
|
204 |
1.1 |
|
206 |
1.1 |
|
223 |
1.1 |
|
229 |
1.1 2.2 |
|
230 |
1.1 |
|
232 |
1.1 |
|
234 |
1.1 2.2 |
|
235 |
1.1 |
|
237 |
1.1 |
|
265 |
1.1 |
|
276 |
1.1 |
|
277 |
1.1 |
|
279 |
1.1 |
|
281 |
1.1 |
|
282 |
1.1 |
|
287 |
1.1 |