Skip to content

Commit

Permalink
ctp: Remove ERC721Refunded events (#3522)
Browse files Browse the repository at this point in the history
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
maurelian and mergify[bot] committed Sep 22, 2022
1 parent 2a7e48c commit 3883f34
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-laws-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/contracts-periphery': patch
---

Remove ERC721Refunded events
32 changes: 10 additions & 22 deletions packages/contracts-periphery/contracts/L1/L1ERC721Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,33 +56,22 @@ contract L1ERC721Bridge is ERC721Bridge, Semver {
bytes calldata _extraData
) external onlyOtherBridge {
try this.completeOutboundTransfer(_localToken, _remoteToken, _to, _tokenId) {
if (_from == otherBridge) {
// The _from address is the address of the remote bridge if a transfer fails to be
// 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
);
}
// slither-disable-next-line reentrancy-events
emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
} catch {
// 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
// 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.
// 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(
L2ERC721Bridge.finalizeBridgeERC721.selector,
_remoteToken,
_localToken,
address(this), // Set the new _from address to be this contract since the NFT was
// never transferred to the recipient on this chain.
_to,
_from, // Refund the NFT to the original owner on the remote chain.
_tokenId,
_extraData
Expand Down Expand Up @@ -115,14 +104,13 @@ contract L1ERC721Bridge is ERC721Bridge, Semver {
address _to,
uint256 _tokenId
) external onlySelf {
// Checks that the L1/L2 NFT pair has a token ID that is escrowed in the L1 Bridge. Without
// this check, an attacker could steal a legitimate L1 NFT by supplying an arbitrary L2 NFT
// that maps to the L1 NFT.
require(_localToken != address(this), "L1ERC721Bridge: local token cannot be self");

// Checks that the L1/L2 NFT pair has a token ID that is escrowed in the L1 Bridge.
require(
deposits[_localToken][_remoteToken][_tokenId] == true,
"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
// Bridge.
Expand Down
35 changes: 12 additions & 23 deletions packages/contracts-periphery/contracts/L2/L2ERC721Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,43 +52,30 @@ contract L2ERC721Bridge is ERC721Bridge, Semver {
bytes calldata _extraData
) external onlyOtherBridge {
try this.completeOutboundTransfer(_localToken, _remoteToken, _to, _tokenId) {
if (_from == otherBridge) {
// The _from address is the address of the remote bridge if a transfer fails to be
// 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
);
}
// slither-disable-next-line reentrancy-events
emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData);
} catch {
// 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.
// 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.
// 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
// 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(
L1ERC721Bridge.finalizeBridgeERC721.selector,
_remoteToken,
_localToken,
address(this), // Set the new _from address to be this contract since the NFT was
// never transferred to the recipient on this chain.
_to,
_from, // Refund the NFT to the original owner on the remote chain.
_tokenId,
_extraData
);

// Send message up to L1 bridge
// Send the message to the L1 bridge
// slither-disable-next-line reentrancy-events
messenger.sendMessage(otherBridge, message, 0);

Expand All @@ -115,16 +102,18 @@ contract L2ERC721Bridge is ERC721Bridge, Semver {
address _to,
uint256 _tokenId
) external onlySelf {
require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self");

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),
"L2ERC721Bridge: local token interface is not compliant"
);
require(
_remoteToken == IOptimismMintableERC721(_localToken).remoteToken(),
"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
// on L2. Note that safeMint makes a callback to the _to address which is user provided.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ describe('L1ERC721Bridge', () => {
IL2ERC721Bridge.encodeFunctionData('finalizeBridgeERC721', [
DUMMY_L2_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,
tokenId,
NON_NULL_BYTES32,
Expand Down Expand Up @@ -452,41 +452,6 @@ describe('L1ERC721Bridge', () => {
)
).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)
})
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ describe('L2ERC721Bridge', () => {
[
DUMMY_L1ERC721_ADDRESS,
NonCompliantERC721.address,
L2ERC721Bridge.address, // the 'from' address is the l2 bridge to indicate that the l2 recipient never controlled the nft.
bobsAddress,
aliceAddress,
TOKEN_ID,
NON_NULL_BYTES32,
Expand Down Expand Up @@ -211,41 +211,6 @@ describe('L2ERC721Bridge', () => {
const tokenIdOwner = await L2ERC721.ownerOf(TOKEN_ID)
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 () => {
Expand Down

0 comments on commit 3883f34

Please sign in to comment.