Skip to content

Commit

Permalink
(feat) Ability to remove tokens.
Browse files Browse the repository at this point in the history
(fix) Fix behaviour when scale == 0
(feat) Start of V4 tests
  • Loading branch information
rrw-zilliqa committed Jan 7, 2025
1 parent 0034e16 commit b2a3f4c
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,20 @@ import {ITokenManagerEvents, ITokenManagerStructs} from "contracts/periphery/Tok
import {TokenManagerFees, ITokenManagerFees} from "contracts/periphery/TokenManagerV2/TokenManagerFees.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";

interface ITokenManagerV4Structs {
struct ScaledRemoteToken {
address token;
address tokenManager;
uint chainId;
int8 scale;
}
}

interface ITokenManagerV4Events {
event TokenScaleChanged (
event TokenRegisteredWithScale(
address indexed token,
address remoteToken,
address remoteTokenManager,
uint remoteChainId,
int8 remoteScale
);
int8 scale);
}

interface ITokenManager is
ITokenManagerEvents,
ITokenManagerStructs,
ITokenManagerFees,
ITokenManagerV4Structs,
ITokenManagerV4Events
ITokenManagerV4Events
{
error InvalidSourceChainId();
error InvalidTokenManager();
Expand Down Expand Up @@ -126,10 +117,6 @@ abstract contract TokenManagerUpgradeableV4 is
return $.remoteTokens[token][remoteChainId];
}

function getRemoteTokenScale(address token, uint remoteChainId) public view returns (int8) {
TokenManagerStorage storage $ = _getTokenManagerStorage();
return $.scaleForRemoteTokens[token][remoteChainId];
}

modifier onlyGateway() {
if (_msgSender() != address(getGateway())) {
Expand Down Expand Up @@ -167,6 +154,9 @@ abstract contract TokenManagerUpgradeableV4 is
) internal {
TokenManagerStorage storage $ = _getTokenManagerStorage();
$.remoteTokens[localToken][remoteToken.chainId] = remoteToken;
// Forcibly reset the scale. not strictly necessary as the default is zero,
// but here as insurance.
$.scaleForRemoteTokens[localToken][remoteToken.chainId] = 0;
emit TokenRegistered(
localToken,
remoteToken.token,
Expand All @@ -175,14 +165,20 @@ abstract contract TokenManagerUpgradeableV4 is
);
}

function _setScaleForToken(address localToken,
uint remoteChainId,
int8 scale) internal {
// You need to do this all together, because otherwise there is a point
// where the token doesn't have the right scale, and this is exploitable.
function _registerTokenWithScale(address localToken,
RemoteToken memory remoteToken,
int8 scale) internal {
_registerToken(localToken, remoteToken);
TokenManagerStorage storage $ = _getTokenManagerStorage();
$.scaleForRemoteTokens[localToken][remoteChainId] = scale;
emit TokenScaleChanged( localToken,
remoteChainId,
scale );
$.scaleForRemoteTokens[localToken][remoteToken.chainId] = scale;
emit TokenRegisteredWithScale(
localToken,
remoteToken.token,
remoteToken.tokenManager,
remoteToken.chainId,
scale);
}


Expand All @@ -194,7 +190,7 @@ abstract contract TokenManagerUpgradeableV4 is

function _scaleAmount(uint amount,
address localToken,
uint remoteChainId) internal returns (uint)
uint remoteChainId) internal view returns (uint)
{
int8 scale = _getScaleForToken(localToken, remoteChainId);
uint adjusted;
Expand All @@ -210,14 +206,16 @@ abstract contract TokenManagerUpgradeableV4 is
uint multiplier = uint(10)**uint(int256(scale));
adjusted = amount * multiplier;
reconstructed = amount / multiplier;
} else {
adjusted = amount;
reconstructed = amount;
}
// If scale == 0, nothing is done, which is what is intended.
if (adjusted != reconstructed) {
revert InvalidTokenAmount(amount, adjusted, reconstructed);
}
return adjusted;
}

// Token Overrides
function registerToken(
address token,
Expand Down Expand Up @@ -251,12 +249,24 @@ abstract contract TokenManagerUpgradeableV4 is
}

// V4 new function
function setScaleForToken(address localToken,
uint remoteChainId,
int8 scale) external virtual onlyOwner {
_setScaleForToken(localToken, remoteChainId, scale);
function registerTokenWithScale(address token,
RemoteToken memory remoteToken,
int8 scale) external virtual onlyOwner {
_registerTokenWithScale(token, remoteToken, scale);
}


// V4 new function
function getRemoteTokenWithScale(address token, uint remoteChainId) public view returns (RemoteToken memory, int8) {
TokenManagerStorage storage $ = _getTokenManagerStorage();
return ($.remoteTokens[token][remoteChainId], $.scaleForRemoteTokens[token][remoteChainId]);
}

// V4 new function
function removeToken(address token, uint remoteChainId) external virtual onlyOwner {
_removeToken(token, remoteChainId);
}

// TO OVERRIDE – Incoming
function _handleTransfer(
address token,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.20;

import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {TokenManagerUpgradeableV4, ITokenManagerV4Events} from "contracts/periphery/TokenManagerV4/TokenManagerUpgradeableV4.sol";
import {ITokenManager, ITokenManagerFees, ITokenManagerStructs, ITokenManagerEvents} from "contracts/periphery/TokenManagerV2/TokenManagerUpgradeableV2.sol";
import {ITokenManagerFeesEvents} from "contracts/periphery/TokenManagerV2/TokenManagerFees.sol";
import {IRelayer} from "contracts/core/Relayer.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {TestToken} from "test/Helpers.sol";
import {LockAndReleaseTokenManagerDeployer} from "test/periphery/TokenManagerDeployers/LockAndReleaseTokenManagerDeployer.sol";

contract TestTokenManagerUpgradeableV4 is TokenManagerUpgradeableV4 {
event TransferEvent(address indexed token, address indexed from, uint indexed amount);
event AcceptEvent(address indexed token, address indexed from, uint indexed amount);

constructor() {
_disableInitializers();
}

function initialize(uint fees) external initializer {
__TokenManager_init(address(0));
_setFees(fees);
}

function _handleTransfer(address token, address from, uint amount) internal override {
emit TransferEvent(token, from, amount);
}

function _handleAccept(address token, address recipient, uint amount) internal override {
emit AcceptEvent(token, recipient, amount);
}

}

abstract contract TestTokenManagerDeployer {
function deployTestTokenManagerV4(uint fees) public returns (TestTokenManagerUpgradeableV4) {
address implementation = address(new TestTokenManagerUpgradeableV4());
address proxy = address(new ERC1967Proxy(implementation,
abi.encodeCall(
TestTokenManagerUpgradeableV4.initialize,
fees)));
return TestTokenManagerUpgradeableV4(proxy);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ contract LockAndReleaseTokenManagerUpgradeableV4Tests is
tokenManagerV2.registerToken(address(token), remoteToken);
unregisteredToken = new TestToken(transferAmount);


// Premint some tokens for user testing
token.transfer(user, transferAmount);
assertEq(token.balanceOf(user), transferAmount);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.20;

import {Tester} from "test/Tester.sol";
import {TokenManagerUpgradeableV4, ITokenManagerV4Events} from "contracts/periphery/TokenManagerV4/TokenManagerUpgradeableV4.sol";
import {ITokenManager, ITokenManagerFees, ITokenManagerStructs, ITokenManagerEvents} from "contracts/periphery/TokenManagerV2/TokenManagerUpgradeableV2.sol";
import {ITokenManagerFeesEvents} from "contracts/periphery/TokenManagerV2/TokenManagerFees.sol";
import {IRelayer} from "contracts/core/Relayer.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {TestToken} from "test/Helpers.sol";
import { TestTokenManagerDeployer } from "test/periphery/TokenManagerDeployers/TestTokenManagerDeployer.sol";


contract TokenManagerUpgradeableV4Tests is Tester, ITokenManagerStructs, ITokenManagerEvents, ITokenManagerV4Events, TestTokenManagerDeployer {
address deployer = vm.addr(1);
address user = vm.createWallet("user").addr;
uint fees = 0.1 ether;
TokenManagerUpgradeableV4 tokenManager;
TestToken token1;
address remoteTokenAddr = vm.createWallet("remoteToken").addr;
address remoteTokenManagerAddr = vm.createWallet("remoteTokenManager").addr;
uint remoteChainId = 101;
RemoteToken remoteToken =
RemoteToken({
token: remoteTokenAddr,
tokenManager: remoteTokenManagerAddr,
chainId: remoteChainId
});
uint transferAmount = 10 ether;

function setUp() external {
vm.startPrank(deployer);
tokenManager = deployTestTokenManagerV4(fees);
token1 = new TestToken(transferAmount);
vm.stopPrank();
}

// You shouldn't be able to remove a token without being the owner
function test_removeTokenPermissions() external {
startHoax(deployer);
assertEq(tokenManager.owner(), deployer);

tokenManager.registerTokenWithScale(address(token1), remoteToken, 2);
{
RemoteToken memory tokrec;
int8 tokscale;
(tokrec, tokscale) = tokenManager.getRemoteTokenWithScale(address(token1), remoteChainId);
assertEq(tokrec.chainId, remoteChainId);
assertEq(tokrec.token, remoteTokenAddr);
assertEq(tokrec.tokenManager, remoteTokenManagerAddr);
assertEq(tokscale, 2);
}
vm.stopPrank();

startHoax(user);
vm.expectRevert();
tokenManager.removeToken(address(token1), remoteChainId);
vm.stopPrank();
}

// You shouldn't be able to register a token without being the owner.
function test_registerWithScalePermissions() external {
startHoax(user);
vm.expectRevert();
tokenManager.registerTokenWithScale(address(token1), remoteToken, 0);

// Scale for unregistered tokens should be 0
RemoteToken memory tokrec;
int8 tokscale;
(tokrec, tokscale) = tokenManager.getRemoteTokenWithScale(address(token1), 101);
assertEq(tokrec.chainId, 0);
assertEq(tokrec.token, address(0));
assertEq(tokrec.tokenManager, address(0));
assertEq(tokscale, 0);

vm.stopPrank();
}


// Legacy registrations are handled by other tests.

// Test the registration function.
function test_registerWithScale() external {
startHoax(deployer);

assertEq(tokenManager.owner(), deployer);

tokenManager.registerTokenWithScale(address(token1), remoteToken, 2);
{
RemoteToken memory tokrec;
int8 tokscale;
(tokrec, tokscale) = tokenManager.getRemoteTokenWithScale(address(token1), remoteChainId);
assertEq(tokrec.chainId, remoteChainId);
assertEq(tokrec.token, remoteTokenAddr);
assertEq(tokrec.tokenManager, remoteTokenManagerAddr);
assertEq(tokscale, 2);
}

tokenManager.removeToken(address(token1), remoteChainId);
{
RemoteToken memory tokrec;
int8 tokscale;
(tokrec, tokscale) = tokenManager.getRemoteTokenWithScale(address(token1), remoteChainId);
assertEq(tokrec.chainId, 0);
assertEq(tokrec.token, address(0));
assertEq(tokrec.tokenManager, address(0));
assertEq(tokscale, 0);
}

vm.stopPrank();
}
}

0 comments on commit b2a3f4c

Please sign in to comment.