use crate::build_solidity;
use ink::env::{
hash::{Blake2x256, CryptoHash},
topics::PrefixedValue,
};
use ink::primitives::AccountId;
use parity_scale_codec::Encode;
use solang::{file_resolver::FileResolver, Target};
use std::ffi::OsStr;
fn topic_hash(encoded: &[u8]) -> Vec<u8> {
let mut buf = [0; 32];
if encoded.len() <= 32 {
buf[..encoded.len()].copy_from_slice(encoded);
} else {
<Blake2x256 as CryptoHash>::hash(encoded, &mut buf);
};
buf.into()
}
#[test]
fn anonymous() {
let mut runtime = build_solidity(
r##"
contract a {
event foo(bool b) anonymous;
function emit_event() public {
emit foo(true);
}
}"##,
);
runtime.constructor(0, Vec::new());
runtime.function("emit_event", Vec::new());
assert_eq!(runtime.events.len(), 1);
let event = &runtime.events[0];
assert_eq!(event.topics.len(), 0);
assert_eq!(event.data, (0u8, true).encode());
}
#[test]
fn emit() {
#[derive(Encode)]
enum Event {
Foo(bool, u32, i64),
Bar(u32, u64, String),
}
let mut runtime = build_solidity(
r##"
contract a {
event foo(bool,uint32,int64 indexed i);
event bar(uint32,uint64,string indexed s);
function emit_event() public {
emit foo(true, 102, 1);
emit bar(0xdeadcafe, 102, "foobar");
}
}"##,
);
runtime.constructor(0, Vec::new());
runtime.function("emit_event", Vec::new());
assert_eq!(runtime.events.len(), 2);
let event = &runtime.events[0];
assert_eq!(event.topics.len(), 2);
assert_eq!(event.topics[0], topic_hash(b"\0a::foo")[..]);
let topic = PrefixedValue {
prefix: b"a::foo::i",
value: &1i64,
}
.encode();
assert_eq!(event.topics[1], topic_hash(&topic[..])[..]);
assert_eq!(event.data, Event::Foo(true, 102, 1).encode());
let event = &runtime.events[1];
assert_eq!(event.topics.len(), 2);
println!(
"topic hash: {}",
std::str::from_utf8(&event.topics[0]).unwrap()
);
println!(
"topic hash: {}",
std::str::from_utf8(&event.topics[0]).unwrap()
);
assert_eq!(event.topics[0], topic_hash(b"\0a::bar")[..]);
let topic = PrefixedValue {
prefix: b"a::bar::s",
value: &String::from("foobar"),
}
.encode();
assert_eq!(event.topics[1].to_vec(), topic_hash(&topic[..]));
assert_eq!(
event.data,
Event::Bar(0xdeadcafe, 102, "foobar".into()).encode()
);
}
#[test]
fn event_imported() {
let mut cache = FileResolver::new();
cache.set_file_contents(
"a.sol",
r#"
import "b.sol";
contract foo {
function emit_event() public {
emit bar(102, true);
}
}
"#
.to_string(),
);
cache.set_file_contents(
"b.sol",
r#"
event bar (uint32 indexed f1, bool x);
"#
.to_string(),
);
let ns =
solang::parse_and_resolve(OsStr::new("a.sol"), &mut cache, Target::default_substrate());
assert!(!ns.diagnostics.any_errors());
let mut cache = FileResolver::new();
cache.set_file_contents(
"a.sol",
r#"
import "b.sol";
contract foo {
function emit_event() public {
emit baz.bar(102, true);
}
}
"#
.to_string(),
);
cache.set_file_contents(
"b.sol",
r#"
abstract contract baz {
event bar (uint32 indexed f1, bool x);
}
"#
.to_string(),
);
let ns =
solang::parse_and_resolve(OsStr::new("a.sol"), &mut cache, Target::default_substrate());
assert!(!ns.diagnostics.any_errors());
let mut cache = FileResolver::new();
cache.set_file_contents(
"a.sol",
r#"
import "b.sol" as X;
contract foo {
function emit_event() public {
emit X.baz.bar(102, true);
}
}
"#
.to_string(),
);
cache.set_file_contents(
"b.sol",
r#"
abstract contract baz {
event bar (uint32 indexed f1, bool x);
}
"#
.to_string(),
);
let ns =
solang::parse_and_resolve(OsStr::new("a.sol"), &mut cache, Target::default_substrate());
assert!(!ns.diagnostics.any_errors());
let mut cache = FileResolver::new();
cache.set_file_contents(
"a.sol",
r#"
import "b.sol" as X;
contract foo {
function emit_event() public {
emit X.bar(102, true);
}
}
"#
.to_string(),
);
cache.set_file_contents(
"b.sol",
r#"
event bar (uint32 indexed f1, bool x);
"#
.to_string(),
);
let ns =
solang::parse_and_resolve(OsStr::new("a.sol"), &mut cache, Target::default_substrate());
assert!(!ns.diagnostics.any_errors());
}
#[test]
fn erc20_ink_example() {
#[derive(Encode)]
enum Event {
Transfer(AccountId, AccountId, u128),
}
#[derive(Encode)]
struct Transfer {
from: AccountId,
to: AccountId,
value: u128,
}
let mut runtime = build_solidity(
r##"
contract Erc20 {
event Transfer(
address indexed from,
address indexed to,
uint128 value
);
function emit_event(address from, address to, uint128 value) public {
emit Transfer(from, to, value);
}
}"##,
);
runtime.constructor(0, Vec::new());
let from = AccountId::from([1; 32]);
let to = AccountId::from([2; 32]);
let value = 10;
runtime.function("emit_event", Transfer { from, to, value }.encode());
assert_eq!(runtime.events.len(), 1);
let event = &runtime.events[0];
assert_eq!(event.data, Event::Transfer(from, to, value).encode());
assert_eq!(event.topics.len(), 3);
assert_eq!(event.topics[0], topic_hash(b"\0Erc20::Transfer")[..]);
let expected_topic = PrefixedValue {
prefix: b"Erc20::Transfer::from",
value: &from,
};
assert_eq!(event.topics[1], topic_hash(&expected_topic.encode())[..]);
let expected_topic = PrefixedValue {
prefix: b"Erc20::Transfer::to",
value: &to,
};
assert_eq!(event.topics[2], topic_hash(&expected_topic.encode())[..]);
}
#[test]
fn freestanding() {
let mut runtime = build_solidity(
r##"
event A(bool indexed b);
function foo() {
emit A(true);
}
contract a {
function emit_event() public {
foo();
}
}"##,
);
runtime.constructor(0, Vec::new());
runtime.function("emit_event", Vec::new());
assert_eq!(runtime.events.len(), 1);
let event = &runtime.events[0];
assert_eq!(event.data, (0u8, true).encode());
assert_eq!(event.topics[0], topic_hash(b"\0a::A")[..]);
let expected_topic = PrefixedValue {
prefix: b"a::A::b",
value: &true,
};
assert_eq!(event.topics[1], topic_hash(&expected_topic.encode())[..]);
}
#[test]
fn different_contract() {
let mut runtime = build_solidity(
r##"abstract contract A { event X(bool indexed foo); }
contract B { function emit_event() public { emit A.X(true); } }"##,
);
runtime.constructor(0, Vec::new());
runtime.function("emit_event", Vec::new());
assert_eq!(runtime.events.len(), 1);
let event = &runtime.events[0];
assert_eq!(event.data, (0u8, true).encode());
assert_eq!(event.topics[0], topic_hash(b"\0A::X")[..]);
let expected_topic = PrefixedValue {
prefix: b"A::X::foo",
value: &true,
};
assert_eq!(event.topics[1], topic_hash(&expected_topic.encode())[..]);
}