Mismatch between nft asset and token to pay causing a loss of funds
Description: The function InheritanceManager::createEstateNFT
create nft of a real estate that need to be pay in a token asset name assetToPay
function createEstateNFT(string memory _description, uint256 _value, address _asset) external onlyOwner {
uint256 nftID = nft.createEstate(_description);
nftValue[nftID] = _value;
assetToPay = _asset;
}
The problem is that when we create multiple nft with different token to pay, the last token will be in use for every asset created before.
function buyOutEstateNFT(uint256 _nftID) external onlyBeneficiaryWithIsInherited {
uint256 value = nftValue[_nftID];
uint256 divisor = beneficiaries.length;
uint256 multiplier = beneficiaries.length - 1;
uint256 finalAmount = (value / divisor) * multiplier;
IERC20(assetToPay).safeTransferFrom(msg.sender, address(this), finalAmount);
for (uint256 i = 0; i < beneficiaries.length; i++) {
if (msg.sender == beneficiaries[i]) {
return;
} else {
IERC20(assetToPay).safeTransfer(beneficiaries[i], finalAmount / divisor);
}
}
nft.burnEstate(_nftID);
}
Impact: Loss of fund, when someone want to buy for example the nft 2 with tokenB and the nft 3 exist, the user will in fact be deducted with tokens of the nft 3.
Proof of Concept: Add the following line in InheritanceManagerTest.t.sol
InheritanceManager im;
ERC20Mock usdc;
ERC20Mock weth;
+ ERC20Mock link;
+ ERC20Mock uni;
address owner = makeAddr("owner");
address user1 = makeAddr("user1");
function setUp() public {
vm.prank(owner);
im = new InheritanceManager();
usdc = new ERC20Mock();
weth = new ERC20Mock();
+ link = new ERC20Mock();
+ uni = new ERC20Mock();
}
and add this test
function test_buyOutEstateNFTWith3Estate() public {
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
vm.warp(1);
vm.startPrank(owner);
im.addBeneficiery(user1);
im.addBeneficiery(user2);
im.addBeneficiery(user3);
im.createEstateNFT("our beach-house", 3e6, address(usdc));
im.createEstateNFT("our wood storage", 2e6, address(link));
im.createEstateNFT("our mountain-house", 5e6, address(uni));
vm.stopPrank();
usdc.mint(user3, 4e6);
link.mint(user3, 15e6);
uni.mint(user3, 7e6);
assertEq(4e6, usdc.balanceOf(user3));
assertEq(15e6, link.balanceOf(user3));
assertEq(7e6, uni.balanceOf(user3));
vm.warp(1 + 90 days);
vm.startPrank(user3);
usdc.approve(address(im), 4e6);
link.approve(address(im), 15e6);
uni.approve(address(im), 7e6);
im.inherit();
im.buyOutEstateNFT(2);
vm.stopPrank();
assertEq(7e6, uni.balanceOf(user3));
}
Recommended Mitigation: Change this line Trustee.sol
+ mapping(uint256 NftIndex => address asset) assetToPay;
- address assetToPay;
and change the function Trustee::setAssetToPay
and Trustee::getAssetToPay
- function setAssetToPay(address _asset) external onlyTrustee {
- assetToPay = _asset;
}
+ function setAssetToPay(uint256 _index,address _asset) external onlyTrustee {
+ assetToPay[_index] = _asset;
}
- function getAssetToPay() public view returns (address) {
- return assetToPay;
}
+ function getAssetToPay(uint256 _index) public view returns (address) {
+ return assetToPay[_index];
}
and change the function InheritanceManager::createEstateNFT
and InheritanceManager::buyOutEstateNFT
function createEstateNFT(string memory _description, uint256 _value, address _asset) external onlyOwner {
uint256 nftID = nft.createEstate(_description);
nftValue[nftID] = _value;
- assetToPay = _asset;
+ assetToPay[nftID] = _asset;
}
function buyOutEstateNFT(uint256 _nftID) external onlyBeneficiaryWithIsInherited {
uint256 value = nftValue[_nftID];
+ uint256 asset = assetToPay[_nftID];
uint256 divisor = beneficiaries.length;
uint256 multiplier = beneficiaries.length - 1;
uint256 finalAmount = (value / divisor) * multiplier;
@ IERC20(asset).safeTransferFrom(msg.sender, address(this), finalAmount);
for (uint256 i = 0; i < beneficiaries.length; i++) {
if (msg.sender == beneficiaries[i]) {
return;
} else {
@ IERC20(asset).safeTransfer(beneficiaries[i], finalAmount / divisor);
}
}
nft.burnEstate(_nftID);
}