Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(withdrawer): update AstriaWithdrawer to check that withdrawal value is sufficient #1148

Merged
merged 9 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions crates/astria-bridge-withdrawer/ethereum/local.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@ PRIVATE_KEY=0x
# default local rpc url
RPC_URL="http://localhost:8545"

# divide withdrawn values by 10^ASSET_WITHDRAWAL_DECIMALS
ASSET_WITHDRAWAL_DECIMALS=12
### contract deployment values

# divide withdrawn values by 10^(18-BASE_CHAIN_ASSET_PRECISION)
BASE_CHAIN_ASSET_PRECISION=9

### contract call values

# contract address
ASTRIA_WITHDRAWER=0x5FbDB2315678afecb367f032d93F642f64180aa3

# destination chain address to withdraw to on the sequencer
# if withdrawing to the sequencer, this must be set to
# the address to withdraw to on the sequencer
SEQUENCER_DESTINATION_CHAIN_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

# destination chain address to withdraw to on the origin chain
ORIGIN_DESTINATION_CHAIN_ADDRESS="astring"
# if withdrawing to another chain via IBC, this must be set to
# the destination chain address to withdraw to
ORIGIN_DESTINATION_CHAIN_ADDRESS="someaddress"

# amount to withdraw
# amount to withdraw, must be greater than 10^ASSET_WITHDRAWAL_DECIMALS
AMOUNT=1000000000

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ contract AstriaWithdrawerScript is Script {
function setUp() public {}

function deploy() public {
uint32 assetWithdrawalDecimals = uint32(vm.envUint("ASSET_WITHDRAWAL_DECIMALS"));
uint32 baseChainAssetPrecision = uint32(vm.envUint("BASE_CHAIN_ASSET_PRECISION"));
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
AstriaWithdrawer astriaWithdrawer = new AstriaWithdrawer(assetWithdrawalDecimals);
console.logAddress(address(astriaWithdrawer));
new AstriaWithdrawer(baseChainAssetPrecision);
vm.stopBroadcast();
}

Expand All @@ -30,7 +29,7 @@ contract AstriaWithdrawerScript is Script {
vm.stopBroadcast();
}

function withdrawToOriginChain() public {
function withdrawToIbcChain() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

Expand All @@ -39,7 +38,7 @@ contract AstriaWithdrawerScript is Script {

string memory destinationChainAddress = vm.envString("ORIGIN_DESTINATION_CHAIN_ADDRESS");
uint256 amount = vm.envUint("AMOUNT");
astriaWithdrawer.withdrawToOriginChain{value: amount}(destinationChainAddress, "");
astriaWithdrawer.withdrawToIbcChain{value: amount}(destinationChainAddress, "");

vm.stopBroadcast();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@ pragma solidity ^0.8.21;
//
// Funds can be withdrawn to either the sequencer or the origin chain via IBC.
contract AstriaWithdrawer {
// the number of decimal places more the asset has on the rollup versus the base chain.
// the precision of the asset on the base chain.
//
// the amount transferred on the base chain will be divided by 10^ASSET_WITHDRAWAL_DECIMALS.
// the amount transferred on the base chain will be divided by 10 ^ (18 - BASE_CHAIN_ASSET_PRECISION).
//
// for example, if the rollup specifies the asset has 18 decimal places and the base chain specifies 6,
// the ASSET_WITHDRAWAL_DECIMALS would be 12.
uint32 public immutable ASSET_WITHDRAWAL_DECIMALS;
// for example, if base chain asset is precision is 6, the divisor would be 10^12.
uint32 public immutable BASE_CHAIN_ASSET_PRECISION;

constructor(uint32 assetWithdrawalDecimals) {
ASSET_WITHDRAWAL_DECIMALS = assetWithdrawalDecimals;
// the divisor used to convert the rollup asset amount to the base chain denomination
//
// set to 10^ASSET_WITHDRAWAL_DECIMALS on contract creation
uint256 private immutable DIVISOR;

constructor(uint32 _baseChainAssetPrecision) {
if (_baseChainAssetPrecision > 18) {
revert("AstriaWithdrawer: base chain asset precision must be less than or equal to 18");
}
BASE_CHAIN_ASSET_PRECISION = _baseChainAssetPrecision;
DIVISOR = 10 ** (18 - _baseChainAssetPrecision);
}

// emitted when a withdrawal to the sequencer is initiated
Expand All @@ -30,12 +38,17 @@ contract AstriaWithdrawer {
// the `destinationChainAddress` is the address on the origin chain the funds will be sent to
// the `memo` is an optional field that will be used as the ICS20 packet memo
event Ics20Withdrawal(address indexed sender, uint256 indexed amount, string destinationChainAddress, string memo);

modifier sufficientValue(uint256 amount) {
require(amount / DIVISOR > 0, "AstriaWithdrawer: insufficient value, must be greater than 10 ** (18 - BASE_CHAIN_ASSET_PRECISION)");
_;
}

function withdrawToSequencer(address destinationChainAddress) external payable {
function withdrawToSequencer(address destinationChainAddress) external payable sufficientValue(msg.value) {
emit SequencerWithdrawal(msg.sender, msg.value, destinationChainAddress);
}

function withdrawToOriginChain(string calldata destinationChainAddress, string calldata memo) external payable {
function withdrawToIbcChain(string calldata destinationChainAddress, string calldata memo) external payable sufficientValue(msg.value) {
emit Ics20Withdrawal(msg.sender, msg.value, destinationChainAddress, memo);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ use ethers::{
utils::AnvilInstance,
};

#[derive(Default)]
pub(crate) struct ConfigureAstriaWithdrawerDeployer {
pub(crate) base_chain_asset_precision: u32,
}

impl ConfigureAstriaWithdrawerDeployer {
pub(crate) async fn deploy(
&mut self,
) -> (Address, Arc<Provider<Ws>>, LocalWallet, AnvilInstance) {
if self.base_chain_asset_precision == 0 {
self.base_chain_asset_precision = 18;
}
deploy_astria_withdrawer(self.base_chain_asset_precision.into()).await
}
}

/// Starts a local anvil instance and deploys the `AstriaWithdrawer` contract to it.
///
/// Returns the contract address, provider, wallet, and anvil instance.
Expand All @@ -20,8 +36,9 @@ use ethers::{
/// - if the contract cannot be compiled
/// - if the provider fails to connect to the anvil instance
/// - if the contract fails to deploy
pub(crate) async fn deploy_astria_withdrawer()
-> (Address, Arc<Provider<Ws>>, LocalWallet, AnvilInstance) {
pub(crate) async fn deploy_astria_withdrawer(
base_chain_asset_precision: U256,
) -> (Address, Arc<Provider<Ws>>, LocalWallet, AnvilInstance) {
// compile contract for testing
let source = Path::new(&env!("CARGO_MANIFEST_DIR")).join("ethereum/src/AstriaWithdrawer.sol");
let input = CompilerInput::new(source.clone())
Expand Down Expand Up @@ -53,9 +70,13 @@ pub(crate) async fn deploy_astria_withdrawer()
wallet.clone().with_chain_id(anvil.chain_id()),
);

// deploy contract with ASSET_WITHDRAWAL_DECIMALS as 0
let factory = ContractFactory::new(abi, bytecode, signer.into());
let contract = factory.deploy(U256::from(0)).unwrap().send().await.unwrap();
let contract = factory
.deploy(base_chain_asset_precision)
.unwrap()
.send()
.await
.unwrap();
let contract_address = contract.address();

(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,16 @@ impl Watcher {
);
let contract = AstriaWithdrawer::new(contract_address, provider);

let asset_withdrawal_decimals = contract
.asset_withdrawal_decimals()
let base_chain_asset_precision = contract
.base_chain_asset_precision()
.call()
.await
.wrap_err("failed to get asset withdrawal decimals")?;
let asset_withdrawal_divisor = 10u128.pow(asset_withdrawal_decimals);
let asset_withdrawal_divisor =
10u128.pow(18u32.checked_sub(base_chain_asset_precision).expect(
"base_chain_asset_precision must be <= 18, as the contract constructor enforces \
this",
));

let batcher = Batcher::new(
event_rx,
Expand Down Expand Up @@ -323,7 +327,7 @@ mod tests {
SequencerWithdrawalFilter,
},
convert::EventWithMetadata,
test_utils::deploy_astria_withdrawer,
test_utils::ConfigureAstriaWithdrawerDeployer,
};

#[test]
Expand Down Expand Up @@ -368,10 +372,30 @@ mod tests {
receipt
}

#[tokio::test]
#[ignore = "requires foundry and solc to be installed"]
async fn astria_withdrawer_invalid_value_fails() {
let (contract_address, provider, wallet, _anvil) = ConfigureAstriaWithdrawerDeployer {
base_chain_asset_precision: 15,
}
.deploy()
.await;
let signer = Arc::new(SignerMiddleware::new(provider, wallet.clone()));
let contract = AstriaWithdrawer::new(contract_address, signer.clone());

let value: U256 = 999.into(); // 10^3 - 1
let recipient = [0u8; 20].into();
let tx = contract.withdraw_to_sequencer(recipient).value(value);
tx.send()
.await
.expect_err("`withdraw` transaction should have failed due to value < 10^3");
}

#[tokio::test]
#[ignore = "requires foundry and solc to be installed"]
async fn watcher_can_watch_sequencer_withdrawals() {
let (contract_address, provider, wallet, anvil) = deploy_astria_withdrawer().await;
let (contract_address, provider, wallet, anvil) =
ConfigureAstriaWithdrawerDeployer::default().deploy().await;
let signer = Arc::new(SignerMiddleware::new(provider, wallet.clone()));
let contract = AstriaWithdrawer::new(contract_address, signer.clone());

Expand Down Expand Up @@ -428,7 +452,7 @@ mod tests {
recipient: String,
) -> TransactionReceipt {
let tx = contract
.withdraw_to_origin_chain(recipient, "nootwashere".to_string())
.withdraw_to_ibc_chain(recipient, "nootwashere".to_string())
.value(value);
let receipt = tx
.send()
Expand All @@ -449,7 +473,8 @@ mod tests {
#[tokio::test]
#[ignore = "requires foundry and solc to be installed"]
async fn watcher_can_watch_ics20_withdrawals() {
let (contract_address, provider, wallet, anvil) = deploy_astria_withdrawer().await;
let (contract_address, provider, wallet, anvil) =
ConfigureAstriaWithdrawerDeployer::default().deploy().await;
let signer = Arc::new(SignerMiddleware::new(provider, wallet.clone()));
let contract = AstriaWithdrawer::new(contract_address, signer.clone());

Expand Down
Loading