mirror of
https://github.com/placeholder-soft/gifted-contracts-v2.git
synced 2026-01-12 15:23:44 +08:00
919 lines
30 KiB
Solidity
919 lines
30 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "forge-std/Test.sol";
|
|
import "forge-std/console.sol";
|
|
import "forge-std/Vm.sol";
|
|
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
import "../src/GiftedBox.sol";
|
|
import { GiftedAccount, IERC6551Account } from "../src/GiftedAccount.sol";
|
|
import "../src/GiftedAccountGuardian.sol";
|
|
import "../src/GiftedAccountProxy.sol";
|
|
import "../src/erc6551/evm/ERC6551Registry.sol";
|
|
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
|
|
import "@openzeppelin-contracts-upgradeable/contracts/token/ERC721/ERC721Upgradeable.sol";
|
|
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
import "../src/Vault.sol";
|
|
import "../src/GasSponsorBook.sol";
|
|
import "../src/UnifiedStore.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
|
|
contract MockERC721 is ERC721 {
|
|
constructor() ERC721("MockERC721", "M721") { }
|
|
|
|
function mint(address to, uint256 tokenId) public {
|
|
_mint(to, tokenId);
|
|
}
|
|
}
|
|
|
|
contract MockERC1155 is ERC1155 {
|
|
constructor() ERC1155("") { }
|
|
|
|
function mint(address to, uint256 tokenId, uint256 amount) public {
|
|
_mint(to, tokenId, amount, "");
|
|
}
|
|
|
|
function mintBatch(address to, uint256[] memory tokenIds, uint256[] memory amounts) public {
|
|
_mintBatch(to, tokenIds, amounts, "");
|
|
}
|
|
}
|
|
|
|
contract MockERC20 is ERC20 {
|
|
constructor() ERC20("MockERC20", "M20") { }
|
|
|
|
function mint(address to, uint256 amount) public {
|
|
_mint(to, amount);
|
|
}
|
|
}
|
|
|
|
interface TestEvents {
|
|
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
|
|
}
|
|
|
|
contract GiftedBoxTest is Test, TestEvents {
|
|
MockERC721 internal mockERC721;
|
|
MockERC1155 internal mockERC1155;
|
|
GiftedBox internal giftedBox;
|
|
ERC6551Registry internal registry;
|
|
GiftedAccountGuardian internal guardian = new GiftedAccountGuardian();
|
|
GiftedAccount internal giftedAccount;
|
|
Vault public vault;
|
|
GasSponsorBook public sponsorBook;
|
|
address gasRelayer = vm.addr(32000);
|
|
MockERC20 internal mockERC20;
|
|
UnifiedStore internal unifiedStore = new UnifiedStore();
|
|
|
|
// global testing variable to be used in multiple tests
|
|
// to avoid stacks to deeps error
|
|
uint256 giftedBoxTokenId = 0;
|
|
address giftSender = vm.addr(1);
|
|
address giftRecipient = vm.addr(2);
|
|
address tokenRecipient = vm.addr(3);
|
|
address giftOperator = vm.addr(4);
|
|
uint256 erc20Amount = 100;
|
|
uint256 erc721TokenId = 1;
|
|
uint256 erc1155TokenId = 1;
|
|
uint256 erc1155Amount = 10;
|
|
uint256 etherAmount = 0.1 ether;
|
|
|
|
// region Setup
|
|
function setUp() public {
|
|
mockERC721 = new MockERC721();
|
|
mockERC1155 = new MockERC1155();
|
|
|
|
GiftedAccount giftedAccountImpl = new GiftedAccount();
|
|
guardian.setGiftedAccountImplementation(address(giftedAccountImpl));
|
|
|
|
GiftedAccountProxy accountProxy = new GiftedAccountProxy(address(guardian));
|
|
giftedAccount = GiftedAccount(payable(address(accountProxy)));
|
|
|
|
registry = new ERC6551Registry();
|
|
|
|
address implementation = address(new GiftedBox());
|
|
bytes memory data = abi.encodeCall(GiftedBox.initialize, address(this));
|
|
address proxy = address(new ERC1967Proxy(implementation, data));
|
|
giftedBox = GiftedBox(proxy);
|
|
|
|
giftedBox.setAccountImpl(payable(address(giftedAccount)));
|
|
giftedBox.setRegistry(address(registry));
|
|
giftedBox.setUnifiedStore(address(guardian));
|
|
giftedBox.grantRole(giftedBox.CLAIMER_ROLE(), gasRelayer);
|
|
|
|
vault = new Vault();
|
|
vault.initialize(address(this));
|
|
sponsorBook = new GasSponsorBook();
|
|
vault.grantRole(vault.CONTRACT_ROLE(), address(sponsorBook));
|
|
vault.grantRole(vault.CONTRACT_ROLE(), address(giftedBox));
|
|
|
|
sponsorBook.setVault(vault);
|
|
giftedBox.setGasSponsorBook(address(sponsorBook));
|
|
sponsorBook.grantRole(sponsorBook.SPONSOR_ROLE(), address(giftedBox));
|
|
sponsorBook.grantRole(sponsorBook.CONSUMER_ROLE(), gasRelayer);
|
|
|
|
giftedBox.setVault(address(vault));
|
|
|
|
mockERC20 = new MockERC20();
|
|
|
|
unifiedStore.setAddress("GiftedAccountGuardian", address(guardian));
|
|
|
|
vm.deal(gasRelayer, 100 ether);
|
|
}
|
|
|
|
// endregion
|
|
|
|
// region Gifting Actions
|
|
function testSendGift() public {
|
|
uint256 tokenId = 0;
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
assertEq(address(giftedBox), IERC721(giftedBox).ownerOf(tokenId));
|
|
|
|
(address sender, address recipient, address operator) = giftedBox.giftingRecords(tokenId);
|
|
assertEq(giftSender, sender);
|
|
assertEq(giftRecipient, recipient);
|
|
assertEq(giftOperator, operator);
|
|
}
|
|
|
|
function testClaimGiftByRecipient() public {
|
|
uint256 tokenId = 0;
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(tokenId, GiftingRole.RECIPIENT);
|
|
|
|
assertEq(giftRecipient, IERC721(giftedBox).ownerOf(tokenId));
|
|
(address sender, address recipient, address operator) = giftedBox.giftingRecords(tokenId);
|
|
assertEq(sender, address(0));
|
|
assertEq(recipient, address(0));
|
|
assertEq(operator, address(0));
|
|
}
|
|
|
|
function testClaimGiftBySender() public {
|
|
uint256 tokenId = 0;
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
vm.prank(giftSender);
|
|
giftedBox.claimGift(tokenId, GiftingRole.SENDER);
|
|
|
|
assertEq(giftSender, IERC721(giftedBox).ownerOf(tokenId));
|
|
(address sender, address recipient, address operator) = giftedBox.giftingRecords(tokenId);
|
|
assertEq(sender, address(0));
|
|
assertEq(recipient, address(0));
|
|
assertEq(operator, address(0));
|
|
}
|
|
|
|
function testclaimGiftByClaimer() public {
|
|
uint256 tokenId = 0;
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
giftedBox.claimGiftByClaimer(tokenId, GiftingRole.SENDER);
|
|
|
|
assertEq(giftSender, IERC721(giftedBox).ownerOf(tokenId));
|
|
{
|
|
(address sender, address recipient, address operator) = giftedBox.giftingRecords(tokenId);
|
|
assertEq(sender, address(0));
|
|
assertEq(recipient, address(0));
|
|
assertEq(operator, address(0));
|
|
}
|
|
|
|
tokenId++;
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
giftedBox.claimGiftByClaimer(tokenId, GiftingRole.RECIPIENT);
|
|
assertEq(giftRecipient, IERC721(giftedBox).ownerOf(tokenId));
|
|
{
|
|
(address sender, address recipient, address operator) = giftedBox.giftingRecords(tokenId);
|
|
assertEq(sender, address(0));
|
|
assertEq(recipient, address(0));
|
|
assertEq(operator, address(0));
|
|
}
|
|
|
|
tokenId++;
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
vm.expectRevert("!invalid-role");
|
|
giftedBox.claimGiftByClaimer(tokenId, GiftingRole.OPERATOR);
|
|
}
|
|
|
|
// endregion Gifting Actions
|
|
|
|
// region TokenBound Account
|
|
|
|
function testTokenBoundAccountCallDirectly() public {
|
|
uint256 tokenId = 0;
|
|
|
|
vm.prank(giftSender);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
address account = registry.account(address(giftedAccount), block.chainid, address(giftedBox), tokenId, 0);
|
|
|
|
address tokenAccount = giftedBox.tokenAccountAddress(tokenId);
|
|
assertTrue(account != address(0));
|
|
assertTrue(account != vm.addr(1));
|
|
assertEq(account, tokenAccount);
|
|
|
|
IERC6551Account accountInstance = IERC6551Account(payable(account));
|
|
|
|
vm.prank(vm.addr(1));
|
|
giftedBox.claimGift(tokenId, GiftingRole.SENDER);
|
|
|
|
assertEq(accountInstance.owner(), vm.addr(1));
|
|
|
|
vm.deal(account, 1 ether);
|
|
|
|
vm.prank(vm.addr(1));
|
|
accountInstance.executeCall(payable(vm.addr(2)), 0.5 ether, "");
|
|
|
|
assertEq(account.balance, 0.5 ether);
|
|
assertEq(vm.addr(2).balance, 0.5 ether);
|
|
assertEq(accountInstance.nonce(), 1);
|
|
|
|
vm.prank(vm.addr(1));
|
|
giftedBox.transferFrom(vm.addr(1), vm.addr(2), tokenId);
|
|
assertEq(accountInstance.owner(), vm.addr(2));
|
|
|
|
vm.prank(vm.addr(1));
|
|
vm.expectRevert();
|
|
accountInstance.executeCall(payable(vm.addr(2)), 0.5 ether, "");
|
|
|
|
vm.prank(vm.addr(2));
|
|
accountInstance.executeCall(payable(vm.addr(2)), 0.5 ether, "");
|
|
assertEq(vm.addr(2).balance, 1 ether);
|
|
assertEq(accountInstance.nonce(), 2);
|
|
}
|
|
|
|
function testTokenBoundAccountERC721() public {
|
|
uint256 tokenId = 0;
|
|
|
|
address randomAccount = vm.addr(30);
|
|
|
|
vm.prank(giftSender);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
address tokenAccount = giftedBox.tokenAccountAddress(tokenId);
|
|
|
|
// !important: only the token owner can transfer the token to tokenBound account
|
|
// to prevent front running attack
|
|
mockERC721.mint(randomAccount, 100);
|
|
vm.expectRevert("!sender-not-authorized");
|
|
vm.prank(randomAccount);
|
|
mockERC721.safeTransferFrom(randomAccount, tokenAccount, 100);
|
|
|
|
// sender is able to transfer the token to tokenBound account
|
|
mockERC721.mint(giftSender, 101);
|
|
vm.prank(giftSender);
|
|
mockERC721.safeTransferFrom(giftSender, tokenAccount, 101);
|
|
|
|
// recipient is not able to transfer the token to tokenBound account
|
|
mockERC721.mint(giftRecipient, 102);
|
|
vm.expectRevert("!sender-not-authorized");
|
|
vm.prank(giftRecipient);
|
|
mockERC721.safeTransferFrom(giftRecipient, tokenAccount, 102);
|
|
}
|
|
|
|
function testTokenBoundAccountERC1155() public {
|
|
uint256 tokenId = 0;
|
|
|
|
address randomAccount = vm.addr(30);
|
|
|
|
vm.prank(giftSender);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
address tokenAccount = giftedBox.tokenAccountAddress(tokenId);
|
|
|
|
// !important: only the token owner can transfer the token to tokenBound account
|
|
// to prevent front running attack
|
|
mockERC1155.mint(randomAccount, 100, 1);
|
|
vm.expectRevert("!sender-not-authorized");
|
|
vm.prank(randomAccount);
|
|
mockERC1155.safeTransferFrom(randomAccount, tokenAccount, 100, 1, "");
|
|
|
|
// sender is able to transfer the token to tokenBound account
|
|
mockERC1155.mint(giftSender, 101, 1);
|
|
vm.prank(giftSender);
|
|
mockERC1155.safeTransferFrom(giftSender, tokenAccount, 101, 1, "");
|
|
|
|
// recipient is not able to transfer the token to tokenBound account
|
|
mockERC1155.mint(giftRecipient, 102, 1);
|
|
vm.expectRevert("!sender-not-authorized");
|
|
vm.prank(giftRecipient);
|
|
mockERC1155.safeTransferFrom(giftRecipient, tokenAccount, 102, 1, "");
|
|
|
|
// Test cases for onERC1155BatchReceived
|
|
uint256[] memory ids = new uint256[](2);
|
|
uint256[] memory amounts = new uint256[](2);
|
|
ids[0] = 200;
|
|
ids[1] = 201;
|
|
amounts[0] = 1;
|
|
amounts[1] = 2;
|
|
|
|
// randomAccount tries to transfer batch tokens to tokenBound account
|
|
mockERC1155.mintBatch(randomAccount, ids, amounts);
|
|
vm.expectRevert("!sender-not-authorized");
|
|
vm.prank(randomAccount);
|
|
mockERC1155.safeBatchTransferFrom(randomAccount, tokenAccount, ids, amounts, "");
|
|
|
|
// giftSender transfers batch tokens to tokenBound account
|
|
mockERC1155.mintBatch(giftSender, ids, amounts);
|
|
vm.prank(giftSender);
|
|
mockERC1155.safeBatchTransferFrom(giftSender, tokenAccount, ids, amounts, "");
|
|
|
|
// giftRecipient tries to transfer batch tokens to tokenBound account
|
|
mockERC1155.mintBatch(giftRecipient, ids, amounts);
|
|
vm.expectRevert("!sender-not-authorized");
|
|
vm.prank(giftRecipient);
|
|
mockERC1155.safeBatchTransferFrom(giftRecipient, tokenAccount, ids, amounts, "");
|
|
}
|
|
|
|
// endregion TokenBound Account
|
|
|
|
// region Transfer ERC721
|
|
function testTransferERC721() public {
|
|
uint256 tokenId = 100;
|
|
|
|
// Send gift
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
// Mint ERC721 token to giftSender
|
|
mockERC721.mint(giftOperator, tokenId);
|
|
|
|
// Transfer ERC721 token to token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
vm.prank(giftOperator);
|
|
mockERC721.safeTransferFrom(giftOperator, address(tokenAccount), tokenId);
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage = giftedBox.transferERC721PermitMessage(
|
|
giftedBoxTokenId, address(mockERC721), tokenId, tokenRecipient, block.timestamp + 1 days
|
|
);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Transfer ERC721 token using permit
|
|
vm.prank(giftSender);
|
|
giftedBox.transferERC721(
|
|
giftedBoxTokenId, address(mockERC721), tokenId, tokenRecipient, block.timestamp + 1 days, v, r, s
|
|
);
|
|
|
|
// Verify token ownership
|
|
assertEq(mockERC721.ownerOf(tokenId), tokenRecipient);
|
|
}
|
|
|
|
function testTransferERC721Sponsor() public {
|
|
uint256 tokenId = 100;
|
|
uint256 feePerTicket = giftedBox.feePerSponsorTicket();
|
|
vm.deal(giftOperator, feePerTicket);
|
|
|
|
// Send gift
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift{ value: feePerTicket }(giftSender, giftRecipient);
|
|
|
|
// Mint ERC721 token to giftSender
|
|
mockERC721.mint(giftOperator, tokenId);
|
|
|
|
// Transfer ERC721 token to token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
vm.prank(giftOperator);
|
|
mockERC721.safeTransferFrom(giftOperator, address(tokenAccount), tokenId);
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage = giftedBox.transferERC721PermitMessage(
|
|
giftedBoxTokenId, address(mockERC721), tokenId, tokenRecipient, block.timestamp + 1 days
|
|
);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Transfer ERC721 token using sponsor
|
|
vm.prank(gasRelayer);
|
|
giftedBox.transferERC721Sponsor(
|
|
giftedBoxTokenId, address(mockERC721), tokenId, tokenRecipient, block.timestamp + 1 days, v, r, s
|
|
);
|
|
|
|
// Verify token ownership
|
|
assertEq(mockERC721.ownerOf(tokenId), tokenRecipient);
|
|
}
|
|
|
|
// endregion Transfer ERC721
|
|
|
|
// region Transfer ERC1155
|
|
function testTransferERC1155() public {
|
|
uint256 tokenId = 100;
|
|
uint256 amount = 10;
|
|
|
|
// Send gift
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
// Mint ERC1155 token to giftSender
|
|
mockERC1155.mint(giftOperator, tokenId, amount);
|
|
|
|
// Transfer ERC1155 token to token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
vm.prank(giftOperator);
|
|
mockERC1155.safeTransferFrom(giftOperator, address(tokenAccount), tokenId, amount, "");
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage = giftedBox.transferERC1155PermitMessage(
|
|
giftedBoxTokenId, address(mockERC1155), tokenId, amount, tokenRecipient, block.timestamp + 1 days
|
|
);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Transfer ERC1155 token using permit
|
|
vm.prank(giftSender);
|
|
giftedBox.transferERC1155(
|
|
giftedBoxTokenId, address(mockERC1155), tokenId, amount, tokenRecipient, block.timestamp + 1 days, v, r, s
|
|
);
|
|
|
|
// Verify token ownership
|
|
assertEq(mockERC1155.balanceOf(tokenRecipient, tokenId), amount);
|
|
}
|
|
|
|
function testTransferERC1155Sponsor() public {
|
|
uint256 tokenId = 100;
|
|
uint256 amount = 10;
|
|
uint256 feePerTicket = giftedBox.feePerSponsorTicket();
|
|
vm.deal(giftOperator, feePerTicket);
|
|
|
|
// Send gift
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift{ value: feePerTicket }(giftSender, giftRecipient);
|
|
|
|
// Mint ERC1155 token to giftSender
|
|
mockERC1155.mint(giftOperator, tokenId, amount);
|
|
|
|
// Transfer ERC1155 token to token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
vm.prank(giftOperator);
|
|
mockERC1155.safeTransferFrom(giftOperator, address(tokenAccount), tokenId, amount, "");
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage = giftedBox.transferERC1155PermitMessage(
|
|
giftedBoxTokenId, address(mockERC1155), tokenId, amount, tokenRecipient, block.timestamp + 1 days
|
|
);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Transfer ERC1155 token using sponsor
|
|
vm.prank(gasRelayer);
|
|
giftedBox.transferERC1155Sponsor(
|
|
giftedBoxTokenId, address(mockERC1155), tokenId, amount, tokenRecipient, block.timestamp + 1 days, v, r, s
|
|
);
|
|
|
|
// Verify token ownership
|
|
assertEq(mockERC1155.balanceOf(tokenRecipient, tokenId), amount);
|
|
}
|
|
|
|
// endregion Transfer ERC1155
|
|
|
|
// region Gas Sponsor
|
|
function testGasSponsorBookWithConsumer() public {
|
|
uint256 tokenId = 0;
|
|
uint256 feePerTicket = giftedBox.feePerSponsorTicket();
|
|
vm.deal(giftOperator, feePerTicket * 2);
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift{ value: feePerTicket * 2 }(giftSender, giftRecipient);
|
|
|
|
uint256 beforeBalance = gasRelayer.balance;
|
|
vm.prank(gasRelayer);
|
|
giftedBox.claimGiftByClaimerConsumeSponsorTicket(tokenId, GiftingRole.SENDER);
|
|
vm.assertEq(gasRelayer.balance, beforeBalance + feePerTicket);
|
|
}
|
|
|
|
// endregion Gas Sponsor
|
|
|
|
// region Send Gift with Fees
|
|
function testSendGiftWithMintingFee() public {
|
|
uint256 tokenId = 0;
|
|
uint256 mintingFee = 0.01 ether;
|
|
|
|
vm.deal(giftOperator, mintingFee);
|
|
|
|
uint256 vaultBalanceBefore = address(vault).balance;
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift{ value: mintingFee }(giftSender, giftRecipient, giftOperator, mintingFee);
|
|
|
|
assertEq(address(giftedBox), IERC721(giftedBox).ownerOf(tokenId), "!owner");
|
|
assertEq(address(vault).balance, vaultBalanceBefore + mintingFee, "!vault-balance");
|
|
|
|
(address sender, address recipient, address operator) = giftedBox.giftingRecords(tokenId);
|
|
assertEq(giftSender, sender, "!sender");
|
|
assertEq(giftRecipient, recipient, "!recipient");
|
|
assertEq(giftOperator, operator, "!operator");
|
|
}
|
|
|
|
function testSendGiftWithSponsorTicket() public {
|
|
uint256 tokenId = 0;
|
|
uint256 sponsorFee = giftedBox.feePerSponsorTicket();
|
|
|
|
vm.deal(giftOperator, sponsorFee);
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift{ value: sponsorFee }(giftSender, giftRecipient);
|
|
|
|
assertEq(address(giftedBox), IERC721(giftedBox).ownerOf(tokenId), "!owner");
|
|
assertEq(giftedBox.sponsorTickets(tokenId), 1, "!sponsor-tickets");
|
|
|
|
(address sender, address recipient, address operator) = giftedBox.giftingRecords(tokenId);
|
|
assertEq(giftSender, sender, "!sender");
|
|
assertEq(giftRecipient, recipient, "!recipient");
|
|
assertEq(giftOperator, operator, "!operator");
|
|
}
|
|
|
|
function testSendGiftWithMintingFeeAndSponsorTicket() public {
|
|
uint256 tokenId = 0;
|
|
uint256 mintingFee = 0.01 ether;
|
|
uint256 sponsorFee = giftedBox.feePerSponsorTicket();
|
|
uint256 totalFee = mintingFee + sponsorFee;
|
|
|
|
vm.deal(giftOperator, totalFee);
|
|
|
|
uint256 vaultBalanceBefore = address(vault).balance;
|
|
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift{ value: totalFee }(giftSender, giftRecipient, giftOperator, mintingFee);
|
|
|
|
assertEq(address(giftedBox), IERC721(giftedBox).ownerOf(tokenId), "!owner");
|
|
assertEq(address(vault).balance, vaultBalanceBefore + mintingFee + sponsorFee, "!vault-balance");
|
|
assertEq(giftedBox.sponsorTickets(tokenId), 1, "!sponsor-tickets");
|
|
|
|
(address sender, address recipient, address operator) = giftedBox.giftingRecords(tokenId);
|
|
assertEq(giftSender, sender, "!sender");
|
|
assertEq(giftRecipient, recipient, "!recipient");
|
|
assertEq(giftOperator, operator, "!operator");
|
|
}
|
|
|
|
// endregion Send Gift with Fees
|
|
|
|
// region Transfer ERC20
|
|
function testTransferERC20() public {
|
|
uint256 amount = 100;
|
|
|
|
// Send gift
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
// Mint ERC20 tokens to giftOperator
|
|
mockERC20.mint(giftOperator, amount);
|
|
|
|
// Transfer ERC20 tokens to token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
vm.prank(giftOperator);
|
|
mockERC20.transfer(address(tokenAccount), amount);
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage = giftedBox.transferERC20PermitMessage(
|
|
giftedBoxTokenId, address(mockERC20), amount, tokenRecipient, block.timestamp + 1 days
|
|
);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Transfer ERC20 tokens using permit
|
|
vm.prank(giftSender);
|
|
giftedBox.transferERC20(
|
|
giftedBoxTokenId, address(mockERC20), amount, tokenRecipient, block.timestamp + 1 days, v, r, s
|
|
);
|
|
|
|
// Verify token ownership
|
|
assertEq(mockERC20.balanceOf(tokenRecipient), amount);
|
|
}
|
|
|
|
function testTransferERC20Sponsor() public {
|
|
uint256 amount = 100;
|
|
uint256 feePerTicket = giftedBox.feePerSponsorTicket();
|
|
vm.deal(giftOperator, feePerTicket);
|
|
|
|
// Send gift with sponsor ticket
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift{ value: feePerTicket }(giftSender, giftRecipient);
|
|
|
|
// Mint ERC20 tokens to giftOperator
|
|
mockERC20.mint(giftOperator, amount);
|
|
|
|
// Transfer ERC20 tokens to token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
vm.prank(giftOperator);
|
|
mockERC20.transfer(address(tokenAccount), amount);
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage = giftedBox.transferERC20PermitMessage(
|
|
giftedBoxTokenId, address(mockERC20), amount, tokenRecipient, block.timestamp + 1 days
|
|
);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Transfer ERC20 tokens using sponsor
|
|
vm.prank(gasRelayer);
|
|
giftedBox.transferERC20Sponsor(
|
|
giftedBoxTokenId, address(mockERC20), amount, tokenRecipient, block.timestamp + 1 days, v, r, s
|
|
);
|
|
|
|
// Verify token ownership
|
|
assertEq(mockERC20.balanceOf(tokenRecipient), amount);
|
|
}
|
|
|
|
function testTransferERC20PermitMessage() public {
|
|
uint256 amount = 100;
|
|
uint256 deadline = block.timestamp + 1 days;
|
|
|
|
// Send gift
|
|
vm.prank(giftSender);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage =
|
|
giftedBox.transferERC20PermitMessage(giftedBoxTokenId, address(mockERC20), amount, tokenRecipient, deadline);
|
|
|
|
// Verify permit message content
|
|
assertEq(
|
|
permitMessage,
|
|
string(
|
|
abi.encodePacked(
|
|
"I authorize the transfer of ERC20 tokens",
|
|
"\n Token Contract: ",
|
|
Strings.toHexString(uint256(uint160(address(mockERC20))), 20),
|
|
"\n Amount: ",
|
|
Strings.toString(amount),
|
|
"\n To: ",
|
|
Strings.toHexString(uint256(uint160(tokenRecipient)), 20),
|
|
"\n Deadline: ",
|
|
Strings.toString(deadline),
|
|
"\n Nonce: ",
|
|
Strings.toString(0),
|
|
"\n Chain ID: ",
|
|
Strings.toString(block.chainid),
|
|
"\n BY: ",
|
|
"GiftedAccount",
|
|
"\n Version: ",
|
|
"0.0.2"
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
// endregion Transfer ERC20
|
|
|
|
// region Transfer Ether
|
|
function testTransferEther() public {
|
|
address payable etherRecipient = payable(vm.addr(3));
|
|
uint256 amount = 1 ether;
|
|
|
|
// Send gift
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
// Send Ether to token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
vm.deal(address(tokenAccount), amount);
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage =
|
|
giftedBox.transferEtherPermitMessage(giftedBoxTokenId, amount, etherRecipient, block.timestamp + 1 days);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Transfer Ether using permit
|
|
uint256 initialBalance = etherRecipient.balance;
|
|
vm.prank(giftSender);
|
|
giftedBox.transferEther(giftedBoxTokenId, amount, etherRecipient, block.timestamp + 1 days, v, r, s);
|
|
|
|
// Verify Ether transfer
|
|
assertEq(etherRecipient.balance, initialBalance + amount);
|
|
assertEq(address(tokenAccount).balance, 0);
|
|
}
|
|
|
|
function testTransferEtherSponsor() public {
|
|
address payable etherRecipient = payable(vm.addr(3));
|
|
uint256 amount = 1 ether;
|
|
uint256 feePerTicket = giftedBox.feePerSponsorTicket();
|
|
vm.deal(giftOperator, feePerTicket);
|
|
|
|
// Send gift with sponsor ticket
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift{ value: feePerTicket }(giftSender, giftRecipient);
|
|
|
|
// Send Ether to token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
vm.deal(address(tokenAccount), amount);
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage =
|
|
giftedBox.transferEtherPermitMessage(giftedBoxTokenId, amount, etherRecipient, block.timestamp + 1 days);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Transfer Ether using sponsor
|
|
uint256 initialBalance = etherRecipient.balance;
|
|
vm.prank(gasRelayer);
|
|
giftedBox.transferEtherSponsor(giftedBoxTokenId, amount, etherRecipient, block.timestamp + 1 days, v, r, s);
|
|
|
|
// Verify Ether transfer
|
|
assertEq(etherRecipient.balance, initialBalance + amount);
|
|
assertEq(address(tokenAccount).balance, 0);
|
|
}
|
|
|
|
function testTransferEtherPermitMessage() public {
|
|
address etherRecipient = vm.addr(30);
|
|
uint256 amount = 1 ether;
|
|
uint256 deadline = block.timestamp + 1 days;
|
|
|
|
// Send gift
|
|
vm.prank(giftSender);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
// Generate permit message
|
|
string memory permitMessage =
|
|
giftedBox.transferEtherPermitMessage(giftedBoxTokenId, amount, etherRecipient, deadline);
|
|
|
|
// Verify permit message content
|
|
assertEq(
|
|
permitMessage,
|
|
string(
|
|
abi.encodePacked(
|
|
"I authorize the transfer of Ether",
|
|
"\n Amount: ",
|
|
Strings.toString(amount),
|
|
"\n To: ",
|
|
Strings.toHexString(uint256(uint160(etherRecipient)), 20),
|
|
"\n Deadline: ",
|
|
Strings.toString(deadline),
|
|
"\n Nonce: ",
|
|
Strings.toString(0),
|
|
"\n Chain ID: ",
|
|
Strings.toString(block.chainid),
|
|
"\n BY: ",
|
|
"GiftedAccount",
|
|
"\n Version: ",
|
|
"0.0.2"
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
// endregion Transfer Ether
|
|
|
|
function testBatchTransfer() public {
|
|
// Send gift
|
|
vm.prank(giftOperator);
|
|
giftedBox.sendGift(giftSender, giftRecipient);
|
|
|
|
// Mint and transfer tokens to the token-bound account
|
|
GiftedAccount tokenAccount = GiftedAccount(payable(giftedBox.tokenAccountAddress(giftedBoxTokenId)));
|
|
|
|
mockERC20.mint(giftOperator, erc20Amount);
|
|
mockERC721.mint(giftOperator, erc721TokenId);
|
|
mockERC1155.mint(giftOperator, erc1155TokenId, erc1155Amount);
|
|
|
|
vm.startPrank(giftOperator);
|
|
mockERC20.transfer(address(tokenAccount), erc20Amount);
|
|
mockERC721.transferFrom(giftOperator, address(tokenAccount), erc721TokenId);
|
|
mockERC1155.safeTransferFrom(giftOperator, address(tokenAccount), erc1155TokenId, erc1155Amount, "");
|
|
vm.stopPrank();
|
|
vm.deal(address(tokenAccount), etherAmount);
|
|
|
|
// Claim gift to recipient
|
|
vm.prank(giftRecipient);
|
|
giftedBox.claimGift(giftedBoxTokenId, GiftingRole.RECIPIENT);
|
|
|
|
// Prepare batch transfer data
|
|
bytes[] memory batchData = new bytes[](4);
|
|
batchData[0] = abi.encodeWithSignature(
|
|
"transferERC20(address,uint256,address,address,uint256)",
|
|
address(mockERC20),
|
|
erc20Amount,
|
|
tokenRecipient,
|
|
giftRecipient,
|
|
block.timestamp + 1 days
|
|
);
|
|
batchData[1] = abi.encodeWithSignature(
|
|
"transferERC721(address,uint256,address,address,uint256)",
|
|
address(mockERC721),
|
|
erc721TokenId,
|
|
tokenRecipient,
|
|
giftRecipient,
|
|
block.timestamp + 1 days
|
|
);
|
|
batchData[2] = abi.encodeWithSignature(
|
|
"transferERC1155(address,uint256,uint256,address,address,uint256)",
|
|
address(mockERC1155),
|
|
erc1155TokenId,
|
|
erc1155Amount,
|
|
tokenRecipient,
|
|
giftRecipient,
|
|
block.timestamp + 1 days
|
|
);
|
|
batchData[3] = abi.encodeWithSignature(
|
|
"transferEther(address,uint256,address,uint256)",
|
|
payable(tokenRecipient),
|
|
etherAmount,
|
|
giftRecipient,
|
|
block.timestamp + 1 days
|
|
);
|
|
|
|
// Generate batch transfer permit message
|
|
string memory permitMessage =
|
|
giftedBox.batchTransferPermitMessage(giftedBoxTokenId, batchData, block.timestamp + 1 days);
|
|
|
|
// Sign permit message
|
|
(uint8 v, bytes32 r, bytes32 s) = vm.sign(
|
|
2, // giftRecipient's private key
|
|
tokenAccount.hashPersonalSignedMessage(bytes(permitMessage))
|
|
);
|
|
|
|
// Execute batch transfer
|
|
vm.prank(giftSender);
|
|
giftedBox.batchTransfer(giftedBoxTokenId, batchData, block.timestamp + 1 days, v, r, s);
|
|
|
|
// Verify transfers
|
|
assertEq(mockERC20.balanceOf(tokenRecipient), erc20Amount);
|
|
assertEq(mockERC721.ownerOf(erc721TokenId), tokenRecipient);
|
|
assertEq(mockERC1155.balanceOf(tokenRecipient, erc1155TokenId), erc1155Amount);
|
|
assertEq(tokenRecipient.balance, etherAmount);
|
|
|
|
// Verify token-bound account balances
|
|
assertEq(mockERC20.balanceOf(address(tokenAccount)), 0);
|
|
assertEq(mockERC721.balanceOf(address(tokenAccount)), 0);
|
|
assertEq(mockERC1155.balanceOf(address(tokenAccount), erc1155TokenId), 0);
|
|
assertEq(address(tokenAccount).balance, 0);
|
|
}
|
|
}
|