problem: ERC20 Balancer subsription checks balancer on each block, even if no transfer happened
solution: use ERC20 Logs to determine changes to ERC20 balance
This commit is contained in:
@@ -6,20 +6,31 @@ 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.ERC20Balance
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumMultistream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumSubscribe
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.ConnectLogs
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.json.LogMessage
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.emeraldpay.etherjar.domain.BlockHash
|
||||
import io.emeraldpay.etherjar.domain.TransactionId
|
||||
import io.emeraldpay.etherjar.hex.Hex32
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import io.emeraldpay.etherjar.domain.Address
|
||||
import io.emeraldpay.etherjar.erc20.ERC20Token
|
||||
import io.emeraldpay.etherjar.hex.HexData
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionCallJson
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Ignore
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class TrackERC20AddressSpec extends Specification {
|
||||
|
||||
def "Init with single token"() {
|
||||
@@ -121,38 +132,6 @@ class TrackERC20AddressSpec extends Specification {
|
||||
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([]))
|
||||
@@ -175,4 +154,89 @@ class TrackERC20AddressSpec extends Specification {
|
||||
.setBalance("1234")
|
||||
.build()
|
||||
}
|
||||
|
||||
def "Check balance when event happens"() {
|
||||
setup:
|
||||
def events = [
|
||||
new LogMessage(
|
||||
Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9"),
|
||||
BlockHash.from("0x0c0d2969c843d0b61fbab1b2302cf24d6681b2ae0a140a3c2908990d048f7631"),
|
||||
13668750,
|
||||
HexData.from("0x0000000000000000000000000000000000000000000000000000000048f2fc7b"),
|
||||
1,
|
||||
[
|
||||
Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
|
||||
Hex32.from("0x000000000000000000000000b02f1329d6a6acef07a763258f8509c2847a0a3e"),
|
||||
Hex32.from("0x00000000000000000000000016c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b")
|
||||
],
|
||||
TransactionId.from("0x5a7898e27120575c33d3d0179af3b6353c7268bbad4255df079ed26b743a21a5"),
|
||||
1,
|
||||
false
|
||||
)
|
||||
]
|
||||
def logs = Mock(ConnectLogs) {
|
||||
1 * start(
|
||||
[Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")],
|
||||
[Hex32.from("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")]
|
||||
) >> { args ->
|
||||
println("ConnectLogs.start $args")
|
||||
Flux.fromIterable(events)
|
||||
}
|
||||
}
|
||||
def sub = Mock(EthereumSubscribe) {
|
||||
1 * getLogs() >> logs
|
||||
}
|
||||
def up = Mock(EthereumMultistream) {
|
||||
1 * getSubscribe() >> sub
|
||||
_ * cast(EthereumMultistream) >> { args ->
|
||||
it
|
||||
}
|
||||
}
|
||||
def mup = Mock(MultistreamHolder) {
|
||||
_ * getUpstream(Chain.ETHEREUM) >> up
|
||||
}
|
||||
TokensConfig tokens = new TokensConfig([
|
||||
new TokensConfig.Token().tap {
|
||||
id = "test"
|
||||
blockchain = Chain.ETHEREUM
|
||||
name = "TEST"
|
||||
type = TokensConfig.Type.ERC20
|
||||
address = Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9")
|
||||
}
|
||||
])
|
||||
TrackERC20Address track = new TrackERC20Address(mup, tokens)
|
||||
track.init()
|
||||
track.erc20Balance = Mock(ERC20Balance) {
|
||||
2 * it.getBalance(_, _, _) >>> [
|
||||
Mono.just(100000.toBigInteger()),
|
||||
Mono.just(150000.toBigInteger())
|
||||
]
|
||||
}
|
||||
def request = BlockchainOuterClass.BalanceRequest.newBuilder()
|
||||
.setAddress(
|
||||
Common.AnyAddress.newBuilder()
|
||||
.setAddressSingle(Common.SingleAddress.newBuilder().setAddress("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
|
||||
)
|
||||
.setAsset(
|
||||
Common.Asset.newBuilder()
|
||||
.setChain(Common.ChainRef.CHAIN_ETHEREUM)
|
||||
.setCode("TEST")
|
||||
)
|
||||
.build()
|
||||
when:
|
||||
def act = track.subscribe(request)
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNextMatches {
|
||||
println("Received: $it")
|
||||
it.getBalance() == "100000"
|
||||
}
|
||||
.expectNextMatches {
|
||||
println("Received: $it")
|
||||
it.getBalance() == "150000"
|
||||
}
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||
import io.emeraldpay.dshackle.test.ReaderMock
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.emeraldpay.etherjar.domain.Address
|
||||
import io.emeraldpay.etherjar.erc20.ERC20Token
|
||||
import io.emeraldpay.etherjar.hex.HexData
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionCallJson
|
||||
import io.emeraldpay.grpc.Chain
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
class ERC20BalanceSpec extends Specification {
|
||||
|
||||
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)
|
||||
ERC20Token token = new ERC20Token(Address.from("0x54EedeAC495271d0F6B175474E89094C44Da98b9"))
|
||||
ERC20Balance query = new ERC20Balance()
|
||||
|
||||
when:
|
||||
def act = query.getBalance(upstream, token, Address.from("0x16c15c65ad00b6dfbcc2cb8a7b6c2d0103a3883b"))
|
||||
.block(Duration.ofSeconds(1))
|
||||
|
||||
then:
|
||||
act.toLong() == 0x1f28d72868
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user