Skip to content

Commit a25004b

Browse files
committed
test: assets precompile integration test
1 parent 475ceca commit a25004b

File tree

16 files changed

+1341
-8
lines changed

16 files changed

+1341
-8
lines changed

Cargo.lock

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"crates/ink/ir",
1212
"crates/ink/macro",
1313
"crates/metadata",
14+
"crates/precompiles",
1415
"crates/revive-types",
1516
"crates/prelude",
1617
"crates/primitives",
@@ -115,6 +116,7 @@ ink_env = { version = "=6.0.0-alpha.4", path = "crates/env", default-features =
115116
ink_ir = { version = "=6.0.0-alpha.4", path = "crates/ink/ir", default-features = false }
116117
ink_macro = { version = "=6.0.0-alpha.4", path = "crates/ink/macro", default-features = false }
117118
ink_metadata = { version = "=6.0.0-alpha.4", path = "crates/metadata", default-features = false }
119+
ink_precompiles = { version = "=6.0.0-alpha.4", path = "crates/precompiles", default-features = false }
118120
ink_prelude = { version = "=6.0.0-alpha.4", path = "crates/prelude", default-features = false }
119121
ink_primitives = { version = "=6.0.0-alpha.4", path = "crates/primitives", default-features = false }
120122
ink_revive_types = { version = "=6.0.0-alpha.4", path = "crates/revive-types", default-features = false }

crates/e2e/src/contract_results.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,31 @@ impl<E: Environment, V, EventLog, Abi> CallResult<E, V, EventLog, Abi> {
259259
pub fn return_data(&self) -> &[u8] {
260260
&self.dry_run.exec_return_value().data
261261
}
262+
263+
/// Returns the root cause error from nested contract calls (e.g., precompile errors)
264+
/// if available in the trace, otherwise returns the raw error data.
265+
pub fn extract_error(&self) -> Option<String> {
266+
if !self.dry_run.did_revert() {
267+
return None;
268+
}
269+
270+
// Check trace for error information
271+
if let Some(trace) = &self.trace {
272+
// // Check nested calls first (more specific errors)
273+
for call in &trace.calls {
274+
if let Some(error) = &call.error {
275+
return Some(error.clone());
276+
}
277+
}
278+
279+
// Then check top-level error
280+
if let Some(error) = &trace.error {
281+
return Some(error.clone());
282+
}
283+
}
284+
// Fallback to raw data
285+
Some(format!("{:?}", self.return_data()))
286+
}
262287
}
263288

264289
// TODO(#xxx) Improve the `Debug` implementation.

crates/precompiles/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "ink_precompiles"
3+
version.workspace = true
4+
authors = ["Use Ink <[email protected]>"]
5+
edition.workspace = true
6+
7+
license.workspace = true
8+
readme = "README.md"
9+
repository.workspace = true
10+
documentation = "https://docs.rs/ink_precompiles/"
11+
homepage.workspace = true
12+
description = "[ink!] Precompile interfaces for pallet-revive smart contracts."
13+
keywords.workspace = true
14+
categories.workspace = true
15+
include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"]
16+
17+
[dependencies]
18+
ink = { workspace = true, default-features = false }
19+
20+
[features]
21+
default = ["std"]
22+
std = [
23+
"ink/std",
24+
]
25+

crates/precompiles/src/erc20.rs

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// Copyright (C) Use Ink (UK) Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! ERC-20 precompile interface for pallet-assets.
16+
//!
17+
//! This module provides the standard ERC-20 token interface for interacting with
18+
//! assets managed by `pallet-assets` through the precompile mechanism.
19+
//!
20+
//! # Overview
21+
//!
22+
//! The ERC-20 Assets precompile allows smart contracts to interact with fungible assets
23+
//! from `pallet-assets` using the ERC-20 interface. Each asset gets its own
24+
//! precompile address, calculated by encoding the asset ID in the address.
25+
//!
26+
//! # Precompile Address
27+
//!
28+
//! - **Index**: `0x0120`
29+
//! - **Address Format**: `[asset_id][...zeros...][0x0120]0000`
30+
//!
31+
//! Use [`crate::prefixed_address`] to calculate the correct address for a specific asset.
32+
//!
33+
//! # Example
34+
//!
35+
//! ```rust,ignore
36+
//! use ink_precompiles::{erc20::{Erc20Ref, PRECOMPILE_INDEX}, prefixed_address};
37+
//!
38+
//! #[ink::contract]
39+
//! mod my_contract {
40+
//! use super::*;
41+
//!
42+
//! #[ink(storage)]
43+
//! pub struct MyContract {
44+
//! asset_id: u32,
45+
//! }
46+
//!
47+
//! impl MyContract {
48+
//! #[ink(constructor)]
49+
//! pub fn new(asset_id: u32) -> Self {
50+
//! Self { asset_id }
51+
//! }
52+
//!
53+
//! pub fn get_balance(&self, account: ink::Address) -> ink::U256 {
54+
//! let precompile_addr = prefixed_address(PRECOMPILE_INDEX, self.asset_id);
55+
//! let erc20: Erc20Ref = precompile_addr.into();
56+
//! erc20.balanceOf(account)
57+
//! }
58+
//! }
59+
//! }
60+
//! ```
61+
//!
62+
//! # References
63+
//!
64+
//! - [Polkadot SDK Assets Precompile](https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/assets/src/precompiles.rs)
65+
//! - [ERC-20 Token Standard](https://eips.ethereum.org/EIPS/eip-20)
66+
67+
/// ERC-20 Assets precompile index.
68+
pub const PRECOMPILE_INDEX: u16 = 0x0120;
69+
70+
/// Type alias for asset IDs.
71+
pub type AssetId = u32;
72+
73+
/// Defines the ERC-20 interface of the Asset Hub precompile.
74+
#[ink::contract_ref(abi = "sol")]
75+
pub trait Erc20 {
76+
/// Returns the total supply of tokens.
77+
///
78+
/// # Solidity Signature
79+
///
80+
/// ```solidity
81+
/// function totalSupply() external view returns (uint256);
82+
/// ```
83+
#[ink(message)]
84+
#[allow(non_snake_case)]
85+
fn totalSupply(&self) -> ink::U256;
86+
87+
/// Returns the balance of an account.
88+
///
89+
/// # Arguments
90+
/// * `account` - The address to query the balance of
91+
///
92+
/// # Solidity Signature
93+
///
94+
/// ```solidity
95+
/// function balanceOf(address account) external view returns (uint256);
96+
/// ```
97+
#[ink(message)]
98+
#[allow(non_snake_case)]
99+
fn balanceOf(&self, account: ink::Address) -> ink::U256;
100+
101+
/// Transfers tokens to another account.
102+
///
103+
/// # Arguments
104+
/// * `to` - The recipient address
105+
/// * `value` - The amount of tokens to transfer
106+
///
107+
/// # Returns
108+
///
109+
/// Returns `true` if the transfer was successful.
110+
///
111+
/// # Solidity Signature
112+
///
113+
/// ```solidity
114+
/// function transfer(address to, uint256 value) external returns (bool);
115+
/// ```
116+
#[ink(message)]
117+
fn transfer(&mut self, to: ink::Address, value: ink::U256) -> bool;
118+
119+
/// Returns the allowance for a spender on behalf of an owner.
120+
///
121+
/// This shows how many tokens `spender` is allowed to spend on behalf of `owner`.
122+
///
123+
/// # Arguments
124+
/// * `owner` - The token owner's address
125+
/// * `spender` - The spender's address
126+
///
127+
/// # Solidity Signature
128+
///
129+
/// ```solidity
130+
/// function allowance(address owner, address spender) external view returns (uint256);
131+
/// ```
132+
#[ink(message)]
133+
fn allowance(&self, owner: ink::Address, spender: ink::Address) -> ink::U256;
134+
135+
/// Approves a spender to spend tokens on behalf of the caller.
136+
///
137+
/// # Arguments
138+
/// * `spender` - The address authorized to spend tokens
139+
/// * `value` - The maximum amount the spender can spend
140+
///
141+
/// # Returns
142+
///
143+
/// Returns `true` if the approval was successful.
144+
///
145+
/// # Solidity Signature
146+
///
147+
/// ```solidity
148+
/// function approve(address spender, uint256 value) external returns (bool);
149+
/// ```
150+
#[ink(message)]
151+
fn approve(&mut self, spender: ink::Address, value: ink::U256) -> bool;
152+
153+
/// Transfers tokens from one account to another using allowance.
154+
///
155+
/// The caller must have sufficient allowance from the `from` account.
156+
///
157+
/// # Arguments
158+
/// * `from` - The address to transfer tokens from
159+
/// * `to` - The recipient address
160+
/// * `value` - The amount of tokens to transfer
161+
///
162+
/// # Returns
163+
///
164+
/// Returns `true` if the transfer was successful.
165+
///
166+
/// # Solidity Signature
167+
///
168+
/// ```solidity
169+
/// function transferFrom(address from, address to, uint256 value) external returns (bool);
170+
/// ```
171+
#[ink(message)]
172+
#[allow(non_snake_case)]
173+
fn transferFrom(
174+
&mut self,
175+
from: ink::Address,
176+
to: ink::Address,
177+
value: ink::U256,
178+
) -> bool;
179+
}
180+
181+
/// Creates a new ERC-20 precompile reference for the given asset ID.
182+
///
183+
/// # Arguments
184+
/// * `asset_id` - The ID of the asset to interact with
185+
///
186+
/// # Returns
187+
///
188+
/// Returns an `Erc20Ref` that can be used to call precompile methods.
189+
///
190+
/// # Example
191+
///
192+
/// ```rust,ignore
193+
/// use ink_precompiles::erc20::erc20;
194+
///
195+
/// let asset_id = 1;
196+
/// let erc20_ref = erc20(asset_id);
197+
/// let balance = erc20_ref.balanceOf(account);
198+
/// ```
199+
pub fn erc20(asset_id: AssetId) -> Erc20Ref {
200+
let address = crate::prefixed_address(PRECOMPILE_INDEX, asset_id);
201+
address.into()
202+
}
203+
204+
#[cfg(test)]
205+
mod tests {
206+
use super::*;
207+
208+
#[test]
209+
fn erc20_precompile_address_format() {
210+
// ERC20 Assets precompile for asset ID 1 should be at the correct address
211+
let expected = [
212+
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
213+
0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00,
214+
];
215+
216+
let address = crate::prefixed_address(PRECOMPILE_INDEX, 1);
217+
let address_bytes: [u8; 20] = address.into();
218+
219+
assert_eq!(address_bytes, expected);
220+
}
221+
222+
#[test]
223+
fn erc20_precompile_address_for_multiple_assets() {
224+
// Test asset ID 42
225+
let address_42 = crate::prefixed_address(PRECOMPILE_INDEX, 42);
226+
let bytes_42: [u8; 20] = address_42.into();
227+
228+
// First 4 bytes should be asset ID (42 = 0x0000002a)
229+
assert_eq!(&bytes_42[0..4], &[0x00, 0x00, 0x00, 0x2a]);
230+
231+
// Bytes 16-19 should be precompile index (0x0120)
232+
assert_eq!(&bytes_42[16..20], &[0x01, 0x20, 0x00, 0x00]);
233+
}
234+
}

0 commit comments

Comments
 (0)