Commit 9142adc4 authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat(ctp): introduce TeleportrWithdrawer (#2709)

Introduces a simple middleware contract for withdrawing funds from
Teleportr.
Co-authored-by: default avatarmergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
parent 24e4c073
---
'@eth-optimism/contracts-periphery': patch
---
Adds new TeleportrWithdrawer contract for withdrawing from Teleportr
import { DeployConfig } from '../../src'
const config: DeployConfig = {
ddd: '0x9C6373dE60c2D3297b18A8f964618ac46E011B58',
retroReceiverOwner: '0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5',
drippieOwner: '0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5',
}
......
import { DeployConfig } from '../../src'
const config: DeployConfig = {
ddd: '0x9C6373dE60c2D3297b18A8f964618ac46E011B58',
retroReceiverOwner: '0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5',
drippieOwner: '0xc37f6a6c4AB335E20d10F034B90386E2fb70bbF5',
}
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract MockTeleportr {
function withdrawBalance() external {
payable(msg.sender).transfer(address(this).balance);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { AssetReceiver } from "./AssetReceiver.sol";
/**
* @notice Stub interface for Teleportr.
*/
interface Teleportr {
function withdrawBalance() external;
}
/**
* @title TeleportrWithdrawer
* @notice The TeleportrWithdrawer is a simple contract capable of withdrawing funds from the
* TeleportrContract and sending them to some recipient address.
*/
contract TeleportrWithdrawer is AssetReceiver {
/**
* @notice Address of the Teleportr contract.
*/
address public teleportr;
/**
* @notice Address that will receive Teleportr withdrawals.
*/
address public recipient;
/**
* @notice Data to be sent to the recipient address.
*/
bytes public data;
/**
* @param _owner Initial owner of the contract.
*/
constructor(address _owner) AssetReceiver(_owner) {}
/**
* @notice Allows the owner to update the recipient address.
*
* @param _recipient New recipient address.
*/
function setRecipient(address _recipient) external onlyOwner {
recipient = _recipient;
}
/**
* @notice Allows the owner to update the Teleportr contract address.
*
* @param _teleportr New Teleportr contract address.
*/
function setTeleportr(address _teleportr) external onlyOwner {
teleportr = _teleportr;
}
/**
* @notice Allows the owner to update the data to be sent to the recipient address.
*
* @param _data New data to be sent to the recipient address.
*/
function setData(bytes memory _data) external onlyOwner {
data = _data;
}
/**
* @notice Withdraws the full balance of the Teleportr contract to the recipient address.
* Anyone is allowed to trigger this function since the recipient address cannot be
* controlled by the msg.sender.
*/
function withdrawFromTeleportr() external {
Teleportr(teleportr).withdrawBalance();
(bool success, ) = recipient.call{ value: address(this).balance }(data);
require(success, "TeleportrWithdrawer: send failed");
}
}
......@@ -19,6 +19,5 @@ const deployFn: DeployFunction = async (hre) => {
}
deployFn.tags = ['RetroReceiver']
deployFn.dependencies = ['OptimismAuthority']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { getDeployConfig } from '../src'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const config = getDeployConfig(hre.network.name)
const { deploy } = await hre.deployments.deterministic(
'TeleportrWithdrawer',
{
salt: hre.ethers.utils.solidityKeccak256(
['string'],
['TeleportrWithdrawer']
),
from: deployer,
args: [config.ddd],
log: true,
}
)
await deploy()
}
deployFn.tags = ['TeleportrWithdrawer']
export default deployFn
......@@ -19,6 +19,5 @@ const deployFn: DeployFunction = async (hre) => {
}
deployFn.tags = ['Drippie']
deployFn.dependencies = ['OptimismAuthority']
export default deployFn
......@@ -4,6 +4,15 @@ import { ethers } from 'ethers'
* Defines the configuration for a deployment.
*/
export interface DeployConfig {
/**
* Dedicated Deterministic Deployer address (DDD).
* When deploying authenticated deterministic smart contracts to the same address on various
* chains, it's necessary to have a single root address that will initially own the contract and
* later transfer ownership to the final contract owner. We call this address the DDD. We expect
* the DDD to transfer ownership to the final contract owner very quickly after deployment.
*/
ddd: string
/**
* Initial RetroReceiver owner.
*/
......@@ -24,6 +33,9 @@ const configSpec: {
default?: any
}
} = {
ddd: {
type: 'address',
},
retroReceiverOwner: {
type: 'address',
},
......
import hre from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { Contract } from 'ethers'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../setup'
import { deploy } from '../../helpers'
describe('TeleportrWithdrawer', () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before('signer setup', async () => {
;[signer1, signer2] = await hre.ethers.getSigners()
})
let SimpleStorage: Contract
let MockTeleportr: Contract
let TeleportrWithdrawer: Contract
beforeEach('deploy contracts', async () => {
SimpleStorage = await deploy('SimpleStorage')
MockTeleportr = await deploy('MockTeleportr')
TeleportrWithdrawer = await deploy('TeleportrWithdrawer', {
signer: signer1,
args: [signer1.address],
})
})
describe('setRecipient', () => {
describe('when called by authorized address', () => {
it('should set the recipient', async () => {
await TeleportrWithdrawer.setRecipient(signer1.address)
expect(await TeleportrWithdrawer.recipient()).to.equal(signer1.address)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
TeleportrWithdrawer.connect(signer2).setRecipient(signer2.address)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('setTeleportr', () => {
describe('when called by authorized address', () => {
it('should set the recipient', async () => {
await TeleportrWithdrawer.setTeleportr(MockTeleportr.address)
expect(await TeleportrWithdrawer.teleportr()).to.equal(
MockTeleportr.address
)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
TeleportrWithdrawer.connect(signer2).setTeleportr(signer2.address)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('setData', () => {
const data = `0x${'ff'.repeat(64)}`
describe('when called by authorized address', () => {
it('should set the data', async () => {
await TeleportrWithdrawer.setData(data)
expect(await TeleportrWithdrawer.data()).to.equal(data)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
TeleportrWithdrawer.connect(signer2).setData(data)
).to.be.revertedWith('UNAUTHORIZED')
})
})
})
describe('withdrawTeleportrBalance', () => {
const recipient = `0x${'11'.repeat(20)}`
const amount = hre.ethers.constants.WeiPerEther
beforeEach(async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
MockTeleportr.address,
toRpcHexString(amount),
])
await TeleportrWithdrawer.setRecipient(recipient)
await TeleportrWithdrawer.setTeleportr(MockTeleportr.address)
})
describe('when target is an EOA', () => {
it('should withdraw the balance', async () => {
await TeleportrWithdrawer.withdrawFromTeleportr()
expect(await hre.ethers.provider.getBalance(recipient)).to.equal(amount)
})
})
describe('when target is a contract', () => {
it('should withdraw the balance and trigger code', async () => {
const key = `0x${'dd'.repeat(32)}`
const val = `0x${'ee'.repeat(32)}`
await TeleportrWithdrawer.setRecipient(SimpleStorage.address)
await TeleportrWithdrawer.setData(
SimpleStorage.interface.encodeFunctionData('set', [key, val])
)
await TeleportrWithdrawer.withdrawFromTeleportr()
expect(
await hre.ethers.provider.getBalance(SimpleStorage.address)
).to.equal(amount)
expect(await SimpleStorage.get(key)).to.equal(val)
})
})
})
})
......@@ -5,7 +5,7 @@ import { Contract } from 'ethers'
import { expect } from '../../setup'
import { decodeSolidityRevert, deploy } from '../../helpers'
describe('AssetReceiver', () => {
describe('Transactor', () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before('signer setup', 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