All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonTest.dev.icerock.moko.web3.Web3Test.kt Maven / Gradle / Ivy

There is a newer version: 0.18.4-ktor2_ionspinbignum
Show newest version
/*
 * Copyright 2020 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
 */

@file:Suppress("EXPERIMENTAL_API_USAGE")

package dev.icerock.moko.web3

import com.ionspin.kotlin.bignum.decimal.toBigDecimal
import com.ionspin.kotlin.bignum.integer.BigInteger
import com.ionspin.kotlin.bignum.integer.toBigInteger
import dev.icerock.moko.web3.contract.*
import dev.icerock.moko.web3.entity.RpcResponse
import dev.icerock.moko.web3.entity.TransactionReceipt
import dev.icerock.moko.web3.hex.Hex32String
import dev.icerock.moko.web3.hex.HexString
import dev.icerock.moko.web3.hex.internal.toHex
import dev.icerock.moko.web3.requests.*
import dev.icerock.moko.web3.requests.polling.newBlocksShortPolling
import dev.icerock.moko.web3.requests.polling.newLogsShortPolling
import io.ktor.client.engine.mock.*
import io.ktor.client.request.*
import io.ktor.content.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class Web3Test {
    private val infuraUrl = "https://rinkeby.infura.io/v3/5a3d2c30cf72450c9e13b0570a737b62"

    private fun createTestWeb3(
        handler: suspend MockRequestHandleScope.(HttpRequestData) -> HttpResponseData
    ): Web3 = Web3(
        httpClient = createMockClient(handler),
        endpointUrl = infuraUrl,
        json = Json
    )

    @Test
    fun `read transaction`() {
        val web3 = createTestWeb3 { request ->
            val body = request.body
            assertTrue(body is TextContent)
            assertEquals(
                expected = """[{"jsonrpc":"2.0","id":0,"method":"eth_getTransactionByHash","params":["0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352"]}]""",
                actual = body.text
            )
            respond(content = """[{"jsonrpc":"2.0","id":0,"result":{"blockHash":"0x3d62f862d25cf7015a485868b07825484fd7a51f77a9e7863fe45ec8a61db01b","blockNumber":"0x60f03b","from":"0xde7ec4e4895d7d148906a0dfaaf7f21ac5c5b80c","gas":"0x4e9e4","gasPrice":"0x1dcd65000","hash":"0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352","input":"0x852a12e300000000000000000000000000000000000000000000000000470de4df820000","nonce":"0x4a","r":"0x5aeca7a54ae3bb0f67a29aece00d71bc75c0d06b89950ea600a0b3b6bbfe5e8c","s":"0x68fefa5333e94443dca19e30562bf297b8a687abf15c3fe2671de6233299fff0","to":"0xd6801a1dffcd0a410336ef88def4320d6df1883e","transactionIndex":"0x1","type":"0x0","v":"0x2c","value":"0x0"}}]""")
        }
        val txHash = TransactionHash("0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352")

        val result = runTest {
            web3.getTransaction(txHash)
        }

        assertEquals(actual = result.txHash, expected = txHash)
        assertEquals(actual = result.gasPrice, expected = BigInteger(8000000000))
        assertEquals(actual = result.gasLimit, expected = BigInteger(322020))
        assertEquals(actual = result.transactionIndex, expected = BigInteger(1))
        assertEquals(actual = result.nonce, expected = BigInteger(74))
        assertEquals(
            actual = result.blockHash,
            expected = BlockHash("0x3d62f862d25cf7015a485868b07825484fd7a51f77a9e7863fe45ec8a61db01b")
        )
        assertEquals(
            actual = result.blockNumber,
            expected = BigInteger(6352955)
        )
        assertEquals(
            actual = result.senderAddress,
            expected = WalletAddress("0xde7ec4e4895d7d148906a0dfaaf7f21ac5c5b80c")
        )
        assertEquals(
            actual = result.receiverAddress,
            expected = WalletAddress("0xd6801a1dffcd0a410336ef88def4320d6df1883e")
        )
        assertEquals(
            actual = result.input,
            expected = "0x852a12e300000000000000000000000000000000000000000000000000470de4df820000"
        )
    }

    @Test
    fun `read transaction receipt`() {
        val web3 = createTestWeb3 { request ->
            val body = request.body
            assertTrue(body is TextContent)
            assertEquals(
                expected = """[{"jsonrpc":"2.0","id":0,"method":"eth_getTransactionReceipt","params":["0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352"]}]""",
                actual = body.text
            )
            respond(
                content = """[{"jsonrpc":"2.0","id":0,"result":{"blockHash":"0x3d62f862d25cf7015a485868b07825484fd7a51f77a9e7863fe45ec8a61db01b","blockNumber":"0x60f03b","contractAddress":null,"cumulativeGasUsed":"0x3ae8d","effectiveGasPrice":"0x1dcd65000","from":"0xde7ec4e4895d7d148906a0dfaaf7f21ac5c5b80c","gasUsed":"0x34725","logs":[{"address":"0xd6801a1dffcd0a410336ef88def4320d6df1883e","blockHash":"0x3d62f862d25cf7015a485868b07825484fd7a51f77a9e7863fe45ec8a61db01b","blockNumber":"0x60f03b","data":"0x00000000000000000000000000000000000000000000000000078a9f2e72421c000000000000000000000000000000000000000000000000103938f95c5bb8de00000000000000000000000000000000000000000000007e6f395eb639577b12","logIndex":"0x0","removed":false,"topics":["0x875352fb3fadeb8c0be7cbbe8ff761b308fa7033470cd0287f02f3436fd76cb9"],"transactionHash":"0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352","transactionIndex":"0x1"},{"address":"0xd6801a1dffcd0a410336ef88def4320d6df1883e","blockHash":"0x3d62f862d25cf7015a485868b07825484fd7a51f77a9e7863fe45ec8a61db01b","blockNumber":"0x60f03b","data":"0x00000000000000000000000000000000000000000000000000000000052f2dd9","logIndex":"0x1","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000de7ec4e4895d7d148906a0dfaaf7f21ac5c5b80c","0x000000000000000000000000d6801a1dffcd0a410336ef88def4320d6df1883e"],"transactionHash":"0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352","transactionIndex":"0x1"},{"address":"0xd6801a1dffcd0a410336ef88def4320d6df1883e","blockHash":"0x3d62f862d25cf7015a485868b07825484fd7a51f77a9e7863fe45ec8a61db01b","blockNumber":"0x60f03b","data":"0x000000000000000000000000de7ec4e4895d7d148906a0dfaaf7f21ac5c5b80c00000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000000000000000052f2dd9","logIndex":"0x2","removed":false,"topics":["0xe5b754fb1abb7f01b499791d0b820ae3b6af3424ac1c59768edb53f4ec31a929"],"transactionHash":"0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352","transactionIndex":"0x1"}],"logsBloom":"0x00000080000000000000000000000000000000000000000000000000000000000000040000100000800000000000000000000000000000000000000000000000000000000000000000000808000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000010000000000000000080000000000000000000000000000000000000800000000000000001000000000000000000000000000000000000000000000000000000002000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000020200000000004","status":"0x1","to":"0xd6801a1dffcd0a410336ef88def4320d6df1883e","transactionHash":"0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352","transactionIndex":"0x1","type":"0x0"}}]"""
            )
        }
        val txHash = TransactionHash("0x627914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352")

        val result = runTest {
            web3.getTransactionReceipt(txHash) ?: error("This transaction should exist")
        }

        assertEquals(actual = result.txHash, expected = txHash)
        assertEquals(actual = result.contractAddress, expected = null)
        assertEquals(actual = result.cumulativeGasUsed, expected = BigInteger(241293))
        assertEquals(actual = result.gasUsed, expected = BigInteger(214821))
        assertEquals(actual = result.transactionIndex, expected = BigInteger(1))
        assertEquals(actual = result.status, expected = TransactionReceipt.Status.SUCCESS)
        assertEquals(actual = result.blockNumber, expected = BigInteger(6352955))
        assertEquals(
            actual = result.blockHash,
            expected = BlockHash("0x3d62f862d25cf7015a485868b07825484fd7a51f77a9e7863fe45ec8a61db01b")
        )
        assertEquals(
            actual = result.senderAddress,
            expected = WalletAddress("0xde7ec4e4895d7d148906a0dfaaf7f21ac5c5b80c")
        )
        assertEquals(
            actual = result.receiverAddress,
            expected = WalletAddress("0xd6801a1dffcd0a410336ef88def4320d6df1883e")
        )

        result.logs.forEach { event ->
            assertEquals(
                actual = event.blockHash,
                expected = BlockHash("0x3d62f862d25cf7015a485868b07825484fd7a51f77a9e7863fe45ec8a61db01b")
            )
            assertEquals(actual = event.transactionHash, expected = txHash)
            assertEquals(actual = event.blockNumber, expected = BigInteger(6352955))
            assertEquals(actual = result.transactionIndex, expected = BigInteger(1))
        }

        assertEquals(
            actual = result.logs[0].topics,
            expected = listOf(Hex32String("0x875352fb3fadeb8c0be7cbbe8ff761b308fa7033470cd0287f02f3436fd76cb9"))
        )

        assertEquals(
            actual = result.logs[1].topics,
            expected = listOf(
                "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
                "0x000000000000000000000000de7ec4e4895d7d148906a0dfaaf7f21ac5c5b80c",
                "0x000000000000000000000000d6801a1dffcd0a410336ef88def4320d6df1883e"
            ).map(::Hex32String)
        )

        assertEquals(
            actual = result.logs[2].topics,
            expected = listOf(Hex32String("0xe5b754fb1abb7f01b499791d0b820ae3b6af3424ac1c59768edb53f4ec31a929"))
        )
    }

    @Test
    fun `smart contract params`() {
        val json = Json
        val executor = createTestWeb3 { respondBadRequest() }
        val smartContract = SmartContract(
            executor = executor,
            contractAddress = ContractAddress("0x6b175474e89094c44da98b954eedeac495271d0f"),
            abiJson = createErc20TokenAbi(json)
        )

        val addr = "9a0A2498Ec7f105ef65586592a5B6d4Da3590D74".toBigInteger(16)

        val result = runTest {
            ABIEncoder.encodeCallDataForMethod(
                abi = createErc20TokenAbi(json),
                method = "transfer",
                params = listOf(
                    addr,
                    BigInteger(0x10001000)
                )
            )
        }

        assertEquals(
            expected = "0xa9059cbb0000000000000000000000009a0a2498ec7f105ef65586592a5b6d4da3590d740000000000000000000000000000000000000000000000000000000010001000".let(
                ::HexString
            ),
            actual = result
        )
    }

    @Test
    fun `address encoder`() {
        val param = AddressParam
        val addr = "9a0A2498Ec7f105ef65586592a5B6d4Da3590D74".toBigInteger(16)
        val result = param.encode(addr)
        val hex = result.toHex()

        assertEquals(
            expected = "0000000000000000000000009a0a2498ec7f105ef65586592a5b6d4da3590d74",
            actual = hex.lowercase()
        )

        val address = param.decode(result)
        assertEquals(
            expected = addr,
            actual = address
        )
    }

    @Test
    fun `unit256 encoder`() {
        val param = UInt256Param
        val input = "1234567891011adfdfdeadfea123d34cd342dcd234234ffeedd342432ddff555".toBigInteger(16)
        val result = param.encode(input)
        val hex = result.toHex()

        assertEquals(
            expected = "1234567891011adfdfdeadfea123d34cd342dcd234234ffeedd342432ddff555",
            actual = hex.lowercase()
        )

        val output = param.decode(result)
        assertEquals(
            expected = input,
            actual = output
        )
    }

    @Test
    fun `smart contract encoding`() {
        val json = Json
        val executor = createTestWeb3 { respondBadRequest() }
        val smartContract = SmartContract(
            executor = executor,
            contractAddress = ContractAddress(value = "0x0000000000000000000000000000000000000000"),
            abiJson = createTestAbi(json)
        )

        val address = "9a0A2498Ec7f105ef65586592a5B6d4Da3590D74".toBigInteger(16)
        val BigInteger = "16345785d8a0000".toBigInteger(16)
        val list = listOf(address, BigInteger)

        val result = runTest {
            ABIEncoder.encodeCallDataForMethod(
                abi = createTestAbi(json),
                method = "test",
                params = listOf(
                    address,
                    BigInteger,
                    list,
                    listOf(
                        address,
                        list
                    )
                )
            )
        }
        assertEquals(
            expected = "0x170159cd0000000000000000000000009a0a2498ec7f105ef65586592a5b6d4da3590d74000000000000000000000000000000000000000000000000016345785d8a0000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009a0a2498ec7f105ef65586592a5b6d4da3590d74000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000009a0a2498ec7f105ef65586592a5b6d4da3590d74000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009a0a2498ec7f105ef65586592a5b6d4da3590d74000000000000000000000000000000000000000000000000016345785d8a0000".let(
                ::HexString
            ),
            actual = result
        )
    }

    @Test
    fun `read balance`() {
        val web3 = createTestWeb3 { request ->
            val body = request.body
            assertTrue(body is TextContent)
            assertEquals(
                expected = """[{"jsonrpc":"2.0","id":0,"method":"eth_getBalance","params":["0xdE7eC4E4895D7d148906a0DFaAF7f21ac5C5B80C","latest"]}]""",
                actual = body.text
            )
            respond(content = """[{"jsonrpc":"2.0","id":0,"result":"0xd94ec060b14773c7"}]""")
        }

        val result = runTest {
            web3.getNativeBalance(WalletAddress("0xdE7eC4E4895D7d148906a0DFaAF7f21ac5C5B80C"))
        }

        assertEquals(
            expected = "15658664475937436615".toBigInteger(),
            actual = result
        )
    }

    @Test
    fun `get nonce`() {
        val web3 = createTestWeb3 { request ->
            val body = request.body
            assertTrue(body is TextContent)
            assertEquals(
                expected = """[{"jsonrpc":"2.0","id":0,"method":"eth_getTransactionCount","params":["0xdE7eC4E4895D7d148906a0DFaAF7f21ac5C5B80C","pending"]}]""",
                actual = body.text
            )
            respond(content = """[{"jsonrpc":"2.0","id":0,"result":"0x1275"}]""")
        }

        val result = runTest {
            web3.getNativeTransactionCount(WalletAddress("0xdE7eC4E4895D7d148906a0DFaAF7f21ac5C5B80C"))
        }

        assertEquals(
            expected = 4725.toBigInteger(),
            actual = result
        )
    }

    @Test
    fun `batch test`() {
        val web3 = createTestWeb3 { request ->
            val body = request.body
            assertTrue(body is TextContent)
            assertEquals(
                expected = """[{"jsonrpc":"2.0","id":0,"method":"eth_getTransactionCount","params":["0xdE7eC4E4895D7d148906a0DFaAF7f21ac5C5B80C","pending"]},{"jsonrpc":"2.0","id":1,"method":"eth_getBalance","params":["0xdE7eC4E4895D7d148906a0DFaAF7f21ac5C5B80C","latest"]}]""",
                actual = body.text
            )
            respond(content = """[{"jsonrpc":"2.0","id":0,"result":"0x1275"},{"jsonrpc":"2.0","id":1,"result":"0xd94ec060b14773c7"}]""")
        }

        val wallet = WalletAddress("0xdE7eC4E4895D7d148906a0DFaAF7f21ac5C5B80C")

        val (nonce, balance) = runTest {
            web3.executeBatch(
                Web3Requests.getNativeTransactionCount(wallet),
                Web3Requests.getNativeBalance(wallet),
            )
        }

        assertEquals(
            expected = 4725.toBigInteger(),
            actual = nonce
        )
        assertEquals(
            expected = "15658664475937436615".toBigInteger(),
            actual = balance
        )
    }

    @Test
    fun `gas price test`() {
        val web3 = createTestWeb3 { request ->
            val body = request.body
            assertTrue(body is TextContent)
            assertEquals(
                expected = """[{"jsonrpc":"2.0","id":0,"method":"eth_gasPrice","params":[]}]""",
                actual = body.text
            )
            respond(content = """[{"jsonrpc":"2.0","id":0,"result":"0x3b9aca08"}]""")
        }
        runTest {
            assertEquals(
                expected = 1000000008.toBigInteger(),
                actual = web3.getGasPrice()
            )
        }
    }

    //    @Test
    fun `short polling test`() {
        runTest {
            println(
                Json.decodeFromString(
                    RpcResponse.serializer(JsonElement.serializer()),
                    """{"jsonrpc":"","id":0,"result":null}"""
                ).result as JsonNull
            )
            val web3 = Web3("https://rinkeby.infura.io/v3/5a3d2c30cf72450c9e13b0570a737b62")
            web3.waitForTransactionReceipt(
                TransactionHash("0x6f7914c8d005ab0dc7f44719dc658af72e534e083867a2a316d4b25555515352"),
                timeOutMillis = 5_000
            )
        }
    }

    //    @Test
    fun `new blocks short polling test`() {
        runBlocking {
            val web3 = Web3("https://rinkeby.infura.io/v3/5a3d2c30cf72450c9e13b0570a737b62")
            web3.newBlocksShortPolling(pollingInterval = 5_000)
                .collect { println("Block ${it.hash} mined!") }
        }
    }

    //    @Test
    fun `new logs short polling test`() {
        runBlocking {
            val web3 = Web3("https://rinkeby.infura.io/v3/5a3d2c30cf72450c9e13b0570a737b62")
            web3.newLogsShortPolling(pollingInterval = 5_000)
                .collect { println("Log $it caught!") }
        }
    }

    //    @Test
    fun legacyTransactionForming() {
        runBlocking {
            val web3 = Web3("https://api.avax-test.network/ext/bc/C/rpc")
            val price = web3.getGasPrice()
            println("GAS Price: $price")
        }
    }

    //    @Test
    fun legacyExtendedTransactionForming() {
        runBlocking {
            val web3 = Web3("https://bsc.getblock.io/testnet/?api_key=94c96d69-74f0-40e7-8202-eac4b49e6bfc")
            val price = web3.getGasPrice()
            val callData =
                HexString("0x38ed17390000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000002e57839a043800000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000140e21fcfb1e602a1626198d3dbbb58087b59b4e0000000000000000000000000000000000000000000000000000000061e8f26700000000000000000000000000000000000000000000000000000000000000030000000000000000000000009a01bf917477dd9f5d715d188618fc8b7350cd22000000000000000000000000ae13d989dac2f0debff460ac112a837c89baa7cd00000000000000000000000041b5984f45afb2560a0ed72bb69a98e8b32b3cca")
            val to = ContractAddress("0xc43d2c472cf882e0b190063d66ee8ce78bf54da1")
            val from = EthereumAddress("0x140e21fcfb1e602a1626198d3dbbb58087b59b4e")
            val value = 2_000_000_000_000_000.toBigInteger()
            println("GAS Price: $price")
            println(
                "GAS Limit: ${
                    web3.getEstimateGas(
                        to = to,
                        from = from,
                        gasPrice = price,
                        callData = callData,
                        value = value
                    ).toString().toBigDecimal().times(1.1.toBigDecimal())
                }"
            )
        }
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy