axelarnetwork / support

Your source for support with the Axelar Network
3 stars 1 forks source link

ITS Custom Token Linking Reverts on Proxy setup phase #117

Closed VoR0220 closed 6 months ago

VoR0220 commented 6 months ago

Hello! I'm currently trying to make a bridhge using the ITS Custom Linking setup. I've been following along with this guide to create general foundry setup off of a forked network of eth sepolia and base sepolia. Not expecting the cross interchain to work but I am expecting the dry runs to atleast work from a transaction perspective and not fail. This is not the case.

I've started off by deploying my tokens using create2 for deterministic addresses on both networks. This goes fine without a hitch. My token is not a mint/burn type so I've opted to go toward the LOCK_UNLOCK type Token Manager. I have encoded a salt. I have encoded my params argument for it as (address ownerAddress, address tokenAddress). I have given .009 ether amount of gas amount into the system, because better it be overkill than not enough.

Currently I am running into an issue where it fails on deploying the token manager, specifically at calling the setup function on IProxy in the constructor of the token manager itself:

https://github.com/axelarnetwork/interchain-token-service/blob/main/contracts/proxies/TokenManagerProxy.sol#L42

It would appear that it is reverting due to calling a contract that currently doesn't exist. I've scanned the codebase and tried to piece together how this logically is supposed to work but so far I am drawing a blank. Help me understand how this is supposed to work so that I can work to solve this?

For reference I am calling the function this way in foundry:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import "forge-std/Test.sol";

import "src/token/Token.sol"; // see below to see what token looks like

// note: needed to avoid conflict with ERC20 interface in OpenZeppelin
import {InterchainTokenService} from "interchain-token-service/InterchainTokenService.sol";
import {ITokenManagerType} from "interchain-token-service/interfaces/ITokenManagerType.sol";
import {TokenManagerDeployer} from "interchain-token-service/utils/TokenManagerDeployer.sol";

contract BridgeTokenTest is Test {

    uint privateKey;
    address ownerAddress;
    address minterAddress;

    Token token;
    InterchainTokenService tokenService;

    function setUp() public {
        privateKey = vm.envUint("DEPLOYMENT_OWNER_KEY");
        ownerAddress = vm.envAddress("DEPLOYMENT_OWNER");
        minterAddress = vm.envAddress("DEPLOYMENT_MINTER");
        vm.createSelectFork("sepolia_chain");
        token = new Token{salt: bytes32(0x534d454c4c494e475f53414c5453000000000000000000000000000000000000)}(ownerAddress, minterAddress);
        tokenService = InterchainTokenService(vm.envAddress("INTERCHAIN_TOKEN_SERVICE"));

         bytes32 tokenId = tokenService.deployTokenManager(
            bytes32(0x534d454c4c494e475f53414c5453000000000000000000000000000000000000),
            "", 
            ITokenManagerType.TokenManagerType.LOCK_UNLOCK, 
            abi.encode(ownerAddress, address(wave)),
            .009 ether
        );
   }

    function testSendTokenCrossChain() public {
        bytes32 tokenId = vm.envBytes32("TOKEN_ID");
        string memory destinationChain = vm.envString("DESTINATION_CHAIN");
        bytes memory destinationAddress = abi.encode(vm.envAddress("DESTINATION_ADDRESS"));
        uint amount = vm.envUint("AMOUNT");

        tokenService.interchainTransfer(
            tokenId,
            destinationChain,
            destinationAddress,
            amount,
            bytes(""),
            .01 ether
        ); 
    }
}

the token is constructed as such:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

// Import OpenZeppelin contracts for ERC20 standard token implementations,
// burnable tokens, access control mechanisms, and permit functionality
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

contract Token is ERC20, ERC20Burnable, AccessControl, ERC20Permit {

// Define a constant for the minter role using keccak256 to generate a unique hash
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    constructor(address defaultAdmin, address minter)
        ERC20("Token", "TOKE") // Set token name and symbol
        ERC20Permit("Toke") // Initialize ERC20Permit with the token name
    {
        // Mint an initial supply of tokens to the message sender
        _mint(msg.sender, 10000 * 10 ** decimals());

        // Grant the DEFAULT_ADMIN_ROLE to the specified defaultAdmin address
        _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);

        // Grant the MINTER_ROLE to the specified minter address
        // Addresses with the minter role are allowed to mint new tokens
        _grantRole(MINTER_ROLE, minter);
    }

    // Mint new tokens. Only addresses with the MINTER_ROLE can call this function
    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }

    // Burn tokens from a specified account
    function burn(address account, uint256 amount) public onlyRole(MINTER_ROLE) {
        _burn(account, amount);
    }
}
VoR0220 commented 6 months ago

It would appear that this is also somewhat related to #116 because as I can see when I try to run that example script it fails right at the same step which would be right here:

https://github.com/axelarnetwork/axelar-examples/blob/main/examples/evm/its-custom-token/index.js#L39

The unpredictable gas limit can be further traced to a revert in the EVM which no doubt is due to the proxy issue above. So...how does this get fixed?

benjamin852 commented 6 months ago

Hey can you try set a gasLimit when deploying the token Manager? I had a similar issue which I solved by passing in a gasLimit for the tx

VoR0220 commented 6 months ago

Hey @benjamin852 thanks for the quick response!

I did try overriding the gas limit and while the unpredictable gas limit error is gone, the revert in the contracts remains. It would appear that there have been some not so insignificant changes to the ITS contracts since April when the guide for custom linking was written and this could possibly be factoring in? If you could perhaps explain to me what is exactly supposed to be happening with that proxy deployment where it calls setup in the constructor that I linked earlier, I think we may be able to more easily get to the bottom of this. Everything else in the ITS token deployment manager makes sense to me along the way until we get to this setup function which I'm not understanding what its exactly running? It's clearly trying to set up a proxy of some kind, but there doesn't appear to be a setup function defined?

Anyways, here's the error below:

Error: processing response error (body="{\"id\":53,\"jsonrpc\":\"2.0\",\"error\":{\"message\":\"VM Exception while processing transaction: revert\",\"stack\":\"RuntimeError: VM Exception while processing transaction: revert\\n    at EIP1559FeeMarketTransaction.fillFromResult (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:234543)\\n    at Miner.<anonymous> (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:174897)\\n    at async Miner.<anonymous> (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:173310)\\n    at async Miner.mine (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:177695)\\n    at async Blockchain.mine (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:87561)\\n    at async Promise.all (index 0)\\n    at async TransactionPool.emit (/Users/rj/axelar-examples/node_modules/ganache/node_modules/emittery/index.js:303:3)\",\"code\":-32000,\"name\":\"RuntimeError\",\"data\":{\"hash\":\"0x8956b1db3ad7b6f57fce56be9d3fea3d1e1edc896cb9c1ec1ab30dfca965f141\",\"programCounter\":132,\"result\":\"0x8956b1db3ad7b6f57fce56be9d3fea3d1e1edc896cb9c1ec1ab30dfca965f141\",\"reason\":null,\"message\":\"revert\"}}}", error={"code":-32000,"data":{"hash":"0x8956b1db3ad7b6f57fce56be9d3fea3d1e1edc896cb9c1ec1ab30dfca965f141","programCounter":132,"result":"0x8956b1db3ad7b6f57fce56be9d3fea3d1e1edc896cb9c1ec1ab30dfca965f141","reason":null,"message":"revert"}}, requestBody="{\"method\":\"eth_sendRawTransaction\",\"params\":[\"0x02f901d38209c6038459682f00845d22d79a830f424094c13323a75343432bc9c5db570532ecb9eb07f81280b9016498d78c82a457d6c043b7288454773321a440ba8866d47f96d924d4c38a50b2b0698fae4600000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000400000000000000000000000002f9682a17e2fb9b1545b711971b77a6e5d90ffe10000000000000000000000000000000000000000000000000000000000000014068ea5cd133662655657bcd6206398da72feed04000000000000000000000000c080a04f1286aefefbd22205434ef1e53de197a18dac206d3e9a88b42b4b37a29ff099a0133045fcca9746c5248189de549a682d84722d7fbb5a3a4dadf2c35aec6498cf\"],\"id\":53,\"jsonrpc\":\"2.0\"}", requestMethod="POST", url="http://localhost:8500/2", code=SERVER_ERROR, version=web/5.7.1)
    at Logger.makeError (/Users/rj/axelar-examples/node_modules/@ethersproject/logger/lib/index.js:238:21)
    at Logger.throwError (/Users/rj/axelar-examples/node_modules/@ethersproject/logger/lib/index.js:247:20)
    at /Users/rj/axelar-examples/node_modules/@ethersproject/web/lib/index.js:313:32
    at step (/Users/rj/axelar-examples/node_modules/@ethersproject/web/lib/index.js:33:23)
    at Object.next (/Users/rj/axelar-examples/node_modules/@ethersproject/web/lib/index.js:14:53)
    at fulfilled (/Users/rj/axelar-examples/node_modules/@ethersproject/web/lib/index.js:5:58)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  reason: 'processing response error',
  code: 'SERVER_ERROR',
  body: '{"id":53,"jsonrpc":"2.0","error":{"message":"VM Exception while processing transaction: revert","stack":"RuntimeError: VM Exception while processing transaction: revert\\n    
  at EIP1559FeeMarketTransaction.fillFromResult (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:234543)\\n    at Miner.<anonymous> (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:174897)\\n    at async Miner.<anonymous> (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:173310)\\n    at async Miner.mine (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:177695)\\n    at async Blockchain.mine (/Users/rj/axelar-examples/node_modules/ganache/dist/node/1.js:2:87561)\\n    at async Promise.all (index 0)\\n    
  at async TransactionPool.emit (/Users/rj/axelar-examples/node_modules/ganache/node_modules/emittery/index.js:303:3)","code":-32000,"name":"RuntimeError","data":{"hash":"0x8956b1db3ad7b6f57fce56be9d3fea3d1e1edc896cb9c1ec1ab30dfca965f141","programCounter":132,"result":"0x8956b1db3ad7b6f57fce56be9d3fea3d1e1edc896cb9c1ec1ab30dfca965f141","reason":null,"message":"revert"}}}',

this is failing precisely at this line in the example script:

https://github.com/axelarnetwork/axelar-examples/blob/main/examples/evm/its-custom-token/index.js#L39

This is while using Node.js v18.20.3

benjamin852 commented 6 months ago

Hey so just looking at this again a few things. Where is address(wave) coming from? Don't see it defined. Also why are you passing .009 ether this last param should match the msg.value being sent into the contract (0 in your case). The ownerAddress which you pass in as well should be of type bytes as the setup() function attempts to decode a bytes type not address type here.

Regarding your question though the setup() in TokenManager is run to set roles such as flowLimiter and operator for the tokenManager. It's defined here

VoR0220 commented 6 months ago

Oh my bad that address(wave) is from me copying over and trying to change names and I lost that one in the process. Should be address(token). I have since also tried the above but the encoding it as a bytes doesn't seem to work. Could try encoding it from an env var but I'm running out of ideas for the bytes. The pointing out where the setup() function exists does help though, now I'm wondering why this contract might not be deploying.

        bytes memory ownerAddressBytes = abi.encode(ownerAddress);
        bytes memory params = abi.encode(ownerAddressBytes, address(token));
        console.logBytes(params);
        bytes32 tokenId = tokenService.deployTokenManager(
            salt,
            "", 
            ITokenManagerType.TokenManagerType.MINT_BURN, 
            params,
            0
        );

The above reverts with InvalidBytesLength when trying to use the toAddress function in the AddressBytes.sol lib in axelar-gmp-sdk-solidity.

Update since I started writing this. I found the solution! The solution is to use abi.encodePacked(address ownerAddress) here and then encode that in the ABI. Its a tight packing solution that will get it past this step. Will ask questions in the discord if need further assistance. In the meantime, highly recommend an update of docs to make a note that if you're using foundry to use packed encoding to get past this step!

Example:

        console.log("expected token manager address: ", tokenService.tokenManagerAddress(expectedTokenId));
        bytes memory ownerAddressBytes = abi.encodePacked(ownerAddress);
        bytes memory params = abi.encode(ownerAddressBytes, address(token));
        console.logBytes(params);
        tokenId = tokenService.deployTokenManager(
            salt,
            "", 
            ITokenManagerType.TokenManagerType.LOCK_UNLOCK, 
            params,
            0
        );