Skip to content

Commit

Permalink
feat: test hardcoded constants (#119)
Browse files Browse the repository at this point in the history
* renamed folder and changed version

* npmignore

* npmignore

* change version

* using include pattern instead.

* Fixed most of the things least auhority suggested.

* made lint happy

* Apply suggestions from code review

* fixed some bugs

* added events

* rename set to transfer for distributor and operator

* changed standardized token to always allow token managers to mint/burn it.

* using immutable storage for remoteAddressValidator address to save gas

* Added some recommended changes

* added milap's suggested changes

* Fixed some names and some minor gas optimizations

* prettier and lint

* stash

* import .env in hardhat.config

* trying to fix .env.example

* Added some getters in IRemoteAddressValidator and removed useless check for distributor in the InterchainTokenService.

* removed ternary operators

* made lint happy

* made lint happy

* Added a new token manager to handle fee on transfer and added some tests for it as well

* fixed the liquidity pool check.

* fix a duplication bug

* lint

* added some more tests

* Added more tests

* Added proper re-entrancy protection for fee on transfer token managers.

* change to tx.origin for refunds

* Added support for more kinds of addresses.

* some minor gas opts

* some more gas optimizations.

* Added a getter for chain name to the remote address validator.

* moved the tokenManager getter functionality to a separate contract which saves almost a kilobyte of codesize.

* made lint happy

* Removed tokenManagerGetter and put params into tokenManagers

* Added separate tokenManager interfaces

* addressed ackeeblockchains's 3.0 report

* prettier

* added interchain transfer methods to the service and unified receiving tokens a bit.

* made lint happy

* rename sendToken to interchainTransfer

* changed sendToken everywhere

* changed from uint256.max to a const

* change setting to zero to delete for storage slots.

* rearange storage variables to save a bit of gas.

* Removed unecesairy casts

* made as many event params inexed as possible

* Removed unused imports

* domain separator is calculated each time.

* added some natspec

* added an example for using pre-existing custom tokens.

* added a comment

* feat: improved test coverage part one

* feat: live testnet support

* fix: remove exclusive mocha test

* fix: remove hardcoded gas options

* feat: increased test coverage part two

* fix: remove comment

* feat: add test

* feat: increased test coverage part three

* fix(InvalidStandardizedToken): imports

* feat: address comments

* feat: test hardcoded constants

* fix: ITS tests

* fix: deployed bytecode

* fix: tests

* fix: tests

* fix: remove broken test

* feat: add tests

* feat: temp file rename

* feat: capitalize test file names

* fix: remove duplicate lockUnlockFee deploy functions

* fix: re-add hardcoded constants utils tests

---------

Co-authored-by: Foivos <[email protected]>
Co-authored-by: Milap Sheth <[email protected]>
Co-authored-by: Dean Amiel <[email protected]>
Co-authored-by: Kiryl Yermakou <[email protected]>
  • Loading branch information
5 people authored Oct 12, 2023
1 parent d4e26d4 commit dad864f
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 25 deletions.
61 changes: 61 additions & 0 deletions contracts/test/HardCodedConstantsTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { TokenManagerLiquidityPool } from '../token-manager/implementations/TokenManagerLiquidityPool.sol';
import { Distributable } from '../utils/Distributable.sol';
import { FlowLimit } from '../utils/FlowLimit.sol';
import { NoReEntrancy } from '../utils/NoReEntrancy.sol';
import { Operatable } from '../utils/Operatable.sol';
import { Pausable } from '../utils/Pausable.sol';

contract TestTokenManager is TokenManagerLiquidityPool {
string public constant NAME = 'TestTokenManager';

constructor(address interchainTokenService_) TokenManagerLiquidityPool(interchainTokenService_) {
require(TOKEN_ADDRESS_SLOT == uint256(keccak256('token-address')) - 1, 'invalid constant');
require(LIQUIDITY_POOL_SLOT == uint256(keccak256('liquidity-pool-slot')) - 1, 'invalid constant');
}
}

contract TestDistributable is Distributable {
string public constant NAME = 'TestDistributable';

constructor() {
require(DISTRIBUTOR_SLOT == uint256(keccak256('distributor')) - 1, 'invalid constant');
require(PROPOSED_DISTRIBUTOR_SLOT == uint256(keccak256('proposed-distributor')) - 1, 'invalid constant');
}
}

contract TestFlowLimit is FlowLimit {
string public constant NAME = 'TestFlowLimit';

constructor() {
require(FLOW_LIMIT_SLOT == uint256(keccak256('flow-limit')) - 1, 'invalid constant');
}
}

contract TestNoReEntrancy is NoReEntrancy {
string public constant NAME = 'TestNoReEntrancy';

constructor() {
require(ENTERED_SLOT == uint256(keccak256('entered')) - 1, 'invalid constant');
}
}

contract TestOperatable is Operatable {
string public constant NAME = 'TestOperatable';

constructor() {
require(OPERATOR_SLOT == uint256(keccak256('operator')) - 1, 'invalid constant');
require(PROPOSED_OPERATOR_SLOT == uint256(keccak256('proposed-operator')) - 1, 'invalid constant');
}
}

contract TestPausable is Pausable {
string public constant NAME = 'TestPausable';

constructor() {
require(PAUSE_SLOT == uint256(keccak256('paused')) - 1, 'invalid constant');
}
}
95 changes: 95 additions & 0 deletions test/StandardizedToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
'use strict';

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

const StandardizedToken = require('../artifacts/contracts/token-implementations/StandardizedToken.sol/StandardizedToken.json');
const StandardizedTokenProxy = require('../artifacts/contracts/proxies/StandardizedTokenProxy.sol/StandardizedTokenProxy.json');

describe('StandardizedToken', () => {
let standardizedToken, standardizedTokenDeployer;

const name = 'tokenName';
const symbol = 'tokenSymbol';
const decimals = 18;
const mintAmount = 123;

let token;
let tokenProxy;

let owner;

before(async () => {
const wallets = await ethers.getSigners();
owner = wallets[0];

standardizedToken = await deployContract(owner, 'StandardizedToken');
standardizedTokenDeployer = await deployContract(owner, 'StandardizedTokenDeployer', [standardizedToken.address]);

const salt = getRandomBytes32();

const tokenAddress = await standardizedTokenDeployer.deployedAddress(salt);

token = new Contract(tokenAddress, StandardizedToken.abi, owner);
tokenProxy = new Contract(tokenAddress, StandardizedTokenProxy.abi, owner);

await standardizedTokenDeployer
.deployStandardizedToken(salt, owner.address, owner.address, name, symbol, decimals, mintAmount, owner.address)
.then((tx) => tx.wait());
});

describe('Standardized Token Proxy', () => {
it('should revert if standardized token implementation is invalid', async () => {
const invalidStandardizedToken = await deployContract(owner, 'InvalidStandardizedToken');
standardizedTokenDeployer = await deployContract(owner, 'StandardizedTokenDeployer', [invalidStandardizedToken.address]);

const salt = getRandomBytes32();

await expect(
standardizedTokenDeployer.deployStandardizedToken(
salt,
owner.address,
owner.address,
name,
symbol,
decimals,
mintAmount,
owner.address,
getGasOptions(),
),
).to.be.reverted;
});

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

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

describe('Standardized Token', () => {
it('revert on setup if not called by the proxy', async () => {
const implementationAddress = await tokenProxy.implementation();
const implementation = new Contract(implementationAddress, StandardizedToken.abi, owner);

const params = '0x';
await expectRevert((gasOptions) => implementation.setup(params, gasOptions), token, 'NotProxy');
});
});
});
123 changes: 123 additions & 0 deletions test/TokenManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use strict';

const chai = require('chai');
const { ethers } = require('hardhat');
const {
utils: { toUtf8Bytes, defaultAbiCoder },
constants: { AddressZero },
} = ethers;
const { expect } = chai;
const { expectRevert } = require('./utils');
const { deployContract } = require('../scripts/deploy');

describe('Token Manager', () => {
let owner, user, token, service, liquidityPool;
let tokenManagerLockUnlock, tokenManagerMintBurn, tokenManagerLiquidityPool, tokenManagerLockUnlockFeeOnTransfer;

before(async () => {
[owner, user, token, service, liquidityPool] = await ethers.getSigners();

tokenManagerLockUnlock = await deployContract(owner, `TokenManagerLockUnlock`, [service.address]);
tokenManagerMintBurn = await deployContract(owner, `TokenManagerMintBurn`, [service.address]);
tokenManagerLiquidityPool = await deployContract(owner, `TokenManagerLiquidityPool`, [service.address]);
tokenManagerLockUnlockFeeOnTransfer = await deployContract(owner, `TokenManagerLockUnlockFee`, [service.address]);
});

it('Should calculate hardcoded constants correctly', async () => {
await expect(deployContract(owner, `TestTokenManager`, [service.address])).to.not.be.reverted;
});

it('Should revert on token manager deployment with invalid service address', async () => {
await expectRevert(
(gasOptions) => deployContract(owner, `TokenManagerLockUnlock`, [AddressZero, gasOptions]),
tokenManagerLockUnlock,
'TokenLinkerZeroAddress',
);
});

it('Should revert on setup if not called by the proxy', async () => {
const params = '0x';
await expectRevert((gasOptions) => tokenManagerLockUnlock.setup(params, gasOptions), tokenManagerLockUnlock, 'NotProxy');
});

it('Should revert on transmitInterchainTransfer if not called by the token', async () => {
const sender = owner.address;
const destinationChain = 'Dest Chain';
const destinationAddress = toUtf8Bytes(user.address);
const amount = 10;
const metadata = '0x00000000';

await expectRevert(
(gasOptions) =>
tokenManagerLockUnlock.transmitInterchainTransfer(
sender,
destinationChain,
destinationAddress,
amount,
metadata,
gasOptions,
),
tokenManagerLockUnlock,
'NotToken',
);
});

it('Should revert on giveToken if not called by the service', async () => {
const destinationAddress = user.address;
const amount = 10;

await expectRevert(
(gasOptions) => tokenManagerLockUnlock.giveToken(destinationAddress, amount, gasOptions),
tokenManagerLockUnlock,
'NotService',
);
});

it('Should revert on takeToken if not called by the service', async () => {
const sourceAddress = user.address;
const amount = 10;

await expectRevert(
(gasOptions) => tokenManagerLockUnlock.takeToken(sourceAddress, amount, gasOptions),
tokenManagerLockUnlock,
'NotService',
);
});

it('Should revert on setFlowLimit if not called by the operator', async () => {
const flowLimit = 100;

await expectRevert(
(gasOptions) => tokenManagerLockUnlock.setFlowLimit(flowLimit, gasOptions),
tokenManagerLockUnlock,
'NotOperator',
);
});

it('Should return the correct parameters for lock/unlock token manager', async () => {
const expectedParams = defaultAbiCoder.encode(['bytes', 'address'], [toUtf8Bytes(owner.address), token.address]);
const params = await tokenManagerLockUnlock.getParams(toUtf8Bytes(owner.address), token.address);
expect(expectedParams).to.eq(params);
});

it('Should return the correct parameters for mint/burn token manager', async () => {
const expectedParams = defaultAbiCoder.encode(['bytes', 'address'], [toUtf8Bytes(owner.address), token.address]);
const params = await tokenManagerMintBurn.getParams(toUtf8Bytes(owner.address), token.address);
expect(expectedParams).to.eq(params);
});

it('Should return the correct parameters for liquidity pool token manager', async () => {
const expectedParams = defaultAbiCoder.encode(
['bytes', 'address', 'address'],
[toUtf8Bytes(owner.address), token.address, liquidityPool.address],
);
const params = await tokenManagerLiquidityPool.getParams(toUtf8Bytes(owner.address), token.address, liquidityPool.address);
expect(expectedParams).to.eq(params);
});

it('Should return the correct parameters for fee on transfer token manager', async () => {
const expectedParams = defaultAbiCoder.encode(['bytes', 'address'], [toUtf8Bytes(owner.address), token.address]);
const params = await tokenManagerLockUnlockFeeOnTransfer.getParams(toUtf8Bytes(owner.address), token.address);
expect(expectedParams).to.eq(params);
});
});
24 changes: 0 additions & 24 deletions test/tokenService.js → test/TokenService.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,30 +67,6 @@ describe('Interchain Token Service', () => {
return [token, tokenManager, tokenId];
};

deployFunctions.lockUnlockFee = async function deployNewLockUnlock(
tokenName,
tokenSymbol,
tokenDecimals,
mintAmount = 0,
skipApprove = false,
) {
const salt = getRandomBytes32();
const tokenId = await service.getCustomTokenId(wallet.address, salt);
const tokenManager = new Contract(await service.getTokenManagerAddress(tokenId), TokenManager.abi, wallet);

const token = await deployContract(wallet, 'FeeOnTransferTokenTest', [tokenName, tokenSymbol, tokenDecimals, tokenManager.address]);
const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]);

await (await service.deployCustomTokenManager(salt, LOCK_UNLOCK_FEE_ON_TRANSFER, params)).wait();

if (mintAmount > 0) {
await (await token.mint(wallet.address, mintAmount)).wait();
if (!skipApprove) await (await token.approve(tokenManager.address, mintAmount)).wait();
}

return [token, tokenManager, tokenId];
};

deployFunctions.lockUnlockFee = async function deployNewLockUnlock(
tokenName,
tokenSymbol,
Expand Down
File renamed without changes.
21 changes: 20 additions & 1 deletion test/UtilsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const { time } = require('@nomicfoundation/hardhat-network-helpers');
const { Wallet, Contract } = ethers;
const { AddressZero } = ethers.constants;
const { defaultAbiCoder, arrayify, toUtf8Bytes, hexlify } = ethers.utils;

const { expect } = chai;
const { getRandomBytes32, expectRevert } = require('./utils');
const { deployContract } = require('../scripts/deploy');
Expand All @@ -30,6 +29,10 @@ describe('Operatable', () => {
test = await deployContract(ownerWallet, 'OperatorableTest', [ownerWallet.address]);
});

it('Should calculate hardcoded constants correctly', async () => {
await expect(deployContract(ownerWallet, `TestOperatable`, [])).to.not.be.reverted;
});

it('Should be able to run the onlyOperatorable function as the operator', async () => {
await (await test.testOperatorable()).wait();

Expand Down Expand Up @@ -73,6 +76,10 @@ describe('Distributable', () => {
test = await deployContract(ownerWallet, 'DistributableTest', [ownerWallet.address]);
});

it('Should calculate hardcoded constants correctly', async () => {
await expect(deployContract(ownerWallet, `TestDistributable`, [])).to.not.be.reverted;
});

it('Should be able to run the onlyDistributor function as the distributor', async () => {
await (await test.testDistributable()).wait();

Expand Down Expand Up @@ -184,6 +191,10 @@ describe('FlowLimit', async () => {
await time.increaseTo(next);
}

it('Should calculate hardcoded constants correctly', async () => {
await expect(deployContract(ownerWallet, `TestFlowLimit`, [])).to.not.be.reverted;
});

it('Should be able to set the flow limit', async () => {
await expect(test.setFlowLimit(flowLimit)).to.emit(test, 'FlowLimitSet').withArgs(flowLimit);

Expand Down Expand Up @@ -311,6 +322,10 @@ describe('Pausable', () => {
test = await deployContract(ownerWallet, 'PausableTest');
});

it('Should calculate hardcoded constants correctly', async () => {
await expect(deployContract(ownerWallet, `TestPausable`, [])).to.not.be.reverted;
});

it('Should be able to set paused to true or false', async () => {
await expect(test.setPaused(true)).to.emit(test, 'PausedSet').withArgs(true);

Expand Down Expand Up @@ -429,6 +444,10 @@ describe('StandardizedTokenDeployer', () => {
noReEntrancy = await deployContract(ownerWallet, 'NoReEntrancyTest');
});

it('Should calculate hardcoded constants correctly', async () => {
await expect(deployContract(ownerWallet, `TestNoReEntrancy`, [])).to.not.be.reverted;
});

it('Should revert on reentrancy', async function () {
await expect(noReEntrancy.testFunction()).to.be.revertedWithCustomError(noReEntrancy, 'ReEntrancy');
});
Expand Down

0 comments on commit dad864f

Please sign in to comment.