Skip to content

Commit

Permalink
add test for AEX-9 FungibleToken contraect
Browse files Browse the repository at this point in the history
  • Loading branch information
marc0olo committed Dec 9, 2019
1 parent 626efad commit d0215b4
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 4 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,16 @@
<plugin>
<groupId>com.kryptokrauts</groupId>
<artifactId>contraect-maven-plugin</artifactId>
<version>0.9.0</version>
<version>0.9.1</version>
<executions>
<execution>
<phase>generate-sources</phase>
</execution>
</executions>
<configuration>
<targetPath>target/generated-sources/contraect</targetPath>
<targetPackage>com.kryptokrauts.generated.contraect</targetPackage>
<datatypePackage>com.kryptokrauts.generated.contraect.datatypes</datatypePackage>
<targetPackage>com.kryptokrauts.contraect.generated</targetPackage>
<datatypePackage>com.kryptokrauts.contraect.generated.datatypes</datatypePackage>
<directories>
<directory>${project.basedir}/src/test/resources</directory>
</directories>
Expand Down
70 changes: 69 additions & 1 deletion src/test/java/com/kryptokrauts/ContraectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@
import com.kryptokrauts.aeternity.sdk.constants.VirtualMachine;
import com.kryptokrauts.aeternity.sdk.domain.secret.impl.BaseKeyPair;
import com.kryptokrauts.aeternity.sdk.service.aeternity.AeternityServiceConfiguration;
import com.kryptokrauts.aeternity.sdk.service.aeternity.AeternityServiceFactory;
import com.kryptokrauts.aeternity.sdk.service.aeternity.impl.AeternityService;
import com.kryptokrauts.aeternity.sdk.service.keypair.KeyPairService;
import com.kryptokrauts.aeternity.sdk.service.keypair.KeyPairServiceFactory;
import com.kryptokrauts.generated.contraect.CryptoHamster;
import com.kryptokrauts.aeternity.sdk.util.UnitConversionUtil;
import com.kryptokrauts.aeternity.sdk.util.UnitConversionUtil.Unit;
import com.kryptokrauts.contraect.generated.CryptoHamster;
import com.kryptokrauts.contraect.generated.FungibleToken;
import com.kryptokrauts.contraect.generated.datatypes.Address;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
Expand All @@ -23,10 +33,17 @@ public class ContraectTest {
private static final String HAMSTER_DNA =
"#9227ef8e61954f009822f1d1b9b4077f95ab81be96f21b37937d1f85effe261b";

private static final BigInteger KRAUT_TOKEN_TOTAL_SUPPLY =
new BigInteger("21000000000000000000000000");

private static BaseKeyPair baseKeyPair;

private static AeternityServiceConfiguration config;

private static AeternityService aeternityService;

private static KeyPairService keyPairService = new KeyPairServiceFactory().getService();

@BeforeAll
public static void init() {
KeyPairService keyPairService = new KeyPairServiceFactory().getService();
Expand All @@ -39,6 +56,7 @@ public static void init() {
.network(Network.DEVNET)
.targetVM(VirtualMachine.FATE)
.compile();
aeternityService = new AeternityServiceFactory().getService(config);
}

@Test
Expand All @@ -65,4 +83,54 @@ public void cryptoHamsterTest() {
log.info("hamster dna: {}", hamsterDna);
Assertions.assertEquals(HAMSTER_DNA, hamsterDna);
}

@Test
public void fungibleTokenTest() {
// currently needed for the values that are returned as Option
Pattern pattern = Pattern.compile(".*\\[ *(.*) *\\].*");

FungibleToken krautTokenInstance = new FungibleToken(config, null);

String contractId = krautTokenInstance.deploy("kryptokrauts", BigInteger.valueOf(18), "KRAUT");
log.info("contract id: {}", contractId);
Assertions.assertEquals(KRAUT_TOKEN_TOTAL_SUPPLY, krautTokenInstance.total_supply());

BaseKeyPair recipientKeyPair = keyPairService.generateBaseKeyPair();

BigInteger tokensToSend =
UnitConversionUtil.toAettos(new BigDecimal("1.337"), Unit.AE).toBigInteger();

Object krautTokenMetaInfo = krautTokenInstance.meta_info();
log.info(krautTokenMetaInfo.toString());

Object ownerBalance = krautTokenInstance.balance(new Address(baseKeyPair.getPublicKey()));
log.info(ownerBalance.toString());
Matcher m = pattern.matcher(ownerBalance.toString());
m.find();
log.info(m.group(1));
Assertions.assertEquals(KRAUT_TOKEN_TOTAL_SUPPLY.toString(), m.group(1));

krautTokenInstance.transfer(new Address(recipientKeyPair.getPublicKey()), tokensToSend);

Object recipientBalance =
krautTokenInstance.balance(new Address(recipientKeyPair.getPublicKey()));
log.info(recipientBalance.toString());
m = pattern.matcher(recipientBalance.toString());
m.find();
Assertions.assertEquals(tokensToSend.toString(), m.group(1));

log.info(krautTokenInstance.balances().toString());

ownerBalance = krautTokenInstance.balance(new Address(baseKeyPair.getPublicKey()));
log.info(ownerBalance.toString());
m = pattern.matcher(ownerBalance.toString());
m.find();
Assertions.assertEquals(KRAUT_TOKEN_TOTAL_SUPPLY.subtract(tokensToSend).toString(), m.group(1));

Object noKRAUTlerBalance =
krautTokenInstance.balance(
new Address(keyPairService.generateBaseKeyPair().getPublicKey()));
log.info(noKRAUTlerBalance.toString());
Assertions.assertEquals("None", noKRAUTlerBalance.toString());
}
}
2 changes: 2 additions & 0 deletions src/test/resources/CryptoHamster.aes
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@compiler >= 4

contract CryptoHamster =
datatype event = NewHamster(indexed int, string, hash)

Expand Down
111 changes: 111 additions & 0 deletions src/test/resources/FungibleToken.aes
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// ISC License
//
// Copyright (c) 2017, aeternity developers
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.


// THIS IS NOT SECURITY AUDITED
// DO NEVER USE THIS WITHOUT SECURITY AUDIT FIRST

@compiler >= 4

/// @title - Fungible token basic
contract FungibleToken =

// This defines the state of type record encapsulating the conract's mutable state
record state =
{ owner : address // the smart contract's owner address
, total_supply : int // total token supply
, balances : balances // balances for each account
, meta_info : meta_info } // token meta info (name, symbol, decimals)

// This is the meta information record type
record meta_info =
{ name : string
, symbol : string
, decimals : int }

// This is a type alias for the balances map
type balances = map(address, int)

// Declaration and structure of datatype event
// and events that will be emitted on changes
datatype event = Transfer(address, address, int)

// List of implemented extensions for the deployed contract
entrypoint aex9_extensions() : list(string) = []

// Create a fungible token with
// the following `name` `symbol` and `decimals`
// and set the inital smart contract state
entrypoint init(name: string, decimals : int, symbol : string) =
// If the `name` lenght is less than 1 symbol abort the execution
require(String.length(name) >= 1, "STRING_TOO_SHORT_NAME")
// If the `symbol` length is less than 1 symbol abort the execution
require(String.length(symbol) >= 1, "STRING_TOO_SHORT_SYMBOL")
// If the provided value for `decimals` is negative abort the execution
require_non_negative_value(decimals)
{ owner = Call.caller,
total_supply = 21000000000000000000000000,
balances = {[Call.caller] = 21000000000000000000000000},
meta_info = { name = name, symbol = symbol, decimals = decimals } }

// Get the token meta info
entrypoint meta_info() : meta_info =
state.meta_info

// Get the token total supply
entrypoint total_supply() : int =
state.total_supply

// Get the token owner address
entrypoint owner() : address =
state.owner

// Get the balances state
entrypoint balances() : balances =
state.balances

// Get balance for address of `owner`
// returns option(int)
// If the `owner` address haven't had any token balance
// in this smart contract the return value is None
// Otherwise Some(int) is returned with the current balance
entrypoint balance(owner: address) : option(int) =
Map.lookup(owner, state.balances)

/// Transfer the balance of `value` from `Call.caller` to `to_account` account
stateful entrypoint transfer(to_account: address, value: int) =
internal_transfer(Call.caller, to_account, value)

// INTERNAL FUNCTIONS

function require_owner() =
require(Call.caller == state.owner, "ONLY_OWNER_CALL_ALLOWED")

function require_non_negative_value(value : int) =
require(value >= 0, "NON_NEGATIVE_VALUE_REQUIRED")

function require_balance(account : address, value : int) =
switch(balance(account))
Some(balance) =>
require(balance >= value, "ACCOUNT_INSUFFICIENT_BALANCE")
None => abort("BALANCE_ACCOUNT_NOT_EXISTENT")

stateful function internal_transfer(from_account: address, to_account: address, value: int) =
require_non_negative_value(value)
require_balance(from_account, value)
put(state{ balances[from_account] @ b = b - value })
put(state{ balances[to_account = 0] @ b = b + value })
Chain.event(Transfer(from_account, to_account, value))
18 changes: 18 additions & 0 deletions src/test/resources/FungibleTokenInterface.aes
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@compiler >= 4

contract FungibleTokenInterface =
record meta_info =
{ name : string
, symbol : string
, decimals : int }

datatype event =
Transfer(address, address, int)

entrypoint aex9_extensions : () => list(string)
entrypoint meta_info : () => meta_info
entrypoint total_supply : () => int
entrypoint owner : () => address
entrypoint balances : () => map(address, int)
entrypoint balance : (address) => option(int)
entrypoint transfer : (address, int) => unit
129 changes: 129 additions & 0 deletions src/test/resources/PaymentSplitter.aes
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// ISC License
//
// Copyright (c) 2019, kryptokrauts.com
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
//

@compiler >= 4

payable contract PaymentSplitter =
record state =
{ owner: address,
recipientConditions: map(address, int), // map of recipients with percentage to receive (value between 1 and 100)
totalAmountSplitted: int }

// CONTRACT EVENTS
datatype event = AddingInitialRecipients()
| RecipientAdded(indexed address, indexed int)
| AddressUpdated(indexed address, indexed address)
| UpdatingAllRecipients()
| PaymentReceivedAndSplitted(indexed address, indexed int, indexed int)

// INIT FUNCTION
entrypoint init(recipientConditions: map(address, int)) : state =
require(sumWeights(recipientConditions) == 100, "sum of weights needs to be 100")
Chain.event(AddingInitialRecipients)
{ owner = Call.caller,
recipientConditions = recipientConditions,
totalAmountSplitted = 0}

// READ ONLY FUNCTIONS

entrypoint getOwner() : address =
state.owner

entrypoint getRecipientsCount() : int =
Map.size(state.recipientConditions)

entrypoint isRecipient(who: address) : bool =
Map.member(who, state.recipientConditions)

entrypoint getWeight(recipient: address) : int =
Map.lookup_default(recipient, state.recipientConditions, 0)

entrypoint getTotalAmountSplitted() : int =
state.totalAmountSplitted

// PAY-AND-SPLIT FUNCTION
stateful payable entrypoint payAndSplit() =
require(Contract.balance > 0, "contract didn't receive any payment")
let recipientConditions: list(address * int) = Map.to_list(state.recipientConditions)
put(state{totalAmountSplitted = Contract.balance + state.totalAmountSplitted})
split(recipientConditions, Contract.balance)
Chain.event(PaymentReceivedAndSplitted(Call.caller, Call.value, Contract.balance))

// STATEFUL FUNCTIONS

stateful entrypoint transferOwnership(newOwner: address) =
onlyOwner()
put(state{owner = newOwner})

stateful entrypoint updateAddress(oldAddress: address, newAddress: address) =
onlyOwnerOrRecipient(oldAddress)
let weight: int = state.recipientConditions[oldAddress]
put(state{recipientConditions @ rc = Map.delete(oldAddress, rc)}) // remove old address
put(state{recipientConditions[newAddress] = weight}) // add new address
Chain.event(AddressUpdated(oldAddress, newAddress))

stateful entrypoint updateRecipientConditions(recipients: map(address, int)) =
onlyOwner()
Chain.event(UpdatingAllRecipients)
require(sumWeights(recipients) == 100, "sum of weights needs to be 100")
put(state{recipientConditions = recipients})
fireRecipientAddedEvents(Map.to_list(state.recipientConditions))

// PRIVATE FUNCTIONS

function onlyOwner() =
require(Call.caller == state.owner, "caller must be the owner")

function onlyOwnerOrRecipient(recipient: address) =
require(Call.caller == state.owner || Call.caller == recipient, "caller must be the owner or the recipient")

function sumWeights(recipients: map(address, int)) : int =
let recipientList: list(address * int) = Map.to_list(recipients)
let intList: list(int) = map(pair_second, recipientList)
sum(intList, (x) => x)

function fireRecipientAddedEvents(recipientConditions: list(address * int)) =
switch(recipientConditions)
[] => ()
(recipient, weight) :: l' =>
Chain.event(RecipientAdded(recipient, weight))

stateful function split(recipientConditions: list(address * int), totalValue: int) =
switch(recipientConditions)
[] => ()
(recipient, weight) :: l' =>
Chain.spend(recipient, totalValue / 100 * weight)
split(l', totalValue)

// GENERIC HELPER FUNCTIONS

function map(f : 'a => 'b, l : list('a)) : list('b) =
switch(l)
[] => []
e :: l' => f(e) :: map(f, l')

function foldr(f : (('a, 'b) => 'b), z: 'b, l : list('a)) : 'b =
switch(l)
[] => z
e :: l' => f(e, foldr(f, z, l'))

function sum(l : list('a), f : 'a => int) : int =
foldr((x, y) => x + y, 0, map(f, l))

function pair_second(tuple) =
switch(tuple)
(_, e) => e

0 comments on commit d0215b4

Please sign in to comment.