generated from PaulRBerg/hardhat-template
-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from pnetwork-association/feat/pnetwork-v2
feat(evm): add pNetwork v2 adapters
- Loading branch information
Showing
16 changed files
with
1,208 additions
and
0 deletions.
There are no files selected for viewing
41 changes: 41 additions & 0 deletions
41
packages/evm/contracts/adapters/PNetwork/PNetworkAdapter.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
_; | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
packages/evm/contracts/adapters/PNetwork/PNetworkHeaderReporter.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
19
packages/evm/contracts/adapters/PNetwork/PNetworkMessageRelay.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
44
packages/evm/contracts/adapters/PNetwork/PNetworkReporter.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
packages/evm/contracts/adapters/PNetwork/interfaces/IErc20Vault.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
12
packages/evm/contracts/adapters/PNetwork/interfaces/IPToken.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
25
packages/evm/contracts/adapters/PNetwork/test/ERC777Token.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
143
packages/evm/contracts/adapters/PNetwork/test/MockVault.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.