Skip to content

Commit

Permalink
Add IMX deposit limit
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjimmutable committed Nov 3, 2023
1 parent 0b76195 commit 9b93427
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 19 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,12 @@ CHILD_GAS_SERVICE_ADDRESS=
ROOT_CHAIN_NAME="ROOT"
CHILD_CHAIN_NAME="CHILD"
ROOT_IMX_ADDRESS=
INITIAL_IMX_CUMULATIVE_DEPOSIT_LIMIT="0"
```
where `{ROOT,CHILD}_{GATEWAY,GAS_SERVICE}_ADDRESS` refers to the gateway and gas service addresses used by Axelar.

`INITIAL_IMX_CUMULATIVE_DEPOSIT_LIMIT` refers to the cumulative amount of IMX that can be deposited. A value of `0` indicated unlimited.

We can just use dummy gateway/gas service addresses if we only want to test the deployment, and not bridging functionality. If wanting to use dummy addresses, any valid Ethereum address can be used here.

4. Run the deploy script.
Expand Down Expand Up @@ -138,6 +141,7 @@ ROOT_GATEWAY_ADDRESS="0x013459EC3E8Aeced878C5C4bFfe126A366cd19E9"
CHILD_GATEWAY_ADDRESS="0xc7B788E88BAaB770A6d4936cdcCcd5250E1bbAd8"
ROOT_GAS_SERVICE_ADDRESS="0x28f8B50E1Be6152da35e923602a2641491E71Ed8"
CHILD_GAS_SERVICE_ADDRESS="0xC573c722e21eD7fadD38A8f189818433e01Ae466"
INITIAL_IMX_CUMULATIVE_DEPOSIT_LIMIT="0"
ENVIRONMENT="local"
```
(Note that `{ROOT,CHILD}_PRIVATE_KEY` can be any of the standard localhost private keys that get funded)
Expand Down
6 changes: 3 additions & 3 deletions script/DeployRootContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ contract DeployRootContracts is Script {
rootChainChildTokenTemplate.initialize(address(123), "TEMPLATE", "TPT", 18);

RootERC20Bridge rootERC20BridgeImplementation = new RootERC20Bridge();
rootERC20BridgeImplementation.initialize(address(1), address(1), "filler", address(1), address(1), address(1));
rootERC20BridgeImplementation.initialize(
address(1), address(1), "filler", address(1), address(1), address(1), 1
);
TransparentUpgradeableProxy rootERC20BridgeProxy = new TransparentUpgradeableProxy(
address(rootERC20BridgeImplementation),
address(proxyAdmin),
""
);

// TODO add dummy initialize of implementation contracts!

RootAxelarBridgeAdaptor rootBridgeAdaptorImplementation = new RootAxelarBridgeAdaptor();
rootBridgeAdaptorImplementation.initialize(
address(rootERC20BridgeImplementation), "Filler", address(1), address(1)
Expand Down
7 changes: 5 additions & 2 deletions script/InitializeRootContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct InitializeRootContractsParams {
string childChainName;
address rootGateway;
address rootGasService;
uint256 initialIMXCumulativeDepositLimit;
}

contract InitializeRootContracts is Script {
Expand All @@ -42,7 +43,8 @@ contract InitializeRootContracts is Script {
rootWETHToken: vm.envAddress("ROOT_WETH_ADDRESS"),
childChainName: vm.envString("CHILD_CHAIN_NAME"),
rootGateway: vm.envAddress("ROOT_GATEWAY_ADDRESS"),
rootGasService: vm.envAddress("ROOT_GAS_SERVICE_ADDRESS")
rootGasService: vm.envAddress("ROOT_GAS_SERVICE_ADDRESS"),
initialIMXCumulativeDepositLimit: vm.envUint("INITIAL_IMX_CUMULATIVE_DEPOSIT_LIMIT")
});

string[] memory checksumInputs = Utils.getChecksumInputs(params.childBridgeAdaptor);
Expand All @@ -60,7 +62,8 @@ contract InitializeRootContracts is Script {
childBridgeAdaptorChecksum,
params.rootChainChildTokenTemplate,
params.rootIMXToken,
params.rootWETHToken
params.rootWETHToken,
params.initialIMXCumulativeDepositLimit
);

params.rootBridgeAdaptor.initialize(
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/root/IRootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ interface IRootERC20Bridge {
}

interface IRootERC20BridgeEvents {
/// @notice Emitted when the child chain bridge adaptor is updated.
event NewRootBridgeAdaptor(address oldRootBridgeAdaptor, address newRootBridgeAdaptor);
/// @notice Emitted when the IMX deposit limit is updated.
event NewImxDepositLimit(uint256 oldImxDepositLimit, uint256 newImxDepositLimit);
/// @notice Emitted when a map token message is sent to the child chain.
event L1TokenMapped(address indexed rootToken, address indexed childToken);
/// @notice Emitted when an ERC20 deposit message is sent to the child chain.
Expand Down Expand Up @@ -83,4 +87,8 @@ interface IRootERC20BridgeErrors {
error BalanceInvariantCheckFailed(uint256 actualBalance, uint256 expectedBalance);
/// @notice Error when the given child chain bridge adaptor is invalid.
error InvalidChildERC20BridgeAdaptor();
/// @notice Error when the total IMX deposit limit is exceeded
error ImxDepositLimitExceeded();
/// @notice Error when the IMX deposit limit is set below the amount of IMX already deposited
error ImxDepositLimitTooLow();
}
36 changes: 35 additions & 1 deletion src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ contract RootERC20Bridge is
address public childETHToken;
/// @dev The address of the wETH ERC20 token on L1.
address public rootWETHToken;
/// @dev The maximum cumulative amount of IMX that can be deposited into the bridge.
/// @dev A limit of zero indicates unlimited.
uint256 public imxCumulativeDepositLimit;

/**
* @notice Initilization function for RootERC20Bridge.
Expand All @@ -61,6 +64,7 @@ contract RootERC20Bridge is
* @param newChildTokenTemplate Address of child token template to clone.
* @param newRootIMXToken Address of ERC20 IMX on the root chain.
* @param newRootWETHToken Address of ERC20 WETH on the root chain.
* @param newImxCumulativeDepositLimit The cumulative IMX deposit limit.
* @dev Can only be called once.
*/
function initialize(
Expand All @@ -69,7 +73,8 @@ contract RootERC20Bridge is
string memory newChildBridgeAdaptor,
address newChildTokenTemplate,
address newRootIMXToken,
address newRootWETHToken
address newRootWETHToken,
uint256 newImxCumulativeDepositLimit
) public initializer {
if (
newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0)
Expand All @@ -89,15 +94,38 @@ contract RootERC20Bridge is
);
rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor);
childBridgeAdaptor = newChildBridgeAdaptor;
imxCumulativeDepositLimit = newImxCumulativeDepositLimit;
}

/**
* @notice Updates the root bridge adaptor.
* @param newRootBridgeAdaptor Address of new root bridge adaptor.
* @dev Can only be called by owner.
*/
function updateRootBridgeAdaptor(address newRootBridgeAdaptor) external onlyOwner {
if (newRootBridgeAdaptor == address(0)) {
revert ZeroAddress();
}
emit NewRootBridgeAdaptor(address(rootBridgeAdaptor), newRootBridgeAdaptor);
rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor);
}

// TODO add updating of child bridge adaptor. Part of SMR-1908

/**
* @notice Updates the IMX deposit limit.
* @param newImxCumulativeDepositLimit The new cumulative IMX deposit limit.
* @dev Can only be called by owner.
* @dev The limit can decrease, but it can never decrease to below the contract's IMX balance.
*/
function updateImxCumulativeDepositLimit(uint256 newImxCumulativeDepositLimit) external onlyOwner {
if (newImxCumulativeDepositLimit < IERC20Metadata(rootIMXToken).balanceOf(address(this))) {
revert ImxDepositLimitTooLow();
}
emit NewImxDepositLimit(imxCumulativeDepositLimit, newImxCumulativeDepositLimit);
imxCumulativeDepositLimit = newImxCumulativeDepositLimit;
}

/**
* @dev method to receive the ETH back from the WETH contract when it is unwrapped
*/
Expand Down Expand Up @@ -227,6 +255,12 @@ contract RootERC20Bridge is
if (amount == 0) {
revert ZeroAmount();
}
if (
address(rootToken) == rootIMXToken && imxCumulativeDepositLimit != 0
&& IERC20Metadata(rootIMXToken).balanceOf(address(this)) + amount > imxCumulativeDepositLimit
) {
revert ImxDepositLimitExceeded();
}

// ETH, WETH and IMX do not need to be mapped since it should have been mapped on initialization
// ETH also cannot be transferred since it was received in the payable function call
Expand Down
6 changes: 4 additions & 2 deletions test/integration/root/RootERC20Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx
address constant IMX_TOKEN_ADDRESS = address(0xccc);
address constant NATIVE_ETH = address(0xeee);
address constant WRAPPED_ETH = address(0xddd);
uint256 constant unlimitedDepositLimit = 0;

uint256 constant mapTokenFee = 300;
uint256 constant depositFee = 200;
Expand All @@ -34,8 +35,9 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx
function setUp() public {
deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH);

(imxToken, token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) =
integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, WRAPPED_ETH);
(imxToken, token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = integrationSetup(
CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, WRAPPED_ETH, unlimitedDepositLimit
);
}

/**
Expand Down
105 changes: 96 additions & 9 deletions test/unit/root/RootERC20Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid
address constant WRAPPED_ETH = address(0xddd);
uint256 constant mapTokenFee = 300;
uint256 constant depositFee = 200;
uint256 constant unlimitedIMXDeposits = 0;

ERC20PresetMinterPauser public token;
RootERC20Bridge public rootBridge;
Expand All @@ -53,7 +54,8 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid
CHILD_BRIDGE_ADAPTOR_STRING,
address(token),
IMX_TOKEN,
WRAPPED_ETH
WRAPPED_ETH,
unlimitedIMXDeposits
);
}

Expand All @@ -75,50 +77,112 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid
CHILD_BRIDGE_ADAPTOR_STRING,
address(token),
IMX_TOKEN,
WRAPPED_ETH
WRAPPED_ETH,
unlimitedIMXDeposits
);
}

function test_RevertIf_InitializeWithAZeroAddressRootAdapter() public {
RootERC20Bridge bridge = new RootERC20Bridge();
vm.expectRevert(ZeroAddress.selector);
bridge.initialize(address(0), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1), address(1));
bridge.initialize(
address(0),
address(1),
CHILD_BRIDGE_ADAPTOR_STRING,
address(1),
address(1),
address(1),
unlimitedIMXDeposits
);
}

function test_RevertIf_InitializeWithAZeroAddressChildBridge() public {
RootERC20Bridge bridge = new RootERC20Bridge();
vm.expectRevert(ZeroAddress.selector);
bridge.initialize(address(1), address(0), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1), address(1));
bridge.initialize(
address(1),
address(0),
CHILD_BRIDGE_ADAPTOR_STRING,
address(1),
address(1),
address(1),
unlimitedIMXDeposits
);
}

function test_RevertIf_InitializeWithEmptyChildAdapter() public {
RootERC20Bridge bridge = new RootERC20Bridge();
vm.expectRevert(InvalidChildERC20BridgeAdaptor.selector);
bridge.initialize(address(1), address(1), "", address(1), address(1), address(1));
bridge.initialize(address(1), address(1), "", address(1), address(1), address(1), unlimitedIMXDeposits);
}

function test_RevertIf_InitializeWithAZeroAddressTokenTemplate() public {
RootERC20Bridge bridge = new RootERC20Bridge();
vm.expectRevert(ZeroAddress.selector);
bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(0), address(1), address(1));
bridge.initialize(
address(1),
address(1),
CHILD_BRIDGE_ADAPTOR_STRING,
address(0),
address(1),
address(1),
unlimitedIMXDeposits
);
}

function test_RevertIf_InitializeWithAZeroAddressIMXToken() public {
RootERC20Bridge bridge = new RootERC20Bridge();
vm.expectRevert(ZeroAddress.selector);
bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(0), address(1));
bridge.initialize(
address(1),
address(1),
CHILD_BRIDGE_ADAPTOR_STRING,
address(1),
address(0),
address(1),
unlimitedIMXDeposits
);
}

function test_RevertIf_InitializeWithAZeroAddressWETHToken() public {
RootERC20Bridge bridge = new RootERC20Bridge();
vm.expectRevert(ZeroAddress.selector);
bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1), address(0));
bridge.initialize(
address(1),
address(1),
CHILD_BRIDGE_ADAPTOR_STRING,
address(1),
address(1),
address(0),
unlimitedIMXDeposits
);
}

function test_RevertIf_InitializeWithAZeroAddressAll() public {
RootERC20Bridge bridge = new RootERC20Bridge();
vm.expectRevert(ZeroAddress.selector);
bridge.initialize(address(0), address(0), "", address(0), address(0), address(0));
bridge.initialize(address(0), address(0), "", address(0), address(0), address(0), unlimitedIMXDeposits);
}

/**
* UPDATE IMX CUMULATIVE DEPOSIT LIMIT
*/
function test_RevertsIf_IMXDepositLimitTooLow() public {
uint256 imxCumulativeDepositLimit = 700;
uint256 depositAmount = imxCumulativeDepositLimit + 1;

rootBridge.updateImxCumulativeDepositLimit(imxCumulativeDepositLimit);

setupDeposit(IMX_TOKEN, rootBridge, mapTokenFee, depositFee, depositAmount, false);

IERC20Metadata(IMX_TOKEN).approve(address(rootBridge), type(uint256).max);

rootBridge.updateImxCumulativeDepositLimit(depositAmount);

rootBridge.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN), depositAmount);

vm.expectRevert(ImxDepositLimitTooLow.selector);
rootBridge.updateImxCumulativeDepositLimit(imxCumulativeDepositLimit);
}

/**
Expand Down Expand Up @@ -406,6 +470,29 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid
* DEPOSIT TOKEN
*/

function test_RevertsIf_IMXDepositLimitExceeded() public {
uint256 imxCumulativeDepositLimit = 700;

uint256 amount = 300;
setupDeposit(IMX_TOKEN, rootBridge, mapTokenFee, depositFee, amount, false);

IERC20Metadata(IMX_TOKEN).approve(address(rootBridge), type(uint256).max);

rootBridge.updateImxCumulativeDepositLimit(imxCumulativeDepositLimit);

// Valid
rootBridge.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN), amount);

setupDeposit(IMX_TOKEN, rootBridge, mapTokenFee, depositFee, amount, false);
// Valid
rootBridge.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN), amount);

setupDeposit(IMX_TOKEN, rootBridge, mapTokenFee, depositFee, amount, false);
// Invalid
vm.expectRevert(ImxDepositLimitExceeded.selector);
rootBridge.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN), amount);
}

function test_depositCallsSendMessage() public {
uint256 amount = 100;
(, bytes memory predictedPayload) =
Expand Down
6 changes: 4 additions & 2 deletions test/utils.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ contract Utils is Test {
address childBridgeAdaptor,
string memory childBridgeName,
address imxTokenAddress,
address wethTokenAddress
address wethTokenAddress,
uint256 imxCumulativeDepositLimit
)
public
returns (
Expand Down Expand Up @@ -55,7 +56,8 @@ contract Utils is Test {
Strings.toHexString(childBridgeAdaptor),
address(token),
imxTokenAddress,
wethTokenAddress
wethTokenAddress,
imxCumulativeDepositLimit
);

axelarAdaptor.initialize(
Expand Down

0 comments on commit 9b93427

Please sign in to comment.