Skip to content

Commit

Permalink
Add pre-hook call functions with allowlists and corresponding tests t…
Browse files Browse the repository at this point in the history
…o BridgerL2 contract.
  • Loading branch information
ylv-io committed Jan 15, 2025
1 parent 44e5632 commit 8210d1b
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 1 deletion.
74 changes: 73 additions & 1 deletion src/bridger/BridgerL2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ pragma solidity ^0.8.18;
import {IBridger} from "@kinto-core/interfaces/bridger/IBridger.sol";
import {IKintoID} from "@kinto-core/interfaces/IKintoID.sol";

import "@kinto-core/interfaces/bridger/IBridgerL2.sol";
import {
IBridgerL2,
SrcPreHookCallParams,
DstPreHookCallParams,
TransferInfo
} from "@kinto-core/interfaces/bridger/IBridgerL2.sol";
import "@kinto-core/interfaces/IKintoWalletFactory.sol";
import "@kinto-core/interfaces/IKintoWallet.sol";
import {IBridge} from "@kinto-core/interfaces/bridger/IBridge.sol";
Expand All @@ -19,6 +24,7 @@ import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/**
* @title BridgerL2 - The vault that holds the bridged assets during Phase IV
Expand All @@ -31,6 +37,7 @@ contract BridgerL2 is Initializable, UUPSUpgradeable, OwnableUpgradeable, Reentr
using SignatureChecker for address;
using ECDSA for bytes32;
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;

/* ============ Constants ============ */

Expand All @@ -54,6 +61,10 @@ contract BridgerL2 is Initializable, UUPSUpgradeable, OwnableUpgradeable, Reentr
address[] public depositedAssets;
/// @notice List of allowed vaults for the Bridge.
mapping(address => bool) public bridgeVaults;
// addresses that can receive assets while being not wallets on dstPreHookCall
EnumerableSet.AddressSet private _receiveAllowlist;
// addresses that can bypass funder whitelisted check on dstPreHookCall
EnumerableSet.AddressSet private _senderAllowlist;

/* ============ Constructor & Upgrades ============ */
constructor(address _walletFactory, address _kintoID) {
Expand Down Expand Up @@ -245,6 +256,67 @@ contract BridgerL2 is Initializable, UUPSUpgradeable, OwnableUpgradeable, Reentr
}
}

/* ============ Hook ============ */

function srcPreHookCall(SrcPreHookCallParams memory params_) external view {
address sender = params_.msgSender;
if (walletFactory.walletTs(sender) == 0) revert InvalidSender(sender);
if (!kintoID.isKYC(IKintoWallet(sender).owners(0))) {
revert KYCRequired(sender);
}
}

function dstPreHookCall(DstPreHookCallParams memory params_) external view {
address receiver = params_.transferInfo.receiver;
address msgSender = abi.decode(params_.transferInfo.data, (address));

if (!_receiveAllowlist.contains(receiver)) {
if (walletFactory.walletTs(receiver) == 0) {
revert InvalidReceiver(receiver);
}

if (!kintoID.isKYC(IKintoWallet(receiver).owners(0))) {
revert KYCRequired(receiver);
}
}

if (!_senderAllowlist.contains(msgSender)) {
if (!IKintoWallet(receiver).isFunderWhitelisted(msgSender)) {
revert SenderNotAllowed(msgSender);
}
}
}

function setReceiver(address[] memory receiver, bool[] memory allowed) external onlyOwner {
for (uint256 index = 0; index < receiver.length; index++) {
if (allowed[index]) {
_receiveAllowlist.add(receiver[index]);
} else {
_receiveAllowlist.remove(receiver[index]);
}
}
emit ReceiverSet(receiver, allowed);
}

function setSender(address[] memory sender, bool[] memory allowed) external onlyOwner {
for (uint256 index = 0; index < sender.length; index++) {
if (allowed[index]) {
_senderAllowlist.add(sender[index]);
} else {
_senderAllowlist.remove(sender[index]);
}
}
emit SenderSet(sender, allowed);
}

function receiveAllowlist() external view returns (address[] memory) {
return _receiveAllowlist.values();
}

function senderAllowlist() external view returns (address[] memory) {
return _senderAllowlist.values();
}

/* ============ Internals ============ */

/**
Expand Down
24 changes: 24 additions & 0 deletions src/interfaces/bridger/IBridgerL2.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

struct SrcPreHookCallParams {
address connector;
address msgSender;
TransferInfo transferInfo;
}

struct DstPreHookCallParams {
address connector;
bytes connectorCache;
TransferInfo transferInfo;
}

struct TransferInfo {
address receiver;
uint256 amount;
bytes data;
}

interface IBridgerL2 {
/* ============ Errors ============ */

error KYCRequired(address user);
error SenderNotAllowed(address wallet);
error InvalidSender(address wallet);
error InvalidReceiver(address wallet);
error InvalidWallet(address wallet);
error NotUnlockedYet();
Expand All @@ -27,6 +47,10 @@ interface IBridgerL2 {

event Withdraw(address indexed user, address indexed l1Address, address indexed inputAsset, uint256 amount);

event ReceiverSet(address[] indexed receiver, bool[] allowed);

event SenderSet(address[] indexed sender, bool[] allowed);

/* ============ Structs ============ */

/* ============ State Change ============ */
Expand Down
95 changes: 95 additions & 0 deletions test/unit/bridger/BridgerL2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.18;
import "@kinto-core/interfaces/bridger/IBridger.sol";
import "@kinto-core/bridger/BridgerL2.sol";

import "@kinto-core-test/helpers/ArrayHelpers.sol";
import "@kinto-core-test/helpers/UUPSProxy.sol";
import "@kinto-core-test/helpers/SignatureHelper.sol";
import "@kinto-core-test/helpers/SignatureHelper.sol";
Expand All @@ -20,6 +21,8 @@ contract BridgerL2NewUpgrade is BridgerL2 {
}

contract BridgerL2Test is SignatureHelper, SharedSetup {
using ArrayHelpers for *;

address sDAI = 0x4190A8ABDe37c9A85fAC181037844615BA934711; // virtual sDAI
address sDAIL2 = 0x5da1004F7341D510C6651C67B4EFcEEA76Cac0E8; // sDAI L2 representation

Expand Down Expand Up @@ -179,6 +182,98 @@ contract BridgerL2Test is SignatureHelper, SharedSetup {
_bridgerL2.claimCommitment();
}

/* ============ Hooks ============ */

function testSrcPreHookCall__WhenSenderNotKintoWallet() external {
address sender = address(0xdead);

vm.expectRevert(abi.encodeWithSelector(IBridgerL2.InvalidSender.selector, sender));
_bridgerL2.srcPreHookCall(
SrcPreHookCallParams(address(0), address(sender), TransferInfo(address(0), 0, bytes("")))
);
}

function testSrcPreHookCall__SenderNotKYCd() external {
// revoke KYC to sender
vm.prank(_kycProvider);
KintoID(_kintoID).addSanction(_owner, 1);

vm.expectRevert(abi.encodeWithSelector(IBridgerL2.KYCRequired.selector, _kintoWallet));
_bridgerL2.srcPreHookCall(
SrcPreHookCallParams(address(0), address(_kintoWallet), TransferInfo(address(0), 0, bytes("")))
);
}

function testSrcPreHookCall() external {
_bridgerL2.srcPreHookCall(
SrcPreHookCallParams(address(0), address(_kintoWallet), TransferInfo(address(0), 0, bytes("")))
);
}

function testDstPreHookCall__ReceiverNotKintoWallet() external {
address sender = _owner;
address receiver = address(0xdead);

vm.expectRevert(abi.encodeWithSelector(IBridgerL2.InvalidReceiver.selector, receiver));
_bridgerL2.dstPreHookCall(
DstPreHookCallParams(address(0), bytes(""), TransferInfo(receiver, 0, abi.encode(sender)))
);
}

function testDstPreHookCall__ReceiverNotKYCd() external {
// revoke KYC to receiver
vm.prank(_kycProvider);
KintoID(_kintoID).addSanction(_owner, 1);

vm.expectRevert(abi.encodeWithSelector(IBridgerL2.KYCRequired.selector, _kintoWallet));
_bridgerL2.dstPreHookCall(
DstPreHookCallParams(address(0), bytes(""), TransferInfo(address(_kintoWallet), 0, abi.encode(_owner)))
);
}

function testDstPreHookCall__SenderNotAllowed() external {
address sender = address(0xdead);

vm.expectRevert(abi.encodeWithSelector(IBridgerL2.SenderNotAllowed.selector, sender));
_bridgerL2.dstPreHookCall(
DstPreHookCallParams(address(0), bytes(""), TransferInfo(address(_kintoWallet), 0, abi.encode(sender)))
);
}

function testDstPreHookCallCall__WhenReceiverIsInAllowlist() external {
address sender = _owner;
address receiver = address(_kintoWallet);

// revoke KYC to receiver
vm.prank(_kycProvider);
KintoID(_kintoID).addSanction(_owner, 1);

vm.prank(_bridgerL2.owner());
_bridgerL2.setReceiver([receiver].toMemoryArray(), [true].toMemoryArray());

_bridgerL2.dstPreHookCall(
DstPreHookCallParams(address(0), bytes(""), TransferInfo(receiver, 0, abi.encode(sender)))
);
}

function testDstPreHookCallCall__WhenSenderIsInAllowlist() external {
address sender = address(0xdead);

vm.prank(_bridgerL2.owner());
_bridgerL2.setSender([sender].toMemoryArray(), [true].toMemoryArray());

vm.prank(_bridgerL2.owner());
_bridgerL2.dstPreHookCall(
DstPreHookCallParams(address(0), bytes(""), TransferInfo(address(_kintoWallet), 0, abi.encode(sender)))
);
}

function testDstPreHookCallCall__WhenSenderIsKintoWalletSigner() external {
_bridgerL2.dstPreHookCall(
DstPreHookCallParams(address(0), bytes(""), TransferInfo(address(_kintoWallet), 0, abi.encode(_owner)))
);
}

/* ============ Viewers ============ */

function testGetUserDeposits() public {
Expand Down

0 comments on commit 8210d1b

Please sign in to comment.