Skip to content

Commit

Permalink
Add unit tests for IRC2Basic
Browse files Browse the repository at this point in the history
  • Loading branch information
sink772 committed Oct 29, 2020
1 parent cf29ed0 commit f7849a3
Show file tree
Hide file tree
Showing 14 changed files with 1,022 additions and 1 deletion.
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
rootProject.name = 'javaee-tokens'
include 'tokens'
include 'tokens', 'testsvc'
7 changes: 7 additions & 0 deletions testsvc/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
id 'java-library'
}

dependencies {
compileOnly 'foundation.icon:javaee-api:0.8.6'
}
85 changes: 85 additions & 0 deletions testsvc/src/main/java/com/iconloop/testsvc/Account.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2020 ICONLOOP Inc.
*
* 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.
*/

package com.iconloop.testsvc;

import score.Address;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

public class Account {
private static final Map<Address, Account> accounts = new HashMap<>();

private final Address address;
private final Map<String, BigInteger> balances = new HashMap<>();

public static Account newExternalAccount(int seed) {
var acct = new Account(0, seed);
accounts.put(acct.getAddress(), acct);
return acct;
}

public static Account newScoreAccount(int seed) {
var acct = new Account(1, seed);
accounts.put(acct.getAddress(), acct);
return acct;
}

public static Account getAccount(Address address) {
return accounts.get(address);
}

private Account(int type, int seed) {
var ba = new byte[Address.LENGTH];
ba[0] = (byte) type;
var index = ba.length - 1;
ba[index--] = (byte) seed;
ba[index--] = (byte) (seed >> 8);
ba[index--] = (byte) (seed >> 16);
ba[index] = (byte) (seed >> 24);
this.address = new Address(ba);
}

public Address getAddress() {
return address;
}

public void addBalance(String symbol, BigInteger value) {
balances.put(symbol, getBalance(symbol).add(value));
}

public void subtractBalance(String symbol, BigInteger value) {
balances.put(symbol, getBalance(symbol).subtract(value));
}

public BigInteger getBalance(String symbol) {
return balances.getOrDefault(symbol, BigInteger.ZERO);
}

public BigInteger getBalance() {
return getBalance("ICX");
}

@Override
public String toString() {
return "Account{" +
"address=" + address +
", balances=" + balances +
'}';
}
}
86 changes: 86 additions & 0 deletions testsvc/src/main/java/com/iconloop/testsvc/Score.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2020 ICONLOOP Inc.
*
* 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.
*/

package com.iconloop.testsvc;

import score.Address;

import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;

public class Score extends TestBase {
private static final ServiceManager sm = getServiceManager();

private final Account score;
private final Account owner;
private Object instance;

public Score(Account score, Account owner) {
this.score = score;
this.owner = owner;
}

public Account getAccount() {
return this.score;
}

public Address getAddress() {
return this.score.getAddress();
}

public Account getOwner() {
return this.owner;
}

public void setInstance(Object newInstance) {
this.instance = newInstance;
}

public Object getInstance() {
return this.instance;
}

public Object call(String method, Object... params) {
return call(null, true, BigInteger.ZERO, method, params);
}

public void invoke(Account from, String method, Object... params) {
sm.getBlock().increase();
call(from, false, BigInteger.ZERO, method, params);
}

Object call(Account from, boolean readonly, BigInteger value, String method, Object... params) {
sm.pushFrame(from, this.score, readonly, method, value);
Class<?>[] paramClasses = new Class<?>[params.length];
for (int i = 0; i < params.length; i++) {
Class<?> type = params[i].getClass();
paramClasses[i] = (type == Boolean.class) ? Boolean.TYPE : type;
}
try {
Class<?> clazz = instance.getClass();
var m = clazz.getMethod(method, paramClasses);
return m.invoke(instance, params);
} catch (NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new AssertionError(e.getTargetException().getMessage());
} finally {
sm.popFrame();
}
}
}
214 changes: 214 additions & 0 deletions testsvc/src/main/java/com/iconloop/testsvc/ServiceManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* Copyright 2020 ICONLOOP Inc.
*
* 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.
*/

package com.iconloop.testsvc;

import score.Address;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Stack;

public class ServiceManager {
private static final BigInteger ICX = BigInteger.TEN.pow(18);

private final Stack<Frame> contexts = new Stack<>();
private final Map<Class<?>, Score> classScoreMap = new HashMap<>();
private final Map<Address, Score> addressScoreMap = new HashMap<>();
private int nextCount = 1;

public Score deploy(Account owner, Class<?> mainClass, Object... params) throws Exception {
getBlock().increase();
var score = new Score(Account.newScoreAccount(nextCount++), owner);
classScoreMap.put(mainClass, score);
addressScoreMap.put(score.getAddress(), score);
pushFrame(owner, score.getAccount(), false, "<init>", BigInteger.ZERO);
try {
Class<?>[] paramClasses = new Class<?>[params.length];
for (int i = 0; i < params.length; i++) {
paramClasses[i] = params[i].getClass();
}
Constructor<?> ctor = mainClass.getConstructor(paramClasses);
score.setInstance(ctor.newInstance(params));
} catch (InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
e.printStackTrace();
throw e;
} finally {
popFrame();
}
return score;
}

public Account createAccount() {
return createAccount(0);
}

public Account createAccount(int initialIcx) {
var acct = Account.newExternalAccount(nextCount++);
acct.addBalance("ICX", ICX.multiply(BigInteger.valueOf(initialIcx)));
return acct;
}

public Address getOwner() {
var address = getCurrentFrame().to.getAddress();
return getScoreFromAddress(address).getOwner().getAddress();
}

public Address getOrigin() {
return getFirstFrame().from.getAddress();
}

public Address getCaller() {
return getCurrentFrame().from.getAddress();
}

public Address getAddress() {
return getCurrentFrame().to.getAddress();
}

private Score getScoreFromClass(Class<?> caller) {
var score = classScoreMap.get(caller);
if (score == null) {
throw new IllegalStateException(caller.getName() + " not found");
}
return score;
}

private Score getScoreFromAddress(Address target) {
var score = addressScoreMap.get(target);
if (score == null) {
throw new IllegalStateException("ScoreNotFound");
}
return score;
}

public Object call(Account from, BigInteger value, Address targetAddress, String method, Object... params) {
Score score = getScoreFromAddress(targetAddress);
return score.call(from, false, value, method, params);
}

public Object call(Class<?> caller, BigInteger value, Address targetAddress, String method, Object... params) {
Score from = getScoreFromClass(caller);
if ("fallback".equals(method) || "".equals(method)) {
transfer(from.getAccount(), targetAddress, value);
return null;
} else {
return call(from.getAccount(), value, targetAddress, method, params);
}
}

public void transfer(Account from, Address targetAddress, BigInteger value) {
getBlock().increase();
var fromBalance = from.getBalance();
if (fromBalance.compareTo(value) < 0) {
throw new IllegalStateException("OutOfBalance");
}
var to = Account.getAccount(targetAddress);
if (to == null) {
throw new IllegalStateException("NoAccount");
}
from.subtractBalance("ICX", value);
to.addBalance("ICX", value);
if (targetAddress.isContract()) {
call(from, value, targetAddress, "fallback");
}
}

public static class Block {
private static Block sInstance;

private long height;
private long timestamp;

public Block(long height, long timestamp) {
this.height = height;
this.timestamp = timestamp;
}

public static Block getInstance() {
if (sInstance == null) {
Random rand = new Random();
sInstance = new Block(rand.nextInt(1000), System.nanoTime() * 1000);
}
return sInstance;
}

public long getHeight() {
return height;
}

public long getTimestamp() {
return timestamp;
}

public void increase() {
increase(1);
}

public void increase(long delta) {
height += delta;
timestamp = System.nanoTime() * 1000;
}
}

public Block getBlock() {
return Block.getInstance();
}

public static class Frame {
Account from;
Account to;
String method;
boolean readonly;
BigInteger value;

public Frame(Account from, Account to, boolean readonly, String method, BigInteger value) {
this.from = from;
this.to = to;
this.readonly = readonly;
this.method = method;
this.value = value;
}

public boolean isReadonly() {
return readonly;
}

public BigInteger getValue() {
return value;
}
}

protected void pushFrame(Account from, Account to, boolean readonly, String method, BigInteger value) {
contexts.push(new Frame(from, to, readonly, method, value));
}

protected void popFrame() {
contexts.pop();
}

public Frame getCurrentFrame() {
return contexts.peek();
}

public Frame getFirstFrame() {
return contexts.firstElement();
}
}
Loading

0 comments on commit f7849a3

Please sign in to comment.