Skip to content

Commit

Permalink
Merge pull request #35 from pnetwork-association/feat/pnetwork-v2
Browse files Browse the repository at this point in the history
feat(evm): add pNetwork v2 adapters
  • Loading branch information
allemanfredi authored Dec 14, 2023
2 parents b994a9a + f10894b commit cb65b4e
Show file tree
Hide file tree
Showing 16 changed files with 1,208 additions and 0 deletions.
41 changes: 41 additions & 0 deletions packages/evm/contracts/adapters/PNetwork/PNetworkAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { HeaderOracleAdapter } from "../HeaderOracleAdapter.sol";
import { PNetworkBase } from "./PNetworkBase.sol";

contract PNetworkAdapter is HeaderOracleAdapter, PNetworkBase {
error InvalidSender(address sender, address expected);
error InvalidNetworkId(bytes4 networkId, bytes4 expected);
error UnauthorizedPNetworkReceive();

constructor(
uint256 reporterChain,
address reporterAddress,
address pNetworkVault,
address pNetworkToken,
bytes4 pNetworkReporterNetworkId
)
HeaderOracleAdapter(reporterChain, reporterAddress)
PNetworkBase(pNetworkVault, pNetworkToken, pNetworkReporterNetworkId)
{} // solhint-disable no-empty-blocks

// Implement the ERC777TokensRecipient interface
function tokensReceived(
address,
address from,
address,
uint256,
bytes calldata data,
bytes calldata
) external override onlySupportedToken(msg.sender) {
if (from != VAULT) revert InvalidSender(from, VAULT);
(, bytes memory userData, bytes4 networkId, address sender) = abi.decode(
data,
(bytes1, bytes, bytes4, address)
);
if (networkId != PNETWORK_REF_NETWORK_ID) revert InvalidNetworkId(networkId, PNETWORK_REF_NETWORK_ID);
if (sender != REPORTER_ADDRESS) revert UnauthorizedPNetworkReceive();
_receivePayload(userData);
}
}
41 changes: 41 additions & 0 deletions packages/evm/contracts/adapters/PNetwork/PNetworkBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/interfaces/IERC777Recipient.sol";
import "@openzeppelin/contracts-upgradeable/interfaces/IERC1820RegistryUpgradeable.sol";

abstract contract PNetworkBase is IERC777Recipient {
address public immutable VAULT;
address public immutable TOKEN;
bytes4 public immutable PNETWORK_REF_NETWORK_ID;
IERC1820RegistryUpgradeable private constant ERC1820 =
IERC1820RegistryUpgradeable(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
bytes32 private constant TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");

error InvalidToken(address token, address expected);
error InvalidReceiver(address receiver, address expected);

constructor(address pNetworkVault, address pNetworkToken, bytes4 pNetworkRefNetworkId) {
VAULT = pNetworkVault;
TOKEN = pNetworkToken;
PNETWORK_REF_NETWORK_ID = pNetworkRefNetworkId;
ERC1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this));
}

// Implement the ERC777TokensRecipient interface
function tokensReceived(
address,
address,
address to,
uint256,
bytes calldata,
bytes calldata
) external virtual onlySupportedToken(msg.sender) {
if (to != address(this)) revert InvalidReceiver(to, address(this));
}

modifier onlySupportedToken(address _tokenAddress) {
if (_tokenAddress != TOKEN) revert InvalidToken(_tokenAddress, TOKEN);
_;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { HeaderReporter } from "../HeaderReporter.sol";
import { PNetworkReporter } from "./PNetworkReporter.sol";

contract PNetworkHeaderReporter is HeaderReporter, PNetworkReporter {
constructor(
address headerStorage,
uint64 adapterChain,
address pNetworkVault,
address pNetworkToken,
bytes4 pNetworkAdapterNetworkId
)
HeaderReporter(headerStorage, adapterChain)
PNetworkReporter(pNetworkVault, pNetworkToken, pNetworkAdapterNetworkId)
{} // solhint-disable no-empty-blocks

function _sendPayload(bytes memory payload, address adapter) internal override {
_pNetworkSend(payload, adapter);
}
}
19 changes: 19 additions & 0 deletions packages/evm/contracts/adapters/PNetwork/PNetworkMessageRelay.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { MessageRelay } from "../MessageRelay.sol";
import { PNetworkReporter } from "./PNetworkReporter.sol";

contract PNetworkMessageRelay is MessageRelay, PNetworkReporter {
constructor(
address yaho,
uint64 adapterChain,
address pNetworkVault,
address pNetworktoken,
bytes4 pNetworkAdapterNetworkId
) MessageRelay(yaho, adapterChain) PNetworkReporter(pNetworkVault, pNetworktoken, pNetworkAdapterNetworkId) {} // solhint-disable no-empty-blocks

function _sendPayload(bytes memory payload, address adapter) internal override {
_pNetworkSend(payload, adapter);
}
}
44 changes: 44 additions & 0 deletions packages/evm/contracts/adapters/PNetwork/PNetworkReporter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.17;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { IErc20Vault } from "./interfaces/IErc20Vault.sol";
import { IPToken } from "./interfaces/IPToken.sol";
import { PNetworkBase } from "./PNetworkBase.sol";

abstract contract PNetworkReporter is PNetworkBase {
uint256 private constant SWAP_AMOUNT = 1;

constructor(
address pNetworkVault,
address pNetworkToken,
bytes4 pNetworkAdapterNetworkId
) PNetworkBase(pNetworkVault, pNetworkToken, pNetworkAdapterNetworkId) {} // solhint-disable no-empty-blocks

function _char(bytes1 b) internal pure returns (bytes1 c) {
if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
else return bytes1(uint8(b) + 0x57);
}

function _pNetworkSend(bytes memory payload, address adapter) internal {
if (VAULT != address(0)) {
IERC20(TOKEN).approve(VAULT, SWAP_AMOUNT);
IErc20Vault(VAULT).pegIn(SWAP_AMOUNT, TOKEN, _toAsciiString(adapter), payload, PNETWORK_REF_NETWORK_ID);
} else {
IPToken(TOKEN).redeem(SWAP_AMOUNT, payload, _toAsciiString(adapter), PNETWORK_REF_NETWORK_ID);
}
}

function _toAsciiString(address x) internal pure returns (string memory) {
bytes memory s = new bytes(40);
for (uint i = 0; i < 20; i++) {
bytes1 b = bytes1(uint8(uint(uint160(x)) / (2 ** (8 * (19 - i)))));
bytes1 hi = bytes1(uint8(b) / 16);
bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
s[2 * i] = _char(hi);
s[2 * i + 1] = _char(lo);
}
return string(s);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

interface IErc20Vault {
function pegIn(
uint256 tokenAmount,
address tokenAddress,
string memory destinationAddress,
bytes memory userData,
bytes4 destinationChainId
) external returns (bool);
}
12 changes: 12 additions & 0 deletions packages/evm/contracts/adapters/PNetwork/interfaces/IPToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

interface IPToken {
function redeem(
uint256 amount,
bytes memory userData,
string memory underlyingAssetRecipient,
bytes4 destinationChainId
) external;
}
25 changes: 25 additions & 0 deletions packages/evm/contracts/adapters/PNetwork/test/ERC777Token.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/token/ERC777/ERC777.sol";

contract ERC777Token is ERC777 {
constructor(
string memory name,
string memory symbol,
address[] memory defaultOperators
) ERC777(name, symbol, defaultOperators) {
_mint(msg.sender, 1000000, "", "");
}

function mint(
address recipient,
uint256 value,
bytes memory userData,
bytes memory operatorData
) public returns (bool) {
require(recipient != address(this), "Recipient cannot be the token contract address!");
_mint(recipient, value, userData, operatorData);
return true;
}
}
143 changes: 143 additions & 0 deletions packages/evm/contracts/adapters/PNetwork/test/MockVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: MIT

// NOTE: This special version of the pTokens-erc20-vault is for ETH mainnet, and includes custom
// logic to handle ETHPNT<->PNT fungibility, as well as custom logic to handle GALA tokens after
// they upgraded from v1 to v2.

pragma solidity ^0.8.17;

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC777/IERC777Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC777/IERC777RecipientUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/introspection/IERC1820RegistryUpgradeable.sol";

contract MockVault is Initializable, IERC777RecipientUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;

IERC1820RegistryUpgradeable private constant _erc1820 =
IERC1820RegistryUpgradeable(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
bytes32 private constant TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");
bytes32 private constant Erc777Token_INTERFACE_HASH = keccak256("ERC777Token");
EnumerableSetUpgradeable.AddressSet private supportedTokens;
bytes4 public ORIGIN_CHAIN_ID;

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

function initialize(address[] memory _tokensToSupport, bytes4 _originChainId) public initializer {
for (uint256 i = 0; i < _tokensToSupport.length; i++) {
supportedTokens.add(_tokensToSupport[i]);
}
_erc1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this));
ORIGIN_CHAIN_ID = _originChainId;
}

modifier onlySupportedTokens(address _tokenAddress) {
require(supportedTokens.contains(_tokenAddress), "Token at supplied address is NOT supported!");
_;
}

function pegIn(
uint256 _tokenAmount,
address _tokenAddress,
string calldata _destinationAddress,
bytes4 _destinationChainId
) external returns (bool) {
return pegIn(_tokenAmount, _tokenAddress, _destinationAddress, "", _destinationChainId);
}

function pegIn(
uint256 _tokenAmount,
address _tokenAddress,
string memory _destinationAddress,
bytes memory _userData,
bytes4 _destinationChainId
) public onlySupportedTokens(_tokenAddress) returns (bool) {
require(_tokenAmount > 0, "Token amount must be greater than zero!");
IERC20Upgradeable(_tokenAddress).safeTransferFrom(msg.sender, address(this), _tokenAmount);

require(_tokenAddress != address(0), "`_tokenAddress` is set to zero address!");

emit PegIn(
_tokenAddress,
msg.sender,
_tokenAmount,
_destinationAddress,
_userData,
ORIGIN_CHAIN_ID,
_destinationChainId
);

return true;
}

/**
* @dev Implementation of IERC777Recipient.
*/
function tokensReceived(
address /*operator*/,
address from,
address to,
uint256 amount,
bytes calldata userData,
bytes calldata /*operatorData*/
) external override onlySupportedTokens(msg.sender) {
require(to == address(this), "Token receiver is not this contract");
if (userData.length > 0) {
require(amount > 0, "Token amount must be greater than zero!");
(bytes32 tag, string memory _destinationAddress, bytes4 _destinationChainId) = abi.decode(
userData,
(bytes32, string, bytes4)
);
require(tag == keccak256("ERC777-pegIn"), "Invalid tag for automatic pegIn on ERC777 send");
emit PegIn(msg.sender, from, amount, _destinationAddress, userData, ORIGIN_CHAIN_ID, _destinationChainId);
}
}

function pegOut(
address payable _tokenRecipient,
address _tokenAddress,
uint256 _tokenAmount,
bytes calldata _userData
) external returns (bool success) {
return pegOutTokens(_tokenAddress, _tokenRecipient, _tokenAmount, _userData);
}

function pegOutTokens(
address _tokenAddress,
address _tokenRecipient,
uint256 _tokenAmount,
bytes memory _userData
) internal returns (bool success) {
if (tokenIsErc777(_tokenAddress)) {
// NOTE: This is an ERC777 token, so let's use its `send` function so that hooks are called...
IERC777Upgradeable(_tokenAddress).send(_tokenRecipient, _tokenAmount, _userData);
} else {
// NOTE: Otherwise, we use standard ERC20 transfer function instead.
IERC20Upgradeable(_tokenAddress).safeTransfer(_tokenRecipient, _tokenAmount);
}
return true;
}

function tokenIsErc777(address _tokenAddress) internal view returns (bool) {
return _erc1820.getInterfaceImplementer(_tokenAddress, Erc777Token_INTERFACE_HASH) != address(0);
}

receive() external payable {}

function changeOriginChainId(bytes4 _newOriginChainId) public returns (bool success) {
ORIGIN_CHAIN_ID = _newOriginChainId;
return true;
}
}
Loading

0 comments on commit cb65b4e

Please sign in to comment.