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

Add deposit/withdraw wETH functions to balances ERC20 precompile #1106

Merged
merged 10 commits into from
Jan 4, 2022
37 changes: 37 additions & 0 deletions precompiles/balances-erc20/ERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ interface IERC20 {
function transferFrom(address from, address to, uint256 value)
external returns (bool);

/**
* @dev Provide compatibility for contracts that expect wETH design.
* Returns funds to sender as this precompile tokens and the native tokens are the same.
* Selector: d0e30db0
*/
function deposit() external payable;

/**
* @dev Provide compatibility for contracts that expect wETH design.
* Does nothing.
* Selector: 2e1a7d4d
* @param Amount to withdraw/unwrap.
*/
function withdraw(uint256 value) external;

/**
* @dev Event emited when a transfer has been performed.
* Selector: ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
Expand All @@ -108,4 +123,26 @@ interface IERC20 {
address indexed spender,
uint256 value
);

/**
* @dev Event emited when deposit() has been called.
* Selector: e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c
* @param owner address Owner of the tokens
* @param value uint256 The amount of tokens "wrapped".
*/
event Deposit(
address indexed owner,
uint value
);

/**
* @dev Event emited when withdraw(uint256) has been called.
* Selector: 7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65
* @param owner address Owner of the tokens
* @param value uint256 The amount of tokens "unwrapped".
*/
event Withdrawal(
address indexed owner,
uint value
);
}
85 changes: 84 additions & 1 deletion precompiles/balances-erc20/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ pub const SELECTOR_LOG_TRANSFER: [u8; 32] = keccak256!("Transfer(address,address
/// Solidity selector of the Approval log, which is the Keccak of the Log signature.
pub const SELECTOR_LOG_APPROVAL: [u8; 32] = keccak256!("Approval(address,address,uint256)");

/// Solidity selector of the Deposit log, which is the Keccak of the Log signature.
pub const SELECTOR_LOG_DEPOSIT: [u8; 32] = keccak256!("Deposit(address,uint256)");

/// Solidity selector of the Withdraw log, which is the Keccak of the Log signature.
pub const SELECTOR_LOG_WITHDRAWAL: [u8; 32] = keccak256!("Withdrawal(address,uint256)");

/// Associates pallet Instance to a prefix used for the Approves storage.
/// This trait is implemented for () and the 16 substrate Instance.
pub trait InstanceToPrefix {
Expand Down Expand Up @@ -126,6 +132,8 @@ pub enum Action {
Name = "name()",
Symbol = "symbol()",
Decimals = "decimals()",
Deposit = "deposit()",
Withdraw = "withdraw(uint256)",
}

/// Metadata of an ERC20 token.
Expand Down Expand Up @@ -167,7 +175,8 @@ where
let mut gasometer = Gasometer::new(target_gas);
let gasometer = &mut gasometer;

let (mut input, selector) = EvmDataReader::new_with_selector(gasometer, input)?;
let (mut input, selector) = EvmDataReader::new_with_selector(gasometer, input)
.unwrap_or_else(|_| (EvmDataReader::new(input), Action::Deposit));
let input = &mut input;

match selector {
Expand All @@ -180,6 +189,8 @@ where
Action::Name => Self::name(input, gasometer, context),
Action::Symbol => Self::symbol(input, gasometer, context),
Action::Decimals => Self::decimals(input, gasometer, context),
Action::Deposit => Self::deposit(input, gasometer, context),
Action::Withdraw => Self::withdraw(input, gasometer, context),
}
}
}
Expand Down Expand Up @@ -472,6 +483,78 @@ where
})
}

fn deposit(
_: &mut EvmDataReader,
gasometer: &mut Gasometer,
context: &Context,
) -> EvmResult<PrecompileOutput> {
let caller: Runtime::AccountId = Runtime::AddressMapping::into_account_id(context.caller);
let precompile = Runtime::AddressMapping::into_account_id(context.address);
let amount = Self::u256_to_amount(gasometer, context.apparent_value)?;

if amount.into() == U256::from(0u32) {
return Err(gasometer.revert("deposited amount must be non-zero"));
}

gasometer.record_log_costs_manual(2, 32)?;

// Send back funds received by the precompile.
RuntimeHelper::<Runtime>::try_dispatch(
Some(precompile).into(),
pallet_balances::Call::<Runtime, Instance>::transfer {
dest: Runtime::Lookup::unlookup(caller),
value: amount,
},
gasometer,
)?;

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
cost: gasometer.used_gas(),
output: Default::default(),
logs: LogsBuilder::new(context.address)
.log2(
SELECTOR_LOG_DEPOSIT,
context.caller,
EvmDataWriter::new().write(context.apparent_value).build(),
)
.build(),
})
}

fn withdraw(
input: &mut EvmDataReader,
gasometer: &mut Gasometer,
context: &Context,
) -> EvmResult<PrecompileOutput> {
gasometer.record_log_costs_manual(2, 32)?;

let withdrawn_amount: U256 = input.read(gasometer)?;

let account_amount: U256 = {
let owner: Runtime::AccountId =
Runtime::AddressMapping::into_account_id(context.caller);
pallet_balances::Pallet::<Runtime, Instance>::usable_balance(&owner).into()
};

if withdrawn_amount > account_amount {
return Err(gasometer.revert("trying to withdraw more than owned"));
}

Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
cost: gasometer.used_gas(),
output: Default::default(),
logs: LogsBuilder::new(context.address)
.log2(
SELECTOR_LOG_WITHDRAWAL,
context.caller,
EvmDataWriter::new().write(withdrawn_amount).build(),
)
.build(),
})
}

fn u256_to_amount(
gasometer: &mut Gasometer,
value: U256,
Expand Down
7 changes: 7 additions & 0 deletions precompiles/balances-erc20/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,10 @@ impl ExtBuilder {
ext
}
}

pub(crate) fn events() -> Vec<Event> {
System::events()
.into_iter()
.map(|r| r.event)
.collect::<Vec<_>>()
}
Loading