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: fee on transfer as a separate token manager #96

Merged
merged 45 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fbaded0
renamed folder and changed version
Foivos Jul 6, 2023
8a6d845
npmignore
Foivos Jul 6, 2023
607de5c
npmignore
Foivos Jul 6, 2023
52fc054
Merge remote-tracking branch 'origin/main' into feat/dist
Foivos Jul 6, 2023
f5b5f56
change version
Foivos Jul 6, 2023
4c5738d
using include pattern instead.
Foivos Jul 6, 2023
15e606b
Merge remote-tracking branch 'origin/main' into feat/dist0-1-1
Foivos Jul 6, 2023
d998f1b
Merge branch 'main' into feat/dist0-1-1
Foivos Jul 11, 2023
0548a6e
Fixed most of the things least auhority suggested.
Foivos Jul 11, 2023
8397b07
made lint happy
Foivos Jul 11, 2023
f2e5ea8
Apply suggestions from code review
milapsheth Jul 12, 2023
32ce490
fixed some bugs
Foivos Jul 19, 2023
80616c5
Merge remote-tracking branch 'origin/main' into feat/least-autority-f…
Foivos Jul 19, 2023
dc516fe
added events
Foivos Jul 19, 2023
d6fa384
rename set to transfer for distributor and operator
Foivos Jul 24, 2023
5cc8fbb
changed standardized token to always allow token managers to mint/bur…
Foivos Jul 24, 2023
ea255e0
using immutable storage for remoteAddressValidator address to save gas
Foivos Jul 24, 2023
09bf99a
Merge remote-tracking branch 'origin/main' into feat/gas-optimizations
Foivos Jul 24, 2023
650ca71
Added some recommended changes
Foivos Jul 26, 2023
c69e0a4
added milap's suggested changes
Foivos Jul 26, 2023
63a18e4
Merge remote-tracking branch 'origin/main' into feat/gas-optimizations
Foivos Jul 26, 2023
2993930
Fixed some names and some minor gas optimizations
Foivos Jul 26, 2023
9074ed1
prettier and lint
Foivos Jul 26, 2023
d3cb150
stash
Foivos Jul 27, 2023
53fe6fe
import .env in hardhat.config
Foivos Jul 27, 2023
7eb5207
trying to fix .env.example
Foivos Jul 27, 2023
bf603eb
Added some getters in IRemoteAddressValidator and removed useless che…
Foivos Jul 27, 2023
f1723c3
removed ternary operators
Foivos Jul 27, 2023
bc8ed59
made lint happy
Foivos Jul 27, 2023
7661e1f
Merge branch 'feat/gas-optimizations' into feat/use-create3-lib
Foivos Jul 27, 2023
4bf89db
made lint happy
Foivos Jul 27, 2023
512d126
Added a new token manager to handle fee on transfer and added some te…
Foivos Jul 27, 2023
9e43b1c
fixed the liquidity pool check.
Foivos Jul 28, 2023
ba29a2a
Merge remote-tracking branch 'origin/main' into feat/use-create3-lib
Foivos Jul 28, 2023
1e2aeb3
fix a duplication bug
Foivos Jul 28, 2023
6c62270
lint
Foivos Jul 28, 2023
30c1e53
added some more tests
Foivos Jul 28, 2023
51b70c1
Added more tests
Foivos Jul 28, 2023
6b3f5ed
Merge branch 'feat/use-create3-lib' into feat/fee-on-transfer-separate
Foivos Jul 28, 2023
3e6d85f
Added proper re-entrancy protection for fee on transfer token managers.
Foivos Jul 28, 2023
d4075b8
change to tx.origin for refunds
Foivos Jul 28, 2023
5d9d8ce
Added support for more kinds of addresses.
Foivos Jul 28, 2023
1b35cbf
some minor gas opts
Foivos Jul 28, 2023
412dce9
some more gas optimizations.
Foivos Jul 28, 2023
7b5ecfa
Merge remote-tracking branch 'origin/main' into feat/fee-on-transfer-…
Foivos Aug 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions contracts/interchain-token-service/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ contract InterchainTokenService is

address internal immutable implementationLockUnlock;
address internal immutable implementationMintBurn;
address internal immutable implementationLockUnlockFee;
address internal immutable implementationLiquidityPool;
IAxelarGasService public immutable gasService;
IRemoteAddressValidator public immutable remoteAddressValidator;
Expand Down Expand Up @@ -103,6 +104,10 @@ contract InterchainTokenService is

implementationLockUnlock = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LOCK_UNLOCK);
implementationMintBurn = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.MINT_BURN);
implementationLockUnlockFee = _sanitizeTokenManagerImplementation(
tokenManagerImplementations,
TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER
);
implementationLiquidityPool = _sanitizeTokenManagerImplementation(tokenManagerImplementations, TokenManagerType.LIQUIDITY_POOL);

chainName = chainName_.toBytes32();
Expand Down Expand Up @@ -143,14 +148,6 @@ contract InterchainTokenService is
return CONTRACT_ID;
}

/**
* @notice Getter for the chain name.
* @return name the name of the chain
*/
function getChainName() public view returns (string memory name) {
name = chainName.toTrimmedString();
}

/**
* @notice Calculates the address of a TokenManager from a specific tokenId. The TokenManager does not need to exist already.
* @param tokenId the tokenId.
Expand Down Expand Up @@ -225,6 +222,8 @@ contract InterchainTokenService is
return implementationLockUnlock;
} else if (TokenManagerType(tokenManagerType) == TokenManagerType.MINT_BURN) {
return implementationMintBurn;
} else if (TokenManagerType(tokenManagerType) == TokenManagerType.LOCK_UNLOCK_FEE_ON_TRANSFER) {
return implementationLockUnlockFee;
} else if (TokenManagerType(tokenManagerType) == TokenManagerType.LIQUIDITY_POOL) {
return implementationLiquidityPool;
}
Expand Down Expand Up @@ -439,6 +438,7 @@ contract InterchainTokenService is
/**
* @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing
* sendToken that matches the parameters passed here.
* @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller.
* @param tokenId the tokenId of the TokenManager used.
* @param destinationAddress the destinationAddress for the sendToken.
* @param amount the amount of token to give.
Expand All @@ -459,6 +459,7 @@ contract InterchainTokenService is
/**
* @notice Uses the caller's tokens to fullfill a callContractWithInterchainToken ahead of time. Use this only if you have
* detected an outgoing sendToken that matches the parameters passed here.
* @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller and it will pass an incorrect amount to the contract.
* @param tokenId the tokenId of the TokenManager used.
* @param sourceChain the name of the chain where the call came from.
* @param sourceAddress the caller of callContractWithInterchainToken.
Expand Down Expand Up @@ -513,15 +514,15 @@ contract InterchainTokenService is
bytes memory payload;
if (metadata.length < 4) {
payload = abi.encode(SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount);
_callContract(destinationChain, payload, msg.value, sourceAddress);
_callContract(destinationChain, payload, msg.value);
emit TokenSent(tokenId, destinationChain, destinationAddress, amount);
return;
}
uint32 version;
(version, metadata) = _decodeMetadata(metadata);
if (version > 0) revert InvalidMetadataVersion(version);
payload = abi.encode(SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata);
_callContract(destinationChain, payload, msg.value, sourceAddress);
_callContract(destinationChain, payload, msg.value);
emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata);
}

Expand Down Expand Up @@ -719,17 +720,16 @@ contract InterchainTokenService is
* @param destinationChain The target chain where the contract will be called
* @param payload The data payload for the transaction
* @param gasValue The amount of gas to be paid for the transaction
* @param refundTo The address where the unused gas amount should be refunded to
*/
function _callContract(string calldata destinationChain, bytes memory payload, uint256 gasValue, address refundTo) internal {
function _callContract(string calldata destinationChain, bytes memory payload, uint256 gasValue) internal {
string memory destinationAddress = remoteAddressValidator.getRemoteAddress(destinationChain);
if (gasValue > 0) {
gasService.payNativeGasForContractCall{ value: gasValue }(
address(this),
destinationChain,
destinationAddress,
payload,
refundTo
payload, // solhint-disable-next-line avoid-tx-origin
tx.origin
);
}
gateway.callContract(destinationChain, destinationAddress, payload);
Expand Down Expand Up @@ -758,7 +758,7 @@ contract InterchainTokenService is
bytes memory params
) internal {
bytes memory payload = abi.encode(SELECTOR_DEPLOY_TOKEN_MANAGER, tokenId, tokenManagerType, params);
_callContract(destinationChain, payload, gasValue, msg.sender);
_callContract(destinationChain, payload, gasValue);
emit RemoteTokenManagerDeploymentInitialized(tokenId, destinationChain, gasValue, tokenManagerType, params);
}

Expand Down Expand Up @@ -798,7 +798,7 @@ contract InterchainTokenService is
mintAmount,
operator
);
_callContract(destinationChain, payload, gasValue, msg.sender);
_callContract(destinationChain, payload, gasValue);
emit RemoteStandardizedTokenAndManagerDeploymentInitialized(
tokenId,
name,
Expand Down
6 changes: 0 additions & 6 deletions contracts/interfaces/IInterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,6 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx
*/
function standardizedTokenDeployer() external view returns (address standardizedTokenDeployerAddress);

/**
* @notice Returns the name of the current chain.
* @return name The name of the current chain.
*/
function getChainName() external view returns (string memory name);

/**
* @notice Returns the address of the token manager associated with the given tokenId.
* @param tokenId The tokenId of the token manager.
Expand Down
12 changes: 12 additions & 0 deletions contracts/interfaces/INoReEntrancy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @title Pausable
* @notice This contract provides a mechanism to halt the execution of specific functions
* if a pause condition is activated.
*/
interface INoReEntrancy {
error ReEntrancy();
}
1 change: 1 addition & 0 deletions contracts/interfaces/ITokenManagerType.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface ITokenManagerType {
enum TokenManagerType {
LOCK_UNLOCK,
MINT_BURN,
LOCK_UNLOCK_FEE_ON_TRANSFER,
LIQUIDITY_POOL
}
}
15 changes: 8 additions & 7 deletions contracts/remote-address-validator/RemoteAddressValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable {
*/
function _lowerCase(string memory s) internal pure returns (string memory) {
uint256 length = bytes(s).length;

for (uint256 i; i < length; i++) {
uint8 b = uint8(bytes(s)[i]);
if ((b >= 65) && (b <= 70)) bytes(s)[i] = bytes1(b + uint8(32));
uint8 b;
for (uint256 i; i < length; ++i) {
b = uint8(bytes(s)[i]);
if ((b >= 65) && (b <= 90)) bytes(s)[i] = bytes1(b + uint8(32));
}

return s;
Expand Down Expand Up @@ -152,9 +152,9 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable {
*/
function addGatewaySupportedChains(string[] calldata chainNames) external onlyOwner {
uint256 length = chainNames.length;

string calldata chainName;
for (uint256 i; i < length; ++i) {
string calldata chainName = chainNames[i];
chainName = chainNames[i];
supportedByGateway[chainName] = true;

emit GatewaySupportedChainAdded(chainName);
Expand All @@ -167,9 +167,10 @@ contract RemoteAddressValidator is IRemoteAddressValidator, Upgradable {
*/
function removeGatewaySupportedChains(string[] calldata chainNames) external onlyOwner {
uint256 length = chainNames.length;
string calldata chainName;

for (uint256 i; i < length; ++i) {
string calldata chainName = chainNames[i];
chainName = chainNames[i];
supportedByGateway[chainName] = false;

emit GatewaySupportedChainRemoved(chainName);
Expand Down
72 changes: 72 additions & 0 deletions contracts/test/FeeOnTransferTokenTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { InterchainToken } from '../interchain-token/InterchainToken.sol';
import { Distributable } from '../utils/Distributable.sol';
import { ITokenManager } from '../interfaces/ITokenManager.sol';
import { IERC20BurnableMintable } from '../interfaces/IERC20BurnableMintable.sol';

contract FeeOnTransferTokenTest is InterchainToken, Distributable, IERC20BurnableMintable {
ITokenManager public tokenManager_;
bool internal tokenManagerRequiresApproval_ = true;
string public name;
string public symbol;
uint8 public decimals;

constructor(string memory name_, string memory symbol_, uint8 decimals_, address tokenManagerAddress) {
name = name_;
symbol = symbol_;
decimals = decimals_;
_setDistributor(msg.sender);
tokenManager_ = ITokenManager(tokenManagerAddress);
}

function tokenManager() public view override returns (ITokenManager) {
return tokenManager_;
}

function _beforeInterchainTransfer(
address sender,
string calldata /*destinationChain*/,
bytes calldata /*destinationAddress*/,
uint256 amount,
bytes calldata /*metadata*/
) internal override {
if (!tokenManagerRequiresApproval_) return;
address tokenManagerAddress = address(tokenManager_);
uint256 allowance_ = allowance[sender][tokenManagerAddress];
if (allowance_ != type(uint256).max) {
if (allowance_ > type(uint256).max - amount) {
allowance_ = type(uint256).max - amount;
}

_approve(sender, tokenManagerAddress, allowance_ + amount);
}
}

function setTokenManagerRequiresApproval(bool requiresApproval) public {
tokenManagerRequiresApproval_ = requiresApproval;
}

function mint(address account, uint256 amount) external onlyDistributor {
_mint(account, amount);
}

function burn(address account, uint256 amount) external onlyDistributor {
_burn(account, amount);
}

function setTokenManager(ITokenManager tokenManagerAddress) external {
tokenManager_ = tokenManagerAddress;
}

// Always transfer 10 less base tokens.
function _transfer(address sender, address recipient, uint256 amount) internal override {
if (sender == address(0) || recipient == address(0)) revert InvalidAccount();

balanceOf[sender] -= amount;
balanceOf[recipient] += amount - 10;
emit Transfer(sender, recipient, amount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.8.0;

import { TokenManagerAddressStorage } from './TokenManagerAddressStorage.sol';
import { NoReEntrancy } from '../../utils/NoReEntrancy.sol';
import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol';

import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/SafeTransfer.sol';
Expand All @@ -14,7 +15,7 @@ import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/c
* @dev This contract extends TokenManagerAddressStorage and provides implementation for its abstract methods.
* It uses the Axelar SDK to safely transfer tokens.
*/
contract TokenManagerLiquidityPool is TokenManagerAddressStorage {
contract TokenManagerLiquidityPool is TokenManagerAddressStorage, NoReEntrancy {
// uint256(keccak256('liquidity-pool-slot')) - 1
uint256 internal constant LIQUIDITY_POOL_SLOT = 0x8e02741a3381812d092c5689c9fc701c5185c1742fdf7954c4c4472be4cc4807;

Expand All @@ -26,7 +27,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage {
constructor(address interchainTokenService_) TokenManagerAddressStorage(interchainTokenService_) {}

function implementationType() external pure returns (uint256) {
return 2;
return 3;
}

/**
Expand Down Expand Up @@ -74,7 +75,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage {
* @param amount The amount of tokens to transfer
* @return uint The actual amount of tokens transferred. This allows support for fee-on-transfer tokens.
*/
function _takeToken(address from, uint256 amount) internal override returns (uint256) {
function _takeToken(address from, uint256 amount) internal override noReEntrancy returns (uint256) {
IERC20 token = IERC20(tokenAddress());
address liquidityPool_ = liquidityPool();
uint256 balance = token.balanceOf(liquidityPool_);
Expand All @@ -91,7 +92,7 @@ contract TokenManagerLiquidityPool is TokenManagerAddressStorage {
* @param amount The amount of tokens to transfer
* @return uint The actual amount of tokens transferred
*/
function _giveToken(address to, uint256 amount) internal override returns (uint256) {
function _giveToken(address to, uint256 amount) internal override noReEntrancy returns (uint256) {
IERC20 token = IERC20(tokenAddress());
uint256 balance = IERC20(token).balanceOf(to);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,10 @@ contract TokenManagerLockUnlock is TokenManagerAddressStorage {
*/
function _takeToken(address from, uint256 amount) internal override returns (uint256) {
IERC20 token = IERC20(tokenAddress());
uint256 balance = token.balanceOf(address(this));

SafeTokenTransferFrom.safeTransferFrom(token, from, address(this), amount);

// Note: This allows support for fee-on-transfer tokens
return IERC20(token).balanceOf(address(this)) - balance;
return amount;
}

/**
Expand All @@ -59,10 +57,9 @@ contract TokenManagerLockUnlock is TokenManagerAddressStorage {
*/
function _giveToken(address to, uint256 amount) internal override returns (uint256) {
IERC20 token = IERC20(tokenAddress());
uint256 balance = IERC20(token).balanceOf(to);

SafeTokenTransfer.safeTransfer(token, to, amount);

return IERC20(token).balanceOf(to) - balance;
return amount;
}
}
Loading
Loading