Commit 3883f34b authored by Maurelian's avatar Maurelian Committed by GitHub

ctp: Remove ERC721Refunded events (#3522)

Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 2a7e48cd
---
'@eth-optimism/contracts-periphery': patch
---
Remove ERC721Refunded events
...@@ -56,33 +56,22 @@ contract L1ERC721Bridge is ERC721Bridge, Semver { ...@@ -56,33 +56,22 @@ contract L1ERC721Bridge is ERC721Bridge, Semver {
bytes calldata _extraData bytes calldata _extraData
) external onlyOtherBridge { ) external onlyOtherBridge {
try this.completeOutboundTransfer(_localToken, _remoteToken, _to, _tokenId) { try this.completeOutboundTransfer(_localToken, _remoteToken, _to, _tokenId) {
if (_from == otherBridge) { // slither-disable-next-line reentrancy-events
// The _from address is the address of the remote bridge if a transfer fails to be emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
// finalized on the remote chain.
// slither-disable-next-line reentrancy-events
emit ERC721Refunded(_localToken, _remoteToken, _to, _tokenId, _extraData);
} else {
// slither-disable-next-line reentrancy-events
emit ERC721BridgeFinalized(
_localToken,
_remoteToken,
_from,
_to,
_tokenId,
_extraData
);
}
} catch { } catch {
// If the token ID for this L1/L2 NFT pair is not escrowed in the L1 Bridge or if // If the token ID for this L1/L2 NFT pair is not escrowed in the L1 Bridge or if
// another error occurred during finalization, we initiate a cross-domain message to // another error occurred during finalization, we initiate a cross-domain message to
// send the NFT back to its original owner on L2. This can happen if an L2 native NFT is // send the NFT back to its original owner on L2. This can happen if an L2 native NFT is
// bridged to L1, or if a user mistakenly entered an incorrect L1 ERC721 address. // bridged to L1, or if a user mistakenly entered an incorrect L1 ERC721 address.
// In either case, we stop the process here and construct a withdrawal in which we
// flip the to and from addresses. This ensures that event-based accounting
// will indicate net-zero transfer to the recipient. The ERC721BridgeFailed event
// emitted below can also be used to identify this occurence.
bytes memory message = abi.encodeWithSelector( bytes memory message = abi.encodeWithSelector(
L2ERC721Bridge.finalizeBridgeERC721.selector, L2ERC721Bridge.finalizeBridgeERC721.selector,
_remoteToken, _remoteToken,
_localToken, _localToken,
address(this), // Set the new _from address to be this contract since the NFT was _to,
// never transferred to the recipient on this chain.
_from, // Refund the NFT to the original owner on the remote chain. _from, // Refund the NFT to the original owner on the remote chain.
_tokenId, _tokenId,
_extraData _extraData
...@@ -115,14 +104,13 @@ contract L1ERC721Bridge is ERC721Bridge, Semver { ...@@ -115,14 +104,13 @@ contract L1ERC721Bridge is ERC721Bridge, Semver {
address _to, address _to,
uint256 _tokenId uint256 _tokenId
) external onlySelf { ) external onlySelf {
// Checks that the L1/L2 NFT pair has a token ID that is escrowed in the L1 Bridge. Without require(_localToken != address(this), "L1ERC721Bridge: local token cannot be self");
// this check, an attacker could steal a legitimate L1 NFT by supplying an arbitrary L2 NFT
// that maps to the L1 NFT. // Checks that the L1/L2 NFT pair has a token ID that is escrowed in the L1 Bridge.
require( require(
deposits[_localToken][_remoteToken][_tokenId] == true, deposits[_localToken][_remoteToken][_tokenId] == true,
"L1ERC721Bridge: Token ID is not escrowed in the L1 Bridge" "L1ERC721Bridge: Token ID is not escrowed in the L1 Bridge"
); );
require(_localToken != address(this), "L1ERC721Bridge: local token cannot be self");
// Mark that the token ID for this L1/L2 token pair is no longer escrowed in the L1 // Mark that the token ID for this L1/L2 token pair is no longer escrowed in the L1
// Bridge. // Bridge.
......
...@@ -52,43 +52,30 @@ contract L2ERC721Bridge is ERC721Bridge, Semver { ...@@ -52,43 +52,30 @@ contract L2ERC721Bridge is ERC721Bridge, Semver {
bytes calldata _extraData bytes calldata _extraData
) external onlyOtherBridge { ) external onlyOtherBridge {
try this.completeOutboundTransfer(_localToken, _remoteToken, _to, _tokenId) { try this.completeOutboundTransfer(_localToken, _remoteToken, _to, _tokenId) {
if (_from == otherBridge) { // slither-disable-next-line reentrancy-events
// The _from address is the address of the remote bridge if a transfer fails to be emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
// finalized on the remote chain.
// slither-disable-next-line reentrancy-events
emit ERC721Refunded(_localToken, _remoteToken, _to, _tokenId, _extraData);
} else {
// slither-disable-next-line reentrancy-events
emit ERC721BridgeFinalized(
_localToken,
_remoteToken,
_from,
_to,
_tokenId,
_extraData
);
}
} catch { } catch {
// Either the L2 token which is being deposited-into disagrees about the correct address // Either the L2 token which is being deposited-into disagrees about the correct address
// of its L1 token, or does not support the correct interface. // of its L1 token, or does not support the correct interface.
// This should only happen if there is a malicious L2 token, or if a user somehow // This should only happen if there is a malicious L2 token, or if a user somehow
// specified the wrong L2 token address to deposit into. // specified the wrong L2 token address to deposit into.
// In either case, we stop the process here and construct a withdrawal
// message so that users can get their NFT out in some cases.
// There is no way to prevent malicious token contracts altogether, but this does limit // There is no way to prevent malicious token contracts altogether, but this does limit
// user error and mitigate some forms of malicious contract behavior. // user error and mitigate some forms of malicious contract behavior.
/// In either case, we stop the process here and construct a withdrawal in which we
// flip the to and from addresses. This ensures that event-based accounting
// will indicate net-zero transfer to the recipient. The ERC721BridgeFailed event
// emitted below can also be used to identify this occurence.
bytes memory message = abi.encodeWithSelector( bytes memory message = abi.encodeWithSelector(
L1ERC721Bridge.finalizeBridgeERC721.selector, L1ERC721Bridge.finalizeBridgeERC721.selector,
_remoteToken, _remoteToken,
_localToken, _localToken,
address(this), // Set the new _from address to be this contract since the NFT was _to,
// never transferred to the recipient on this chain.
_from, // Refund the NFT to the original owner on the remote chain. _from, // Refund the NFT to the original owner on the remote chain.
_tokenId, _tokenId,
_extraData _extraData
); );
// Send message up to L1 bridge // Send the message to the L1 bridge
// slither-disable-next-line reentrancy-events // slither-disable-next-line reentrancy-events
messenger.sendMessage(otherBridge, message, 0); messenger.sendMessage(otherBridge, message, 0);
...@@ -115,8 +102,11 @@ contract L2ERC721Bridge is ERC721Bridge, Semver { ...@@ -115,8 +102,11 @@ contract L2ERC721Bridge is ERC721Bridge, Semver {
address _to, address _to,
uint256 _tokenId uint256 _tokenId
) external onlySelf { ) external onlySelf {
require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self");
require( require(
// slither-disable-next-line reentrancy-events // Note that supportsInterface makes a callback to the _localToken address
// which is user provided.
ERC165Checker.supportsInterface(_localToken, type(IOptimismMintableERC721).interfaceId), ERC165Checker.supportsInterface(_localToken, type(IOptimismMintableERC721).interfaceId),
"L2ERC721Bridge: local token interface is not compliant" "L2ERC721Bridge: local token interface is not compliant"
); );
...@@ -124,7 +114,6 @@ contract L2ERC721Bridge is ERC721Bridge, Semver { ...@@ -124,7 +114,6 @@ contract L2ERC721Bridge is ERC721Bridge, Semver {
_remoteToken == IOptimismMintableERC721(_localToken).remoteToken(), _remoteToken == IOptimismMintableERC721(_localToken).remoteToken(),
"L2ERC721Bridge: wrong remote token for Optimism Mintable ERC721 local token" "L2ERC721Bridge: wrong remote token for Optimism Mintable ERC721 local token"
); );
require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self");
// When a deposit is finalized, we give the NFT with the same tokenId to the account // When a deposit is finalized, we give the NFT with the same tokenId to the account
// on L2. Note that safeMint makes a callback to the _to address which is user provided. // on L2. Note that safeMint makes a callback to the _to address which is user provided.
......
...@@ -407,7 +407,7 @@ describe('L1ERC721Bridge', () => { ...@@ -407,7 +407,7 @@ describe('L1ERC721Bridge', () => {
IL2ERC721Bridge.encodeFunctionData('finalizeBridgeERC721', [ IL2ERC721Bridge.encodeFunctionData('finalizeBridgeERC721', [
DUMMY_L2_ERC721_ADDRESS, DUMMY_L2_ERC721_ADDRESS,
RANDOM_L1_ERC721_ADDRESS, RANDOM_L1_ERC721_ADDRESS,
L1ERC721Bridge.address, // the 'from' address is the l1 bridge to indicate that the l1 recipient never controlled the nft. bobsAddress,
aliceAddress, aliceAddress,
tokenId, tokenId,
NON_NULL_BYTES32, NON_NULL_BYTES32,
...@@ -452,41 +452,6 @@ describe('L1ERC721Bridge', () => { ...@@ -452,41 +452,6 @@ describe('L1ERC721Bridge', () => {
) )
).to.equal(false) ).to.equal(false)
}) })
it('should credit funds to the withdrawer to finalize refund', async () => {
// finalizing a refund emits an ERC721Refunded event with the correct arguments.
await expect(
L1ERC721Bridge.finalizeBridgeERC721(
L1ERC721.address,
DUMMY_L2_ERC721_ADDRESS,
DUMMY_L2_BRIDGE_ADDRESS, // _from is the l2 bridge for refunds
NON_ZERO_ADDRESS,
tokenId,
NON_NULL_BYTES32,
{ from: Fake__L1CrossDomainMessenger.address }
)
)
.to.emit(L1ERC721Bridge, 'ERC721Refunded')
.withArgs(
L1ERC721.address,
DUMMY_L2_ERC721_ADDRESS,
NON_ZERO_ADDRESS,
tokenId,
NON_NULL_BYTES32
)
// NFT is transferred to new owner
expect(await L1ERC721.ownerOf(tokenId)).to.equal(NON_ZERO_ADDRESS)
// deposits state variable is updated
expect(
await L1ERC721Bridge.deposits(
L1ERC721.address,
DUMMY_L2_ERC721_ADDRESS,
tokenId
)
).to.equal(false)
})
}) })
}) })
......
...@@ -155,7 +155,7 @@ describe('L2ERC721Bridge', () => { ...@@ -155,7 +155,7 @@ describe('L2ERC721Bridge', () => {
[ [
DUMMY_L1ERC721_ADDRESS, DUMMY_L1ERC721_ADDRESS,
NonCompliantERC721.address, NonCompliantERC721.address,
L2ERC721Bridge.address, // the 'from' address is the l2 bridge to indicate that the l2 recipient never controlled the nft. bobsAddress,
aliceAddress, aliceAddress,
TOKEN_ID, TOKEN_ID,
NON_NULL_BYTES32, NON_NULL_BYTES32,
...@@ -211,41 +211,6 @@ describe('L2ERC721Bridge', () => { ...@@ -211,41 +211,6 @@ describe('L2ERC721Bridge', () => {
const tokenIdOwner = await L2ERC721.ownerOf(TOKEN_ID) const tokenIdOwner = await L2ERC721.ownerOf(TOKEN_ID)
tokenIdOwner.should.equal(bobsAddress) tokenIdOwner.should.equal(bobsAddress)
}) })
it('should credit funds to the depositer to finalize refund', async () => {
Fake__L2CrossDomainMessenger.xDomainMessageSender.returns(
DUMMY_L1BRIDGE_ADDRESS
)
// Assert that nobody owns the L2 token initially
await expect(L2ERC721.ownerOf(TOKEN_ID)).to.be.revertedWith(
'ERC721: owner query for nonexistent token'
)
// finalizing a refund emits an ERC721Refunded event with the correct arguments.
await expect(
L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeBridgeERC721(
L2ERC721.address,
DUMMY_L1ERC721_ADDRESS,
DUMMY_L1BRIDGE_ADDRESS, // _from is the l1 bridge for refunds
aliceAddress,
TOKEN_ID,
NON_NULL_BYTES32,
{ from: Fake__L2CrossDomainMessenger.address }
)
)
.to.emit(L2ERC721Bridge, 'ERC721Refunded')
.withArgs(
L2ERC721.address,
DUMMY_L1ERC721_ADDRESS,
aliceAddress,
TOKEN_ID,
NON_NULL_BYTES32
)
// NFT is transferred to new owner
expect(await L2ERC721.ownerOf(TOKEN_ID)).to.equal(aliceAddress)
})
}) })
describe('completeOutboundTransfer', async () => { describe('completeOutboundTransfer', async () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment