Skip to content

Commit 74e2524

Browse files
Foivosmilapsheth
andauthored
feat: clone instead of fixed proxy (#168)
* Using minimal proxy to hopefully save some gas. * testing gas * remove gas test * prettier * prettier * added some comments * addressed some comments * fixed tests and prettier * clean up tests * address comments * lint --------- Co-authored-by: Milap Sheth <[email protected]>
1 parent 9954e3f commit 74e2524

14 files changed

+166
-138
lines changed

contracts/interchain-token/InterchainToken.sol

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
pragma solidity ^0.8.0;
44

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

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

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

2321
string public name;
2422
string public symbol;
2523
uint8 public decimals;
2624
address internal tokenManager_;
2725

28-
bytes32 private constant CONTRACT_ID = keccak256('interchain-token');
26+
// bytes32(uint256(keccak256('interchain-token-initialized')) - 1);
27+
bytes32 internal constant INITIALIZED_SLOT = 0xc778385ecb3e8cecb82223fa1f343ec6865b2d64c65b0c15c7e8aef225d9e214;
28+
29+
constructor() {
30+
// Make the implementation act as if it has been setup already to disallow calls to init() (even though that wouldn't achieve anything really)
31+
_initialize();
32+
}
2933

3034
/**
31-
* @notice Getter for the contract id.
35+
* @notice returns true if the contract has be setup.
3236
*/
33-
function contractId() external pure override returns (bytes32) {
34-
return CONTRACT_ID;
37+
function _isInitialized() internal view returns (bool initialized) {
38+
assembly {
39+
initialized := sload(INITIALIZED_SLOT)
40+
}
41+
}
42+
43+
/**
44+
* @notice sets initialized to true, to allow only a single init.
45+
*/
46+
function _initialize() internal {
47+
assembly {
48+
sstore(INITIALIZED_SLOT, true)
49+
}
3550
}
3651

3752
/**
@@ -44,20 +59,30 @@ contract InterchainToken is BaseInterchainToken, ERC20Permit, Implementation, Di
4459

4560
/**
4661
* @notice Setup function to initialize contract parameters
47-
* @param params The setup parameters in bytes
48-
* The setup params include tokenManager, distributor, tokenName, symbol, decimals, mintAmount and mintTo
62+
* @param tokenManagerAddress The address of the token manager of this token
63+
* @param distributor The address of the token distributor
64+
* @param tokenName The name of the token
65+
* @param tokenSymbol The symbopl of the token
66+
* @param tokenDecimals The decimals of the token
4967
*/
50-
function setup(bytes calldata params) external override(Implementation, IImplementation) onlyProxy {
51-
address distributor;
52-
address tokenManagerAddress;
53-
string memory tokenName;
54-
(tokenManagerAddress, distributor, tokenName, symbol, decimals) = abi.decode(params, (address, address, string, string, uint8));
68+
function init(
69+
address tokenManagerAddress,
70+
address distributor,
71+
string calldata tokenName,
72+
string calldata tokenSymbol,
73+
uint8 tokenDecimals
74+
) external {
75+
if (_isInitialized()) revert AlreadyInitialized();
76+
77+
_initialize();
5578

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

5982
tokenManager_ = tokenManagerAddress;
6083
name = tokenName;
84+
symbol = tokenSymbol;
85+
decimals = tokenDecimals;
6186

6287
if (distributor != address(0)) _addDistributor(distributor);
6388
_addDistributor(tokenManagerAddress);

contracts/interfaces/IInterchainToken.sol

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
pragma solidity ^0.8.0;
44

5-
import { IImplementation } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IImplementation.sol';
6-
75
import { IInterchainTokenStandard } from './IInterchainTokenStandard.sol';
86
import { IDistributable } from './IDistributable.sol';
97
import { IERC20MintableBurnable } from './IERC20MintableBurnable.sol';
@@ -12,14 +10,31 @@ import { IERC20Named } from './IERC20Named.sol';
1210
/**
1311
* @title IInterchainToken
1412
*/
15-
interface IInterchainToken is IInterchainTokenStandard, IDistributable, IERC20MintableBurnable, IERC20Named, IImplementation {
13+
interface IInterchainToken is IInterchainTokenStandard, IDistributable, IERC20MintableBurnable, IERC20Named {
1614
error TokenManagerAddressZero();
1715
error TokenNameEmpty();
16+
error AlreadyInitialized();
1817

1918
/**
2019
* @notice Getter for the tokenManager used for this token.
2120
* @dev Needs to be overwitten.
2221
* @return tokenManager_ the TokenManager called to facilitate cross chain transfers.
2322
*/
2423
function tokenManager() external view returns (address tokenManager_);
24+
25+
/**
26+
* @notice Setup function to initialize contract parameters
27+
* @param tokenManagerAddress The address of the token manager of this token
28+
* @param distributor The address of the token distributor
29+
* @param tokenName The name of the token
30+
* @param tokenSymbol The symbopl of the token
31+
* @param tokenDecimals The decimals of the token
32+
*/
33+
function init(
34+
address tokenManagerAddress,
35+
address distributor,
36+
string calldata tokenName,
37+
string calldata tokenSymbol,
38+
uint8 tokenDecimals
39+
) external;
2540
}

contracts/interfaces/IInterchainTokenDeployer.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,5 @@ interface IInterchainTokenDeployer {
3838
string calldata name,
3939
string calldata symbol,
4040
uint8 decimals
41-
) external payable returns (address tokenAddress);
41+
) external returns (address tokenAddress);
4242
}

contracts/proxies/InterchainTokenProxy.sol

Lines changed: 0 additions & 33 deletions
This file was deleted.

contracts/test/InterchainTokenTest.sol renamed to contracts/test/BaseInterchainTokenTest.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { BaseInterchainToken } from '../interchain-token/BaseInterchainToken.sol
66
import { Distributable } from '../utils/Distributable.sol';
77
import { IERC20MintableBurnable } from '../interfaces/IERC20MintableBurnable.sol';
88

9-
contract InterchainTokenTest is BaseInterchainToken, Distributable, IERC20MintableBurnable {
9+
contract BaseInterchainTokenTest is BaseInterchainToken, Distributable, IERC20MintableBurnable {
1010
address public tokenManager_;
1111
bool internal tokenManagerRequiresApproval_ = true;
1212
string public name;

contracts/test/HardCodedConstantsTest.sol

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,31 @@
44
pragma solidity ^0.8.0;
55

66
import { TokenManagerLiquidityPool } from '../token-manager/TokenManagerLiquidityPool.sol';
7-
import { Distributable } from '../utils/Distributable.sol';
87
import { FlowLimit } from '../utils/FlowLimit.sol';
9-
import { Operatable } from '../utils/Operatable.sol';
8+
import { InterchainToken } from '../interchain-token/InterchainToken.sol';
109

1110
error Invalid();
1211

1312
contract TestTokenManager is TokenManagerLiquidityPool {
14-
string public constant NAME = 'TestTokenManager';
13+
string public placeholder;
1514

1615
constructor(address interchainTokenService_) TokenManagerLiquidityPool(interchainTokenService_) {
1716
if (LIQUIDITY_POOL_SLOT != uint256(keccak256('liquidity-pool-slot')) - 1) revert Invalid();
1817
}
1918
}
2019

21-
contract TestDistributable is Distributable {
22-
string public constant NAME = 'TestDistributable';
23-
24-
constructor() {}
25-
}
26-
2720
contract TestFlowLimit is FlowLimit {
28-
string public constant NAME = 'TestFlowLimit';
21+
string public placeholder;
2922

3023
constructor() {
3124
if (FLOW_LIMIT_SLOT != uint256(keccak256('flow-limit')) - 1) revert Invalid();
3225
}
3326
}
3427

35-
contract TestOperatable is Operatable {
36-
string public constant NAME = 'TestOperatable';
28+
contract TestInterchainToken is InterchainToken {
29+
string public placeholder;
3730

38-
constructor() {}
31+
constructor() {
32+
if (INITIALIZED_SLOT != bytes32(uint256(keccak256('interchain-token-initialized')) - 1)) revert Invalid();
33+
}
3934
}

contracts/test/utils/OperatableTest.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma solidity ^0.8.0;
44

55
import { Operatable } from '../../utils/Operatable.sol';
66

7-
contract OperatorableTest is Operatable {
7+
contract OperatableTest is Operatable {
88
uint256 public nonce;
99

1010
constructor(address operator) {

contracts/utils/InterchainTokenDeployer.sol

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ pragma solidity ^0.8.0;
55
import { Create3 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3.sol';
66

77
import { IInterchainTokenDeployer } from '../interfaces/IInterchainTokenDeployer.sol';
8-
9-
import { InterchainTokenProxy } from '../proxies/InterchainTokenProxy.sol';
8+
import { IInterchainToken } from '../interfaces/IInterchainToken.sol';
109

1110
/**
1211
* @title InterchainTokenDeployer
@@ -21,6 +20,7 @@ contract InterchainTokenDeployer is IInterchainTokenDeployer, Create3 {
2120
*/
2221
constructor(address implementationAddress_) {
2322
if (implementationAddress_ == address(0)) revert AddressZero();
23+
2424
implementationAddress = implementationAddress_;
2525
}
2626

@@ -34,21 +34,34 @@ contract InterchainTokenDeployer is IInterchainTokenDeployer, Create3 {
3434
* @param decimals Decimals of the token
3535
* @return tokenAddress Address of the deployed token
3636
*/
37-
// slither-disable-next-line locked-ether
3837
function deployInterchainToken(
3938
bytes32 salt,
4039
address tokenManager,
4140
address distributor,
4241
string calldata name,
4342
string calldata symbol,
4443
uint8 decimals
45-
) external payable returns (address tokenAddress) {
46-
bytes memory params = abi.encode(tokenManager, distributor, name, symbol, decimals);
47-
// slither-disable-next-line too-many-digits
48-
bytes memory bytecode = bytes.concat(type(InterchainTokenProxy).creationCode, abi.encode(implementationAddress, params));
44+
) external returns (address tokenAddress) {
45+
// The minimal proxy bytecode is the same as https://github.com/OpenZeppelin/openzeppelin-contracts/blob/94697be8a3f0dfcd95dfb13ffbd39b5973f5c65d/contracts/proxy/Clones.sol#L28
46+
// The minimal proxy bytecode is 0x37 = 55 bytes long
47+
bytes memory bytecode = new bytes(0x37);
48+
address implementation = implementationAddress;
49+
50+
/// @solidity memory-safe-assembly
51+
assembly {
52+
// The first 0x20 = 32 bytes (0x00 - 0x19) are reserved for the length.
53+
// The next 0x14 = 20 bytes (0x20 - 0x33) are the ones below.
54+
mstore(add(bytecode, 0x20), shl(0x60, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73))
55+
// The next 0x14 = 20 bytes (0x34 - 0x47) are the implementation address.
56+
mstore(add(bytecode, 0x34), shl(0x60, implementation))
57+
// The last 0x0f = 15 bytes (0x48 - 0x56) are the ones below.
58+
mstore(add(bytecode, 0x48), shl(0x88, 0x5af43d82803e903d91602b57fd5bf3))
59+
}
4960

5061
tokenAddress = _create3(bytecode, salt);
5162
if (tokenAddress.code.length == 0) revert TokenDeploymentFailed();
63+
64+
IInterchainToken(tokenAddress).init(tokenManager, distributor, name, symbol, decimals);
5265
}
5366

5467
function deployedAddress(bytes32 salt) external view returns (address tokenAddress) {

docs/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,7 +3501,7 @@ string lastMessage
35013501
function _executeWithInterchainToken(string sourceChain, bytes sourceAddress, bytes data, bytes32 tokenId, address token, uint256 amount) internal
35023502
```
35033503

3504-
## InterchainTokenTest
3504+
## BaseInterchainTokenTest
35053505

35063506
### tokenManager_
35073507

@@ -3791,7 +3791,7 @@ fallback() external payable virtual
37913791
receive() external payable virtual
37923792
```
37933793

3794-
## OperatorableTest
3794+
## OperatableTest
37953795

37963796
### nonce
37973797

test/InterchainToken.js

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
'use strict';
22

3-
const chai = require('chai');
43
const { ethers } = require('hardhat');
5-
const {
6-
getContractAt,
7-
utils: { keccak256, toUtf8Bytes },
8-
} = ethers;
9-
const { expect } = chai;
10-
const { getRandomBytes32, expectRevert, getGasOptions } = require('./utils');
4+
const { getContractAt } = ethers;
5+
const { getRandomBytes32, expectRevert } = require('./utils');
116
const { deployContract } = require('../scripts/deploy');
127

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

2116
let token;
22-
let tokenProxy;
2317

2418
let owner;
2519

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

3731
token = await getContractAt('InterchainToken', tokenAddress, owner);
38-
tokenProxy = await getContractAt('InterchainTokenProxy', tokenAddress, owner);
3932

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

47-
describe('Interchain Token Proxy', () => {
48-
it('should revert if interchain token implementation is invalid', async () => {
49-
const invalidInterchainToken = await deployContract(owner, 'InvalidInterchainToken');
50-
interchainTokenDeployer = await deployContract(owner, 'InterchainTokenDeployer', [invalidInterchainToken.address]);
51-
52-
const salt = getRandomBytes32();
53-
54-
await expect(
55-
interchainTokenDeployer.deployInterchainToken(salt, owner.address, owner.address, name, symbol, decimals, getGasOptions()),
56-
).to.be.reverted;
57-
});
58-
59-
it('should revert if interchain token setup fails', async () => {
60-
const params = '0x1234';
61-
await expectRevert(
62-
(gasOptions) => deployContract(owner, 'InterchainTokenProxy', [interchainToken.address, params, gasOptions]),
63-
tokenProxy,
64-
'SetupFailed',
65-
);
66-
});
67-
68-
it('should return the correct contract ID', async () => {
69-
const contractID = await token.contractId();
70-
const hash = keccak256(toUtf8Bytes('interchain-token'));
71-
expect(contractID).to.equal(hash);
72-
});
73-
});
74-
7540
describe('Interchain Token', () => {
76-
it('revert on setup if not called by the proxy', async () => {
77-
const implementationAddress = await tokenProxy.implementation();
41+
it('revert on init if not called by the proxy', async () => {
42+
const implementationAddress = await interchainTokenDeployer.implementationAddress();
7843
const implementation = await getContractAt('InterchainToken', implementationAddress, owner);
7944

80-
const params = '0x';
81-
await expectRevert((gasOptions) => implementation.setup(params, gasOptions), token, 'NotProxy');
45+
const tokenManagerAddress = owner.address;
46+
const distributor = owner.address;
47+
const tokenName = 'name';
48+
const tokenSymbol = 'symbol';
49+
const tokenDecimals = 7;
50+
await expectRevert(
51+
(gasOptions) => implementation.init(tokenManagerAddress, distributor, tokenName, tokenSymbol, tokenDecimals, gasOptions),
52+
implementation,
53+
'AlreadyInitialized',
54+
);
8255
});
8356
});
8457
});

0 commit comments

Comments
 (0)