The owner can accidentaly input address(0) as one of the mapping.
The owner can change/update the mapping through this external function which in turn call the internal function below this code block:
Both of the functions does not contain any check/validation for zero addresses. If the owner unintentionally change 1 of the existing collection address mapping with zero address, this could cause accidental mapping update that could lead to asset lockdown, as users currently can deposit any tokens but cannot withdraw it if the mapping do not contains the collection address.
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/token/ERC721/IERC721.sol";
import "openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol";
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/IStarklane.sol";
import "../src/IStarklaneEvent.sol";
import "../src/State.sol";
import "../src/Bridge.sol";
import "../src/sn/Cairo.sol";
import "../src/sn/StarknetMessagingLocal.sol";
import "../src/sn/IStarknetMessagingLocal.sol";
import "../src/token/Deployer.sol";
import "../src/token/TokenUtil.sol";
import "./utils/Users.sol";
import "./token/ERC721MintFree.sol";
import "./token/IERC721MintRangeFree.sol";
contract Hack is Test {
address bridge;
address erc721C1;
address erc1155C1;
address snCore;
address payable internal alice;
address payable internal bob;
function computeMessageHashFromL2(
uint256[] memory request
)
public
returns (bytes32)
{
(snaddress starklaneL2Address, felt252 starklaneL2Selector)
= IStarklane(bridge).l2Info();
assertTrue(felt252.unwrap(starklaneL2Selector) > 0);
bytes32 msgHash = keccak256(
abi.encodePacked(
snaddress.unwrap(starklaneL2Address),
uint256(uint160(bridge)),
request.length,
request)
);
return msgHash;
}
function testAccidentalMappingUpdate() public{
Users genusers = new Users();
address payable[] memory users = genusers.create(5);
alice = users[0];
bob = users[1];
erc721C1 = address(new ERC721MintFree("name 1", "S1"));
IERC721MintRangeFree(erc721C1).mintRangeFree(alice, 0, 10);
uint256[] memory ids = new uint256[](3);
ids[0]=1;
ids[1]=2;
ids[2]=0;
snCore = address(new StarknetMessagingLocal());
address impl = address(new Starklane());
bytes memory dataInit = abi.encodeWithSelector(
Starklane.initialize.selector,
abi.encode(
address(this),
snCore,
0x1,
0x2
)
);
bridge = address(new ERC1967Proxy(impl, dataInit));
IStarklane(bridge).enableBridge(true);
IStarklane(bridge).setL1L2CollectionMapping(address(erc721C1), Cairo.snaddressWrap(0x123), true);
console.log("This: ", address(this));
console.log("Bridge: ", bridge);
console.log("SNCore: ", snCore);
console.log("Impl: ", impl);
console.log("ERC721C1: ", erc721C1);
console.log("Alice: " , alice);
console.log("Bob: " , bob);
vm.startPrank(alice);
console.log("DEPOSIT");
console.log("---------------------------------------");
console.log(ERC721(erc721C1).ownerOf(0));
console.log(ERC721(erc721C1).ownerOf(1));
console.log(ERC721(erc721C1).ownerOf(2));
uint256 salt = 0x1;
snaddress to = Cairo.snaddressWrap(0x1);
IERC721(erc721C1).setApprovalForAll(address(bridge), true);
IStarklane(bridge).depositTokens{value: 1}(
salt,
address(erc721C1),
to,
ids,
false
);
console.log("WITHDRAW");
console.log("---------------------------------------");
console.log(ERC721(erc721C1).ownerOf(0));
console.log(ERC721(erc721C1).ownerOf(1));
console.log(ERC721(erc721C1).ownerOf(2));
vm.stopPrank();
IStarklane(bridge).setL1L2CollectionMapping(address(erc721C1), Cairo.snaddressWrap(0x0), true);
vm.startPrank(alice);
uint256[] memory values = new uint256[](0);
string[] memory uris = new string[](0);
uint256[] memory newOwners = new uint256[](0);
felt252 header = Protocol.requestHeaderV1(CollectionType.ERC721, false, false);
Request memory reqFull = Request ({
header: header,
hash: 0x0,
collectionL1: erc721C1,
collectionL2: Cairo.snaddressWrap(0x123),
ownerL1: alice,
ownerL2: Cairo.snaddressWrap(0x0),
name: "Good",
symbol: "GOOD",
uri: "URIURI",
tokenIds: ids,
tokenValues: values,
tokenURIs: uris,
newOwners: newOwners
});
uint256[] memory reqSerialized = Protocol.requestSerialize(reqFull);
uint256[] memory msgHashes = new uint256[](1);
msgHashes[0] = uint256( computeMessageHashFromL2(reqSerialized) );
IStarknetMessagingLocal(snCore).addMessageHashesFromL2(msgHashes);
IStarklane(bridge).withdrawTokens(reqSerialized);
console.log("---------------------------------------");
console.log(ERC721(erc721C1).ownerOf(0));
console.log(ERC721(erc721C1).ownerOf(1));
console.log(ERC721(erc721C1).ownerOf(2));
}
}