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

Commit ddac879

Browse files
authored
feat: allow liquidity withdrawal from EOA or multisig (#275)
1 parent 170468e commit ddac879

File tree

8 files changed

+71
-14
lines changed

8 files changed

+71
-14
lines changed

Diff for: 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 {

Diff for: 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";
@@ -94,7 +94,7 @@ contract ERC1155Handler is IHandler, ERCHandlerHelpers, ERC1155Safe, ERC1155Hold
9494
@param data Consists of ABI-encoded {tokenAddress}, {recipient}, {tokenIDs},
9595
{amounts}, and {transferData} of types address, address, uint[], uint[], bytes.
9696
*/
97-
function withdraw(bytes memory data) external override onlyBridge {
97+
function withdraw(bytes memory data) external override onlyAuthorized {
9898
address tokenAddress;
9999
address recipient;
100100
uint[] memory tokenIDs;

Diff for: contracts/handlers/ERC20Handler.sol

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

55
import "../interfaces/IHandler.sol";
66
import "./ERCHandlerHelpers.sol";
7+
import { ERC20Safe } from "../ERC20Safe.sol";
78
import "./DepositDataHelper.sol";
8-
import "../ERC20Safe.sol";
99
import "../utils/ExcessivelySafeCall.sol";
1010

1111
/**
@@ -112,7 +112,7 @@ contract ERC20Handler is IHandler, ERCHandlerHelpers, DepositDataHelper, ERC20Sa
112112
recipient address bytes 32 - 64
113113
amount uint bytes 64 - 96
114114
*/
115-
function withdraw(bytes memory data) external override onlyBridge {
115+
function withdraw(bytes memory data) external override onlyAuthorized {
116116
address tokenAddress;
117117
address recipient;
118118
uint amount;

Diff for: 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

@@ -123,7 +123,7 @@ contract ERC721Handler is IHandler, ERCHandlerHelpers, ERC721Safe {
123123
recipient address bytes 32 - 64
124124
tokenID uint bytes 64 - 96
125125
*/
126-
function withdraw(bytes memory data) external override onlyBridge {
126+
function withdraw(bytes memory data) external override onlyAuthorized {
127127
address tokenAddress;
128128
address recipient;
129129
uint tokenID;

Diff for: contracts/handlers/ERCHandlerHelpers.sol

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

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

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

1619
uint8 public constant defaultDecimals = 18;
@@ -28,6 +31,7 @@ contract ERCHandlerHelpers is IERCHandler {
2831
}
2932

3033
error ContractAddressNotWhitelisted(address contractAddress);
34+
error NotAuthorized();
3135

3236
// resourceID => token contract address
3337
mapping (bytes32 => address) public _resourceIDToTokenContractAddress;
@@ -42,19 +46,30 @@ contract ERCHandlerHelpers is IERCHandler {
4246
_;
4347
}
4448

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

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

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

Diff for: 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.

Diff for: test/contractBridge/admin.js

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

2222
const expectedBridgeAdmin = accounts[0];
23+
const authorizedAddress = accounts[2];
2324
const someAddress = "0xcafecafecafecafecafecafecafecafecafecafe";
2425
const nullAddress = "0x0000000000000000000000000000000000000000";
2526
const topologyHash = "549f715f5b06809ada23145c2dc548db";
2627
const txHash =
2728
"0x59d881e01ca682130e550e3576b6de760951fb45b1d5dd81342132f57920bbfa";
28-
29+
const depositAmount = 10;
2930
const bytes32 = "0x0";
3031
const emptySetResourceData = "0x";
3132

@@ -425,6 +426,47 @@ contract("Bridge - [admin]", async (accounts) => {
425426
)
426427
});
427428

429+
it("Should allow to withdraw funds if called by authorized address", async () => {
430+
const tokenOwner = accounts[0];
431+
const ERC20HandlerInstance = await ERC20HandlerContract.new(
432+
BridgeInstance.address,
433+
DefaultMessageReceiverInstance.address
434+
435+
);
436+
const ERC20MintableInstance = await ERC20MintableContract.new(
437+
"token",
438+
"TOK"
439+
);
440+
await ERC20MintableInstance.mint(ERC20HandlerInstance.address, depositAmount)
441+
442+
expect(await ERC20HandlerInstance.hasRole(
443+
await ERC20HandlerInstance.LIQUIDITY_MANAGER_ROLE(),
444+
tokenOwner
445+
)).to.be.equal(false);
446+
447+
await ERC20HandlerInstance.grantRole(
448+
await ERC20HandlerInstance.LIQUIDITY_MANAGER_ROLE(),
449+
authorizedAddress,
450+
{
451+
from: tokenOwner
452+
}
453+
);
454+
455+
const recipientBalanceBefore = await ERC20MintableInstance.balanceOf(tokenOwner);
456+
const withdrawData = Helpers.createERCWithdrawData(
457+
ERC20MintableInstance.address,
458+
tokenOwner,
459+
depositAmount,
460+
);
461+
462+
await TruffleAssert.passes(ERC20HandlerInstance.withdraw(withdrawData, {from: authorizedAddress}));
463+
const recipientBalanceAfter = await ERC20MintableInstance.balanceOf(tokenOwner);
464+
465+
expect(
466+
new Ethers.BigNumber.from(depositAmount).add(recipientBalanceBefore.toString()).toString()
467+
).to.be.equal(recipientBalanceAfter.toString());
468+
});
469+
428470
// Set nonce
429471

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

Diff for: 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)