Skip to content

Commit

Permalink
feat(forwarders): add dandelion voting forwarder
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviera9 committed Feb 21, 2024
1 parent 050ef3c commit 83e1900
Show file tree
Hide file tree
Showing 6 changed files with 1,300 additions and 20 deletions.
54 changes: 54 additions & 0 deletions contracts/forwarder/ForwarderHostPermissioned.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IERC777Recipient} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
import {IERC1820Registry} from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IForwarder} from "../interfaces/IForwarder.sol";
import {IPReceiver} from "../interfaces/external/IPReceiver.sol";
import {IPToken} from "../interfaces/external/IPToken.sol";
import {Helpers} from "../libraries/Helpers.sol";
import {BytesLib} from "../libraries/BytesLib.sol";
import "hardhat/console.sol";

error CallFailed(address target, bytes data);
error InvalidCallParams(address[] targets, bytes[] data, address caller);
error InvalidOriginAddress(address originAddress);
error InvalidCaller(address caller, address expected);

contract ForwarderHostPermissioned is IForwarder, Context, Ownable {
using SafeERC20 for IERC20;

address public immutable caller;
address public immutable token;
mapping(address => bool) private _whitelistedOriginAddresses;

constructor(address _caller, address _token) {
caller = _caller;
token = _token;
}

modifier onlyAdmitted() {
address msgSender = _msgSender();
if (caller != msgSender) {
revert InvalidCaller(msgSender, caller);
}
_;
}

/// @inheritdoc IForwarder
function call(uint256 amount, address to, bytes calldata data, bytes4 chainId) external onlyAdmitted() {
address msgSender = _msgSender();
if (amount > 0) {
IERC20(token).safeTransferFrom(msgSender, address(this), amount);
}

bytes memory effectiveUserData = abi.encode(data, msgSender);
uint256 effectiveAmount = amount == 0 ? 1 : amount;
IPToken(token).redeem(effectiveAmount, effectiveUserData, Helpers.addressToAsciiString(to), chainId);
}
}
105 changes: 105 additions & 0 deletions contracts/forwarder/ForwarderNativePermissioned.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IERC777Recipient} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
import {IERC1820Registry} from "@openzeppelin/contracts/interfaces/IERC1820Registry.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IForwarder} from "../interfaces/IForwarder.sol";
import {IErc20Vault} from "../interfaces/external/IErc20Vault.sol";
import {IPToken} from "../interfaces/external/IPToken.sol";
import {Helpers} from "../libraries/Helpers.sol";
import {BytesLib} from "../libraries/BytesLib.sol";

error CallFailed(address target, bytes data);
error InvalidCallParams(address[] targets, bytes[] data, address caller);
error InvalidOriginAddress(address originAddress);
error InvalidCaller(address caller, address expected);

contract ForwarderNativePermissioned is IForwarder, IERC777Recipient, Context, Ownable {
using SafeERC20 for IERC20;

address public immutable token;
address public immutable vault;
mapping(address => bool) private _whitelistedOriginAddresses;

constructor(address _token, address _vault) {
IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24).setInterfaceImplementer(
address(this),
keccak256("ERC777TokensRecipient"),
address(this)
);

token = _token;
vault = _vault; // set it to 0 on an host chain
}

modifier onlySelf() {
address msgSender = _msgSender();
if (address(this) != msgSender) {
revert InvalidCaller(msgSender, address(this));
}
_;
}

function tokensReceived(
address /*_operator*/,
address _from,
address /*_to,*/,
uint256 /*_amount*/,
bytes calldata _userData,
bytes calldata /*_operatorData*/
) external override {
if (_msgSender() == token && _from == vault) {
(, bytes memory userData, , address originAddress, , , , ) = abi.decode(
_userData,
(bytes1, bytes, bytes4, address, bytes4, address, bytes, bytes)
);

(bytes memory callsAndTargets, address caller) = abi.decode(userData, (bytes, address));

if (!_whitelistedOriginAddresses[originAddress]) {
revert InvalidOriginAddress(originAddress);
}

(address[] memory targets, bytes[] memory data) = abi.decode(callsAndTargets, (address[], bytes[]));

if (targets.length != data.length) {
revert InvalidCallParams(targets, data, caller);
}

for (uint256 i = 0; i < targets.length; ) {
(bool success, ) = targets[i].call(data[i]);
if (!success) {
revert CallFailed(targets[i], data[i]);
}
unchecked {
++i;
}
}
}
}

/// @inheritdoc IForwarder
function call(uint256 amount, address to, bytes calldata data, bytes4 chainId) external onlySelf() {
_call(token, amount, to, data, chainId);
}

function call(address _token, uint256 amount, address to, bytes calldata data, bytes4 chainId) external onlySelf() {
_call(_token, amount, to, data, chainId);
}

function _call(address _token, uint256 amount, address to, bytes calldata data, bytes4 chainId) internal {
bytes memory effectiveUserData = abi.encode(data, address(this));
uint256 effectiveAmount = amount == 0 ? 1 : amount;
IERC20(_token).safeApprove(vault, effectiveAmount);
IErc20Vault(vault).pegIn(effectiveAmount, _token, Helpers.addressToAsciiString(to), effectiveUserData, chainId);
}

function whitelistOriginAddress(address originAddress) external onlyOwner {
_whitelistedOriginAddresses[originAddress] = true;
}
}
17 changes: 17 additions & 0 deletions contracts/interfaces/external/IErc20Vault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,28 @@
pragma solidity ^0.8.17;

interface IErc20Vault {
event PegIn(
address _tokenAddress,
address _tokenSender,
uint256 _tokenAmount,
string _destinationAddress,
bytes _userData,
bytes4 _originChainId,
bytes4 _destinationChainId
);

function pegIn(
uint256 tokenAmount,
address tokenAddress,
string memory destinationAddress,
bytes memory userData,
bytes4 destinationChainId
) external returns (bool);

function pegOut(
address payable _tokenRecipient,
address _tokenAddress,
uint256 _tokenAmount,
bytes calldata _userData
) external returns (bool success);
}
3 changes: 3 additions & 0 deletions tasks/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module.exports = {
ACL_ADDRESS: '0x50b2b8e429cB51bD43cD3E690e5BEB9eb674f6d7',
ACL_V1_ADDRESS: '0xFDcae423E5e92B76FE7D1e2bcabd36fca8a6a8Fe',
DANDELION_VOTING_ADDRESS: '0x0cf759bcCfEf5f322af58ADaE2D28885658B5e02',
DANDELION_VOTING_V1_ADDRESS: '0x2211bfd97b1c02ae8ac305d206e9780ba7d8bff4',
EPOCH_DURATION: 60 * 60 * 24 * 30,
ERC20_VAULT: '0xe396757EC7E6aC7C8E5ABE7285dde47b98F22db8',
FORWARDER_ON_BSC: '0x0000000000000000000000000000000000000000',
Expand All @@ -15,6 +17,7 @@ module.exports = {
PNT_ON_POLYGON_ADDRESS: '0xb6bcae6468760bc0cdfb9c8ef4ee75c9dd23e1ed',
PNT_ON_GNOSIS_ADDRESS: '0x0259461eed4d76d4f0f900f9035f6c4dfb39159a',
DAOPNT_ON_GNOSIS_ADDRESS: '0xFF8Ce5Aca26251Cc3f31e597291c71794C06092a',
ETHPNT_ADDRESS: '0xf4ea6b892853413bd9d9f1a5d3a620a0ba39c5b2',
TOKEN_MANAGER_ADDRESS: '0xCec0058735D50de98d3715792569921FEb9EfDC1',
ZERO_ADDRESS: '0x0000000000000000000000000000000000000000',
START_FIRST_EPOCH_TIMESTAMP: 1701331199,
Expand Down
Loading

0 comments on commit 83e1900

Please sign in to comment.