Summary
The current implementation of the transferPlot function in the Beanstalk protocol does not adequately consider users with smart contract wallets or account abstraction wallets.
Specifically, the function's address validation logic does not account for these types of wallets, which could lead to issues with transfers involving smart contract addresses.
Vulnerability Details
The transferPlot function is responsible for transferring plots from one user to another within the Beanstalk protocol. However, the function's parameter verification
and allowance checks, specifically at line 64 of the MarketplaceFacet.sol contract, do not account for the unique behaviors of smart contract wallets or account abstraction wallets.
function transferPlot(
address sender,
address recipient,
uint256 fieldId,
uint256 index,
uint256 start,
uint256 end
) external payable fundsSafu noNetFlow noSupplyChange nonReentrant {
require(
sender != address(0) && recipient != address(0),
"Field: Transfer to/from 0 address."
);
uint256 transferAmount = validatePlotAndReturnPods(fieldId, sender, index, start, end);
if (
LibTractor._user() != sender &&
allowancePods(sender, LibTractor._user(), fieldId) != type(uint256).max
) {
decrementAllowancePods(sender, LibTractor._user(), fieldId, transferAmount);
}
if (s.sys.podListings[fieldId][index] != bytes32(0)) {
LibMarket._cancelPodListing(sender, fieldId, index);
}
_transferPlot(sender, recipient, fieldId, index, start, transferAmount);
}
Impact
Consider a scenario where a user is using a smart contract wallet that implements custom logic for transaction execution and allowance checks. The current implementation of the transferPlot function does not handle this correctly due to the following reasons:
The function only checks if the sender and recipient are non-zero addresses. It does not account for the fact that these addresses could be smart contracts with specific execution logic.
The function performs allowance checks using standard ERC-20 logic, which may not be compatible with smart contract wallets that use different mechanisms for approving and executing transactions.
Tools Used
Manual Review
Recommendations
Below is the updated transferPlot function with additional considerations for smart contract wallets and account abstraction.
By incorporating these changes, the transferPlot function will be better equipped to handle transfers involving smart contract wallets and account abstraction wallets, ensuring compatibility and security across different wallet types and cross-chain scenarios.
function transferPlot(
address sender,
address recipient,
uint256 fieldId,
uint256 index,
uint256 start,
uint256 end
) external payable fundsSafu noNetFlow noSupplyChange nonReentrant {
require(
sender != address(0) && recipient != address(0),
"Field: Transfer to/from 0 address."
);
bool senderIsContract = isContract(sender);
bool recipientIsContract = isContract(recipient);
if (senderIsContract) {
require(
checkContractAllowance(sender, msg.sender, fieldId, validatePlotAndReturnPods(fieldId, sender, index, start, end)),
"Field: Insufficient contract allowance."
);
} else {
uint256 transferAmount = validatePlotAndReturnPods(fieldId, sender, index, start, end);
if (
LibTractor._user() != sender &&
allowancePods(sender, LibTractor._user(), fieldId) != type(uint256).max
) {
decrementAllowancePods(sender, LibTractor._user(), fieldId, transferAmount);
}
}
if (s.sys.podListings[fieldId][index] != bytes32(0)) {
LibMarket._cancelPodListing(sender, fieldId, index);
}
_transferPlot(sender, recipient, fieldId, index, start, validatePlotAndReturnPods(fieldId, sender, index, start, end));
}
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
function checkContractAllowance(
address owner,
address spender,
uint256 fieldId,
uint256 amount
) internal view returns (bool) {
return IContractWallet(owner).isAllowed(spender, fieldId, amount);
}