solution: track balance of a ERC20 token

This commit is contained in:
Igor Artamonov
2020-07-06 19:04:45 -04:00
parent 6dbe8b96ec
commit b3c619e57a
13 changed files with 490 additions and 26 deletions

View File

@@ -0,0 +1,113 @@
package io.emeraldpay.dshackle.rpc
import io.emeraldpay.api.proto.Common
import io.infinitape.etherjar.domain.Address
import reactor.test.StepVerifier
import spock.lang.Specification
import java.time.Duration
class EthereumAddressesSpec extends Specification {
EthereumAddresses ethereumAddresses = new EthereumAddresses()
def "Extract single address"() {
setup:
def address = Common.AnyAddress.newBuilder()
.setAddressSingle(
Common.SingleAddress.newBuilder()
.setAddress("0xab5c66752a9e8167967685f1450532fb96d5d24f")
)
.build()
when:
def act = ethereumAddresses.extract(address)
then:
StepVerifier.create(act)
.expectNext(Address.from("0xab5c66752a9e8167967685f1450532fb96d5d24f"))
.expectComplete().verify(Duration.ofSeconds(1))
}
def "Extract single addresses passed as multi"() {
setup:
def address = Common.AnyAddress.newBuilder()
.setAddressMulti(
Common.MultiAddress.newBuilder()
.addAddresses(
Common.SingleAddress.newBuilder()
.setAddress("0xab5c66752a9e8167967685f1450532fb96d5d24f")
)
)
.build()
when:
def act = ethereumAddresses.extract(address)
then:
StepVerifier.create(act)
.expectNext(Address.from("0xab5c66752a9e8167967685f1450532fb96d5d24f"))
.expectComplete().verify(Duration.ofSeconds(1))
}
def "Extract two addresses"() {
setup:
def address = Common.AnyAddress.newBuilder()
.setAddressMulti(
Common.MultiAddress.newBuilder()
.addAddresses(
Common.SingleAddress.newBuilder()
.setAddress("0xab5c66752a9e8167967685f1450532fb96d5d24f")
)
.addAddresses(
Common.SingleAddress.newBuilder()
.setAddress("0xfdb16996831753d5331ff813c29a93c76834a0ad")
)
)
.build()
when:
def act = ethereumAddresses.extract(address)
then:
StepVerifier.create(act)
.expectNext(Address.from("0xab5c66752a9e8167967685f1450532fb96d5d24f"))
.expectNext(Address.from("0xfdb16996831753d5331ff813c29a93c76834a0ad"))
.expectComplete().verify(Duration.ofSeconds(1))
}
def "Extract fes addresses"() {
setup:
def address = Common.AnyAddress.newBuilder()
.setAddressMulti(
Common.MultiAddress.newBuilder()
.addAddresses(
Common.SingleAddress.newBuilder()
.setAddress("0xab5c66752a9e8167967685f1450532fb96d5d24f")
)
.addAddresses(
Common.SingleAddress.newBuilder()
.setAddress("0xfdb16996831753d5331ff813c29a93c76834a0ad")
)
.addAddresses(
Common.SingleAddress.newBuilder()
.setAddress("0x46705dfff24256421a05d056c29e81bdc09723b8")
)
.addAddresses(
Common.SingleAddress.newBuilder()
.setAddress("0xadb2b42f6bd96f5c65920b9ac88619dce4166f94")
)
.addAddresses(
Common.SingleAddress.newBuilder()
.setAddress("0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852")
)
)
.build()
when:
def act = ethereumAddresses.extract(address)
then:
StepVerifier.create(act)
.expectNext(Address.from("0xab5c66752a9e8167967685f1450532fb96d5d24f"))
.expectNext(Address.from("0xfdb16996831753d5331ff813c29a93c76834a0ad"))
.expectNext(Address.from("0x46705dfff24256421a05d056c29e81bdc09723b8"))
.expectNext(Address.from("0xadb2b42f6bd96f5c65920b9ac88619dce4166f94"))
.expectNext(Address.from("0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852"))
.expectComplete().verify(Duration.ofSeconds(1))
}
}

View File

@@ -0,0 +1,178 @@
package io.emeraldpay.dshackle.rpc
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
import io.emeraldpay.dshackle.config.TokensConfig
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
import io.emeraldpay.dshackle.test.MultistreamHolderMock
import io.emeraldpay.dshackle.test.ReaderMock
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.MultistreamHolder
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.domain.Address
import io.infinitape.etherjar.erc20.ERC20Token
import io.infinitape.etherjar.hex.HexData
import io.infinitape.etherjar.rpc.json.TransactionCallJson
import reactor.core.publisher.Mono
import spock.lang.Specification
class TrackERC20AddressSpec extends Specification {
def "Init with single token"() {
setup:
MultistreamHolder ups = Mock(MultistreamHolder) {
_ * isAvailable(Chain.ETHEREUM) >> true
}
TokensConfig tokens = new TokensConfig([
new TokensConfig.Token().tap {
id = "dai"
blockchain = Chain.ETHEREUM
name = "DAI"
type = TokensConfig.Type.ERC20
address = Address.from("0x6B175474E89094C44Da98b954EedeAC495271d0F")
}
])
TrackERC20Address track = new TrackERC20Address(ups, tokens)
when:
track.init()
def act = track.tokens
def supportDai = track.isSupported(Chain.ETHEREUM, "DAI")
def supportSai = track.isSupported(Chain.ETHEREUM, "SAI")
then:
act.size() == 1
with(act.keySet().first()) {
chain == Chain.ETHEREUM
name == "dai"
}
with(act.values().first()) {
chain == Chain.ETHEREUM
name == "dai"
token != null
}
supportDai
!supportSai
}
def "Init without tokens"() {
setup:
MultistreamHolder ups = Mock(MultistreamHolder) {
_ * isAvailable(Chain.ETHEREUM) >> true
}
TokensConfig tokens = new TokensConfig([])
TrackERC20Address track = new TrackERC20Address(ups, tokens)
when:
track.init()
def act = track.tokens
def supportDai = track.isSupported(Chain.ETHEREUM, "dai")
def supportSai = track.isSupported(Chain.ETHEREUM, "sai")
then:
act.size() == 0
!supportDai
!supportSai
}
def "Init with two tokens"() {
setup:
MultistreamHolder ups = Mock(MultistreamHolder) {
_ * isAvailable(Chain.ETHEREUM) >> true
}
TokensConfig tokens = new TokensConfig([
new TokensConfig.Token().tap {
id = "dai"
blockchain = Chain.ETHEREUM
name = "DAI"
type = TokensConfig.Type.ERC20
address = Address.from("0x6B175474E89094C44Da98b954EedeAC495271d0F")
},
new TokensConfig.Token().tap {
id = "sai"
blockchain = Chain.ETHEREUM
name = "SAI"
type = TokensConfig.Type.ERC20
address = Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")
}
])
TrackERC20Address track = new TrackERC20Address(ups, tokens)
when:
track.init()
def act = track.tokens
def supportDai = track.isSupported(Chain.ETHEREUM, "dai")
def supportSai = track.isSupported(Chain.ETHEREUM, "sai")
then:
act.size() == 2
with(act[act.keySet().find { it.name == "dai" }]) {
chain == Chain.ETHEREUM
name == "dai"
}
with(act[act.keySet().find { it.name == "sai" }]) {
chain == Chain.ETHEREUM
name == "sai"
}
supportDai
supportSai
}
def "Gets balance from upstream"() {
setup:
ReaderMock api = new ReaderMock()
.with(
new JsonRpcRequest("eth_call", [
new TransactionCallJson().tap { json ->
json.setTo(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9"))
json.setData(HexData.from("0x70a0823100000000000000000000000016c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
},
"latest"
]),
JsonRpcResponse.ok('"0x0000000000000000000000000000000000000000000000000000001f28d72868"')
)
EthereumUpstream upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api)
MultistreamHolder ups = new MultistreamHolderMock(Chain.ETHEREUM, upstream)
TrackERC20Address track = new TrackERC20Address(ups, new TokensConfig([]))
TrackERC20Address.TrackedAddress address = new TrackERC20Address.TrackedAddress(
Chain.ETHEREUM,
Address.from("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"),
new ERC20Token(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")),
"test",
BigInteger.valueOf(1234)
)
when:
def act = track.getBalance(address).block()
then:
act.toLong() == 0x1f28d72868
}
def "Builds response"() {
setup:
TrackERC20Address track = new TrackERC20Address(Stub(MultistreamHolder), new TokensConfig([]))
TrackERC20Address.TrackedAddress address = new TrackERC20Address.TrackedAddress(
Chain.ETHEREUM,
Address.from("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"),
new ERC20Token(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")),
"test",
BigInteger.valueOf(1234)
)
when:
def act = track.buildResponse(address)
then:
act == BlockchainOuterClass.AddressBalance.newBuilder()
.setAddress(Common.SingleAddress.newBuilder().setAddress("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
.setAsset(Common.Asset.newBuilder()
.setChain(Common.ChainRef.CHAIN_ETHEREUM)
.setCode("TEST")
)
.setBalance("1234")
.build()
}
}

View File

@@ -32,6 +32,10 @@ class ReaderMock<K, D> implements Reader<K, D> {
@Override
Mono<D> read(K key) {
return Mono.justOrEmpty(mapping.get(key))
def value = mapping.get(key)
if (value == null) {
println("No mocked value for request: $key")
}
return Mono.justOrEmpty(value)
}
}