Skip to content

Commit

Permalink
Merge pull request from GHSA-54q9-r92x-944r
Browse files Browse the repository at this point in the history
Harden the `L2WormholeGateway` minting flow
  • Loading branch information
lukasz-zimnoch authored Oct 19, 2023
2 parents 75e3ecf + 2a70ac2 commit 7e21841
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction, Deployment } from "hardhat-deploy/types"
import { ContractFactory } from "ethers"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { ethers, helpers, deployments } = hre

const { deployer } = await helpers.signers.getNamedSigners()

const proxyDeployment: Deployment = await deployments.get(
"ArbitrumWormholeGateway"
)
const implementationContractFactory: ContractFactory =
await ethers.getContractFactory("L2WormholeGateway", {
signer: deployer,
})

// Deploy new implementation contract
const newImplementationAddress: string = (await hre.upgrades.prepareUpgrade(
proxyDeployment,
implementationContractFactory,
{
kind: "transparent",
}
)) as string

deployments.log(
`new implementation contract deployed at: ${newImplementationAddress}`
)

// Assemble proxy upgrade transaction.
const proxyAdmin = await hre.upgrades.admin.getInstance()
const proxyAdminOwner = await proxyAdmin.owner()

const upgradeTxData = await proxyAdmin.interface.encodeFunctionData(
"upgrade",
[proxyDeployment.address, newImplementationAddress]
)

deployments.log(
`proxy admin owner ${proxyAdminOwner} is required to upgrade proxy implementation with transaction:\n` +
`\t\tfrom: ${proxyAdminOwner}\n` +
`\t\tto: ${proxyAdmin.address}\n` +
`\t\tdata: ${upgradeTxData}`
)

// Update Deployment Artifact
const gatewayArtifact: Artifact =
hre.artifacts.readArtifactSync("L2WormholeGateway")

await deployments.save("ArbitrumWormholeGateway", {
...proxyDeployment,
abi: gatewayArtifact.abi,
implementation: newImplementationAddress,
})

// Contracts can be verified on L2 Arbiscan in a similar way as we do it on
// L1 Etherscan
if (hre.network.tags.arbiscan) {
// We use `verify` instead of `verify:verify` as the `verify` task is defined
// in "@openzeppelin/hardhat-upgrades" to verify the proxy’s implementation
// contract, the proxy itself and any proxy-related contracts, as well as
// link the proxy to the implementation contract’s ABI on (Ether)scan.
await hre.run("verify", {
address: newImplementationAddress,
constructorArgsParams: proxyDeployment.args,
})
}
}

export default func

func.tags = ["ManualUpgradeArbitrumWormholeGateway"]

// Comment this line when running an upgrade.
// yarn deploy --tags ManualUpgradeArbitrumWormholeGateway --network <network>
func.skip = async () => true
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction, Deployment } from "hardhat-deploy/types"
import { ContractFactory } from "ethers"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { ethers, helpers, deployments } = hre

const { deployer } = await helpers.signers.getNamedSigners()

const proxyDeployment: Deployment = await deployments.get(
"BaseWormholeGateway"
)
const implementationContractFactory: ContractFactory =
await ethers.getContractFactory("L2WormholeGateway", {
signer: deployer,
})

// Deploy new implementation contract
const newImplementationAddress: string = (await hre.upgrades.prepareUpgrade(
proxyDeployment,
implementationContractFactory,
{
kind: "transparent",
}
)) as string

deployments.log(
`new implementation contract deployed at: ${newImplementationAddress}`
)

// Assemble proxy upgrade transaction.
const proxyAdmin = await hre.upgrades.admin.getInstance()
const proxyAdminOwner = await proxyAdmin.owner()

const upgradeTxData = await proxyAdmin.interface.encodeFunctionData(
"upgrade",
[proxyDeployment.address, newImplementationAddress]
)

deployments.log(
`proxy admin owner ${proxyAdminOwner} is required to upgrade proxy implementation with transaction:\n` +
`\t\tfrom: ${proxyAdminOwner}\n` +
`\t\tto: ${proxyAdmin.address}\n` +
`\t\tdata: ${upgradeTxData}`
)

// Update Deployment Artifact
const gatewayArtifact: Artifact =
hre.artifacts.readArtifactSync("L2WormholeGateway")

await deployments.save("BaseWormholeGateway", {
...proxyDeployment,
abi: gatewayArtifact.abi,
implementation: newImplementationAddress,
})

// Contracts can be verified on L2 Basescan in a similar way as we do it on
// L1 Etherscan
if (hre.network.tags.basescan) {
// We use `verify` instead of `verify:verify` as the `verify` task is defined
// in "@openzeppelin/hardhat-upgrades" to verify the proxy’s implementation
// contract, the proxy itself and any proxy-related contracts, as well as
// link the proxy to the implementation contract’s ABI on (Ether)scan.
await hre.run("verify", {
address: newImplementationAddress,
constructorArgsParams: proxyDeployment.args,
})
}
}

export default func

func.tags = ["ManualUpgradeBaseWormholeGateway"]

// Comment this line when running an upgrade.
// yarn deploy --tags ManualUpgradeBaseWormholeGateway --network <network>
func.skip = async () => true
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction, Deployment } from "hardhat-deploy/types"
import { ContractFactory } from "ethers"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { ethers, helpers, deployments } = hre

const { deployer } = await helpers.signers.getNamedSigners()

const proxyDeployment: Deployment = await deployments.get(
"OptimismWormholeGateway"
)
const implementationContractFactory: ContractFactory =
await ethers.getContractFactory("L2WormholeGateway", {
signer: deployer,
})

// Deploy new implementation contract
const newImplementationAddress: string = (await hre.upgrades.prepareUpgrade(
proxyDeployment,
implementationContractFactory,
{
kind: "transparent",
}
)) as string

deployments.log(
`new implementation contract deployed at: ${newImplementationAddress}`
)

// Assemble proxy upgrade transaction.
const proxyAdmin = await hre.upgrades.admin.getInstance()
const proxyAdminOwner = await proxyAdmin.owner()

const upgradeTxData = await proxyAdmin.interface.encodeFunctionData(
"upgrade",
[proxyDeployment.address, newImplementationAddress]
)

deployments.log(
`proxy admin owner ${proxyAdminOwner} is required to upgrade proxy implementation with transaction:\n` +
`\t\tfrom: ${proxyAdminOwner}\n` +
`\t\tto: ${proxyAdmin.address}\n` +
`\t\tdata: ${upgradeTxData}`
)

// Update Deployment Artifact
const gatewayArtifact: Artifact =
hre.artifacts.readArtifactSync("L2WormholeGateway")

await deployments.save("OptimismWormholeGateway", {
...proxyDeployment,
abi: gatewayArtifact.abi,
implementation: newImplementationAddress,
})

// Contracts can be verified on L2 Optimism Etherscan in a similar way as we do it on
// L1 Etherscan
if (hre.network.tags.optimism_etherscan) {
// We use `verify` instead of `verify:verify` as the `verify` task is defined
// in "@openzeppelin/hardhat-upgrades" to verify the proxy’s implementation
// contract, the proxy itself and any proxy-related contracts, as well as
// link the proxy to the implementation contract’s ABI on (Ether)scan.
await hre.run("verify", {
address: newImplementationAddress,
constructorArgsParams: proxyDeployment.args,
})
}
}

export default func

func.tags = ["ManualUpgradeOptimismWormholeGateway"]

// Comment this line when running an upgrade.
// yarn deploy --tags ManualUpgradeOptimismWormholeGateway --network <network>
func.skip = async () => true
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { Artifact, HardhatRuntimeEnvironment } from "hardhat/types"
import type { DeployFunction, Deployment } from "hardhat-deploy/types"
import { ContractFactory } from "ethers"

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { ethers, helpers, deployments } = hre

const { deployer } = await helpers.signers.getNamedSigners()

const proxyDeployment: Deployment = await deployments.get(
"PolygonWormholeGateway"
)
const implementationContractFactory: ContractFactory =
await ethers.getContractFactory("L2WormholeGateway", {
signer: deployer,
})

// Deploy new implementation contract
const newImplementationAddress: string = (await hre.upgrades.prepareUpgrade(
proxyDeployment,
implementationContractFactory,
{
kind: "transparent",
}
)) as string

deployments.log(
`new implementation contract deployed at: ${newImplementationAddress}`
)

// Assemble proxy upgrade transaction.
const proxyAdmin = await hre.upgrades.admin.getInstance()
const proxyAdminOwner = await proxyAdmin.owner()

const upgradeTxData = await proxyAdmin.interface.encodeFunctionData(
"upgrade",
[proxyDeployment.address, newImplementationAddress]
)

deployments.log(
`proxy admin owner ${proxyAdminOwner} is required to upgrade proxy implementation with transaction:\n` +
`\t\tfrom: ${proxyAdminOwner}\n` +
`\t\tto: ${proxyAdmin.address}\n` +
`\t\tdata: ${upgradeTxData}`
)

// Update Deployment Artifact
const gatewayArtifact: Artifact =
hre.artifacts.readArtifactSync("L2WormholeGateway")

await deployments.save("PolygonWormholeGateway", {
...proxyDeployment,
abi: gatewayArtifact.abi,
implementation: newImplementationAddress,
})

// Contracts can be verified on L2 Polygonscan in a similar way as we do it on
// L1 Etherscan
if (hre.network.tags.polygonscan) {
if (hre.network.name === "mumbai") {
// Polygonscan might not include the recently added proxy transaction right
// after deployment. We need to wait some time so that transaction is
// visible on Polygonscan.
await new Promise((resolve) => setTimeout(resolve, 10000)) // 10sec
}
// We use `verify` instead of `verify:verify` as the `verify` task is defined
// in "@openzeppelin/hardhat-upgrades" to verify the proxy’s implementation
// contract, the proxy itself and any proxy-related contracts, as well as
// link the proxy to the implementation contract’s ABI on (Ether)scan.
await hre.run("verify", {
address: newImplementationAddress,
constructorArgsParams: proxyDeployment.args,
})
}
}

export default func

func.tags = ["ManualUpgradePolygonWormholeGateway"]

// Comment this line when running an upgrade.
// yarn deploy --tags ManualUpgradePolygonWormholeGateway --network <network>
func.skip = async () => true
34 changes: 7 additions & 27 deletions solidity/contracts/l2/L2WormholeGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,17 @@ contract L2WormholeGateway is
/// - The receiver of the canonical tBTC should be abi-encoded in the
/// payload.
/// - The receiver of the canonical tBTC must not be the zero address.
///
/// The Wormhole Token Bridge contract has protection against redeeming
/// the same VAA again. When a Token Bridge VAA is redeemed, its
/// message body hash is stored in a map. This map is used to check
/// whether the hash has already been set in this map. For this reason,
/// this function does not have to be nonReentrant.
/// this function does not have to be nonReentrant in theory. However,
/// to make this function non-dependent on Wormhole Bridge implementation,
/// we are making it nonReentrant anyway.
/// @param encodedVm A byte array containing a Wormhole VAA signed by the
/// guardians.
function receiveTbtc(bytes calldata encodedVm) external {
function receiveTbtc(bytes calldata encodedVm) external nonReentrant {
// ITokenBridge.completeTransferWithPayload completes a contract-controlled
// transfer of an ERC20 token. Calling this function is not enough to
// ensure L2WormholeGateway received Wormhole tBTC representation.
Expand Down Expand Up @@ -318,40 +321,17 @@ contract L2WormholeGateway is
if (mintedAmount + amount > mintingLimit) {
bridgeToken.safeTransfer(receiver, amount);
} else {
// The function is non-reentrant given bridge.completeTransferWithPayload
// call that does not allow to use the same VAA again.
// The function is non-reentrant.
// slither-disable-next-line reentrancy-benign
mintedAmount += amount;
tbtc.mint(receiver, amount);
}

// The function is non-reentrant given bridge.completeTransferWithPayload
// call that does not allow to use the same VAA again.
// The function is non-reentrant.
// slither-disable-next-line reentrancy-events
emit WormholeTbtcReceived(receiver, amount);
}

/// @notice Allows to deposit Wormhole tBTC token in exchange for canonical
/// tBTC. Useful in a situation when user received wormhole tBTC
/// instead of canonical tBTC. One example of such situation is
/// when the minting limit was exceeded but the user minted anyway.
/// @dev Requirements:
/// - The sender must have at least `amount` of the Wormhole tBTC and
/// it has to be approved for L2WormholeGateway.
/// - The minting limit must allow for minting the given amount.
/// @param amount The amount of Wormhole tBTC to deposit.
function depositWormholeTbtc(uint256 amount) external {
require(
mintedAmount + amount <= mintingLimit,
"Minting limit exceeded"
);

emit WormholeTbtcDeposited(msg.sender, amount);
mintedAmount += amount;
bridgeToken.safeTransferFrom(msg.sender, address(this), amount);
tbtc.mint(msg.sender, amount);
}

/// @notice Lets the governance to update the tBTC gateway address on the
/// chain with the given Wormhole ID.
/// @dev Use toWormholeAddress function to convert between Ethereum and
Expand Down
Loading

0 comments on commit 7e21841

Please sign in to comment.