Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `#[ink::contract_ref]` attribute - [#2648](https://github.com/use-ink/ink/pull/2648)
- Add `ink_revive_types` (and remove `pallet-revive` dependency from `ink_e2e`) - [#2657](https://github.com/use-ink/ink/pull/2657)
- non-allocating Solidity ABI encoder - [#2655](https://github.com/use-ink/ink/pull/2655)
- Add `ink_precompiles` crate with ERC-20 assets precompile interface - [#2686](https://github.com/use-ink/ink/pull/2686)

### Changed
- Marks the `pallet-revive` host function `account_id` stable - [#2578](https://github.com/use-ink/ink/pull/2578)
Expand Down
46 changes: 46 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"crates/ink/ir",
"crates/ink/macro",
"crates/metadata",
"crates/precompiles",
"crates/revive-types",
"crates/prelude",
"crates/primitives",
Expand Down Expand Up @@ -115,6 +116,7 @@ ink_env = { version = "=6.0.0-alpha.4", path = "crates/env", default-features =
ink_ir = { version = "=6.0.0-alpha.4", path = "crates/ink/ir", default-features = false }
ink_macro = { version = "=6.0.0-alpha.4", path = "crates/ink/macro", default-features = false }
ink_metadata = { version = "=6.0.0-alpha.4", path = "crates/metadata", default-features = false }
ink_precompiles = { version = "=6.0.0-alpha.4", path = "crates/precompiles", default-features = false }
ink_prelude = { version = "=6.0.0-alpha.4", path = "crates/prelude", default-features = false }
ink_primitives = { version = "=6.0.0-alpha.4", path = "crates/primitives", default-features = false }
ink_revive_types = { version = "=6.0.0-alpha.4", path = "crates/revive-types", default-features = false }
Expand Down
25 changes: 25 additions & 0 deletions crates/e2e/src/contract_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,31 @@ impl<E: Environment, V, EventLog, Abi> CallResult<E, V, EventLog, Abi> {
pub fn return_data(&self) -> &[u8] {
&self.dry_run.exec_return_value().data
}

/// Returns the error from nested contract calls (e.g., precompile errors)
/// if available in the trace, otherwise returns the raw error data.
pub fn extract_error(&self) -> Option<String> {
if !self.dry_run.did_revert() {
return None;
}

// Check trace for error information
if let Some(trace) = &self.trace {
// // Check nested calls first (more specific errors)
for call in &trace.calls {
if let Some(error) = &call.error {
return Some(error.clone());
}
}

// Then check top-level error
if let Some(error) = &trace.error {
return Some(error.clone());
}
}
// Fallback to raw data
Some(format!("{:?}", self.return_data()))
}
}

// TODO(#xxx) Improve the `Debug` implementation.
Expand Down
2 changes: 1 addition & 1 deletion crates/ink/macro/src/contract_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub fn analyze_or_err(
#trait_def_impl

// Type alias for contract ref.
type #contract_ref_name =
pub type #contract_ref_name =
<<::ink::reflect::TraitDefinitionRegistry<#env> as #trait_name>
::__ink_TraitInfo as ::ink::codegen::TraitCallForwarder>::Forwarder<#abi_ty>;
))
Expand Down
21 changes: 21 additions & 0 deletions crates/precompiles/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "ink_precompiles"
version.workspace = true
authors = ["Use Ink <[email protected]>"]
edition.workspace = true
license.workspace = true
description = "[ink!] Precompile interfaces for pallet-revive smart contracts."
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
categories.workspace = true

[dependencies]
ink = { workspace = true, default-features = false }

[features]
default = ["std"]
std = [
"ink/std",
]

192 changes: 192 additions & 0 deletions crates/precompiles/src/erc20.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright (C) Use Ink (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! ERC-20 precompile interface for pallet-assets.
//!
//! This module provides the standard ERC-20 token interface for interacting with
//! assets managed by `pallet-assets` through the precompile mechanism.
//!
//! # References
//!
//! - [Polkadot SDK Assets Precompile](https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/assets/src/precompiles.rs)
//! - [ERC-20 Token Standard](https://eips.ethereum.org/EIPS/eip-20)

use ink::{
Address,
U256,
};

/// ERC-20 Assets precompile index.
pub const PRECOMPILE_INDEX: u16 = 0x0120;

/// Type alias for asset IDs.
pub type AssetId = u32;

/// Defines the ERC-20 interface of the Asset Hub precompile.
#[ink::contract_ref(abi = "sol")]
pub trait Erc20 {
/// Returns the total supply of tokens.
///
/// # Solidity Signature
///
/// ```solidity
/// function totalSupply() external view returns (uint256);
/// ```
#[ink(message)]
#[allow(non_snake_case)]
fn totalSupply(&self) -> U256;

/// Returns the balance of an account.
///
/// # Arguments
/// * `account` - The address to query the balance of
///
/// # Solidity Signature
///
/// ```solidity
/// function balanceOf(address account) external view returns (uint256);
/// ```
#[ink(message)]
#[allow(non_snake_case)]
fn balanceOf(&self, account: Address) -> U256;

/// Transfers tokens to another account.
///
/// # Arguments
/// * `to` - The recipient address
/// * `value` - The amount of tokens to transfer
///
/// # Returns
///
/// Returns `true` if the transfer was successful.
///
/// # Solidity Signature
///
/// ```solidity
/// function transfer(address to, uint256 value) external returns (bool);
/// ```
#[ink(message)]
fn transfer(&mut self, to: Address, value: U256) -> bool;

/// Returns the allowance for a spender on behalf of an owner.
///
/// This shows how many tokens `spender` is allowed to spend on behalf of `owner`.
///
/// # Arguments
/// * `owner` - The token owner's address
/// * `spender` - The spender's address
///
/// # Solidity Signature
///
/// ```solidity
/// function allowance(address owner, address spender) external view returns (uint256);
/// ```
#[ink(message)]
fn allowance(&self, owner: Address, spender: Address) -> U256;

/// Approves a spender to spend tokens on behalf of the caller.
///
/// # Arguments
/// * `spender` - The address authorized to spend tokens
/// * `value` - The maximum amount the spender can spend
///
/// # Returns
///
/// Returns `true` if the approval was successful.
///
/// # Solidity Signature
///
/// ```solidity
/// function approve(address spender, uint256 value) external returns (bool);
/// ```
#[ink(message)]
fn approve(&mut self, spender: Address, value: U256) -> bool;

/// Transfers tokens from one account to another using allowance.
///
/// The caller must have sufficient allowance from the `from` account.
///
/// # Arguments
/// * `from` - The address to transfer tokens from
/// * `to` - The recipient address
/// * `value` - The amount of tokens to transfer
///
/// # Returns
///
/// Returns `true` if the transfer was successful.
///
/// # Solidity Signature
///
/// ```solidity
/// function transferFrom(address from, address to, uint256 value) external returns (bool);
/// ```
#[ink(message)]
#[allow(non_snake_case)]
fn transferFrom(&mut self, from: Address, to: Address, value: U256) -> bool;
}

/// Creates a new ERC-20 precompile reference for the given asset ID.
///
/// # Arguments
/// * `asset_id` - The ID of the asset to interact with
///
/// # Returns
///
/// Returns an `Erc20Ref` that can be used to call precompile methods.
///
/// # Example
///
/// ```rust,ignore
/// use ink_precompiles::erc20::erc20;
///
/// let asset_id = 1;
/// let erc20_ref = erc20(asset_id);
/// let balance = erc20_ref.balanceOf(account);
/// ```
pub fn erc20(asset_id: AssetId) -> Erc20Ref {
let address = crate::prefixed_address(PRECOMPILE_INDEX, asset_id);
address.into()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn erc20_precompile_address_format() {
// ERC20 Assets precompile for asset ID 1 should be at the correct address
let expected = [
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00,
];

let address = crate::prefixed_address(PRECOMPILE_INDEX, 1);
let address_bytes: [u8; 20] = address.into();

assert_eq!(address_bytes, expected);
}

#[test]
fn erc20_precompile_address_for_multiple_assets() {
// Test asset ID 42
let address_42 = crate::prefixed_address(PRECOMPILE_INDEX, 42);
let bytes_42: [u8; 20] = address_42.into();

// First 4 bytes should be asset ID (42 = 0x0000002a)
assert_eq!(&bytes_42[0..4], &[0x00, 0x00, 0x00, 0x2a]);

// Bytes 16-19 should be precompile index (0x0120)
assert_eq!(&bytes_42[16..20], &[0x01, 0x20, 0x00, 0x00]);
}
}
Loading
Loading