Skip to content

Commit d65354b

Browse files
authored
feat(zink): add block and transaction properties (#297)
* feat(zink): add block properties * feat(example): tests for properties * feat(example): tests for properties * test: add gas_left function test * optimize code * optimize code * fix(example): update properties tests
1 parent cd1bb5a commit d65354b

File tree

7 files changed

+417
-10
lines changed

7 files changed

+417
-10
lines changed

evm/opcodes/src/cancun.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ opcodes! {
5151
(0x41, COINBASE, 2, 0, 1, "Get the block's beneficiary address.", Frontier, BlockInformation),
5252
(0x42, TIMESTAMP, 2, 0, 1, "Get the block's timestamp.", Frontier, BlockInformation),
5353
(0x43, NUMBER, 2, 0, 1, "Get the block's number.", Frontier, BlockInformation),
54-
(0x44, DIFFICULTY, 2, 0, 1, "Get the block's difficulty.", Frontier, BlockInformation),
54+
(0x44, PREVRANDAO, 2, 0, 1, "Get the previous block’s RANDAO mix", Frontier, BlockInformation),
5555
(0x45, GASLIMIT, 2, 0, 1, "Get the block's gas limit.", Frontier, BlockInformation),
5656
(0x46, CHAINID, 2, 0, 1, "Get the chain ID.", Istanbul, BlockInformation),
5757
(0x47, SELFBALANCE, 5, 0, 1, "Get balance of currently executing account.", Istanbul, BlockInformation),

zink/examples/properties.rs

+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
//! Example for Block and Transaction Properties.
2+
#![cfg_attr(target_arch = "wasm32", no_std)]
3+
#![cfg_attr(target_arch = "wasm32", no_main)]
4+
5+
extern crate zink;
6+
use zink::primitives::{properties, Address, Bytes32};
7+
8+
#[zink::external]
9+
pub fn chainid() -> u64 {
10+
properties::chainid()
11+
}
12+
13+
#[zink::external]
14+
pub fn number() -> u64 {
15+
properties::number()
16+
}
17+
18+
#[zink::external]
19+
pub fn blockhash(number: u64) -> Bytes32 {
20+
properties::blockhash(number)
21+
}
22+
23+
#[zink::external]
24+
pub fn blobhash(index: u64) -> Bytes32 {
25+
properties::blobhash(index)
26+
}
27+
28+
#[zink::external]
29+
pub fn basefee() -> u64 {
30+
properties::basefee()
31+
}
32+
33+
#[zink::external]
34+
pub fn gasprice() -> u64 {
35+
properties::gasprice()
36+
}
37+
38+
#[zink::external]
39+
pub fn blobbasefee() -> u64 {
40+
properties::blobbasefee()
41+
}
42+
43+
#[zink::external]
44+
pub fn gaslimit() -> Bytes32 {
45+
properties::gaslimit()
46+
}
47+
48+
#[zink::external]
49+
pub fn coinbase() -> Address {
50+
properties::coinbase()
51+
}
52+
53+
#[zink::external]
54+
pub fn prevrandao() -> Bytes32 {
55+
properties::prevrandao()
56+
}
57+
58+
#[zink::external]
59+
pub fn timestamp() -> u64 {
60+
properties::timestamp()
61+
}
62+
63+
#[zink::external]
64+
pub fn gasleft() -> Bytes32 {
65+
properties::gasleft()
66+
}
67+
68+
#[cfg(not(target_arch = "wasm32"))]
69+
fn main() {}
70+
71+
#[cfg(test)]
72+
#[cfg(not(target_arch = "wasm32"))]
73+
mod tests {
74+
use zint::{Bytes32, Contract, EVM};
75+
76+
fn hash_to_bytes32(data: &str) -> [u8; 32] {
77+
let hash_bytes = hex::decode(data).unwrap();
78+
let mut hash = [0; 32];
79+
hash.copy_from_slice(&hash_bytes);
80+
hash
81+
}
82+
83+
fn u64_to_bytes32(value: u64) -> Vec<u8> {
84+
let bytes = value.to_be_bytes();
85+
let mut bytes32 = [0; 32];
86+
bytes32[32 - bytes.len()..].copy_from_slice(&bytes);
87+
bytes32.to_vec()
88+
}
89+
90+
#[test]
91+
fn test_block_properties() -> anyhow::Result<()> {
92+
let data = "29045A592007D0C246EF02C2223570DA9522D0CF0F73282C79A1BC8F0BB2C238";
93+
let mut evm = EVM::default()
94+
.chain_id(1)
95+
.block_number(599423555)
96+
.block_hash(599423545, hash_to_bytes32(data))
97+
.commit(true);
98+
let contract = Contract::search("properties")?.compile()?;
99+
let info = evm.deploy(&contract.bytecode()?)?;
100+
let address = info.address;
101+
102+
let info = evm
103+
.calldata(&contract.encode(["chainid()".as_bytes()])?)
104+
.call(address)?;
105+
assert_eq!(info.ret, 1u64.to_bytes32(), "{info:?}");
106+
107+
let info = evm
108+
.calldata(&contract.encode(["number()".as_bytes()])?)
109+
.call(address)?;
110+
assert_eq!(info.ret, u64_to_bytes32(599423555), "{info:?}");
111+
112+
let info = evm
113+
.calldata(
114+
&contract.encode(["blockhash(uint64)".as_bytes(), &u64_to_bytes32(599423545)])?,
115+
)
116+
.call(address)?;
117+
assert_eq!(info.ret, hash_to_bytes32(data), "{info:?}");
118+
Ok(())
119+
}
120+
121+
#[test]
122+
fn test_blob_properties() -> anyhow::Result<()> {
123+
let blobhash =
124+
hash_to_bytes32("0100000000000000000000000000000000000000000000000000000000000001");
125+
let mut evm = EVM::default().blob_hashes(vec![blobhash]).commit(true);
126+
let contract = Contract::search("properties")?.compile()?;
127+
let info = evm.deploy(&contract.bytecode()?)?;
128+
let address = info.address;
129+
130+
let info = evm
131+
.calldata(&contract.encode(["blobhash(uint64)".as_bytes(), &u64_to_bytes32(0)])?)
132+
.call(address)?;
133+
assert_eq!(info.ret, blobhash, "{info:?}");
134+
135+
let info = evm
136+
.calldata(&contract.encode(["blobhash(uint64)".as_bytes(), &u64_to_bytes32(1)])?)
137+
.call(address)?;
138+
assert_eq!(info.ret, 0u64.to_bytes32(), "{info:?}");
139+
Ok(())
140+
}
141+
142+
#[test]
143+
fn test_fee_properties() -> anyhow::Result<()> {
144+
let mut evm = EVM::default()
145+
.basefee(100, 200)
146+
.blob_basefee(50)
147+
.commit(true);
148+
let contract = Contract::search("properties")?.compile()?;
149+
let info = evm.deploy(&contract.bytecode()?)?;
150+
let address = info.address;
151+
152+
let info = evm
153+
.calldata(&contract.encode(["basefee()".as_bytes()])?)
154+
.call(address)?;
155+
assert_eq!(info.ret, 100u64.to_bytes32(), "{info:?}");
156+
157+
let info = evm
158+
.calldata(&contract.encode(["gasprice()".as_bytes()])?)
159+
.call(address)?;
160+
assert_eq!(info.ret, 200u64.to_bytes32(), "{info:?}");
161+
162+
let info = evm
163+
.calldata(&contract.encode(["blobbasefee()".as_bytes()])?)
164+
.call(address)?;
165+
assert_eq!(info.ret, evm.get_blob_basefee(), "{info:?}");
166+
167+
let info = evm
168+
.calldata(&contract.encode(["gaslimit()".as_bytes()])?)
169+
.call(address)?;
170+
assert_eq!(info.ret, [255; 32], "{info:?}");
171+
Ok(())
172+
}
173+
174+
#[test]
175+
fn test_coinbase() -> anyhow::Result<()> {
176+
let data = "29045A592007D0C246EF02C2223570DA9522D0CF0F73282C79A1BC8F0BB2C238";
177+
let mut evm = EVM::default()
178+
.coinbase([1; 20])
179+
.prevrandao(hash_to_bytes32(data))
180+
.timestamp(27)
181+
.commit(false);
182+
let contract = Contract::search("properties")?.compile()?;
183+
let info = evm.deploy(&contract.bytecode()?)?;
184+
let address = info.address;
185+
186+
let info = evm
187+
.calldata(&contract.encode(["coinbase()".as_bytes()])?)
188+
.call(address)?;
189+
assert_eq!(info.ret, [1; 20].to_bytes32(), "{info:?}");
190+
191+
let info = evm
192+
.calldata(&contract.encode(["prevrandao()".as_bytes()])?)
193+
.call(address)?;
194+
assert_eq!(info.ret, hash_to_bytes32(data), "{info:?}");
195+
196+
let info = evm
197+
.calldata(&contract.encode(["timestamp()".as_bytes()])?)
198+
.call(address)?;
199+
assert_eq!(info.ret, 27u64.to_bytes32(), "{info:?}");
200+
Ok(())
201+
}
202+
203+
#[test]
204+
fn test_gas_left() -> anyhow::Result<()> {
205+
let contract = Contract::search("properties")?.compile()?;
206+
207+
let mut evm1 = EVM::default().tx_gas_limit(50000).commit(true);
208+
let info1 = evm1.deploy(&contract.bytecode()?)?;
209+
let info1 = evm1
210+
.calldata(&contract.encode(["gasleft()".as_bytes()])?)
211+
.call(info1.address)?;
212+
let gasleft1 = u64::from_be_bytes(info1.ret[24..].try_into().unwrap());
213+
let gas1 = 50000 - gasleft1;
214+
215+
let mut evm2 = EVM::default().tx_gas_limit(70000).commit(true);
216+
let info2 = evm2.deploy(&contract.bytecode()?)?;
217+
let info2 = evm2
218+
.calldata(&contract.encode(["gasleft()".as_bytes()])?)
219+
.call(info2.address)?;
220+
let gasleft2 = u64::from_be_bytes(info2.ret[24..].try_into().unwrap());
221+
let gas2 = 70000 - gasleft2;
222+
assert_eq!(gas1, gas2);
223+
Ok(())
224+
}
225+
}

zink/src/asm/evm.rs

+36-6
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,6 @@ extern "C" {
134134
/// Get the current message sender
135135
pub fn caller() -> Address;
136136

137-
/// Get the current blob hash at index
138-
pub fn blobhash();
139-
140-
/// Get the current blob base fee
141-
pub fn blobbasefee();
142-
143137
/// Append log record with no topics
144138
pub fn log0(name: &'static [u8]);
145139

@@ -160,4 +154,40 @@ extern "C" {
160154
topic4: Bytes32,
161155
name: &'static [u8],
162156
);
157+
158+
/// Get the current block number.
159+
pub fn number() -> u64;
160+
161+
/// Get the hash of one of the 256 most recent complete blocks.
162+
pub fn blockhash(block_number: u64) -> Bytes32;
163+
164+
/// Get versioned hashes.
165+
pub fn blobhash(index: u64) -> Bytes32;
166+
167+
/// Get the current block’s base fee.
168+
pub fn basefee() -> u64;
169+
170+
/// Get the current block’s blob base fee.
171+
pub fn blobbasefee() -> u64;
172+
173+
/// Get the current chain id.
174+
pub fn chainid() -> u64;
175+
176+
/// Get the block’s beneficiary address.
177+
pub fn coinbase() -> Address;
178+
179+
/// Get the previous block’s RANDAO mix.
180+
pub fn prevrandao() -> Bytes32;
181+
182+
/// Get the current block gaslimit.
183+
pub fn gaslimit() -> Bytes32;
184+
185+
/// Get the amount of available gas.
186+
pub fn gas() -> Bytes32;
187+
188+
/// Get the block’s timestamp.
189+
pub fn timestamp() -> u64;
190+
191+
/// Get the gas price of the transaction.
192+
pub fn gasprice() -> u64;
163193
}

zink/src/primitives/address.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ impl Address {
1515
Address(Bytes20::empty())
1616
}
1717

18-
/// Returns empty address
18+
/// Returns the caller address
1919
#[inline(always)]
2020
pub fn caller() -> Self {
2121
unsafe { asm::evm::caller() }

zink/src/primitives/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
pub mod address;
44
pub mod bytes;
5+
pub mod properties;
56
pub mod u256;
67

78
pub use {address::Address, bytes::*, u256::U256};

zink/src/primitives/properties.rs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use super::{Address, Bytes32};
2+
use crate::asm;
3+
4+
/// Get the current block number.
5+
pub fn number() -> u64 {
6+
unsafe { asm::evm::number() }
7+
}
8+
9+
/// Get the hash of one of the 256 most recent complete blocks.
10+
pub fn blockhash(block_number: u64) -> Bytes32 {
11+
unsafe { asm::evm::blockhash(block_number) }
12+
}
13+
14+
/// Get versioned hashes.
15+
pub fn blobhash(index: u64) -> Bytes32 {
16+
unsafe { asm::evm::blobhash(index) }
17+
}
18+
19+
/// Get the current block’s base fee.
20+
pub fn basefee() -> u64 {
21+
unsafe { asm::evm::basefee() }
22+
}
23+
24+
/// Get the current block’s blob base fee.
25+
pub fn blobbasefee() -> u64 {
26+
unsafe { asm::evm::blobbasefee() }
27+
}
28+
29+
/// Get the current chain id.
30+
pub fn chainid() -> u64 {
31+
unsafe { asm::evm::chainid() }
32+
}
33+
34+
/// Get the block’s beneficiary address.
35+
pub fn coinbase() -> Address {
36+
unsafe { asm::evm::coinbase() }
37+
}
38+
39+
/// Get the previous block’s RANDAO mix.
40+
pub fn prevrandao() -> Bytes32 {
41+
unsafe { asm::evm::prevrandao() }
42+
}
43+
44+
/// Get the current block gaslimit.
45+
pub fn gaslimit() -> Bytes32 {
46+
unsafe { asm::evm::gaslimit() }
47+
}
48+
49+
/// Get the amount of available gas.
50+
pub fn gasleft() -> Bytes32 {
51+
unsafe { asm::evm::gas() }
52+
}
53+
54+
/// Get the block’s timestamp.
55+
pub fn timestamp() -> u64 {
56+
unsafe { asm::evm::timestamp() }
57+
}
58+
59+
/// Get the gas price of the transaction.
60+
pub fn gasprice() -> u64 {
61+
unsafe { asm::evm::gasprice() }
62+
}

0 commit comments

Comments
 (0)