Skip to content

Commit

Permalink
Erc20 contract (#35)
Browse files Browse the repository at this point in the history
* Add missing ULM hooks.

* Add an Address type

* Add a Balance type

* Contract storage

* Events

* Put the contract in a struct

* Add contract implementation

* Erc20 dispatcher

* Erc20 tests
  • Loading branch information
virgil-serbanuta authored Dec 11, 2024
1 parent 3ac68c6 commit 39fafa9
Show file tree
Hide file tree
Showing 10 changed files with 915 additions and 48 deletions.
99 changes: 99 additions & 0 deletions tests/ulm/erc20/rust/src/address.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use bytes::Bytes;
use core::cmp::Ordering;

use crate::decoder::Decodable;
use crate::encoder::{Encodable, EncodingType};
use crate::unsigned::{U160, U256};

#[derive(Debug)]
pub struct Address {
value: U160,
}

impl Address {
fn new(value: U160) -> Self {
Address { value }
}

pub fn zero() -> Self {
Address::new(U160::from_u64(0))
}

pub fn is_zero(&self) -> bool {
self.value == U160::from_u64(0)
}

pub fn into_u160(self) -> U160 {
self.value
}
pub fn into_u256(self) -> U256 {
self.value.into()
}
}

impl From<U160> for Address
{
fn from(value: U160) -> Self {
Address::new(value)
}
}
impl TryFrom<U256> for Address
{
type Error = &'static str;
fn try_from(value: U256) -> Result<Self, Self::Error> {
Ok(Address::new(value.try_into()?))
}
}
impl From<Address> for U160
{
fn from(value: Address) -> Self {
value.into_u160()
}
}
impl From<Address> for U256
{
fn from(value: Address) -> Self {
value.into_u256()
}
}

impl Ord for Address {
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}
impl PartialOrd for Address {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Address {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Address {}
impl Clone for Address {
fn clone(&self) -> Self {
Address { value: self.value.clone() }
}
}

impl Encodable for Address
{
fn encode(&self) -> (EncodingType, Bytes) {
self.value.encode()
}
}
impl Decodable for Address
{
fn encoding_type() -> EncodingType {
U160::encoding_type()
}
fn head_size() -> usize {
U160::head_size()
}
fn decode(bytes: Bytes) -> Self {
Address::new(U160::decode(bytes))
}
}
132 changes: 132 additions & 0 deletions tests/ulm/erc20/rust/src/balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use bytes::Bytes;
use core::cmp::Ordering;
use core::ops::{Add, Sub};

use crate::decoder::Decodable;
use crate::encoder::{Encodable, EncodingType};
use crate::unsigned::U256;

#[derive(Debug)]
pub struct Balance {
value: U256
}

impl Balance {
pub fn new(value: U256) -> Self {
Balance { value }
}

pub fn into_u256(self) -> U256 {
self.value
}

pub fn into_u256_ref(&self) -> &U256 {
&self.value
}
}

impl TryFrom<U256> for Balance
{
type Error = &'static str;
fn try_from(value: U256) -> Result<Self, Self::Error> {
Ok(Balance { value })
}
}
impl From<Balance> for U256
{
fn from(value: Balance) -> Self {
value.into_u256()
}
}

impl Add for &Balance {
type Output = Balance;
fn add(self, other: &Balance) -> Self::Output {
Balance { value: (&self.value) + &other.value }
}
}
impl Add for Balance {
type Output = Balance;
fn add(self, other: Balance) -> Self::Output {
&self + &other
}
}
impl Add<Balance> for &Balance {
type Output = Balance;
fn add(self, other: Balance) -> Self::Output {
self + &other
}
}
impl Add<&Balance> for Balance {
type Output = Balance;
fn add(self, other: &Balance) -> Self::Output {
&self + other
}
}

impl Sub for &Balance {
type Output = Balance;
fn sub(self, other: &Balance) -> Self::Output {
Balance { value: (&self.value) - &other.value }
}
}
impl Sub for Balance {
type Output = Balance;
fn sub(self, other: Balance) -> Self::Output {
&self - &other
}
}
impl Sub<Balance> for &Balance {
type Output = Balance;
fn sub(self, other: Balance) -> Self::Output {
self - &other
}
}
impl Sub<&Balance> for Balance {
type Output = Balance;
fn sub(self, other: &Balance) -> Self::Output {
&self - other
}
}

impl Ord for Balance {
fn cmp(&self, other: &Self) -> Ordering {
self.value.cmp(&other.value)
}
}
impl PartialOrd for Balance {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Balance {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for Balance {}
impl Clone for Balance {
fn clone(&self) -> Self {
Balance { value: self.value.clone() }
}
}
impl Encodable for Balance
{
fn encode(&self) -> (EncodingType, Bytes) {
self.into_u256_ref().encode()
}
}
impl Decodable for Balance
{
fn encoding_type() -> EncodingType {
U256::encoding_type()
}
fn head_size() -> usize {
U256::head_size()
}
fn decode(bytes: Bytes) -> Self {
let value = U256::decode(bytes);
Balance { value }
}
}

148 changes: 148 additions & 0 deletions tests/ulm/erc20/rust/src/erc20.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use std::cell::RefCell;
use std::rc::Rc;

use crate::address::Address;
use crate::assertions::fail;
use crate::balance::Balance;
use crate::encoder::Encoder;
use crate::require;
use crate::storage::{SingleChunkStorage, SingleChunkStorageBuilder};
use crate::ulm::{log3, Ulm};
use crate::ulm;

pub struct Erc20 {
api: Rc<RefCell<dyn Ulm>>
}

impl Erc20 {
pub fn new(api: Rc<RefCell<dyn Ulm>>) -> Self {
Erc20 { api }
}

// ---------------------------

fn s_total_supply<'a>(&self) -> SingleChunkStorage<'a, Balance> {
SingleChunkStorageBuilder::new(self.api.clone(), &("total_supply".to_string())).build()
}

fn s_balances<'a>(&self, address: &Address) -> SingleChunkStorage<'a, Balance> {
let mut builder = SingleChunkStorageBuilder::new(self.api.clone(), &("balances".to_string()));
builder.add_arg(address);
builder.build()
}

fn s_allowances<'a>(&self, account: &Address, spender: &Address) -> SingleChunkStorage<'a, Balance> {
let mut builder = SingleChunkStorageBuilder::new(self.api.clone(), &("allowances".to_string()));
builder.add_arg(account);
builder.add_arg(spender);
builder.build()
}

// ---------------------------

fn transfer_event(&self, from: Address, to: Address, value: &Balance) {
let mut encoder = Encoder::new();
encoder.add(value);
log3(
&*self.api.borrow(),
"Transfer(address,address,u256)",
&from.into(), &to.into(),
encoder.encode()
)
}

fn approval_event(&self, owner: Address, spender: Address, value: &Balance) {
let mut encoder = Encoder::new();
encoder.add(value);
log3(
&*self.api.borrow(),
"Approval(address,address,u256)",
&owner.into(), &spender.into(),
encoder.encode()
)
}

// ---------------------------

pub fn init(&self) {}

pub fn decimals(&self) -> u8 {
18
}

pub fn total_supply(&self) -> Balance {
self.s_total_supply().get()
}

pub fn balance_of(&self, account: &Address) -> Balance {
self.s_balances(account).get()
}

pub fn transfer(&self, to: &Address, value: &Balance) -> bool {
let owner = ulm::caller(&*self.api.borrow());
self._transfer(&owner, to, &value);
true
}

pub fn allowance(&self, owner: &Address, spender: &Address) -> Balance {
self.s_allowances(owner, spender).get()
}

pub fn approve(&self, spender: &Address, value: &Balance) -> bool {
let owner = ulm::caller(&*self.api.borrow());
self._approve(&owner, spender, value, true);
true
}

pub fn transfer_from(&self, from: &Address, to: &Address, value: &Balance) -> bool {
let spender = ulm::caller(&*self.api.borrow());
self._spend_allowance(from, &spender, value);
self._transfer(from, to, value);
true
}

fn _transfer(&self, from: &Address, to: &Address, value: &Balance) {
require!(!from.is_zero(), "Invalid sender");
require!(!to.is_zero(), "Invalid receiver");
self._update(from, to, value);
self.transfer_event(from.clone(), to.clone(), value);
}

fn _update(&self, from: &Address, to: &Address, value: &Balance) {
if from.is_zero() {
self.s_total_supply().set(self.s_total_supply().get() + value);
} else {
let from_balance = self.s_balances(from).get();
require!(value <= &from_balance, "Insufficient balance");
self.s_balances(from).set(self.s_balances(from).get() - value);
};

if to.is_zero() {
self.s_total_supply().set(self.s_total_supply().get() - value);
} else {
self.s_balances(to).set(self.s_balances(to).get() + value);
}
}

pub fn mint(&self, account: &Address, value: &Balance) {
require!(!account.is_zero(), "Zero address");
self._update(&Address::zero(), account, value);
}

fn _approve(&self, owner: &Address, spender: &Address, value: &Balance, emit_event: bool) {
require!(!owner.is_zero(), "Invalid approver");
require!(!spender.is_zero(), "Invalid spender");
self.s_allowances(owner, spender).set(value.clone());
if emit_event {
self.approval_event(owner.clone(), spender.clone(), &value);
}
}

fn _spend_allowance(&self, owner: &Address, spender: &Address, value: &Balance) {
let current_allowance = self.allowance(owner, spender);
require!(value <= &current_allowance, "Insuficient allowance");
self._approve(owner, spender, &(current_allowance - value), false);
}
}


Loading

0 comments on commit 39fafa9

Please sign in to comment.