From 672ad5a07301e927169eb22581c460eec5ee9cd8 Mon Sep 17 00:00:00 2001 From: Antho <43467246+AnthonyBuisset@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:00:20 +0200 Subject: [PATCH] Add Stellar support (#8) --- .../kernel/model/blockchain/Base32Hash.java | 20 +++ .../kernel/model/blockchain/Blockchain.java | 3 +- .../kernel/model/blockchain/Hash.java | 48 ++----- .../kernel/model/blockchain/HexHash.java | 24 ++++ .../model/blockchain/PrefixedHexHash.java | 30 +++++ .../kernel/model/blockchain/Stellar.java | 22 +++ .../blockchain/aptos/AptosAccountAddress.java | 4 +- .../model/blockchain/aptos/AptosCoinType.java | 7 +- .../blockchain/aptos/AptosTransaction.java | 3 +- .../blockchain/evm/EvmAccountAddress.java | 4 +- .../blockchain/evm/EvmContractAddress.java | 4 +- .../model/blockchain/evm/EvmTransaction.java | 3 +- .../starknet/StarknetAccountAddress.java | 4 +- .../starknet/StarknetContractAddress.java | 4 +- .../starknet/StarknetTransaction.java | 5 +- .../blockchain/stellar/StellarAccountId.java | 16 +++ .../stellar/StellarContractAddress.java | 16 +++ .../blockchain/stellar/StellarExpert.java | 14 ++ .../stellar/StellarTransaction.java | 18 +++ .../kernel/model/blockchain/HashTest.java | 127 ++++++++++++++++++ .../kernel/model/blockchain/StellarTest.java | 46 +++++++ 21 files changed, 364 insertions(+), 58 deletions(-) create mode 100644 src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Base32Hash.java create mode 100644 src/main/java/onlydust/com/marketplace/kernel/model/blockchain/HexHash.java create mode 100644 src/main/java/onlydust/com/marketplace/kernel/model/blockchain/PrefixedHexHash.java create mode 100644 src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Stellar.java create mode 100644 src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarAccountId.java create mode 100644 src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarContractAddress.java create mode 100644 src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarExpert.java create mode 100644 src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarTransaction.java create mode 100644 src/test/java/onlydust/com/marketplace/kernel/model/blockchain/HashTest.java create mode 100644 src/test/java/onlydust/com/marketplace/kernel/model/blockchain/StellarTest.java diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Base32Hash.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Base32Hash.java new file mode 100644 index 0000000..154f6bf --- /dev/null +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Base32Hash.java @@ -0,0 +1,20 @@ +package onlydust.com.marketplace.kernel.model.blockchain; + +import lombok.EqualsAndHashCode; +import lombok.NonNull; + +import java.util.regex.Pattern; + +import static onlydust.com.marketplace.kernel.exception.OnlyDustException.badRequest; + +@EqualsAndHashCode(callSuper = true) +public abstract class Base32Hash extends Hash { + private static final Pattern BASE32_PATTERN = Pattern.compile("[A-Z2-7]+"); + + protected Base32Hash(final int maxSize, final @NonNull String hash) { + super(maxSize, hash); + + if (!BASE32_PATTERN.matcher(hash).matches()) + throw badRequest("Provided hash is not base 32"); + } +} diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Blockchain.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Blockchain.java index 17449ba..751caec 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Blockchain.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Blockchain.java @@ -1,7 +1,7 @@ package onlydust.com.marketplace.kernel.model.blockchain; public enum Blockchain { - ETHEREUM, OPTIMISM, STARKNET, APTOS; + ETHEREUM, OPTIMISM, STARKNET, APTOS, STELLAR; public String pretty() { return switch (this) { @@ -9,6 +9,7 @@ public String pretty() { case OPTIMISM -> "Optimism"; case STARKNET -> "StarkNet"; case APTOS -> "Aptos"; + case STELLAR -> "Stellar"; }; } } diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Hash.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Hash.java index 77a9d09..3b0bfcc 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Hash.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Hash.java @@ -1,58 +1,26 @@ package onlydust.com.marketplace.kernel.model.blockchain; -import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.NonNull; -import java.util.regex.Pattern; - import static onlydust.com.marketplace.kernel.exception.OnlyDustException.badRequest; +@EqualsAndHashCode public abstract class Hash { - final @NonNull String inner; - - protected Hash(final int maxByteCount, final @NonNull String hash) { - final var validator = Validator.of(maxByteCount); - validator.check(hash); + private final String inner; - this.inner = validator.sanitize(hash); - } + protected Hash(final int maxSize, final @NonNull String hash) { + if (hash.isEmpty()) + throw badRequest("Provided hash should not be empty"); - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof Hash hash)) - return false; + if (hash.length() > maxSize) + throw badRequest("Provided hash should be less than %d characters".formatted(maxSize)); - return inner.equalsIgnoreCase(hash.inner); - } - - @Override - public int hashCode() { - return inner.hashCode(); + this.inner = hash; } @Override public String toString() { return inner; } - - @AllArgsConstructor(staticName = "of") - @EqualsAndHashCode - private static class Validator { - private static final Pattern HEX_PATTERN = Pattern.compile("^0[xX][a-fA-F0-9]+$"); - private final int maxByteCount; - - public void check(final @NonNull String hash) { - if (!HEX_PATTERN.matcher(hash).matches()) throw badRequest("Provided hash is not hexadecimal"); - if (hash.length() < 3) throw badRequest("Provided hash is too short"); - if (hash.length() > maxByteCount * 2 + 2) - throw badRequest("Provided hash should be less than %d bytes".formatted(maxByteCount)); - } - - public String sanitize(String hash) { - return "0x" + (hash.length() % 2 == 0 ? "" : "0") + hash.substring(2); - } - } } diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/HexHash.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/HexHash.java new file mode 100644 index 0000000..8dc361e --- /dev/null +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/HexHash.java @@ -0,0 +1,24 @@ +package onlydust.com.marketplace.kernel.model.blockchain; + +import lombok.EqualsAndHashCode; +import lombok.NonNull; + +import java.util.regex.Pattern; + +import static onlydust.com.marketplace.kernel.exception.OnlyDustException.badRequest; + +@EqualsAndHashCode(callSuper = true) +public abstract class HexHash extends Hash { + private static final Pattern HEX_PATTERN = Pattern.compile("[0-9a-fA-F]+"); + + protected HexHash(final int maxByteCount, final @NonNull String hash) { + super(maxByteCount * 2, sanitize(hash)); + + if (!HEX_PATTERN.matcher(hash).matches()) + throw badRequest("Provided hash is not hexadecimal"); + } + + private static String sanitize(final @NonNull String hash) { + return (hash.length() % 2 == 0 ? "" : "0") + hash; + } +} diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/PrefixedHexHash.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/PrefixedHexHash.java new file mode 100644 index 0000000..c3bef9b --- /dev/null +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/PrefixedHexHash.java @@ -0,0 +1,30 @@ +package onlydust.com.marketplace.kernel.model.blockchain; + +import lombok.EqualsAndHashCode; +import lombok.NonNull; + +import static java.lang.Math.min; +import static onlydust.com.marketplace.kernel.exception.OnlyDustException.badRequest; + +@EqualsAndHashCode(callSuper = true) +public abstract class PrefixedHexHash extends HexHash { + private final String prefix; + + protected PrefixedHexHash(final int maxByteCount, final @NonNull String hash) { + this(maxByteCount, "0x", hash); + } + + protected PrefixedHexHash(final int maxByteCount, final @NonNull String prefix, final @NonNull String hash) { + super(maxByteCount, hash.substring(min(hash.length(), prefix.length()))); + this.prefix = prefix; + + final var hashPrefix = hash.substring(0, prefix.length()); + if (!hashPrefix.equalsIgnoreCase(prefix)) + throw badRequest("Provided hash should start with %s".formatted(prefix)); + } + + @Override + public String toString() { + return prefix + super.toString(); + } +} diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Stellar.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Stellar.java new file mode 100644 index 0000000..d5a5431 --- /dev/null +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/Stellar.java @@ -0,0 +1,22 @@ +package onlydust.com.marketplace.kernel.model.blockchain; + +import onlydust.com.marketplace.kernel.model.blockchain.stellar.StellarAccountId; +import onlydust.com.marketplace.kernel.model.blockchain.stellar.StellarContractAddress; +import onlydust.com.marketplace.kernel.model.blockchain.stellar.StellarExpert; +import onlydust.com.marketplace.kernel.model.blockchain.stellar.StellarTransaction; + +public interface Stellar { + BlockExplorer BLOCK_EXPLORER = new StellarExpert(); + + static StellarTransaction.Hash transactionHash(String value) { + return new StellarTransaction.Hash(value); + } + + static StellarAccountId accountId(String value) { + return StellarAccountId.of(value); + } + + static StellarContractAddress contractAddress(final String address) { + return StellarContractAddress.of(address); + } +} diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosAccountAddress.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosAccountAddress.java index 44a5dfa..77d232d 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosAccountAddress.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosAccountAddress.java @@ -2,10 +2,10 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; -import onlydust.com.marketplace.kernel.model.blockchain.Hash; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; @EqualsAndHashCode(callSuper = true) -public class AptosAccountAddress extends Hash { +public class AptosAccountAddress extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 32; public AptosAccountAddress(final @NonNull String address) { diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosCoinType.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosCoinType.java index 8d35493..1eb20eb 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosCoinType.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosCoinType.java @@ -2,13 +2,14 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; -import onlydust.com.marketplace.kernel.model.blockchain.Hash; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; @EqualsAndHashCode(callSuper = true) -public class AptosCoinType extends Hash { +public class AptosCoinType extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 32; - @NonNull String coinType; + @NonNull + String coinType; public AptosCoinType(final @NonNull String coinType) { super(MAX_BYTE_COUNT, coinType.split(":")[0]); diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosTransaction.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosTransaction.java index 9da53a2..46deecd 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosTransaction.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/aptos/AptosTransaction.java @@ -2,12 +2,13 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; import java.time.ZonedDateTime; public record AptosTransaction(Hash hash, ZonedDateTime timestamp) { @EqualsAndHashCode(callSuper = true) - public static class Hash extends onlydust.com.marketplace.kernel.model.blockchain.Hash { + public static class Hash extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 32; public Hash(final @NonNull String address) { diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmAccountAddress.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmAccountAddress.java index 2a7795e..2c2458d 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmAccountAddress.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmAccountAddress.java @@ -2,10 +2,10 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; -import onlydust.com.marketplace.kernel.model.blockchain.Hash; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; @EqualsAndHashCode(callSuper = true) -public class EvmAccountAddress extends Hash { +public class EvmAccountAddress extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 20; public EvmAccountAddress(final @NonNull String address) { diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmContractAddress.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmContractAddress.java index 8201934..e13665b 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmContractAddress.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmContractAddress.java @@ -2,10 +2,10 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; -import onlydust.com.marketplace.kernel.model.blockchain.Hash; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; @EqualsAndHashCode(callSuper = true) -public class EvmContractAddress extends Hash { +public class EvmContractAddress extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 20; public EvmContractAddress(final @NonNull String address) { diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmTransaction.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmTransaction.java index e60bb06..b1bad30 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmTransaction.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/evm/EvmTransaction.java @@ -2,13 +2,14 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; import java.time.ZonedDateTime; public record EvmTransaction(Hash hash, ZonedDateTime timestamp) { @EqualsAndHashCode(callSuper = true) - public static class Hash extends onlydust.com.marketplace.kernel.model.blockchain.Hash { + public static class Hash extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 32; public Hash(final @NonNull String address) { diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetAccountAddress.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetAccountAddress.java index f93f296..e61ac36 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetAccountAddress.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetAccountAddress.java @@ -2,10 +2,10 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; -import onlydust.com.marketplace.kernel.model.blockchain.Hash; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; @EqualsAndHashCode(callSuper = true) -public class StarknetAccountAddress extends Hash { +public class StarknetAccountAddress extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 32; public StarknetAccountAddress(final @NonNull String address) { diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetContractAddress.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetContractAddress.java index ec9a9d9..757029e 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetContractAddress.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetContractAddress.java @@ -2,10 +2,10 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; -import onlydust.com.marketplace.kernel.model.blockchain.Hash; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; @EqualsAndHashCode(callSuper = true) -public class StarknetContractAddress extends Hash { +public class StarknetContractAddress extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 32; public StarknetContractAddress(final @NonNull String address) { diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetTransaction.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetTransaction.java index 9b2f9cd..8a279f6 100644 --- a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetTransaction.java +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/starknet/StarknetTransaction.java @@ -2,12 +2,13 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; +import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash; import java.time.ZonedDateTime; -public record StarknetTransaction(onlydust.com.marketplace.kernel.model.blockchain.Hash hash, ZonedDateTime timestamp) { +public record StarknetTransaction(Hash hash, ZonedDateTime timestamp) { @EqualsAndHashCode(callSuper = true) - public static class Hash extends onlydust.com.marketplace.kernel.model.blockchain.Hash { + public static class Hash extends PrefixedHexHash { private static final int MAX_BYTE_COUNT = 32; public Hash(final @NonNull String address) { diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarAccountId.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarAccountId.java new file mode 100644 index 0000000..3025955 --- /dev/null +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarAccountId.java @@ -0,0 +1,16 @@ +package onlydust.com.marketplace.kernel.model.blockchain.stellar; + +import lombok.NonNull; +import onlydust.com.marketplace.kernel.model.blockchain.Base32Hash; + +public class StellarAccountId extends Base32Hash { + private static final int MAX_SIZE = 56; + + private StellarAccountId(final @NonNull String accountId) { + super(MAX_SIZE, accountId); + } + + public static StellarAccountId of(final @NonNull String accountId) { + return new StellarAccountId(accountId); + } +} diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarContractAddress.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarContractAddress.java new file mode 100644 index 0000000..7f388b1 --- /dev/null +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarContractAddress.java @@ -0,0 +1,16 @@ +package onlydust.com.marketplace.kernel.model.blockchain.stellar; + +import lombok.NonNull; +import onlydust.com.marketplace.kernel.model.blockchain.Base32Hash; + +public class StellarContractAddress extends Base32Hash { + private static final int MAX_SIZE = 56; + + private StellarContractAddress(final @NonNull String accountId) { + super(MAX_SIZE, accountId); + } + + public static StellarContractAddress of(final @NonNull String accountId) { + return new StellarContractAddress(accountId); + } +} diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarExpert.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarExpert.java new file mode 100644 index 0000000..66d0a61 --- /dev/null +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarExpert.java @@ -0,0 +1,14 @@ +package onlydust.com.marketplace.kernel.model.blockchain.stellar; + +import onlydust.com.marketplace.kernel.model.blockchain.BlockExplorer; + +import java.net.URI; + +public class StellarExpert implements BlockExplorer { + private static final String BASE_URL = "https://stellar.expert/explorer/public"; + + @Override + public URI url(StellarTransaction.Hash transactionHash) { + return URI.create(BASE_URL + "/tx/" + transactionHash); + } +} diff --git a/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarTransaction.java b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarTransaction.java new file mode 100644 index 0000000..a1d4981 --- /dev/null +++ b/src/main/java/onlydust/com/marketplace/kernel/model/blockchain/stellar/StellarTransaction.java @@ -0,0 +1,18 @@ +package onlydust.com.marketplace.kernel.model.blockchain.stellar; + +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import onlydust.com.marketplace.kernel.model.blockchain.HexHash; + +import java.time.ZonedDateTime; + +public record StellarTransaction(Hash hash, ZonedDateTime timestamp) { + @EqualsAndHashCode(callSuper = true) + public static class Hash extends HexHash { + private static final int MAX_BYTE_COUNT = 32; + + public Hash(final @NonNull String address) { + super(MAX_BYTE_COUNT, address); + } + } +} diff --git a/src/test/java/onlydust/com/marketplace/kernel/model/blockchain/HashTest.java b/src/test/java/onlydust/com/marketplace/kernel/model/blockchain/HashTest.java new file mode 100644 index 0000000..32bf1ac --- /dev/null +++ b/src/test/java/onlydust/com/marketplace/kernel/model/blockchain/HashTest.java @@ -0,0 +1,127 @@ +package onlydust.com.marketplace.kernel.model.blockchain; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class HashTest { + @Nested + class PrefixedHashTest { + @Test + void should_accept_valid_hash() { + new TestHash("0x123456af"); + new TestHash("0X123456AF"); + } + + @Test + void should_reject_invalid_hash() { + assertThatThrownBy(() -> new TestHash("12345678")).hasMessage("Provided hash should start with 0x"); + assertThatThrownBy(() -> new TestHash("0x123456798123")).hasMessage("Provided hash should be less than 8 characters"); + assertThatThrownBy(() -> new TestHash("0x")).hasMessage("Provided hash should not be empty"); + assertThatThrownBy(() -> new TestHash("0x1k")).hasMessage("Provided hash is not hexadecimal"); + } + + @Test + void should_sanitize_hash() { + assertThat(new TestHash("0x012345").toString()).isEqualTo("0x012345"); + assertThat(new TestHash("0x012345").toString()).isEqualTo("0x012345"); + assertThat(new TestHash("0X12345").toString()).isEqualTo("0x012345"); + assertThat(new TestHash("0X12345").toString()).isEqualTo("0x012345"); + } + + static class TestHash extends PrefixedHexHash { + public TestHash(final String hash) { + super(4, hash); + } + } + } + + @Nested + class NonPrefixedHashTest { + @Test + void should_accept_valid_hash() { + new TestHash("12345678"); + } + + @Test + void should_reject_invalid_hash() { + assertThatThrownBy(() -> new TestHash("0x1234")).hasMessage("Provided hash is not hexadecimal"); + assertThatThrownBy(() -> new TestHash("123456798123")).hasMessage("Provided hash should be less than 8 characters"); + assertThatThrownBy(() -> new TestHash("")).hasMessage("Provided hash should not be empty"); + assertThatThrownBy(() -> new TestHash("1k")).hasMessage("Provided hash is not hexadecimal"); + } + + @Test + void should_sanitize_hash() { + assertThat(new TestHash("012345").toString()).isEqualTo("012345"); + assertThat(new TestHash("12345").toString()).isEqualTo("012345"); + } + + static class TestHash extends HexHash { + public TestHash(final String hash) { + super(4, hash); + } + } + } + + @Nested + class CustomPrefixedHashTest { + @Test + void should_accept_valid_hash() { + new TestHash("xxx12345678"); + new TestHash("XXx12345678"); + new TestHash("XXX12345678"); + } + + @Test + void should_reject_invalid_hash() { + assertThatThrownBy(() -> new TestHash("1234")).hasMessage("Provided hash should start with xxx"); + assertThatThrownBy(() -> new TestHash("xxx123456798123")).hasMessage("Provided hash should be less than 8 characters"); + assertThatThrownBy(() -> new TestHash("xxx")).hasMessage("Provided hash should not be empty"); + assertThatThrownBy(() -> new TestHash("xxx1k")).hasMessage("Provided hash is not hexadecimal"); + } + + @Test + void should_sanitize_hash() { + assertThat(new TestHash("xxx012345").toString()).isEqualTo("xxx012345"); + assertThat(new TestHash("xxx012345").toString()).isEqualTo("xxx012345"); + assertThat(new TestHash("XxX12345").toString()).isEqualTo("xxx012345"); + assertThat(new TestHash("XxX12345").toString()).isEqualTo("xxx012345"); + } + + static class TestHash extends PrefixedHexHash { + public TestHash(final String hash) { + super(4, "xxx", hash); + } + } + } + + @Nested + class Base32HashTest { + @Test + void should_accept_valid_hash() { + new TestHash("AHYTD327"); + } + + @Test + void should_reject_invalid_hash() { + assertThatThrownBy(() -> new TestHash("HTYDF3652W")).hasMessage("Provided hash should be less than 8 characters"); + assertThatThrownBy(() -> new TestHash("")).hasMessage("Provided hash should not be empty"); + assertThatThrownBy(() -> new TestHash("12")).hasMessage("Provided hash is not base 32"); + } + + @Test + void should_sanitize_hash() { + assertThat(new TestHash("ABC").toString()).isEqualTo("ABC"); + assertThat(new TestHash("ABCD").toString()).isEqualTo("ABCD"); + } + + static class TestHash extends Base32Hash { + public TestHash(final String hash) { + super(8, hash); + } + } + } +} diff --git a/src/test/java/onlydust/com/marketplace/kernel/model/blockchain/StellarTest.java b/src/test/java/onlydust/com/marketplace/kernel/model/blockchain/StellarTest.java new file mode 100644 index 0000000..6080333 --- /dev/null +++ b/src/test/java/onlydust/com/marketplace/kernel/model/blockchain/StellarTest.java @@ -0,0 +1,46 @@ +package onlydust.com.marketplace.kernel.model.blockchain; + +import onlydust.com.marketplace.kernel.exception.OnlyDustException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class StellarTest { + @ParameterizedTest() + @ValueSource(strings = {"", "02937", "0x", "0x0asdf", "0x12345678901234567890123456789012345678901234567890123456789012345"}) + void should_reject_invalid_account(String value) { + assertThatThrownBy(() -> Stellar.accountId(value)).isInstanceOf(OnlyDustException.class); + } + + @Test + void should_accept_valid_account() { + assertDoesNotThrow(() -> Stellar.accountId("GA6MC3D6BNEFHZBYROFJ67O6TSZ2JZCDH3Y2PFJUUIDOEX26HDBHD4PB")); + } + + @ParameterizedTest() + @ValueSource(strings = {"", "Z02937", "0x", "0x0asdf", "0x01234567890123456789012345678901234567890123456789012345678901234"}) + void should_reject_invalid_hash(String value) { + assertThrows(OnlyDustException.class, () -> Stellar.transactionHash(value)); + } + + @Test + void should_accept_valid_hash() { + assertDoesNotThrow(() -> Stellar.transactionHash("e39906d57d0803f9af7d0d6e0b86c68e6662d26e4a8915c132d50d72869dcc0e")); + } + + @Test + void should_generate_transaction_url() { + assertThat(Stellar.BLOCK_EXPLORER.url(Stellar.transactionHash("123")).toString()).isEqualTo("https://stellar.expert/explorer/public/tx/0123"); + } + + @Test + void should_create_contract_address() { + final var contractAddress = Stellar.contractAddress("CBEOJUP5FU6KKOEZ7RMTSKZ7YLBS5D6LVATIGCESOGXSZEQ2UWQFKZW6"); + assertThat(contractAddress.toString()).isEqualTo("CBEOJUP5FU6KKOEZ7RMTSKZ7YLBS5D6LVATIGCESOGXSZEQ2UWQFKZW6"); + } +} \ No newline at end of file