Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation or example of calling a Contract method? #107

Open
luca992 opened this issue Feb 21, 2022 · 14 comments
Open

Documentation or example of calling a Contract method? #107

luca992 opened this issue Feb 21, 2022 · 14 comments
Labels
documentation Improvements or additions to documentation

Comments

@luca992
Copy link

luca992 commented Feb 21, 2022

I'm trying to simply get the name of a ERC721 contract.

    val apiKey = "..."
    val web3: Web3 = Web3("https://mainnet.infura.io/v3/$apiKey")
    val smartContractString = "..."
    val smartContractJson = Json.parseToJsonElement(smartContractString).jsonObject
    val networkData : JsonObject = smartContractJson["networks"]!!.jsonObject[networkId]?.jsonObject!!
    val adr  = networkData["address"]?.jsonPrimitive?.content!!
    val abis = smartContractJson["abi"]!!.jsonArray

    val sc = SmartContract(web3, ContractAddress(adr), abis))
    
    val names = sc.read("name", emptyList())
            { l ->
                l.map {
                    it.toString()
                }
            }
    val contractName = names[0]
        

First of all I'm not really sure what a valid mapper would look like? Or why it would return multiple values from one read. Do you have any examples? And second, names ends up being empty, so nothing is being returned? Any help on the correct way to call a contract method would be appreciated.

@y9san9
Copy link
Collaborator

y9san9 commented Feb 21, 2022

This process is kinda unclear for now. The result is List<Any?> since "outputs" of a smart contract method may contain multiple types. The example of such an input is below:

Снимок экрана 2022-02-21 в 09 38 39

They automatically decoded using ABI, but we can't know the returning types in compile time, so we have to cast it manually.

class NftContract(
    private val wrapped: SmartContract
) {
    suspend fun name() = wrapped.read(
        method = "name",
        params = emptyList()
    ) { (name) → name as String }
}

@y9san9
Copy link
Collaborator

y9san9 commented Feb 21, 2022

You should know underlying types for encoders/decoders to do mappings. That's kinda internal thing for now and it's not documented properly. Example of mappings:

bytes* → ByteArray
uint* → BigInt
address → BigInt
string → String

@y9san9 y9san9 added the documentation Improvements or additions to documentation label Feb 21, 2022
@luca992

This comment was marked as resolved.

@luca992
Copy link
Author

luca992 commented Feb 21, 2022

Got it working, thanks! I also realized I should have been using localhost instead of infura as my endpoint.

something like this is what I had to use:

class NftContract(
    private val wrapped: SmartContract
) {
    suspend fun name() = wrapped.read(
        method = "name",
        params = emptyList()
    ) { name -> name as List<String> }[0]
}

@luca992 luca992 closed this as completed Feb 21, 2022
@luca992 luca992 reopened this Feb 22, 2022
@luca992
Copy link
Author

luca992 commented Feb 22, 2022

@y9san9

what would be the correct way to write a mapper for this case:

    {
      "inputs": [],
      "name": "getAllTokens",
      "outputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "id",
              "type": "uint256"
            },
            {
              "internalType": "string",
              "name": "uri",
              "type": "string"
            }
          ],
          "internalType": "struct SmartContract.RenderToken[]",
          "name": "",
          "type": "tuple[]"
        }
      ],
      "stateMutability": "view",
      "type": "function",
      "constant": true
    }

I get this error:

Caused by: java.lang.IllegalArgumentException: Call data size should be at least equals params count multiplied by 32, because it should be the head size.Param types: [{"internalType":"uint256","name":"id","type":"uint256"}, {"internalType":"string","name":"uri","type":"string"}]; CallData: 0000000000000000000000000000000000000000000000000000000000000020

I've tried things like:

@kotlinx.serialization.Serializable
class AllTokensResponse(val id: BigInt, val uri: String)
    
    
class NftContract(
    private val wrapped: SmartContract
) {
    suspend fun allTokens() = wrapped.read(
        method = "getAllTokens",
        params = emptyList()
    ) { allTokens -> allTokens as List<List<AllTokensResponse>?> }[0]
}

@luca992
Copy link
Author

luca992 commented Feb 23, 2022

Basically I'm returning an array of a struct. Is that able to be parsed at the moment? Or was that not implemented here? Seem's like that's a newer feature introduced in solidity 0.8.0

@y9san9
Copy link
Collaborator

y9san9 commented Feb 23, 2022

That should work, but how the library would know what is AllTokensResponse?

@y9san9
Copy link
Collaborator

y9san9 commented Feb 23, 2022

But maybe it doesn't implemented, hard to say

@y9san9
Copy link
Collaborator

y9san9 commented Feb 23, 2022

The implementation of array[] type should work for any type

@luca992
Copy link
Author

luca992 commented Feb 23, 2022

If I leave it as a mapper with List<Any?>

And comment out the require that is failing:

        // callData.size is 32 and headSize is 64 in this example
        require(callData.size >= headSize) {
            "Call data size should be at least equals params count multiplied by 32, " +
                    "because it should be the head size." +
                    "Param types: $paramTypes; CallData: ${callData.toHex()}"
        }

This the result is definitely not correct
image

@y9san9
Copy link
Collaborator

y9san9 commented Feb 24, 2022

Give please the calldata you are trying to encode, I will investigate it soon

@luca992
Copy link
Author

luca992 commented Feb 24, 2022

this is the contract.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.1;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract SmartContract is ERC721, Ownable  {
    using Counters for Counters.Counter;
    // using Strings for uint256;
    Counters.Counter _tokenIds;
    mapping(uint256 => string) _tokenURIs;

    struct RenderToken {
        uint256 id;
        string uri;
    }

    constructor() ERC721("Smart Contract", "SC") {}

    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal {
        _tokenURIs[tokenId] = _tokenURI;
    }

    function tokenURI(uint256 tokenId) public view virtual override returns(string memory) {
        require(_exists(tokenId));
        string memory _tokenURI = _tokenURIs[tokenId];
        return _tokenURI;
    }

    function getAllTokens() public view returns(RenderToken[] memory) {
        uint256 latestId = _tokenIds.current();
        RenderToken[] memory res = new RenderToken[](latestId);
        for(uint256 i = 0; i < latestId; i++) {
            if(_exists(i)) {
                string memory uri = tokenURI(i);
                res[i] = RenderToken(i, uri);
            }
        }
        return res;
    }

    function mint(address recipient, string memory uri) public returns(uint256) {
        uint256 newId = _tokenIds.current();
        _mint(recipient, newId);
        _setTokenURI(newId, uri);
        _tokenIds.increment();
        return newId;
    }

}

This is a response after minting one token that needs to be decoded.

{"id":0,"jsonrpc":"2.0","result":"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004a68747470733a2f2f697066732e696e667572612e696f2f697066732f516d5937416d76756b756938344a675072694435346b46556b616f3765586b4b6d357a4848316e794e674c356f5900000000000000000000000000000000000000000000"}

That's what you need right?

@y9san9
Copy link
Collaborator

y9san9 commented Feb 24, 2022

Yep thank you

@y9san9
Copy link
Collaborator

y9san9 commented Jun 29, 2022

Working on this now in symbiosis-finance/moko-web3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants