Skip to content

Commit

Permalink
Merge pull request #1591 from oasisprotocol/mitjat/transfer-event-always
Browse files Browse the repository at this point in the history
runtime-sdk/evm: Emit Transfer event for contract-initiated transfers
  • Loading branch information
mitjat authored Mar 25, 2024
2 parents f21c98f + 1320dd2 commit da60d8d
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 1 deletion.
2 changes: 1 addition & 1 deletion runtime-sdk/modules/evm/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ impl<'ctx, 'backend, 'config, C: Context, Cfg: Config> StackState<'config>
let amount = transfer.value.as_u128();
let amount = token::BaseUnits::new(amount, Cfg::TOKEN_DENOMINATION);

Cfg::Accounts::transfer_silent(from, to, &amount).map_err(|_| ExitError::OutOfFund)
Cfg::Accounts::transfer(from, to, &amount).map_err(|_| ExitError::OutOfFund)
}

fn reset_balance(&mut self, _address: H160) {
Expand Down
88 changes: 88 additions & 0 deletions runtime-sdk/modules/evm/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ use crate::{
/// Test contract code.
static TEST_CONTRACT_CODE_HEX: &str =
include_str!("../../../../tests/e2e/contracts/evm_erc20_test_compiled.hex");
static FAUCET_CONTRACT_CODE_HEX: &str =
include_str!("../../../../tests/e2e/contracts/faucet/faucet.hex");

pub(crate) struct EVMConfig;

Expand Down Expand Up @@ -995,6 +997,92 @@ fn test_fee_refunds() {
assert_eq!(events[0].amount, 24_585);
}

#[test]
fn test_transfer_event() {
let mut mock = mock::Mock::default();
let mut ctx = mock.create_ctx_for_runtime::<EVMRuntime<EVMConfig>>(true);
let mut signer = EvmSigner::new(0, keys::dave::sigspec());

EVMRuntime::<EVMConfig>::migrate(&mut ctx);

// Create contract.
let dispatch_result = signer.call(
&mut ctx,
"evm.Create",
types::Create {
value: 0.into(),
init_code: load_contract_bytecode(FAUCET_CONTRACT_CODE_HEX),
},
);
let result = dispatch_result.result.unwrap();
let result: Vec<u8> = cbor::from_value(result).unwrap();
let contract_address = H160::from_slice(&result);
let contract_address_native = EVMConfig::map_address(contract_address.into());

// Give the faucet some tokens.
Accounts::mint(
contract_address_native,
&token::BaseUnits(1_000_000_000_000, Denomination::NATIVE),
)
.unwrap();

// Call the `withdraw` method on the contract; this initiates a native token transfer from within EVM.
let dispatch_result = signer.call_evm_opts(
&mut ctx,
contract_address,
"withdraw",
&[ParamType::Uint(256)],
&[Token::Uint(1_000_000_000.into())],
CallOptions {
fee: Fee {
amount: token::BaseUnits::new(1_000_000, Denomination::NATIVE),
gas: 100_000,
..Default::default()
},
},
);
assert!(dispatch_result.result.is_success(), "call should succeed");

// Make sure two events were emitted and are properly formatted.
let tags = &dispatch_result.tags;
assert_eq!(tags.len(), 2, "two events should have been emitted");
assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x01"); // accounts.Transfer (code = 1) events
assert_eq!(tags[1].key, b"core\x00\x00\x00\x01"); // core.GasUsed (code = 1) event

#[derive(Debug, Default, cbor::Decode)]
struct TransferEvent {
from: Address,
to: Address,
amount: token::BaseUnits,
}

let events: Vec<TransferEvent> = cbor::from_slice(&tags[0].value).unwrap();
assert_eq!(events.len(), 2); // One event for fee payment, one for the withdrawal.
let event = &events[0];
assert_eq!(event.from, contract_address_native);
assert_eq!(event.to, keys::dave::address());
assert_eq!(
event.amount,
token::BaseUnits::new(1_000_000_000, Denomination::NATIVE)
);
let event = &events[1];
assert_eq!(event.from, keys::dave::address());
assert_eq!(event.to, *ADDRESS_FEE_ACCUMULATOR);
assert_eq!(
event.amount,
token::BaseUnits::new(283_430, Denomination::NATIVE)
);

#[derive(Debug, Default, cbor::Decode)]
struct GasUsedEvent {
amount: u64,
}

let events: Vec<GasUsedEvent> = cbor::from_slice(&tags[1].value).unwrap();
assert_eq!(events.len(), 1);
assert_eq!(events[0].amount, 28_343);
}

#[test]
fn test_return_value_limits() {
let mut mock = mock::Mock::default();
Expand Down
6 changes: 6 additions & 0 deletions tests/e2e/contracts/faucet/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
contract = faucet.sol
abi = faucet.abi
hex = faucet.hex

include ../contracts.mk

1 change: 1 addition & 0 deletions tests/e2e/contracts/faucet/faucet.abi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"uint256","name":"withdraw_amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
1 change: 1 addition & 0 deletions tests/e2e/contracts/faucet/faucet.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
608060405234801561000f575f80fd5b5060d08061001c5f395ff3fe608060405260043610601e575f3560e01c80632e1a7d4d146028575f80fd5b36602457005b5f80fd5b3480156032575f80fd5b506042603e3660046084565b6044565b005b67016345785d8a00008111156057575f80fd5b604051339082156108fc029083905f818181858888f193505050501580156080573d5f803e3d5ffd5b5050565b5f602082840312156093575f80fd5b503591905056fea264697066735822122064dc104c8e0f2cbd31a4d9e6238c2c1e2867b04ca485d3836b2810e582a7079d64736f6c63430008170033
16 changes: 16 additions & 0 deletions tests/e2e/contracts/faucet/faucet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pragma solidity ^0.8.0;

// Minimal faucet contract.
contract Faucet {
// Accept any incoming amount.
receive() external payable {}

// Give out native tokens to anyone who asks.
function withdraw(uint256 withdraw_amount) public {
// Limit withdrawal amount.
require(withdraw_amount <= 100000000000000000);

// Send the amount to the address that requested it.
payable(msg.sender).transfer(withdraw_amount);
}
}

0 comments on commit da60d8d

Please sign in to comment.