Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support soneium #24

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
278 changes: 130 additions & 148 deletions contracts/AstarReceiver.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.10;

import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
Expand All @@ -11,113 +14,85 @@ import "./interfaces/IOFTWithFee.sol";
import "./interfaces/XCM.sol";
import "./interfaces/XCM_v2.sol";
import "./interfaces/Types.sol";
import "./interfaces/IWASTR.sol";
import "./utils/BuildCallData.sol";
import "./utils/AddressToAccount.sol";
import "./AstarSlpx.sol";
import "./DerivativeContract.sol";

contract AstarReceiver is Ownable, IOFTReceiverV2 {
contract AstarReceiver is CCIPReceiver, Ownable, IOFTReceiverV2 {
bytes1 private constant ASTAR_CHAIN_TYPE = 0x00;
bytes2 private constant ASTR_CURRENCY_ID = 0x0803;
bytes2 private constant VASTR_CURRENCY_ID = 0x0903;
uint256 private constant BIFROST_PARA_ID = 2030;
bool private constant IS_RELAY_CHAIN = false;
uint16 public constant destChainId = 257;
uint16 public constant destChainId = 340;
address public constant BNC = 0xfFffFffF00000000000000010000000000000007;
address public constant VASTR = 0xfffFffff00000000000000010000000000000010;
address public constant astarSlpx =
0xc6bf0C5C78686f1D0E2E54b97D6de6e2cEFAe9fD;
address public constant polkadotXcm =
0x0000000000000000000000000000000000005004;
address public constant astrNativeOFT =
0xdf41220C7e322bFEF933D85D01821ad277f90172;
address public constant vAstrProxyOFT =
0xba273b7Fa296614019c71Dcc54DCa6C922A93BcF;
address public astarZkSlpx;
uint256 public astrLayerZeroFee;

uint64 private constant soneiumChainSelector = 12505351618335765396;
address private constant WASTR =
0x37795FdD8C165CaB4D6c05771D564d80439CD093;
address private constant astarRouter =
0x8D5c5CB8ec58285B424C93436189fB865e437feF;
address public soneiumSlpx;

uint256 public vastrLayerZeroFee;
address public scriptTrigger;
mapping(address => address) public callerToDerivativeAddress;
mapping(address => bool) public isDerivativeAddress;

event Mint(
address indexed caller,
address indexed derivativeAddress,
uint256 indexed amount
);
event Redeem(
address indexed caller,
address indexed derivativeAddress,
uint256 indexed amount
);
event SetDerivativeAddress(
address indexed caller,
address indexed derivativeAddress
);
event Receive(address indexed sender, uint256 indexed amount);
event SetLayerZeroFee(
address indexed scriptTrigger,
uint256 indexed astrFee,
uint256 indexed vastrFee
);
event SetScriptTrigger(address indexed scriptTrigger);

constructor(address _astarZkSlpx) {
require(_astarZkSlpx != address(0), "Invalid astarZkSlpx");
astarZkSlpx = _astarZkSlpx;
}

function zkSlpxMint(address _from, address _to, uint256 _amount) internal {
require(_from != address(0), "Invalid from");
require(_to != address(0), "Invalid to");
require(_amount != 0, "Invalid amount");
xcmTransferNativeAsset(_from, _amount);

bytes memory targetChain = abi.encodePacked(ASTAR_CHAIN_TYPE, _to);
bytes memory callData = BuildCallData.buildMintCallBytes(
_from,
ASTR_CURRENCY_ID,
targetChain,
"AstarZkEvm"
);
(uint64 transactWeight, uint256 feeAmount) = AstarSlpx(astarSlpx)
.operationToFeeInfo(AstarSlpx.Operation.Mint);

// xcm transact
require(
XCM(polkadotXcm).remote_transact(
BIFROST_PARA_ID,
IS_RELAY_CHAIN,
BNC,
feeAmount,
callData,
transactWeight
),
"Failed to send xcm"
);
emit Mint(_from, _to, _amount);
constructor(address _soneiumSlpx, address router) CCIPReceiver(router) {
require(_soneiumSlpx != address(0), "Invalid soneiumSlpx");
soneiumSlpx = _soneiumSlpx;
}

function zkSlpxRedeem(
address _from,
address _to,
uint256 _amount
function create_order(
address caller,
address assetAddress,
bytes2 token,
uint128 amount,
address receiver,
uint32 channel_id
) internal {
require(_from != address(0), "Invalid from");
require(_to != address(0), "Invalid to");
require(_amount != 0, "Invalid amount");
xcmTransferAsset(VASTR, _from, _amount);
require(amount > 0, "amount must be greater than 0");
if (assetAddress == address(0)) {
xcmTransferNativeAsset(caller, uint256(amount));
} else {
xcmTransferAsset(assetAddress, caller, uint256(amount));
}

bytes memory targetChain = abi.encodePacked(ASTAR_CHAIN_TYPE, _to);
bytes memory callData = BuildCallData.buildRedeemCallBytes(
_from,
VASTR_CURRENCY_ID,
targetChain
// Build bifrost slpx create order call data
bytes memory callData = BuildCallData.buildCreateOrderCallBytes(
caller,
block.chainid,
block.number,
token,
amount,
abi.encodePacked(ASTAR_CHAIN_TYPE, receiver),
"Soneium",
channel_id
);
// XCM Transact
(uint64 transactWeight, uint256 feeAmount) = AstarSlpx(astarSlpx)
.operationToFeeInfo(AstarSlpx.Operation.Redeem);

// xcm transact
.operationToFeeInfo(AstarSlpx.Operation.Mint);
require(
XCM(polkadotXcm).remote_transact(
BIFROST_PARA_ID,
Expand All @@ -129,7 +104,6 @@ contract AstarReceiver is Ownable, IOFTReceiverV2 {
),
"Failed to send xcm"
);
emit Redeem(_from, _to, _amount);
}

function onOFTReceived(
Expand All @@ -141,43 +115,69 @@ contract AstarReceiver is Ownable, IOFTReceiverV2 {
bytes calldata _payload
) external override {
require(_srcChainId == destChainId, "only receive msg from astar-zk");
require(_msgSender() == vAstrProxyOFT, "only native oft can call");
require(
_msgSender() == astrNativeOFT || _msgSender() == vAstrProxyOFT,
"only native oft can call"
address(uint160(uint(_from))) == soneiumSlpx,
"only receive msg from soneiumSlpx"
);
require(
address(uint160(uint(_from))) == astarZkSlpx,
"only receive msg from astarZkSlpx"
processOFTReceive(_amount, _payload);
}

/// handle a received message
function _ccipReceive(
Client.Any2EVMMessage memory any2EvmMessage
) internal override {
Client.EVMTokenAmount[] memory tokenAmounts = any2EvmMessage
.destTokenAmounts;
address token = tokenAmounts[0].token; // we expect one token to be transfered at once but of course, you can transfer several tokens.
uint256 amount = tokenAmounts[0].amount; // we expect one token to be transfered at once but of course, you can transfer several tokens.
processCCIPReceive(token, amount, any2EvmMessage.data);
}

function processCCIPReceive(address token, uint256 amount, bytes memory data) internal {
require(token == WASTR, "only receive WASTR");
IWASTR(payable(WASTR)).withdraw(amount);

(address caller, Types.Operation operation, address receiver, uint32 channelId) = abi.decode(
data,
(address, Types.Operation, address, uint32)
);
if (callerToDerivativeAddress[receiver] == address(0)) {
setDerivativeAddress(receiver);
}
require(operation == Types.Operation.Mint, "only mint operation is allowed");
create_order(
caller,
address(0),
ASTR_CURRENCY_ID,
uint128(amount),
callerToDerivativeAddress[receiver],
channelId
);
}

function processOFTReceive(uint256 amount, bytes memory data) internal {
(address caller, Types.Operation operation) = abi.decode(
_payload,
data,
(address, Types.Operation)
);
if (callerToDerivativeAddress[caller] == address(0)) {
setDerivativeAddress(caller);
}

if (operation == Types.Operation.Mint) {
IOFTWithFee(astrNativeOFT).withdraw(_amount);
(bool success, ) = scriptTrigger.call{value: astrLayerZeroFee}("");
require(success, "failed to charge");
zkSlpxMint(
caller,
callerToDerivativeAddress[caller],
_amount - astrLayerZeroFee
);
} else if (operation == Types.Operation.Redeem) {
bool success = IERC20(VASTR).transfer(
scriptTrigger,
vastrLayerZeroFee
);
require(success, "failed to charge");
zkSlpxRedeem(
caller,
callerToDerivativeAddress[caller],
_amount - vastrLayerZeroFee
);
}
require(operation == Types.Operation.Redeem, "only redeem operation is allowed");
bool success = IERC20(VASTR).transfer(
scriptTrigger,
vastrLayerZeroFee
);
require(success, "failed to charge");
create_order(
caller,
VASTR,
VASTR_CURRENCY_ID,
uint128(amount - vastrLayerZeroFee),
callerToDerivativeAddress[caller],
0
);
}

function claimVAstr(
Expand Down Expand Up @@ -218,46 +218,44 @@ contract AstarReceiver is Ownable, IOFTReceiverV2 {
);
}

function claimAstr(
address addr,
uint256 _amount,
uint256 _minAmount,
bytes calldata _adapterParams
) external payable {
function claimAstr(address addr, uint256 gasLimit) external payable {
require(_msgSender() == scriptTrigger, "must be scriptTrigger");
DerivativeContract(callerToDerivativeAddress[addr]).withdrawNativeToken(
_amount
);
ICommonOFT.LzCallParams memory callParams = ICommonOFT.LzCallParams(
payable(_msgSender()),
address(0),
_adapterParams
);
bytes32 toAddress = bytes32(uint256(uint160(addr)));
(uint256 estimateFee, ) = IOFTWithFee(astrNativeOFT).estimateSendFee(
destChainId,
toAddress,
_amount,
false,
_adapterParams
uint256 _amount = DerivativeContract(callerToDerivativeAddress[addr]).withdrawNativeToken();

IWASTR(payable(WASTR)).deposit{value: _amount}();
IWASTR(payable(WASTR)).approve(astarRouter, _amount);
Client.EVMTokenAmount[]
memory tokenAmounts = new Client.EVMTokenAmount[](1);
Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({
token: WASTR,
amount: _amount
});
tokenAmounts[0] = tokenAmount;
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(addr),
data: abi.encode(""),
tokenAmounts: tokenAmounts,
extraArgs: Client._argsToBytes(
Client.EVMExtraArgsV1({
gasLimit: gasLimit // Gas limit for the callback on the destination chain
})),
feeToken: WASTR
});

uint256 estimateFee = IRouterClient(this.getRouter()).getFee(
soneiumChainSelector,
message
);
require(msg.value >= estimateFee, "too small fee");
if (msg.value != estimateFee) {
uint256 refundAmount = msg.value - estimateFee;
(bool success, ) = _msgSender().call{value: refundAmount}("");
require(success, "failed to refund");
}
IOFTWithFee(astrNativeOFT).sendFrom{value: _amount + estimateFee}(
address(this),
destChainId,
toAddress,
_amount,
_minAmount,
callParams

require(_amount > estimateFee, "too small fee");
message.tokenAmounts[0].amount = _amount - estimateFee;
IRouterClient(this.getRouter()).ccipSend(
soneiumChainSelector,
message
);
}

function setDerivativeAddress(address addr) public {
function setDerivativeAddress(address addr) internal {
require(
callerToDerivativeAddress[addr] == address(0),
"already set derivativeAddress"
Expand Down Expand Up @@ -287,20 +285,10 @@ contract AstarReceiver is Ownable, IOFTReceiverV2 {
adapterParams
);

(uint256 astrFee, ) = IOFTWithFee(astrNativeOFT).estimateSendFee(
destChainId,
toAddress,
amount,
false,
adapterParams
);

astrLayerZeroFee = astrFee;
vastrLayerZeroFee = vastrFee;

emit SetLayerZeroFee(
scriptTrigger,
astrLayerZeroFee,
vastrLayerZeroFee
);
}
Expand Down Expand Up @@ -355,11 +343,5 @@ contract AstarReceiver is Ownable, IOFTReceiverV2 {
);
}

receive() external payable {
require(
isDerivativeAddress[_msgSender()] || _msgSender() == astrNativeOFT,
"sender is not a derivativeAddress or astrNativeOFT"
);
emit Receive(_msgSender(), msg.value);
}
receive() external payable {}
}
Loading