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

feat: clone instead of fixed proxy #168

Merged
merged 15 commits into from
Nov 13, 2023
52 changes: 38 additions & 14 deletions contracts/interchain-token/InterchainToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
pragma solidity ^0.8.0;

import { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol';
import { IImplementation } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IImplementation.sol';
import { Implementation } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Implementation.sol';

import { IInterchainToken } from '../interfaces/IInterchainToken.sol';

Expand All @@ -17,21 +15,38 @@ import { Distributable } from '../utils/Distributable.sol';
* @notice This contract implements a interchain token which extends InterchainToken functionality.
* This contract also inherits Distributable and Implementation logic.
*/
contract InterchainToken is BaseInterchainToken, ERC20Permit, Implementation, Distributable, IInterchainToken {
contract InterchainToken is BaseInterchainToken, ERC20Permit, Distributable, IInterchainToken {
using AddressBytes for bytes;

string public name;
string public symbol;
uint8 public decimals;
address internal tokenManager_;

bytes32 private constant CONTRACT_ID = keccak256('interchain-token');
// bytes32(uint256(keccak256('interchain-token-is-setup-slot')) - 1);
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
bytes32 internal constant IS_SETUP_SLOT = 0xb39f35de0a5b2620db9237c1e18c03b5e68a71236c9bdfbcd69f3582bab06df6;
milapsheth marked this conversation as resolved.
Show resolved Hide resolved

constructor() {
// Make the implementation act as if it has been setup already to disallow calls to init() (even though that wouldn't achieve anything really)
_setSetup();
}

/**
* @notice returns true if the contract has be setup.
*/
function _isSetup() internal view returns (bool isSetup) {
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
assembly {
isSetup := sload(IS_SETUP_SLOT)
}
}

/**
* @notice Getter for the contract id.
* @notice sets the isSetup to true, to allow only a single setup.
*/
function contractId() external pure override returns (bytes32) {
return CONTRACT_ID;
function _setSetup() internal {
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
assembly {
sstore(IS_SETUP_SLOT, true)
}
}

/**
Expand All @@ -44,20 +59,29 @@ contract InterchainToken is BaseInterchainToken, ERC20Permit, Implementation, Di

/**
* @notice Setup function to initialize contract parameters
* @param params The setup parameters in bytes
* The setup params include tokenManager, distributor, tokenName, symbol, decimals, mintAmount and mintTo
* @param tokenManagerAddress The address of the token manager of this token
* @param distributor The address of the token distributor
* @param tokenName The name of the token
* @param tokenSymbol The symbopl of the token
* @param tokenDecimals The decimals of the token
*/
function setup(bytes calldata params) external override(Implementation, IImplementation) onlyProxy {
address distributor;
address tokenManagerAddress;
string memory tokenName;
(tokenManagerAddress, distributor, tokenName, symbol, decimals) = abi.decode(params, (address, address, string, string, uint8));
function init(
address tokenManagerAddress,
address distributor,
string calldata tokenName,
string calldata tokenSymbol,
uint8 tokenDecimals
) external {
if (_isSetup()) revert AlreadySetup();
_setSetup();
milapsheth marked this conversation as resolved.
Show resolved Hide resolved

if (tokenManagerAddress == address(0)) revert TokenManagerAddressZero();
if (bytes(tokenName).length == 0) revert TokenNameEmpty();

tokenManager_ = tokenManagerAddress;
name = tokenName;
symbol = tokenSymbol;
decimals = tokenDecimals;

if (distributor != address(0)) _addDistributor(distributor);
_addDistributor(tokenManagerAddress);
Expand Down
21 changes: 18 additions & 3 deletions contracts/interfaces/IInterchainToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

pragma solidity ^0.8.0;

import { IImplementation } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IImplementation.sol';

import { IInterchainTokenStandard } from './IInterchainTokenStandard.sol';
import { IDistributable } from './IDistributable.sol';
import { IERC20MintableBurnable } from './IERC20MintableBurnable.sol';
Expand All @@ -12,14 +10,31 @@ import { IERC20Named } from './IERC20Named.sol';
/**
* @title IInterchainToken
*/
interface IInterchainToken is IInterchainTokenStandard, IDistributable, IERC20MintableBurnable, IERC20Named, IImplementation {
interface IInterchainToken is IInterchainTokenStandard, IDistributable, IERC20MintableBurnable, IERC20Named {
error TokenManagerAddressZero();
error TokenNameEmpty();
error AlreadySetup();
milapsheth marked this conversation as resolved.
Show resolved Hide resolved

/**
* @notice Getter for the tokenManager used for this token.
* @dev Needs to be overwitten.
* @return tokenManager_ the TokenManager called to facilitate cross chain transfers.
*/
function tokenManager() external view returns (address tokenManager_);

/**
* @notice Setup function to initialize contract parameters
* @param tokenManagerAddress The address of the token manager of this token
* @param distributor The address of the token distributor
* @param tokenName The name of the token
* @param tokenSymbol The symbopl of the token
* @param tokenDecimals The decimals of the token
*/
function init(
address tokenManagerAddress,
address distributor,
string calldata tokenName,
string calldata tokenSymbol,
uint8 tokenDecimals
) external;
}
33 changes: 0 additions & 33 deletions contracts/proxies/InterchainTokenProxy.sol

This file was deleted.

7 changes: 7 additions & 0 deletions contracts/test/HardCodedConstantsTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TokenManagerLiquidityPool } from '../token-manager/TokenManagerLiquidit
import { Distributable } from '../utils/Distributable.sol';
import { FlowLimit } from '../utils/FlowLimit.sol';
import { Operatable } from '../utils/Operatable.sol';
import { InterchainToken } from '../interchain-token/InterchainToken.sol';

error Invalid();

Expand Down Expand Up @@ -38,3 +39,9 @@ contract TestOperatable is Operatable {

constructor() {}
}

contract TestInterchainToken is InterchainToken {
constructor() {
if (IS_SETUP_SLOT != bytes32(uint256(keccak256('interchain-token-is-setup-slot')) - 1)) revert Invalid();
}
}
15 changes: 10 additions & 5 deletions contracts/utils/InterchainTokenDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ pragma solidity ^0.8.0;
import { Create3 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3.sol';

import { IInterchainTokenDeployer } from '../interfaces/IInterchainTokenDeployer.sol';

import { InterchainTokenProxy } from '../proxies/InterchainTokenProxy.sol';
import { IInterchainToken } from '../interfaces/IInterchainToken.sol';

/**
* @title InterchainTokenDeployer
Expand Down Expand Up @@ -43,12 +42,18 @@ contract InterchainTokenDeployer is IInterchainTokenDeployer, Create3 {
string calldata symbol,
uint8 decimals
) external payable returns (address tokenAddress) {
bytes memory params = abi.encode(tokenManager, distributor, name, symbol, decimals);
// slither-disable-next-line too-many-digits
bytes memory bytecode = bytes.concat(type(InterchainTokenProxy).creationCode, abi.encode(implementationAddress, params));

bytes memory bytecode = new bytes(0x37); //bytes.concat(type(InterchainTokenProxy).creationCode, abi.encode(implementationAddress, params));
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
address implementation = implementationAddress;
assembly {
mstore(add(bytecode, 0x20), shl(0x60, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73))
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
mstore(add(bytecode, 0x34), shl(0x60, implementation))
mstore(add(bytecode, 0x48), shl(0x88, 0x5af43d82803e903d91602b57fd5bf3))
}
tokenAddress = _create3(bytecode, salt);
if (tokenAddress.code.length == 0) revert TokenDeploymentFailed();

IInterchainToken(tokenAddress).init(tokenManager, distributor, name, symbol, decimals);
}

function deployedAddress(bytes32 salt) external view returns (address tokenAddress) {
Expand Down
55 changes: 14 additions & 41 deletions test/InterchainToken.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
'use strict';

const chai = require('chai');
const { ethers } = require('hardhat');
const {
getContractAt,
utils: { keccak256, toUtf8Bytes },
} = ethers;
const { expect } = chai;
const { getRandomBytes32, expectRevert, getGasOptions } = require('./utils');
const { getContractAt } = ethers;
const { getRandomBytes32, expectRevert } = require('./utils');
const { deployContract } = require('../scripts/deploy');

describe('InterchainToken', () => {
Expand All @@ -19,7 +14,6 @@ describe('InterchainToken', () => {
const mintAmount = 123;

let token;
let tokenProxy;

let owner;

Expand All @@ -35,7 +29,6 @@ describe('InterchainToken', () => {
const tokenAddress = await interchainTokenDeployer.deployedAddress(salt);

token = await getContractAt('InterchainToken', tokenAddress, owner);
tokenProxy = await getContractAt('InterchainTokenProxy', tokenAddress, owner);

await interchainTokenDeployer
.deployInterchainToken(salt, owner.address, owner.address, name, symbol, decimals)
Expand All @@ -44,41 +37,21 @@ describe('InterchainToken', () => {
await (await token.mint(owner.address, mintAmount)).wait();
});

describe('Interchain Token Proxy', () => {
it('should revert if interchain token implementation is invalid', async () => {
const invalidInterchainToken = await deployContract(owner, 'InvalidInterchainToken');
interchainTokenDeployer = await deployContract(owner, 'InterchainTokenDeployer', [invalidInterchainToken.address]);

const salt = getRandomBytes32();

await expect(
interchainTokenDeployer.deployInterchainToken(salt, owner.address, owner.address, name, symbol, decimals, getGasOptions()),
).to.be.reverted;
});

it('should revert if interchain token setup fails', async () => {
const params = '0x1234';
await expectRevert(
(gasOptions) => deployContract(owner, 'InterchainTokenProxy', [interchainToken.address, params, gasOptions]),
tokenProxy,
'SetupFailed',
);
});

it('should return the correct contract ID', async () => {
const contractID = await token.contractId();
const hash = keccak256(toUtf8Bytes('interchain-token'));
expect(contractID).to.equal(hash);
});
});

describe('Interchain Token', () => {
it('revert on setup if not called by the proxy', async () => {
const implementationAddress = await tokenProxy.implementation();
it('revert on init if not called by the proxy', async () => {
const implementationAddress = await interchainTokenDeployer.implementationAddress();
const implementation = await getContractAt('InterchainToken', implementationAddress, owner);

const params = '0x';
await expectRevert((gasOptions) => implementation.setup(params, gasOptions), token, 'NotProxy');
const tokenManagerAddress = owner.address;
const distributor = owner.address;
const tokenName = 'name';
const tokenSymbol = 'symbol';
const tokenDecimals = 7;
await expectRevert(
(gasOptions) => implementation.init(tokenManagerAddress, distributor, tokenName, tokenSymbol, tokenDecimals, gasOptions),
implementation,
'AlreadySetup',
);
});
});
});
2 changes: 0 additions & 2 deletions test/UtilsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,15 +266,13 @@ describe('InterchainTokenDeployer', () => {
const tokenAddress = await interchainTokenDeployer.deployedAddress(salt);

const token = await getContractAt('InterchainToken', tokenAddress, ownerWallet);
const tokenProxy = await getContractAt('InterchainTokenProxy', tokenAddress, ownerWallet);

await expect(interchainTokenDeployer.deployInterchainToken(salt, tokenManager, tokenManager, name, symbol, decimals))
.and.to.emit(token, 'RolesAdded')
.withArgs(tokenManager, 1 << DISTRIBUTOR_ROLE)
.to.emit(token, 'RolesAdded')
.withArgs(tokenManager, 1 << DISTRIBUTOR_ROLE);

expect(await tokenProxy.implementation()).to.equal(interchainToken.address);
expect(await token.name()).to.equal(name);
expect(await token.symbol()).to.equal(symbol);
expect(await token.decimals()).to.equal(decimals);
Expand Down
Loading