SKIP TO CONTENT

Demo build — mock protocol data. No chain, no ABIs yet.

  • SYNCING: ……

Seat6551.t.sol

Token bound account deployment and that TBA contents survive a transfer.

131 lines4.5 KBSolidity
Source of test/Seat6551.t.sol, 131 lines of Solidity
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.24;
3 
4import {Base} from "./Base.t.sol";
5import {K401Seat6551Account} from "../src/K401Seat6551.sol";
6import {MockERC20} from "./mocks/MockERC20.sol";
7import {Symbol} from "../src/interfaces/IK401Interfaces.sol";
8 
9/// @dev Reenters `withdrawToken` from an ERC20 transfer hook.
10contract ReentrantToken is MockERC20 {
11 address public target;
12 bool public armed;
13 
14 constructor() MockERC20("Reentrant", "RE", 18) {}
15 
16 function arm(address target_) external {
17 target = target_;
18 armed = true;
19 }
20 
21 function transfer(address to, uint256 amount) public override returns (bool) {
22 if (armed) {
23 armed = false;
24 K401Seat6551Account(payable(target)).withdrawToken(address(this), to, amount);
25 }
26 balanceOf[msg.sender] -= amount;
27 balanceOf[to] += amount;
28 emit Transfer(msg.sender, to, amount);
29 return true;
30 }
31}
32 
33contract Seat6551Test is Base {
34 uint8 internal constant NVDA = uint8(Symbol.NVDA);
35 
36 function test_tbaAddressIsDeterministicAndBoundToTheTokenId() public {
37 _giveSeats(alice, 2);
38 uint256[] memory ids = k401.seatsOf(alice);
39 
40 address a = seat6551.tbaOf(ids[0]);
41 address b = seat6551.tbaOf(ids[1]);
42 assertTrue(a != b);
43 assertEq(a.code.length, 0, "lazy: nothing deployed yet");
44 
45 assertEq(seat6551.tbaFor(ids[0]), a);
46 assertGt(a.code.length, 0);
47 // Idempotent.
48 assertEq(seat6551.tbaFor(ids[0]), a);
49 }
50 
51 function test_accountReportsItsBoundTokenAndOwner() public {
52 _giveSeats(alice, 1);
53 uint256 id = k401.seatsOf(alice)[0];
54 address tba = seat6551.tbaFor(id);
55 
56 (uint256 chainId, address tokenContract, uint256 tokenId) = K401Seat6551Account(payable(tba)).token();
57 assertEq(chainId, block.chainid);
58 assertEq(tokenContract, address(mirror));
59 assertEq(tokenId, id);
60 assertEq(K401Seat6551Account(payable(tba)).owner(), alice);
61 
62 vm.prank(alice);
63 mirror.transferFrom(alice, bob, id);
64 assertEq(K401Seat6551Account(payable(tba)).owner(), bob, "owner follows the NFT");
65 }
66 
67 function test_zeroFeeWithdrawalOnlyByTheHolder() public {
68 _giveSeats(alice, 1);
69 uint256 id = k401.seatsOf(alice)[0];
70 address tba = seat6551.tbaFor(id);
71 nvda.mint(tba, 42e18);
72 
73 vm.prank(bob);
74 vm.expectRevert(K401Seat6551Account.NotAccountOwner.selector);
75 K401Seat6551Account(payable(tba)).withdrawToken(address(nvda), bob, 42e18);
76 
77 vm.prank(alice);
78 K401Seat6551Account(payable(tba)).withdrawToken(address(nvda), alice, 42e18);
79 assertEq(nvda.balanceOf(alice), 42e18, "100% out, 0% fee");
80 assertEq(nvda.balanceOf(tba), 0);
81 }
82 
83 function test_executeIsOwnerOnlyAndCallOnly() public {
84 _giveSeats(alice, 1);
85 uint256 id = k401.seatsOf(alice)[0];
86 address tba = seat6551.tbaFor(id);
87 nvda.mint(tba, 10e18);
88 
89 bytes memory data = abi.encodeCall(MockERC20.transfer, (alice, 10e18));
90 
91 vm.prank(bob);
92 vm.expectRevert(K401Seat6551Account.NotAccountOwner.selector);
93 K401Seat6551Account(payable(tba)).execute(address(nvda), 0, data, 0);
94 
95 vm.prank(alice);
96 vm.expectRevert(K401Seat6551Account.UnsupportedOperation.selector);
97 K401Seat6551Account(payable(tba)).execute(address(nvda), 0, data, 1); // DELEGATECALL
98 
99 vm.prank(alice);
100 K401Seat6551Account(payable(tba)).execute(address(nvda), 0, data, 0);
101 assertEq(nvda.balanceOf(alice), 10e18);
102 assertEq(K401Seat6551Account(payable(tba)).state(), 1);
103 }
104 
105 function test_withdrawalIsReentrancyGuarded() public {
106 _giveSeats(alice, 1);
107 uint256 id = k401.seatsOf(alice)[0];
108 address tba = seat6551.tbaFor(id);
109 
110 ReentrantToken evil = new ReentrantToken();
111 evil.mint(tba, 100e18);
112 evil.arm(tba);
113 
114 vm.prank(alice);
115 vm.expectRevert();
116 K401Seat6551Account(payable(tba)).withdrawToken(address(evil), alice, 100e18);
117 assertEq(evil.balanceOf(alice), 0, "no double withdrawal");
118 }
119 
120 function test_nativeWithdrawal() public {
121 _giveSeats(alice, 1);
122 uint256 id = k401.seatsOf(alice)[0];
123 address payable tba = payable(seat6551.tbaFor(id));
124 vm.deal(tba, 1 ether);
125 
126 vm.prank(alice);
127 K401Seat6551Account(tba).withdrawETH(payable(alice), 1 ether);
128 assertEq(alice.balance, 1 ether);
129 }
130}
131 

Click any line number to deep-link to it — the target line highlights on load.