Files
gifted-contracts-v2/test/GiftedBox.t.sol
2024-12-19 20:52:20 +08:00

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);
}
}