add audit
Some checks failed
CI / Foundry project (push) Has been cancelled

This commit is contained in:
han 2025-02-12 19:57:09 +07:00
parent 878bd34ef6
commit 5726fc6b9a
15 changed files with 630 additions and 5 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
# Compiler files # Compiler files
cache/ cache/
out/ out/
lib/
# Ignores development broadcast logs # Ignores development broadcast logs
!/broadcast !/broadcast

118
audit/H-01.md Normal file
View File

@ -0,0 +1,118 @@
# Blocked User Can Call `SoulboundProfileNFT::mintProfile` Again
## Summary
Due to missing blocked profile handling, any blocked profile can call `SoulboundProfileNFT::mintProfile` again to mint and take part on the system. The purpose of blocking a profile no longer valid, since they could mint a new profile and continue interacting with existing `LikeRegistry` and `MultiSig`.
## Vulnerability Details
When `SoulboundProfileNFT::blockProfile` is called by the owner, it will burn the profile token, delete onchain variable related to the burnt tokenId and delete the profileToToken of the address.
```solidity
function blockProfile(address blockAddress) external onlyOwner {
uint256 tokenId = profileToToken[blockAddress];
require(tokenId != 0, "No profile found");
_burn(tokenId);
delete profileToToken[blockAddress];
delete _profiles[tokenId];
emit ProfileBurned(blockAddress, tokenId);
}
```
So after the `SoulboundNFT::blockProfile` being called, no one can call `LikeRegistry::likeUser` to the blocked address.
```solidity
function likeUser(address liked) external payable {
[...]
require(profileNFT.profileToToken(msg.sender) != 0, "Must have a profile NFT");
require(profileNFT.profileToToken(liked) != 0, "Liked user must have a profile NFT");
[...]
}
```
The `LikeRegistry::likeUser` require both the caller to have existing profile tokens. So blocked user unable to like or being liked.
But the `SoulboundProfileNFT::mintProfile` doesn't have any check if the user or profile is blocked or not. So if someone has already blocked, they can call the mint function again.
```solidity
function mintProfile(string memory name, uint8 age, string memory profileImage) external {
require(profileToToken[msg.sender] == 0, "Profile already exists");
uint256 tokenId = ++_nextTokenId;
_safeMint(msg.sender, tokenId);
// Store metadata on-chain
_profiles[tokenId] = Profile(name, age, profileImage);
profileToToken[msg.sender] = tokenId;
emit ProfileMinted(msg.sender, tokenId, name, age, profileImage);
}
```
There is no check if sender has its profile blocked or not previously.
## POC
```solidity
function testBlockedProfileMintAgain() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
uint256 tokenId = soulboundNFT.profileToToken(user);
assertEq(tokenId, 1, "Token should exist before blocking");
vm.prank(owner);
soulboundNFT.blockProfile(user);
uint256 newTokenId = soulboundNFT.profileToToken(user);
assertEq(newTokenId, 0, "Token should be removed after blocking");
vm.prank(user);
vm.expectRevert();
// blocked profile should not be able to mint from the same wallet address again
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
}
```
## Impact
- Any blocked profile can mint again and continue their activity at this protocol
- Blocked profile can mint again and mint a profile from another wallet, then make the other profile to match with his blocked account, and withdraw all of their own fund.
## Recommendations
Add new storage variable to check if a wallet address is blocked or not.
```diff
--- a/src/SoulboundProfileNFT.sol
+++ b/src/SoulboundProfileNFT.sol
@@ -20,6 +20,7 @@ contract SoulboundProfileNFT is ERC721, Ownable {
mapping(address => uint256) public profileToToken; // Maps user to their profile NFT
mapping(uint256 => Profile) private _profiles; // Stores profile metadata
+ mapping(address => bool) public profileBlocked;
event ProfileMinted(address indexed user, uint256 tokenId, string name, uint8 age, string profileImage);
event ProfileBurned(address indexed user, uint256 tokenId);
@@ -29,6 +30,7 @@ contract SoulboundProfileNFT is ERC721, Ownable {
/// @notice Mint a soulbound NFT representing the user's profile.
function mintProfile(string memory name, uint8 age, string memory profileImage) external {
require(profileToToken[msg.sender] == 0, "Profile already exists");
+ require(profileBlocked[msg.sender] == false, "Profile is blocked");
uint256 tokenId = ++_nextTokenId;
_safeMint(msg.sender, tokenId);
@@ -61,6 +63,7 @@ contract SoulboundProfileNFT is ERC721, Ownable {
_burn(tokenId);
delete profileToToken[blockAddress];
delete _profiles[tokenId];
+ profileBlocked[blockAddress] = true;
emit ProfileBurned(blockAddress, tokenId);
}
```

174
audit/H-02.md Normal file
View File

@ -0,0 +1,174 @@
# H-02. User Fund Sent to LikeRegistry::likeUser is Never Accounted and Make `LikeRegistry::matchRewards` Always Return Zero
## Summary
Due to miscalculation in the `LikeRegistry::matchRewards`, any deployed MultisigWallet will contain 0 fund, and also fees for the protocol is always 0 as well. User funds sent through `LikeRegistry::likeUser` is never being accounted to the user. It leads to the created multisigWallet has no fund in it, and user lose their fund.
## Vulnerability Detail
```solidity
function matchRewards(address from, address to) internal {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0;
userBalances[to] = 0;
uint256 totalRewards = matchUserOne + matchUserTwo;
uint256 matchingFees = (totalRewards * FIXEDFEE) / 100;
uint256 rewards = totalRewards - matchingFees;
totalFees += matchingFees;
// Deploy a MultiSig contract for the matched users
MultiSigWallet multiSigWallet = new MultiSigWallet(from, to);
// Send ETH to the deployed multisig wallet
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
}
```
As we can see at the above snippet, `matchUserOne` and `matchUserTwo` are the main variables for this calculation. Its retrieved from the `userBalances` variable. But `userBalances` variable value actually never increased. As we can see in the below `LikeRegistry::likeUser` code:
```solidity
function likeUser(address liked) external payable {
[...]
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
userBalances[msg.sender] += msg.value;
// Check if mutual like
if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
address multiSigWallet = matchRewards(liked, msg.sender);
}
}
```
The `LikeRegistry::likeUser` require user to send fund greater than or equal to 1 ether, but these 1 ether never being accounted to the sender address.
## POC
We modify the `src/LikeRegistry.sol` file a bit to return the multisigWallet address from `likeUser` and `matchRewards`.
`LikeRegistry::likeUser`
```diff
- function likeUser(address liked) external payable {
+ function likeUser(address liked) external payable returns (address) {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
require(!likes[msg.sender][liked], "Already liked");
require(msg.sender != liked, "Cannot like yourself");
@@ -38,16 +38,23 @@ contract LikeRegistry is Ownable {
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
// Check if mutual like
if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
- matchRewards(liked, msg.sender);
+ address multiSigWallet = matchRewards(liked, msg.sender);
+ return multiSigWallet;
}
+
+ return address(0);
}
```
`LikeRegistry::matchRewards`
```solidity
- function matchRewards(address from, address to) internal {
+ function matchRewards(address from, address to) internal returns (address) {
uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0;
@@ -64,6 +71,8 @@ contract LikeRegistry is Ownable {
// Send ETH to the deployed multisig wallet
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed");
+
+ return address(multiSigWallet);
}
```
`test/testLikeRegistry.t.sol`
```solidity
function testMatchMiscalculation() public {
uint256 likeAmount = 1 ether;
uint256 FIXEDFEE = 10;
uint256 fees = ((likeAmount + likeAmount) * FIXEDFEE) / 100;
uint multiSigFund = likeAmount + likeAmount - fees;
// user mint profile
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
uint256 userTokenId = soulboundNFT.profileToToken(user);
assertEq(userTokenId, 1, "Token ID should be 1");
// user2 mint profile
vm.prank(user2);
soulboundNFT.mintProfile("Bob", 27, "ipfs://profileImage");
uint256 user2TokenId = soulboundNFT.profileToToken(user2);
assertEq(user2TokenId, 2, "Token ID should be 2");
// likeRegistry has 0 ether
assertEq(address(likeRegistry).balance, 0);
// user likes user2 with 1 eth
vm.prank(user);
likeRegistry.likeUser{value: likeAmount}(user2);
// user2 likes uer with 1 eth and match
vm.prank(user2);
address multiSigAddress = likeRegistry.likeUser{value: likeAmount}(user);
assert(multiSigAddress != address(0));
// get user matches
vm.prank(user);
address[] memory userMatches = likeRegistry.getMatches();
assertEq(userMatches.length, 1);
assertEq(userMatches[0], address(user2));
assertEq(address(multiSigAddress).balance, multiSigFund); // giving a like for each user 1 ether should give the wallet 1.8 ether
assertEq(address(likeRegistry).balance, fees); // likeRegistry should have balance equal to `fees`, and send the rest of fund to the MultisigWallet
}
```
## Impact
- The user sending fund to `LikeRegistry::likeUser` has its fund never accounted to, which make a financial losses to the user
- The new created MultisigWallet has no fund in it
- Protocol unable to claim the protocol fee with `LikeRegistry::withdrawFees` despite the protocol balance increases
## Recommendations
Account user fund when user called `LikeRegistry::likeUser` to a storage variable.
```diff
function likeUser(address liked) external payable {
require(msg.value >= 1 ether, "Must send at least 1 ETH");
require(!likes[msg.sender][liked], "Already liked");
require(msg.sender != liked, "Cannot like yourself");
@@ -38,16 +38,23 @@ contract LikeRegistry is Ownable {
likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked);
+ userBalances[msg.sender] += msg.value;
+
// Check if mutual like
if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked);
matches[liked].push(msg.sender);
emit Matched(msg.sender, liked);
matchRewards(liked, msg.sender);
}
}
```

74
audit/H-03.md Normal file
View File

@ -0,0 +1,74 @@
# Fund Sent Directly to `LikeRegistry` Will Not be Able to Withdraw by Anyone
## Summary
Due to missing handling of received funds which transferred directly to the contract address, it can lead to the funds unable to withdraw. Both user and the contract owner are unable to withdraw the fund being sent directly to the contract lead to a loss of fund.
## Vulnerability Details
When a user send fund directly to `LikeRegistry` contract, it will do nothing and doen't account the fund to the user who send it.
```solidity
/// @notice Allows the contract to receive ETH
receive() external payable {}
```
While we see there is a storage variable designed to store and account the user funds called `userBalances`.
```solidity
contract LikeRegistry is Ownable {
[...]
mapping(address => mapping(address => bool)) public likes;
mapping(address => address[]) public matches;
mapping(address => uint256) public userBalances;
[...]
```
## POC
```solidity
function testSendMoneyToLikeRegistry() public {
uint256 sendAmount = 1 ether;
uint256 initialLikeRegistryFund = address(likeRegistry).balance;
// user send money directly to like registry contract
vm.prank(user);
(bool success,) = payable(address(likeRegistry)).call{value: sendAmount}("");
uint256 likeRegistryFundAfterFunded = address(likeRegistry).balance;
assertEq(success, true);
assertEq(initialLikeRegistryFund, 0);
assertEq(likeRegistryFundAfterFunded, sendAmount);
// check userBalances
uint256 userUserBalance = likeRegistry.userBalances(user);
assertEq(userUserBalance, sendAmount); // sending money directly to contract should adjust userBalance to reflect the address fund
// owner try to withdraw money
vm.prank(owner);
vm.expectRevert();
likeRegistry.withdrawFees(); // the contract will assume that there is no fees to withdraw
// try to withdraw money
vm.prank(user);
vm.expectRevert();
likeRegistry.withdrawFees(); // there is no withdraw function for user
}
```
## Impact
- Loss of fund because no one can withdraw it
## Recommendations
Account the fund to address by storing it in the `userBalances` variable
```diff
- receive() external payable {}
+ receive() external payable {
+ userBalances[msg.sender] += msg.value;
+ }
```

55
audit/H-04.md Normal file
View File

@ -0,0 +1,55 @@
# Fund Locked in MultiSig when Match with a Blocked Profile
## Summary
A profile can be blocked by the owner through `SoulboundProfileNFT::blockProfile`. But the fact that the profile is being blocked, doesn't change anything on the MultiSIgWallet. Someone who match up with a blocked profile now in the risk of their fund will never be able to withdraw. There is no other way to withdraw fund from MultiSigWallet other than both of the owner approve the transaction.
## Vulnerability Details
When user being blocked by owner through `SoulBoundProfileNFT::blockProfile`, it doesn't change any state of the `MultiSigWallet`. Transaction execution from `MultiSigWallet::executeTransaction` require both of party to sign through `MultiSigWallet::approveTransaction`.
```solidity
function executeTransaction(uint256 _txId) external onlyOwners {
require(_txId < transactions.length, "Invalid transaction ID");
Transaction storage txn = transactions[_txId];
require(!txn.executed, "Transaction already executed");
require(txn.approvedByOwner1 && txn.approvedByOwner2, "Not enough approvals");
[...]
}
```
## POC
```solidity
function testUserInRelationshipWithBlockedProfileUnableToWithdraw() public {
uint256 initialMultiSigWalletBalance = address(multiSigWallet).balance;
uint256 initialUserBalance = address(user).balance;
uint256 withdrawBalance = initialMultiSigWalletBalance / 2;
vm.startPrank(user);
// submit tx
multiSigWallet.submitTransaction(address(user), withdrawBalance);
// approve tx
multiSigWallet.approveTransaction(0);
// execute
vm.expectRevert();
multiSigWallet.executeTransaction(0);
vm.stopPrank();
(address to, uint256 value, bool approvedByOwner1, bool approvedByOwner2, bool executed) = multiSigWallet.transactions(0);
assertEq(executed, false); // unable to execute because need both of approval, despite the other user is already blocked
assertEq(address(user).balance, initialUserBalance + withdrawBalance); // user should be able to withdraw their fund from multiSigWallet from a blocked profile
}
```
## Impact
- Lost of funds because funds are locked in the MultiSigWallet and require approval from the blocked profile which might rejects all the transaction requests that benefit other user
## Recommendations
Add a function to let user withdraw funds to their wallet if their match up is blocked.

12
audit/template.md Normal file
View File

@ -0,0 +1,12 @@
# Title
## Summary
## Vulnerability Details
## POC
## Impact
## Recommendations

View File

@ -6,6 +6,6 @@ libs = ["lib"]
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
remapping = [ remapping = [
"@openzeppelin//lib/openzeppelin-contracts/", "@openzeppelin/=/lib/openzeppelin-contracts/",
"forge-std/=/lib/forge-std/src/", "forge-std/=/lib/forge-std/src/",
] ]

1
lib/forge-std Submodule

@ -0,0 +1 @@
Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981

@ -0,0 +1 @@
Subproject commit acd4ff74de833399287ed6b31b4debf6b2b35527

View File

@ -28,7 +28,7 @@ contract LikeRegistry is Ownable {
profileNFT = SoulboundProfileNFT(_profileNFT); profileNFT = SoulboundProfileNFT(_profileNFT);
} }
function likeUser(address liked) external payable { function likeUser(address liked) external payable returns (address) {
require(msg.value >= 1 ether, "Must send at least 1 ETH"); require(msg.value >= 1 ether, "Must send at least 1 ETH");
require(!likes[msg.sender][liked], "Already liked"); require(!likes[msg.sender][liked], "Already liked");
require(msg.sender != liked, "Cannot like yourself"); require(msg.sender != liked, "Cannot like yourself");
@ -38,16 +38,23 @@ contract LikeRegistry is Ownable {
likes[msg.sender][liked] = true; likes[msg.sender][liked] = true;
emit Liked(msg.sender, liked); emit Liked(msg.sender, liked);
userBalances[msg.sender] += msg.value;
// Check if mutual like // Check if mutual like
if (likes[liked][msg.sender]) { if (likes[liked][msg.sender]) {
matches[msg.sender].push(liked); matches[msg.sender].push(liked);
matches[liked].push(msg.sender); matches[liked].push(msg.sender);
emit Matched(msg.sender, liked); emit Matched(msg.sender, liked);
matchRewards(liked, msg.sender); address multiSigWallet = matchRewards(liked, msg.sender);
return multiSigWallet;
} }
return address(0);
} }
function matchRewards(address from, address to) internal { function matchRewards(address from, address to) internal returns (address) {
// @audit: will always be zero
// @audit: critical
uint256 matchUserOne = userBalances[from]; uint256 matchUserOne = userBalances[from];
uint256 matchUserTwo = userBalances[to]; uint256 matchUserTwo = userBalances[to];
userBalances[from] = 0; userBalances[from] = 0;
@ -64,6 +71,8 @@ contract LikeRegistry is Ownable {
// Send ETH to the deployed multisig wallet // Send ETH to the deployed multisig wallet
(bool success,) = payable(address(multiSigWallet)).call{value: rewards}(""); (bool success,) = payable(address(multiSigWallet)).call{value: rewards}("");
require(success, "Transfer failed"); require(success, "Transfer failed");
return address(multiSigWallet);
} }
function getMatches() external view returns (address[] memory) { function getMatches() external view returns (address[] memory) {
@ -79,6 +88,10 @@ contract LikeRegistry is Ownable {
require(success, "Transfer failed"); require(success, "Transfer failed");
} }
// @audit: if user A send money to this contract, user A unable to retrieve it
// @audit: critical
/// @notice Allows the contract to receive ETH /// @notice Allows the contract to receive ETH
receive() external payable {} receive() external payable {
userBalances[msg.sender] += msg.value;
}
} }

View File

@ -37,6 +37,7 @@ contract MultiSigWallet {
owner2 = _owner2; owner2 = _owner2;
} }
// @audit: there is a risk of the fund will locked here forever
/// @notice Submit a transaction for approval /// @notice Submit a transaction for approval
function submitTransaction(address _to, uint256 _value) external onlyOwners { function submitTransaction(address _to, uint256 _value) external onlyOwners {
if (_to == address(0)) revert InvalidRecipient(); if (_to == address(0)) revert InvalidRecipient();

View File

@ -59,6 +59,8 @@ contract SoulboundProfileNFT is ERC721, Ownable {
require(tokenId != 0, "No profile found"); require(tokenId != 0, "No profile found");
_burn(tokenId); _burn(tokenId);
// @audit: blocked profile can mint a new profile
// @audit-finding: high
delete profileToToken[blockAddress]; delete profileToToken[blockAddress];
delete _profiles[tokenId]; delete _profiles[tokenId];
@ -71,6 +73,8 @@ contract SoulboundProfileNFT is ERC721, Ownable {
revert SoulboundTokenCannotBeTransferred(); revert SoulboundTokenCannotBeTransferred();
} }
// @audit: what happen with `safeTransferFrom(address, address, uint256) without bytes`
// @audit-comment: no its reverted
function safeTransferFrom(address, address, uint256, bytes memory) public pure override { function safeTransferFrom(address, address, uint256, bytes memory) public pure override {
// Soulbound token cannot be transferred // Soulbound token cannot be transferred
revert SoulboundTokenCannotBeTransferred(); revert SoulboundTokenCannotBeTransferred();

View File

@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/SoulboundProfileNFT.sol";
import "../src/LikeRegistry.sol";
import {MultiSigWallet} from "../src/MultiSig.sol";
contract LikeRegistryTest is Test {
SoulboundProfileNFT soulboundNFT;
LikeRegistry likeRegistry;
address user = address(0x123);
address user2 = address(0x456);
address owner = address(this); // Test contract acts as the owner
function setUp() public {
soulboundNFT = new SoulboundProfileNFT();
likeRegistry = new LikeRegistry(address(soulboundNFT));
vm.deal(user, 10 ether);
vm.deal(user2, 10 ether);
}
function testGetMatches() public {
vm.prank(user);
address[] memory matches = likeRegistry.getMatches();
address[] memory empty;
assertEq(matches, empty);
}
function testMatchMiscalculation() public {
uint256 likeAmount = 1 ether;
uint256 FIXEDFEE = 10;
uint256 fees = ((likeAmount + likeAmount) * FIXEDFEE) / 100;
uint multiSigFund = likeAmount + likeAmount - fees;
// user mint profile
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
uint256 userTokenId = soulboundNFT.profileToToken(user);
assertEq(userTokenId, 1, "Token ID should be 1");
// user2 mint profile
vm.prank(user2);
soulboundNFT.mintProfile("Bob", 27, "ipfs://profileImage");
uint256 user2TokenId = soulboundNFT.profileToToken(user2);
assertEq(user2TokenId, 2, "Token ID should be 2");
// likeRegistry has 0 ether
assertEq(address(likeRegistry).balance, 0);
// user likes user2 with 1 eth
vm.prank(user);
likeRegistry.likeUser{value: likeAmount}(user2);
// user2 likes uer with 1 eth and match
vm.prank(user2);
address multiSigAddress = likeRegistry.likeUser{value: likeAmount}(user);
assert(multiSigAddress != address(0));
// get user matches
vm.prank(user);
address[] memory userMatches = likeRegistry.getMatches();
assertEq(userMatches.length, 1);
assertEq(userMatches[0], address(user2));
assertEq(address(multiSigAddress).balance, multiSigFund); // giving a like for each user 1 ether should give the wallet 1.8 ether
assertEq(address(likeRegistry).balance, fees); // likeRegistry should have balance equal to `fees`, and send the rest of fund to the MultisigWallet
}
function testSendMoneyToLikeRegistry() public {
uint256 sendAmount = 1 ether;
uint256 initialLikeRegistryFund = address(likeRegistry).balance;
// user send money directly to like registry contract
vm.prank(user);
(bool success,) = payable(address(likeRegistry)).call{value: sendAmount}("");
uint256 likeRegistryFundAfterFunded = address(likeRegistry).balance;
assertEq(success, true);
assertEq(initialLikeRegistryFund, 0);
assertEq(likeRegistryFundAfterFunded, sendAmount);
// check userBalances
uint256 userUserBalance = likeRegistry.userBalances(user);
assertEq(userUserBalance, sendAmount); // sending money directly to contract should adjust userBalance to reflect the address fund
// owner try to withdraw money
vm.prank(owner);
vm.expectRevert();
likeRegistry.withdrawFees(); // the contract will assume that there is no fees to withdraw
// try to withdraw money
vm.prank(user);
vm.expectRevert();
likeRegistry.withdrawFees(); // there is no withdraw function for user
}
}

51
test/testMultiSig.t.sol Normal file
View File

@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import {MultiSigWallet} from "../src/MultiSig.sol";
contract MultiSigWalletTest is Test {
MultiSigWallet multiSigWallet;
address user = address(0x123);
address user2 = address(0x456);
function setUp() public {
multiSigWallet = new MultiSigWallet(user, user2);
vm.deal(address(multiSigWallet), 10 ether);
vm.deal(user, 10 ether);
vm.deal(user2, 10 ether);
}
function testSetup() public {
address owner1 = multiSigWallet.owner1();
address owner2 = multiSigWallet.owner2();
assertEq(owner1, user);
assertEq(owner2, user2);
assertEq(address(multiSigWallet).balance, 10 ether);
}
function testUserInRelationshipWithBlockedProfileUnableToWithdraw() public {
uint256 initialMultiSigWalletBalance = address(multiSigWallet).balance;
uint256 initialUserBalance = address(user).balance;
uint256 withdrawBalance = initialMultiSigWalletBalance / 2;
vm.startPrank(user);
// submit tx
multiSigWallet.submitTransaction(address(user), withdrawBalance);
// approve tx
multiSigWallet.approveTransaction(0);
// execute
vm.expectRevert();
multiSigWallet.executeTransaction(0);
vm.stopPrank();
(address to, uint256 value, bool approvedByOwner1, bool approvedByOwner2, bool executed) = multiSigWallet.transactions(0);
assertEq(executed, false); // unable to execute because need both of approval, despite the other user is already blocked
assertEq(address(user).balance, initialUserBalance + withdrawBalance); // user should be able to withdraw their fund from multiSigWallet from a blocked profile
}
}

View File

@ -94,6 +94,27 @@ contract SoulboundProfileNFTTest is Test {
assertEq(newTokenId, 0, "Token should be removed after blocking"); assertEq(newTokenId, 0, "Token should be removed after blocking");
} }
// audit
function testBlockedProfileMintAgain() public {
vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
uint256 tokenId = soulboundNFT.profileToToken(user);
assertEq(tokenId, 1, "Token should exist before blocking");
vm.prank(owner);
soulboundNFT.blockProfile(user);
uint256 newTokenId = soulboundNFT.profileToToken(user);
assertEq(newTokenId, 0, "Token should be removed after blocking");
vm.prank(user);
vm.expectRevert();
// blocked profile should not be able to mint from the same wallet address again
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");
}
// end of audit
function testNonOwnerCannotBlockProfile() public { function testNonOwnerCannotBlockProfile() public {
vm.prank(user); vm.prank(user);
soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage"); soulboundNFT.mintProfile("Alice", 25, "ipfs://profileImage");