// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/utils/Create2.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {ERC1155, IERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {ERC1155Burnable} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol";
import {ERC1155Supply} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
//only for base
address constant VAULTFACTORY = 0x402949029F11bB31cEBde6Ca7E2D8cEccEB00a2F;
address constant VAULTMANAGEMODULE = 0x28F2600c10fEF2c8dB45e2eA89b085bdE7E22411;
address constant ISSUANCEMODULE = 0x26d11e8C94E342FEcFA7E8826e5fA03a31FB0a3C;
address constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address constant CBBTC = 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf;
interface IJasperOption {
/**
* execute a sequence of transactions
*/
function executeBatch(
address[] calldata dest,
uint256[] calldata value,
bytes[] calldata func
) external returns (bytes[] memory results);
function createAccount(address wallet, uint256 salt) external returns (address ret); // vault
struct PremiumOracleSign {
uint256 id;
uint256 chainId;
uint64 productType;
address optionAsset;
uint256 strikePrice;
address strikeAsset;
uint256 strikeAmount;
address lockAsset;
uint256 lockAmount;
uint256 expireDate;
uint256 lockDate;
uint8 optionType;
address premiumAsset;
uint256 premiumFee;
uint256 timestamp;
bytes[] oracleSign;
}
struct ManagedOrder {
address holder;
address writer;
address recipient;
uint256 quantity;
uint256 settingsIndex;
uint256 productTypeIndex;
uint256 oracleIndex;
address nftFreeOption;
PremiumOracleSign premiumSign;
uint8 optionSourceType;
bool liquidationToEOA;
uint256 offerID;
}
function SubmitManagedOrder(ManagedOrder memory _info) external;
}
contract OptionPositionToken is ERC1155, Ownable, ERC1155Supply {
using EnumerableSet for EnumerableSet.UintSet;
constructor() ERC1155("") Ownable(msg.sender) {}
string public baseUri = "https://jasper-simple-be.deno.dev/items/";
function setURI(string memory newuri) public onlyOwner {
baseUri = newuri;
}
function uri(uint256 id) public view override returns (string memory) {
return string.concat(baseUri, Strings.toString(id), ".json");
}
uint256 public _nextTokenId = 1;
struct TokenInfo {
address aa_owner;
uint total;
}
mapping(uint => TokenInfo) public nft_to_info; // nft id -> info
// The following functions are overrides required by Solidity.
function _update(address from, address to, uint256[] memory ids, uint256[] memory values) internal override(ERC1155, ERC1155Supply) {
super._update(from, to, ids, values);
}
event CreateNFTPosition(
address indexed sender,
address indexed aa_owner,
address indexed aa_owner_aa_vault,
uint token_id,
uint token_amount
);
//create position
function mint(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) public {
uint premium;
uint amountPerShare;
uint total_amount;
address premiumAsset;
//Stack too deep
{
//get order info using last calldata
IJasperOption.ManagedOrder memory info = abi.decode(extractCalldata(func[func.length - 1]), (IJasperOption.ManagedOrder));
premium = (info.premiumSign.premiumFee * info.quantity) / 1e18;
amountPerShare = 0.01 * 1e18;
{
address optionAsset = info.premiumSign.optionAsset;
if (optionAsset == CBBTC) {
amountPerShare = 0.001 * 1e18; //0.001 BTC = 1 share
}
}
require(info.quantity % amountPerShare == 0, "can't split amount");
total_amount = (info.quantity / amountPerShare);
premiumAsset = info.premiumSign.premiumAsset;
}
IERC20(premiumAsset).transferFrom(msg.sender, address(this), premium); //一层层传递给 AA vault
_createPosition(msg.sender, _nextTokenId++, total_amount, premiumAsset, premium, dest, value, func);
}
function _createPosition(
address user,
uint tokenId,
uint total_amount,
address premiumAsset,
uint premium,
address[] calldata dest,
uint256[] calldata value,
bytes[] calldata func
) internal {
AAVaultOwner aa_owner = new AAVaultOwner{salt: keccak256(abi.encodePacked(uint(tokenId)))}();
nft_to_info[tokenId] = TokenInfo({aa_owner: address(aa_owner), total: total_amount});
IERC20(premiumAsset).transfer(address(aa_owner), premium); //一层层传递给 AA vault
address aa_vault_address = aa_owner.init(address(this), tokenId, premiumAsset, premium, dest, value, func);
_mint(user, tokenId, total_amount, "");
emit CreateNFTPosition(user, address(aa_owner), aa_vault_address, tokenId, total_amount);
}
//claim porift
function burn(uint tokenId, uint amount, address vault_asset) public {
address user = msg.sender;
TokenInfo storage tokenInfo = nft_to_info[tokenId];
AAVaultOwner(tokenInfo.aa_owner).redeem(user, tokenInfo.total, amount, vault_asset);
_burn(user, tokenId, amount);
}
//reads
function calculateAddr(uint tokenId) public view returns (address predictedAddress) {
bytes32 salt = keccak256(abi.encodePacked(uint(tokenId)));
predictedAddress = address(
uint160(uint(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(type(AAVaultOwner).creationCode)))))
);
}
// utils
function extractCalldata(bytes memory calldataWithSelector) internal pure returns (bytes memory) {
bytes memory calldataWithoutSelector;
require(calldataWithSelector.length >= 4);
assembly {
let totalLength := mload(calldataWithSelector)
let targetLength := sub(totalLength, 4)
calldataWithoutSelector := mload(0x40)
// Set the length of callDataWithoutSelector (initial length - 4)
mstore(calldataWithoutSelector, targetLength)
// Mark the memory space taken for callDataWithoutSelector as allocated
mstore(0x40, add(calldataWithoutSelector, add(0x20, targetLength)))
// Process first 32 bytes (we only take the last 28 bytes)
mstore(add(calldataWithoutSelector, 0x20), shl(0x20, mload(add(calldataWithSelector, 0x20))))
// Process all other data by chunks of 32 bytes
for {
let i := 0x1C
} lt(i, targetLength) {
i := add(i, 0x20)
} {
mstore(add(add(calldataWithoutSelector, 0x20), i), mload(add(add(calldataWithSelector, 0x20), add(i, 0x04))))
}
}
return calldataWithoutSelector;
}
}
contract AAVaultOwner {
address public nft_address;
uint public tokenId;
address public aa_vault;
address public option_vault;
bool internal initialized = false;
function init(
address _nft_address,
uint _tokenId,
address premium_asset,
uint premium_amount,
address[] calldata dest,
uint256[] calldata value,
bytes[] calldata func
) public returns (address aa_vault_address) {
require(!initialized, "already initialized");
initialized = true;
nft_address = _nft_address;
tokenId = _tokenId;
address user = address(this); //等同于 目前 jasper 的用户
// step1 creat aa vault for this owner
aa_vault = IJasperOption(VAULTFACTORY).createAccount(user, 1); //钱在本合约这里, 需要充值/提现到 aa_vault 里
IERC20(premium_asset).transfer(address(aa_vault), premium_amount); //一层层传递给 AA vault , 需要优化
//在客户端里具体操作
//create position
IJasperOption(aa_vault).executeBatch(dest, value, func);
aa_vault_address = aa_vault;
}
function redeem(address nft_owner, uint total_share, uint share, address asset) public {
require(msg.sender == nft_address, "only nft contract can call");
//TODO danger 只能在仓位结算完成后才能调用这个方法
bool is_native_token = asset == NATIVE_TOKEN;
uint vault_amount = is_native_token ? aa_vault.balance : IERC20(asset).balanceOf(aa_vault);
require(vault_amount > 0, "no profit");
uint amount = (share * vault_amount) / total_share;
//aa vault to vault owner
address[] memory assets = new address[](1);
assets[0] = asset;
uint[] memory asset_types = new uint[](1);
asset_types[0] = 1;
uint[] memory amounts = new uint[](1);
amounts[0] = amount;
address[] memory st_targets = new address[](1);
st_targets[0] = ISSUANCEMODULE;
uint[] memory st_values = new uint[](1);
st_values[0] = 0;
bytes[] memory st_funcs = new bytes[](1);
st_funcs[0] = abi.encodeWithSignature("redeem(address,uint256[],address[],uint256[])", aa_vault, asset_types, assets, amounts);
IJasperOption(aa_vault).executeBatch(st_targets, st_values, st_funcs);
//vault owner to current nft owner
if (is_native_token) {
Address.sendValue(payable(nft_owner), amount);
} else {
IERC20(asset).transfer(nft_owner, amount);
}
}
}