Skip to content
168 changes: 168 additions & 0 deletions src/abi/antelope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: Apache-2.0

use crate::sema::ast::{Namespace, Type};
use serde::Serialize;
use solang_parser::pt::FunctionTy;

#[derive(Serialize)]
pub struct AntelopeAbi {
pub version: String,
pub types: Vec<serde_json::Value>,
pub structs: Vec<AbiStruct>,
pub actions: Vec<AbiAction>,
pub tables: Vec<AbiTable>,
}

#[derive(Serialize)]
pub struct AbiStruct {
pub name: String,
pub base: String,
pub fields: Vec<AbiField>,
}

#[derive(Serialize)]
pub struct AbiField {
pub name: String,
#[serde(rename = "type")]
pub ty: String,
}

#[derive(Serialize)]
pub struct AbiAction {
pub name: String,
#[serde(rename = "type")]
pub ty: String,
pub ricardian_contract: String,
}

#[derive(Serialize)]
pub struct AbiTable {
pub name: String,
pub index_type: String,
pub key_names: Vec<String>,
pub key_types: Vec<String>,
#[serde(rename = "type")]
pub ty: String,
}

fn solidity_type_to_antelope(ty: &Type, ns: &Namespace) -> String {
match ty {
Type::Uint(8) => "uint8".to_string(),
Type::Uint(16) => "uint16".to_string(),
Type::Uint(32) => "uint32".to_string(),
Type::Uint(64) => "uint64".to_string(),
Type::Uint(128) => "uint128".to_string(),
Type::Uint(256) => "checksum256".to_string(),
Type::Int(8) => "int8".to_string(),
Type::Int(16) => "int16".to_string(),
Type::Int(32) => "int32".to_string(),
Type::Int(64) => "int64".to_string(),
Type::Int(128) => "int128".to_string(),
Type::Bool => "bool".to_string(),
Type::String => "string".to_string(),
Type::Address(_) => "uint64".to_string(),
_ => "bytes".to_string(),
}
}

/// Generate Antelope ABI for a contract.
pub fn gen_abi(contract_no: usize, ns: &Namespace) -> AntelopeAbi {
let contract = &ns.contracts[contract_no];
let mut structs = Vec::new();
let mut actions = Vec::new();

// For each public function, create a struct (for action params) and an action entry.
for func_no in contract.all_functions.keys() {
let func = &ns.functions[*func_no];

if !func.is_public() {
continue;
}
if func.ty == FunctionTy::Constructor {
continue;
}

let func_name = &func.id.name;

// Antelope action names are max 12 chars, lowercase + 1-5 + dot.
let action_name = func_name
.chars()
.filter(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || *c == '.')
.take(12)
.collect::<String>();

if action_name.is_empty() {
continue;
}

// Build the struct fields from function parameters.
let fields: Vec<AbiField> = func
.params
.iter()
.enumerate()
.map(|(i, p)| AbiField {
name: p
.id
.as_ref()
.map(|id| id.name.clone())
.unwrap_or_else(|| format!("arg{i}")),
ty: solidity_type_to_antelope(&p.ty, ns),
})
.collect();

structs.push(AbiStruct {
name: action_name.clone(),
base: String::new(),
fields,
});

actions.push(AbiAction {
name: action_name.clone(),
ty: action_name,
ricardian_contract: String::new(),
});
}

// Add the "state" table entry so explorers can decode storage.
// Storage model: auto-increment primary key, idx256 secondary index for slot lookup.
// Row data = raw value bytes (variable length: uint64, uint256, strings, etc.).
let has_state_vars = !contract.variables.iter().all(|v| v.constant);
let mut tables = Vec::new();

if has_state_vars {
structs.push(AbiStruct {
name: "state.row".to_string(),
base: String::new(),
fields: vec![
AbiField {
name: "id".to_string(),
ty: "uint64".to_string(),
},
AbiField {
name: "key".to_string(),
ty: "checksum256".to_string(),
},
AbiField {
name: "value".to_string(),
ty: "bytes".to_string(),
},
],
});

tables.push(AbiTable {
name: "state".to_string(),
index_type: "i64".to_string(),
key_names: vec![],
key_types: vec![],
ty: "state.row".to_string(),
});
}

AntelopeAbi {
version: "eosio::abi/1.2".to_string(),
types: Vec::new(),
structs,
actions,
tables,
}
}
13 changes: 13 additions & 0 deletions src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::sema::ast::Namespace;
use crate::Target;

pub mod anchor;
pub mod antelope;
pub mod ethereum;
pub mod polkadot;
mod tests;
Expand Down Expand Up @@ -41,6 +42,18 @@ pub fn generate_abi(

(serde_json::to_string_pretty(&idl).unwrap(), "json")
}
Target::Antelope => {
if verbose {
eprintln!(
"info: Generating Antelope ABI for contract {}",
ns.contracts[contract_no].id
);
}

let abi = antelope::gen_abi(contract_no, ns);

(serde_json::to_string_pretty(&abi).unwrap(), "abi")
}
_ => {
if verbose {
eprintln!(
Expand Down
9 changes: 5 additions & 4 deletions src/bin/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub enum Commands {

#[derive(Args)]
pub struct New {
#[arg(name = "TARGETNAME",required= true, long = "target", value_parser = ["solana", "polkadot", "evm"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
#[arg(name = "TARGETNAME",required= true, long = "target", value_parser = ["solana", "polkadot", "evm", "antelope"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
pub target_name: String,

#[arg(name = "INPUT", help = "Name of the project", num_args = 1, value_parser = ValueParser::os_string())]
Expand Down Expand Up @@ -261,7 +261,7 @@ pub struct CompilerOutput {

#[derive(Args)]
pub struct TargetArg {
#[arg(name = "TARGET",required= true, long = "target", value_parser = ["solana", "polkadot", "evm"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
#[arg(name = "TARGET",required= true, long = "target", value_parser = ["solana", "polkadot", "evm", "antelope"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
pub name: String,

#[arg(name = "ADDRESS_LENGTH", help = "Address length on the Polkadot Parachain", long = "address-length", num_args = 1, value_parser = value_parser!(u64).range(4..1024))]
Expand All @@ -273,7 +273,7 @@ pub struct TargetArg {

#[derive(Args, Deserialize, Debug, PartialEq)]
pub struct CompileTargetArg {
#[arg(name = "TARGET", long = "target", value_parser = ["solana", "polkadot", "evm", "soroban"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
#[arg(name = "TARGET", long = "target", value_parser = ["solana", "polkadot", "evm", "soroban", "antelope"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
pub name: Option<String>,

#[arg(name = "ADDRESS_LENGTH", help = "Address length on the Polkadot Parachain", long = "address-length", num_args = 1, value_parser = value_parser!(u64).range(4..1024))]
Expand Down Expand Up @@ -449,7 +449,7 @@ impl TargetArgTrait for CompileTargetArg {
pub(crate) fn target_arg<T: TargetArgTrait>(target_arg: &T) -> Target {
let target_name = target_arg.get_name();

if target_name == "solana" || target_name == "evm" {
if target_name == "solana" || target_name == "evm" || target_name == "antelope" {
if target_arg.get_address_length().is_some() {
eprintln!("error: address length cannot be modified except for polkadot target");
exit(1);
Expand All @@ -469,6 +469,7 @@ pub(crate) fn target_arg<T: TargetArgTrait>(target_arg: &T) -> Target {
},
"evm" => solang::Target::EVM,
"soroban" => solang::Target::Soroban,
"antelope" => solang::Target::Antelope,
_ => unreachable!(),
};

Expand Down
3 changes: 3 additions & 0 deletions src/codegen/dispatch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ pub(super) fn function_dispatch(
polkadot::function_dispatch(contract_no, all_cfg, ns, opt)
}
Target::Soroban => soroban::function_dispatch(contract_no, all_cfg, ns, opt),
Target::Antelope => {
polkadot::function_dispatch(contract_no, all_cfg, ns, opt)
}
}
}
97 changes: 97 additions & 0 deletions src/codegen/events/antelope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: Apache-2.0

use crate::codegen::cfg::{ControlFlowGraph, Instr};
use crate::codegen::encoding::abi_encode;
use crate::codegen::events::EventEmitter;
use crate::codegen::expression::expression;
use crate::codegen::vartable::Vartable;
use crate::codegen::{Expression, Options};
use crate::emit::antelope::string_to_name;
use crate::sema::ast::{self, Function, Namespace, Type};
use solang_parser::pt;

/// Antelope event emitter.
///
/// Events are emitted as inline actions (send_inline) to the contract itself.
/// topics[0] = eosio::name(event_name) as uint64 — used as the action name.
/// data = ABI-encoded event fields — used as the action data.
pub(super) struct AntelopeEventEmitter<'a> {
pub(super) args: &'a [ast::Expression],
pub(super) ns: &'a Namespace,
pub(super) event_no: usize,
}

impl EventEmitter for AntelopeEventEmitter<'_> {
fn selector(&self, _emitting_contract_no: usize) -> Vec<u8> {
let event = &self.ns.events[self.event_no];
event.id.name.as_bytes().to_vec()
}

fn emit(
&self,
contract_no: usize,
func: &Function,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
opt: &Options,
) {
let loc = pt::Loc::Builtin;
let event = &self.ns.events[self.event_no];

// Encode the event name as eosio::name uint64 at compile time.
// This will be used as the action name in the send_inline call.
let event_name = &event.id.name;
// Antelope names: max 12 chars, lowercase + 1-5 + dot.
// Prefix with "e." to avoid collisions with real contract action names.
// E.g. event "Mint" → "e.mint", "Transfer" → "e.transfer".
let action_name: String = std::iter::once('e')
.chain(std::iter::once('.'))
.chain(
event_name
.to_lowercase()
.chars()
.filter(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
)
.take(12)
.collect();
let name_encoded = string_to_name(&action_name);

let topics = vec![Expression::NumberLiteral {
loc,
ty: Type::Uint(64),
value: name_encoded.into(),
}];

// Evaluate and ABI-encode all event fields.
let data: Vec<Expression> = self
.args
.iter()
.map(|e| expression(e, cfg, contract_no, Some(func), self.ns, vartab, opt))
.collect();

let encoded_data = if data.is_empty() {
Expression::AllocDynamicBytes {
loc,
ty: Type::DynamicBytes,
size: Expression::NumberLiteral {
loc,
ty: Type::Uint(32),
value: 0.into(),
}
.into(),
initializer: Some(Vec::new()),
}
} else {
abi_encode(&loc, data, self.ns, vartab, cfg, false).0
};

cfg.add(
vartab,
Instr::EmitEvent {
event_no: self.event_no,
data: encoded_data,
topics,
},
);
}
}
4 changes: 4 additions & 0 deletions src/codegen/events/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: Apache-2.0

mod antelope;
mod polkadot;
mod solana;

use crate::codegen::cfg::ControlFlowGraph;
use crate::codegen::events::antelope::AntelopeEventEmitter;
use crate::codegen::events::polkadot::PolkadotEventEmitter;
use crate::codegen::events::solana::SolanaEventEmitter;
use crate::codegen::vartable::Vartable;
Expand Down Expand Up @@ -52,5 +54,7 @@ pub(super) fn new_event_emitter<'a>(
}),

Target::Soroban => todo!(),

Target::Antelope => Box::new(AntelopeEventEmitter { args, ns, event_no }),
}
}
Loading