Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit c643ce0

Browse files
committed
feat: allow liquidity withdrawal from EOA
1 parent 0234458 commit c643ce0

File tree

8 files changed

+69
-14
lines changed

8 files changed

+69
-14
lines changed

contracts/TestContracts.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
pragma solidity 0.8.11;
44

55
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
6-
import "./handlers/ERCHandlerHelpers.sol";
6+
import { ERCHandlerHelpers } from "./handlers/ERCHandlerHelpers.sol";
77
import "./interfaces/IERC20Plus.sol";
88

99
contract NoArgument {

contracts/handlers/ERC1155Handler.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma solidity 0.8.11;
44

55
import "../interfaces/IHandler.sol";
66
import "./ERCHandlerHelpers.sol";
7-
import "../ERC1155Safe.sol";
7+
import { ERC1155Safe } from "../ERC1155Safe.sol";
88
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
99
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
1010
import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
@@ -92,7 +92,7 @@ contract ERC1155Handler is IHandler, ERCHandlerHelpers, ERC1155Safe, ERC1155Hold
9292
@param data Consists of ABI-encoded {tokenAddress}, {recipient}, {tokenIDs},
9393
{amounts}, and {transferData} of types address, address, uint[], uint[], bytes.
9494
*/
95-
function withdraw(bytes memory data) external override onlyBridge {
95+
function withdraw(bytes memory data) external override onlyAuthorized {
9696
address tokenAddress;
9797
address recipient;
9898
uint[] memory tokenIDs;

contracts/handlers/ERC20Handler.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma solidity 0.8.11;
44

55
import "../interfaces/IHandler.sol";
66
import "./ERCHandlerHelpers.sol";
7-
import "../ERC20Safe.sol";
7+
import { ERC20Safe } from "../ERC20Safe.sol";
88

99
/**
1010
@title Handles ERC20 deposits and deposit executions.
@@ -97,7 +97,7 @@ contract ERC20Handler is IHandler, ERCHandlerHelpers, ERC20Safe {
9797
recipient address bytes 32 - 64
9898
amount uint bytes 64 - 96
9999
*/
100-
function withdraw(bytes memory data) external override onlyBridge {
100+
function withdraw(bytes memory data) external override onlyAuthorized {
101101
address tokenAddress;
102102
address recipient;
103103
uint amount;

contracts/handlers/ERC721Handler.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma solidity 0.8.11;
44

55
import "../interfaces/IHandler.sol";
66
import "./ERCHandlerHelpers.sol";
7-
import "../ERC721Safe.sol";
7+
import { ERC721Safe } from "../ERC721Safe.sol";
88
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
99
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
1010

@@ -121,7 +121,7 @@ contract ERC721Handler is IHandler, ERCHandlerHelpers, ERC721Safe {
121121
recipient address bytes 32 - 64
122122
tokenID uint bytes 64 - 96
123123
*/
124-
function withdraw(bytes memory data) external override onlyBridge {
124+
function withdraw(bytes memory data) external override onlyAuthorized {
125125
address tokenAddress;
126126
address recipient;
127127
uint tokenID;

contracts/handlers/ERCHandlerHelpers.sol

+16-1
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
pragma solidity 0.8.11;
44

55
import "../interfaces/IERCHandler.sol";
6+
import { AccessControl } from "../utils/AccessControl.sol";
67

78
/**
89
@title Function used across handler contracts.
910
@author ChainSafe Systems.
1011
@notice This contract is intended to be used with the Bridge contract.
1112
*/
12-
contract ERCHandlerHelpers is IERCHandler {
13+
contract ERCHandlerHelpers is IERCHandler, AccessControl {
14+
bytes32 public constant LIQUIDITY_MANAGER_ROLE = keccak256("LIQUIDITY_MANAGER_ROLE");
15+
1316
address public immutable _bridgeAddress;
1417

1518
uint8 public constant defaultDecimals = 18;
@@ -27,6 +30,7 @@ contract ERCHandlerHelpers is IERCHandler {
2730
}
2831

2932
error ContractAddressNotWhitelisted(address contractAddress);
33+
error NotAuthorized();
3034

3135
// resourceID => token contract address
3236
mapping (bytes32 => address) public _resourceIDToTokenContractAddress;
@@ -41,19 +45,30 @@ contract ERCHandlerHelpers is IERCHandler {
4145
_;
4246
}
4347

48+
modifier onlyAuthorized() {
49+
_onlyAuthorized();
50+
_;
51+
}
52+
4453
/**
4554
@param bridgeAddress Contract address of previously deployed Bridge.
4655
*/
4756
constructor(
4857
address bridgeAddress
4958
) {
5059
_bridgeAddress = bridgeAddress;
60+
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
61+
_setupRole(LIQUIDITY_MANAGER_ROLE, bridgeAddress);
5162
}
5263

5364
function _onlyBridge() private view {
5465
require(msg.sender == _bridgeAddress, "sender must be bridge contract");
5566
}
5667

68+
function _onlyAuthorized() private view {
69+
if(!hasRole(LIQUIDITY_MANAGER_ROLE, _msgSender())) revert NotAuthorized();
70+
}
71+
5772
/**
5873
@notice First verifies {contractAddress} is whitelisted, then sets
5974
{_tokenContractAddressToTokenProperties[contractAddress].isBurnable} to true.

contracts/handlers/XC20Handler.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma solidity 0.8.11;
44

55
import "../interfaces/IHandler.sol";
66
import "./ERCHandlerHelpers.sol";
7-
import "../XC20Safe.sol";
7+
import { XC20Safe } from "../XC20Safe.sol";
88

99
/**
1010
@title Handles XC20 deposits and deposit executions.

test/contractBridge/admin.js

+41-1
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ contract("Bridge - [admin]", async (accounts) => {
1919
const nonAdminAddress = accounts[1];
2020

2121
const expectedBridgeAdmin = accounts[0];
22+
const authorizedAddress = accounts[2];
2223
const someAddress = "0xcafecafecafecafecafecafecafecafecafecafe";
2324
const nullAddress = "0x0000000000000000000000000000000000000000";
2425
const topologyHash = "549f715f5b06809ada23145c2dc548db";
2526
const txHash =
2627
"0x59d881e01ca682130e550e3576b6de760951fb45b1d5dd81342132f57920bbfa";
27-
28+
const depositAmount = 10;
2829
const bytes32 = "0x0";
2930
const emptySetResourceData = "0x";
3031

@@ -404,6 +405,45 @@ contract("Bridge - [admin]", async (accounts) => {
404405
)
405406
});
406407

408+
it("Should allow to withdraw funds if called by authorized address", async () => {
409+
const tokenOwner = accounts[0];
410+
const ERC20HandlerInstance = await ERC20HandlerContract.new(
411+
BridgeInstance.address
412+
);
413+
const ERC20MintableInstance = await ERC20MintableContract.new(
414+
"token",
415+
"TOK"
416+
);
417+
await ERC20MintableInstance.mint(ERC20HandlerInstance.address, depositAmount)
418+
419+
expect(await ERC20HandlerInstance.hasRole(
420+
await ERC20HandlerInstance.LIQUIDITY_MANAGER_ROLE(),
421+
tokenOwner
422+
)).to.be.equal(false);
423+
424+
await ERC20HandlerInstance.grantRole(
425+
await ERC20HandlerInstance.LIQUIDITY_MANAGER_ROLE(),
426+
authorizedAddress,
427+
{
428+
from: tokenOwner
429+
}
430+
);
431+
432+
const recipientBalanceBefore = await ERC20MintableInstance.balanceOf(tokenOwner);
433+
const withdrawData = Helpers.createERCWithdrawData(
434+
ERC20MintableInstance.address,
435+
tokenOwner,
436+
depositAmount,
437+
);
438+
439+
await TruffleAssert.passes(ERC20HandlerInstance.withdraw(withdrawData, {from: authorizedAddress}));
440+
const recipientBalanceAfter = await ERC20MintableInstance.balanceOf(tokenOwner);
441+
442+
expect(
443+
new Ethers.BigNumber.from(depositAmount).add(recipientBalanceBefore.toString()).toString()
444+
).to.be.equal(recipientBalanceAfter.toString());
445+
});
446+
407447
// Set nonce
408448

409449
it("Should set nonce", async () => {

test/e2e/erc1155/sameChain.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ contract("E2E ERC1155 - Same Chain", async (accounts) => {
170170
);
171171
});
172172

173-
it("Handler's withdraw function can be called by only bridge", async () => {
173+
it("Handler's withdraw function can be called only by authorized address", async () => {
174174
const withdrawData = Helpers.createERC1155WithdrawData(
175175
ERC1155MintableInstance.address,
176176
depositorAddress,
@@ -179,9 +179,9 @@ contract("E2E ERC1155 - Same Chain", async (accounts) => {
179179
"0x"
180180
);
181181

182-
await Helpers.reverts(
183-
ERC1155HandlerInstance.withdraw(withdrawData, {from: depositorAddress}),
184-
"sender must be bridge contract"
182+
await Helpers.expectToRevertWithCustomError(
183+
ERC1155HandlerInstance.withdraw.call(withdrawData, {from: depositorAddress}),
184+
"NotAuthorized()"
185185
);
186186
});
187187

0 commit comments

Comments
 (0)