-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: introduce LZ script to replay msg https://github.com/DanL0/integrations-foundry-tooling but with better logging in our context * feat: add faucet code + script + test * fix test and forge fmt * fix: pacify slither * fix: use correct IERC165 logic * remove duplicated code * refactor: update test * refactor: update error message * test: update scenario * refactor: use Eid from json in mock
- Loading branch information
1 parent
2481fa9
commit 09197a8
Showing
4 changed files
with
587 additions
and
0 deletions.
There are no files selected for viewing
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,58 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.22; | ||
|
||
import "../test/mocks/AssetsMock.sol"; | ||
import "../test/mocks/DelegationMock.sol"; | ||
|
||
import "../src/interfaces/precompiles/IAssets.sol"; | ||
import "../src/interfaces/precompiles/IDelegation.sol"; | ||
|
||
import {Script, console} from "forge-std/Script.sol"; | ||
import {StdCheats} from "forge-std/StdCheats.sol"; | ||
import "forge-std/StdJson.sol"; | ||
|
||
import {IOAppCore} from "@layerzero-v2/oapp/contracts/oapp/interfaces/IOAppCore.sol"; | ||
|
||
import {Origin} from "../src/lzApp/OAppReceiverUpgradeable.sol"; | ||
|
||
contract SimulateReceive is Script, StdCheats { | ||
|
||
using stdJson for string; | ||
|
||
function run() public { | ||
// https://scan-testnet.layerzero-api.com/v1/messages/tx/<hash> | ||
string memory json = vm.readFile("./scanApiResponse.json"); | ||
uint32 srcEid = uint32(json.readUint(".data[0].pathway.srcEid")); | ||
require(srcEid != 0, "srcEid should not be empty"); | ||
// always monkey-patch a precompile, since with LZ we need them | ||
// TODO: AssetsMock may still complain about a few things. | ||
deployCodeTo("AssetsMock.sol", abi.encode(uint16(srcEid)), ASSETS_PRECOMPILE_ADDRESS); | ||
deployCodeTo("DelegationMock.sol", DELEGATION_PRECOMPILE_ADDRESS); | ||
address senderAddress = json.readAddress(".data[0].pathway.sender.address"); | ||
require(senderAddress != address(0), "senderAddress should not be empty"); | ||
uint64 nonce = uint64(json.readUint(".data[0].pathway.nonce")); | ||
require(nonce != 0, "nonce should not be empty"); | ||
bytes32 sender = addressToBytes32(senderAddress); | ||
require(sender != bytes32(0), "sender should not be empty"); | ||
address receiver = json.readAddress(".data[0].pathway.receiver.address"); | ||
require(receiver != address(0), "receiver should not be empty"); | ||
bytes32 guid = json.readBytes32(".data[0].guid"); | ||
require(guid != bytes32(0), "guid should not be empty"); | ||
bytes memory payload = json.readBytes(".data[0].source.tx.payload"); | ||
require(payload.length != 0, "payload should not be empty"); | ||
|
||
Origin memory origin = Origin({srcEid: srcEid, sender: sender, nonce: nonce}); | ||
bytes memory extraData = ""; | ||
vm.startBroadcast(); | ||
bytes memory encoded = abi.encodeWithSelector( | ||
IOAppCore(receiver).endpoint().lzReceive.selector, origin, receiver, guid, payload, extraData | ||
); | ||
console.logBytes(encoded); | ||
IOAppCore(receiver).endpoint().lzReceive(origin, receiver, guid, payload, extraData); | ||
} | ||
|
||
function addressToBytes32(address _addr) internal pure returns (bytes32) { | ||
return bytes32(uint256(uint160(_addr))); | ||
} | ||
|
||
} |
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,48 @@ | ||
pragma solidity ^0.8.19; | ||
|
||
import {CombinedFaucet} from "../src/core/CombinedFaucet.sol"; | ||
import {BaseScript} from "./BaseScript.sol"; | ||
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; | ||
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; | ||
|
||
import "forge-std/Script.sol"; | ||
|
||
contract DeployScript is BaseScript { | ||
|
||
address tokenAddr; | ||
bool exoEthFaucet; | ||
address faucetOwner; | ||
|
||
function setUp() public virtual override { | ||
super.setUp(); | ||
|
||
string memory prerequisites = vm.readFile("script/prerequisiteContracts.json"); | ||
|
||
tokenAddr = stdJson.readAddress(prerequisites, ".clientChain.erc20Token"); | ||
require(tokenAddr != address(0), "token address should not be empty"); | ||
|
||
clientChain = vm.createSelectFork(clientChainRPCURL); | ||
exocore = vm.createSelectFork(exocoreRPCURL); | ||
|
||
exoEthFaucet = vm.envBool("EXO_ETH_FAUCET"); | ||
// for native token, using a different owner is better since the private key is exposed on the backend | ||
faucetOwner = vm.envAddress("FAUCET_OWNER"); | ||
} | ||
|
||
function run() public { | ||
vm.selectFork(exoEthFaucet ? clientChain : exocore); | ||
vm.startBroadcast(exocoreValidatorSet.privateKey); | ||
address proxyAdmin = address(new ProxyAdmin()); | ||
CombinedFaucet faucetLogic = new CombinedFaucet(); | ||
CombinedFaucet faucet = CombinedFaucet( | ||
payable(address(new TransparentUpgradeableProxy(address(faucetLogic), address(proxyAdmin), ""))) | ||
); | ||
faucet.initialize(exocoreValidatorSet.addr, exoEthFaucet ? tokenAddr : address(0), 1 ether); | ||
vm.stopBroadcast(); | ||
// do not store them as JSON since the address is intentionally kept private | ||
console.log("faucet", address(faucet)); | ||
console.log("faucetLogic", address(faucetLogic)); | ||
console.log("proxyAdmin", address(proxyAdmin)); | ||
} | ||
|
||
} |
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,155 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | ||
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; | ||
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; | ||
|
||
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; | ||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; | ||
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; | ||
|
||
contract CombinedFaucet is | ||
IERC165, | ||
IERC1155Receiver, | ||
IERC721Receiver, | ||
Initializable, | ||
PausableUpgradeable, | ||
OwnableUpgradeable, | ||
ReentrancyGuardUpgradeable | ||
{ | ||
|
||
address public token; | ||
uint256 public tokenAmount; | ||
uint256 public constant ONE_DAY = 1 days; | ||
|
||
mapping(address => uint256) public lastRequestTime; | ||
|
||
event TokenAddressUpdated(address newTokenAddress); | ||
event TokenAmountUpdated(uint256 newTokenAmount); | ||
event TokensRequested(address indexed user, uint256 amount); | ||
|
||
constructor() { | ||
_disableInitializers(); | ||
} | ||
|
||
/// @dev Initialize the contract, set the owner, token address, and token amount | ||
/// @param owner_ The owner of the contract | ||
/// @param token_ The address of the token to distribute | ||
/// @param tokenAmount_ The amount of tokens to distribute at each request | ||
function initialize(address owner_, address token_, uint256 tokenAmount_) public initializer { | ||
// The token address can be 0 to support native token | ||
// slither-disable-next-line missing-zero-check | ||
token = token_; | ||
tokenAmount = tokenAmount_; | ||
|
||
_transferOwnership(owner_); | ||
__Pausable_init_unchained(); | ||
__ReentrancyGuard_init_unchained(); | ||
} | ||
|
||
/// @dev Request tokens from the faucet | ||
/// @notice Users can request tokens once every 24 hours | ||
function requestTokens() external whenNotPaused nonReentrant { | ||
require(token != address(0), "CombinedFaucet: not for native tokens"); | ||
_withdraw(msg.sender); | ||
} | ||
|
||
/// @dev Give native tokens to a user (who doesn't have any to pay for gas) | ||
/// @param user The user to give tokens to | ||
function withdraw(address user) external whenNotPaused onlyOwner { | ||
require(token == address(0), "CombinedFaucet: only for native tokens"); | ||
_withdraw(user); | ||
} | ||
|
||
function _withdraw(address dst) internal { | ||
if (lastRequestTime[dst] != 0) { | ||
require( | ||
block.timestamp >= lastRequestTime[dst] + ONE_DAY, | ||
"CombinedFaucet: Rate limit exceeded. Please wait 24 hours." | ||
); | ||
} | ||
lastRequestTime[dst] = block.timestamp; | ||
if (token != address(0)) { | ||
bool success = IERC20(token).transfer(dst, tokenAmount); | ||
require(success, "CombinedFaucet: token transfer failed"); | ||
} else { | ||
(bool success,) = payable(dst).call{value: tokenAmount}(""); | ||
require(success, "CombinedFaucet: wei transfer failed"); | ||
} | ||
emit TokensRequested(dst, tokenAmount); | ||
} | ||
|
||
/// @dev Update the token address (Only owner can update) | ||
/// @param token_ The new token address | ||
function setTokenAddress(address token_) external onlyOwner { | ||
// The token address can be 0 to support native token | ||
// slither-disable-next-line missing-zero-check | ||
token = token_; | ||
emit TokenAddressUpdated(token_); | ||
} | ||
|
||
/// @dev Update the token amount to distribute (Only owner can update) | ||
/// @param tokenAmount_ The new token amount | ||
function setTokenAmount(uint256 tokenAmount_) external onlyOwner { | ||
tokenAmount = tokenAmount_; | ||
emit TokenAmountUpdated(tokenAmount_); | ||
} | ||
|
||
/// @dev Pause the contract (Only owner can pause) | ||
function pause() external onlyOwner { | ||
_pause(); | ||
} | ||
|
||
/// @dev Unpause the contract (Only owner can unpause) | ||
function unpause() external onlyOwner { | ||
_unpause(); | ||
} | ||
|
||
/// @dev Recover any tokens sent to the contract by mistake (Only owner) | ||
/// @param token_ The token address to recover | ||
/// @param amount_ The amount to recover | ||
function recoverTokens(address token_, uint256 amount_) external nonReentrant onlyOwner { | ||
if (token_ != address(0)) { | ||
bool success = IERC20(token_).transfer(owner(), amount_); | ||
require(success, "CombinedFaucet: token transfer failed"); | ||
} else { | ||
(bool success,) = payable(owner()).call{value: amount_}(""); | ||
require(success, "CombinedFaucet: wei transfer failed"); | ||
} | ||
} | ||
|
||
/// @dev Always revert when ERC721 tokens are sent to this contract. | ||
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { | ||
revert("Faucet: ERC721 tokens not accepted"); | ||
} | ||
|
||
/// @dev Always revert when ERC1155 tokens are sent to this contract. | ||
function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) { | ||
revert("Faucet: ERC1155 tokens not accepted"); | ||
} | ||
|
||
/// @dev Always revert when ERC1155 batch tokens are sent to this contract. | ||
function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) | ||
external | ||
pure | ||
returns (bytes4) | ||
{ | ||
revert("Faucet: ERC1155 batch tokens not accepted"); | ||
} | ||
|
||
/// @dev ERC165 interface support check. | ||
/// Automatically derives the interface selectors for ERC165, ERC721Receiver, and ERC1155Receiver. | ||
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { | ||
return ( | ||
interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC721Receiver).interfaceId | ||
|| interfaceId == type(IERC1155Receiver).interfaceId | ||
); | ||
} | ||
|
||
// Allow the contract to receive native token | ||
receive() external payable {} | ||
|
||
} |
Oops, something went wrong.