diff --git a/.gitignore b/.gitignore
index e4d77b966..81967d29c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,4 +82,4 @@ fabric.properties
# Ignore Gradle build output directory
build
.DS_Store
-.vscode
\ No newline at end of file
+.vscode
diff --git a/core-contracts/BatchDisbursement/build.gradle b/core-contracts/BatchDisbursement/build.gradle
new file mode 100644
index 000000000..3917ae5b2
--- /dev/null
+++ b/core-contracts/BatchDisbursement/build.gradle
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022-2022 Balanced.network.
+ *
+ * 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.
+ */
+
+version = '0.1.0'
+
+repositories {
+ // Use Maven Central for resolving dependencies.
+ mavenCentral()
+}
+
+dependencies {
+ compileOnly 'foundation.icon:javaee-api:0.9.1'
+ implementation 'foundation.icon:javaee-scorex:0.5.2'
+
+ testImplementation 'com.github.sink772:javaee-tokens:0.6.1'
+ testImplementation 'foundation.icon:javaee-unittest:0.9.2'
+ // Use JUnit Jupiter for testing.
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
+ testImplementation 'org.mockito:mockito-core:4.1.0'
+}
+
+optimizedJar {
+ mainClassName = 'network.balanced.score.core.batchDisbursement.BatchDisbursement'
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+ from {
+ configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
+ }
+ enableDebug = false
+}
+
+deployJar {
+ endpoints {
+ sejong {
+ uri = 'https://sejong.net.solidwallet.io/api/v3'
+ nid = 0x53
+ to = "cx3ddc7e3599d270cfa65cc933e1a3487f11f6b6f6"
+ }
+ local {
+ uri = 'http://localhost:9082/api/v3'
+ nid = 0x3
+ }
+ mainnet {
+ uri = 'https://ctz.solidwallet.io/api/v3'
+ nid = 0x1
+ }
+ }
+ keystore = rootProject.hasProperty('keystoreName') ? "$keystoreName" : ''
+ password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : ''
+ parameters { arg('_governance', "cxdeeabbbdd77a3f648cf4ce4a5f3d4bdd1e3833b3") }
+}
+
+tasks.named('test') {
+ // Use JUnit Platform for unit tests.
+ useJUnitPlatform()
+}
diff --git a/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/BatchDisbursement.java b/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/BatchDisbursement.java
new file mode 100644
index 000000000..8588025eb
--- /dev/null
+++ b/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/BatchDisbursement.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2022-2022 Balanced.network.
+ *
+ * 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 network.balanced.score.core.batchDisbursement;
+
+import score.*;
+import score.annotation.EventLog;
+import score.annotation.External;
+import scorex.util.HashMap;
+
+import java.math.BigInteger;
+import java.util.Map;
+
+import static network.balanced.score.core.batchDisbursement.Checks.onlyGovernance;
+import static network.balanced.score.core.batchDisbursement.Checks.onlyOwner;
+
+public class BatchDisbursement {
+
+ public static final VarDB
governance = Context.newVarDB("governance", Address.class);
+ public final BranchDB> userClaimableTokens = Context.newBranchDB(
+ "user_claimable_tokens", BigInteger.class);
+ public final VarDB daofund = Context.newVarDB("daofund", Address.class);
+ public final VarDB reserveFund = Context.newVarDB("reserve_fund", Address.class);
+ public final EnumerableSetDB allowedTokenAddress = new EnumerableSetDB<>("allowed_token_address",
+ Address.class);
+
+ public static final String TAG = "BatchDisbursement";
+
+ public BatchDisbursement(Address _governance) {
+ Context.require(_governance.isContract(), TAG + ": Governance address should be a contract");
+ governance.set(_governance);
+ }
+
+ public static class Disbursement {
+ public Address tokenAddress;
+ public BigInteger tokenAmount;
+ }
+
+ public static class DisbursementRecipient {
+ public Address recipient;
+ public Disbursement[] disbursement;
+ }
+
+ /*
+ * Event Logs
+ */
+
+ @EventLog(indexed = 1)
+ public void Claim(Address user, Address tokenAddress, BigInteger amount) {
+ }
+
+ @EventLog(indexed = 1)
+ protected void TokenTransfer(Address recipient, BigInteger amount, String note) {
+ }
+
+ /*
+ * Setters and getters
+ */
+ @External(readonly = true)
+ public String name() {
+ return "Balanced Batch Disbursement";
+ }
+
+ @External
+ public void setGovernance(Address _address) {
+ onlyOwner();
+ Context.require(_address.isContract(), TAG + ": Address provided is an EOA Address. Contract address required");
+ governance.set(_address);
+ }
+
+ @External(readonly = true)
+ public Address getGovernance() {
+ return governance.get();
+ }
+
+ @External
+ public void setDaofund(Address _address) {
+ onlyOwner();
+ Context.require(_address.isContract(), TAG + ": Address provided is an EOA Address. Contract address required");
+ daofund.set(_address);
+ }
+
+ @External(readonly = true)
+ public Address getDaofund() {
+ return daofund.get();
+ }
+
+ @External
+ public void setReserveFund(Address _address) {
+ onlyOwner();
+ Context.require(_address.isContract(), TAG + ": Address provided is an EOA Address. Contract address required");
+ reserveFund.set(_address);
+ }
+
+ @External(readonly = true)
+ public Address getReserveFund() {
+ return reserveFund.get();
+ }
+
+ @External(readonly = true)
+ public Map getTokenBalances() {
+ Map balances = new HashMap<>();
+ for (int arrayIndex = 0; arrayIndex < allowedTokenAddress.length(); arrayIndex++) {
+ Address tokenAddress = allowedTokenAddress.at(arrayIndex);
+ BigInteger balance = (BigInteger) Context.call(tokenAddress, "balanceOf", Context.getAddress());
+ balances.put(tokenAddress.toString(), balance);
+ }
+ return balances;
+ }
+
+ @External
+ public void uploadDisbursementData(DisbursementRecipient[] disbursementRecipients) {
+ onlyOwner();
+
+ for (DisbursementRecipient disbursementRecipient : disbursementRecipients) {
+ Address user = disbursementRecipient.recipient;
+ DictDB userTokens = userClaimableTokens.at(user);
+ for (Disbursement disbursement : disbursementRecipient.disbursement) {
+ Address token = disbursement.tokenAddress;
+ BigInteger currentAmount = userTokens.getOrDefault(token, BigInteger.ZERO);
+ userTokens.set(disbursement.tokenAddress, currentAmount.add(disbursement.tokenAmount));
+ }
+ }
+ }
+
+ @External(readonly = true)
+ public Map getDisbursementDetail(Address _user) {
+
+ Map userClaimableTokens = new HashMap<>();
+ DictDB userTokens = this.userClaimableTokens.at(_user);
+
+ for (int arrayIndex = 0; arrayIndex < allowedTokenAddress.length(); arrayIndex++) {
+ Address token = allowedTokenAddress.at(arrayIndex);
+ BigInteger tokenAmount = userTokens.getOrDefault(token, BigInteger.ZERO);
+ userClaimableTokens.put(token.toString(), tokenAmount);
+ }
+ return Map.of("user", _user, "claimableTokens", userClaimableTokens);
+ }
+
+ @External
+ public void claim() {
+ Address sender = Context.getCaller();
+ DictDB userTokens = userClaimableTokens.at(sender);
+
+ for (int arrayIndex = 0; arrayIndex < allowedTokenAddress.length(); arrayIndex++) {
+ Address token = allowedTokenAddress.at(arrayIndex);
+ BigInteger tokenAmount = userTokens.getOrDefault(token, BigInteger.ZERO);
+ if (tokenAmount != null && tokenAmount.signum() > 0) {
+ userTokens.set(token, BigInteger.ZERO);
+ sendToken(token, sender, tokenAmount, TAG + ": tokens claimed");
+ Claim(sender, token, tokenAmount);
+ }
+ }
+ }
+
+ @External
+ public void batchDisburse(Address _source) {
+ onlyGovernance();
+ Context.call(_source, "claim");
+ }
+
+ @External
+ public void tokenFallback(Address _from, BigInteger _value, byte[] _data) {
+ Address tokenContract = Context.getCaller();
+
+ Context.require(_from.equals(getDaofund()) || _from.equals(getReserveFund()), TAG+ ": Only receivable from " +
+ "daofund or reserve contract");
+ if (!allowedTokenAddress.contains(tokenContract)) {
+ allowedTokenAddress.add(tokenContract);
+ }
+ }
+
+ private void sendToken(Address tokenAddress, Address to, BigInteger amount, String message) {
+ try {
+ Context.call(tokenAddress, "transfer", to, amount, new byte[0]);
+ TokenTransfer(to, amount, message + ": " + amount + "" + tokenAddress + " tokens sent to " + to);
+ } catch (Exception e) {
+ Context.println(e.getMessage());
+ Context.revert(TAG + ": Error in sending tokens to user- " + to);
+ }
+ }
+
+}
diff --git a/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/Checks.java b/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/Checks.java
new file mode 100644
index 000000000..9ccb6d421
--- /dev/null
+++ b/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/Checks.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022-2022 Balanced.network.
+ *
+ * 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 network.balanced.score.core.batchDisbursement;
+
+import score.Address;
+import score.Context;
+
+public class Checks {
+ public static Address defaultAddress = new Address(new byte[Address.LENGTH]);
+
+ public static void onlyOwner() {
+ Address caller = Context.getCaller();
+ Address owner = Context.getOwner();
+ Context.require(caller.equals(owner), "SenderNotScoreOwner: Sender=" + caller + "Owner=" + owner);
+ }
+
+ public static void onlyGovernance() {
+ Address sender = Context.getCaller();
+ Address governance = BatchDisbursement.governance.getOrDefault(defaultAddress);
+ Context.require(!governance.equals(defaultAddress), BatchDisbursement.TAG + ": Governance address not set");
+ Context.require(sender.equals(governance), BatchDisbursement.TAG + ": Sender not governance contract");
+ }
+}
diff --git a/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/EnumerableSetDB.java b/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/EnumerableSetDB.java
new file mode 100644
index 000000000..fb612e250
--- /dev/null
+++ b/core-contracts/BatchDisbursement/src/main/java/network/balanced/score/core/batchDisbursement/EnumerableSetDB.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2022-2022 Balanced.network.
+ *
+ * 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 network.balanced.score.core.batchDisbursement;
+
+import score.ArrayDB;
+import score.Context;
+import score.DictDB;
+
+public class EnumerableSetDB {
+ private final ArrayDB entries;
+ private final DictDB indexes;
+
+ public EnumerableSetDB(String varKey, Class valueClass) {
+ // array of valueClass
+ this.entries = Context.newArrayDB(varKey + "_es_entries", valueClass);
+ // value => array index
+ this.indexes = Context.newDictDB(varKey + "_es_index", Integer.class);
+ }
+
+ public int length() {
+ return entries.size();
+ }
+
+ public V at(int index) {
+ return entries.get(index);
+ }
+
+ public boolean contains(V value) {
+ return indexes.get(value) != null;
+ }
+
+ public Integer indexOf(V value) {
+ // returns null if value doesn't exist
+ Integer result = indexes.get(value);
+ if (result != null) {
+ return result - 1;
+ }
+ return null;
+ }
+
+ public void add(V value) {
+ if (!contains(value)) {
+ //add new value
+ entries.add(value);
+ indexes.set(value, entries.size());
+ }
+ }
+
+ public V remove(V value) {
+ Integer valueIndex = indexOf(value);
+
+ if (valueIndex != null) {
+ int lastIndex = entries.size();
+ V lastValue = entries.pop();
+ indexes.set(value, null);
+ if (lastIndex != valueIndex) {
+ entries.set(valueIndex - 1, lastValue);
+ indexes.set(lastValue, valueIndex);
+ return lastValue;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/BatchDisbursementTest.java b/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/BatchDisbursementTest.java
new file mode 100644
index 000000000..3219f8fed
--- /dev/null
+++ b/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/BatchDisbursementTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2022-2022 Balanced.network.
+ *
+ * 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 network.balanced.score.core.batchDisbursement;
+
+import com.iconloop.score.test.Account;
+import com.iconloop.score.test.Score;
+import com.iconloop.score.test.ServiceManager;
+import com.iconloop.score.test.TestBase;
+import com.iconloop.score.token.irc2.IRC2Mintable;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+import score.Address;
+
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class BatchDisbursementTest extends TestBase {
+
+ public static final ServiceManager sm = getServiceManager();
+ public static final Account owner = sm.createAccount();
+ public static final Account governanceScore = Account.newScoreAccount(1);
+
+ private Score batchDisbursement;
+ private Score balnTokenScore;
+ private Score daofundScore;
+ private Score reserveFundScore;
+
+ List recipients = new ArrayList<>();
+
+ public static class BalnToken extends IRC2Mintable {
+ public BalnToken(String _name, String _symbol, int _decimals) {
+ super(_name, _symbol, _decimals);
+ }
+ }
+
+ private void expectErrorMessage(Executable contractCall, String expectedErrorMessage) {
+ AssertionError e = Assertions.assertThrows(AssertionError.class, contractCall);
+ assertEquals(expectedErrorMessage, e.getMessage());
+ }
+
+ @BeforeEach
+ void setUp() throws Exception {
+ batchDisbursement = sm.deploy(owner, BatchDisbursement.class, governanceScore.getAddress());
+ assert (batchDisbursement.getAddress().isContract());
+
+ balnTokenScore = sm.deploy(owner, BalnToken.class, "Balanced Token", "BALN", 18);
+ assert (balnTokenScore.getAddress().isContract());
+
+ daofundScore = sm.deploy(owner, MockDaofund.class, balnTokenScore.getAddress());
+ assert (daofundScore.getAddress().isContract());
+
+ reserveFundScore = sm.deploy(owner, MockReserve.class, balnTokenScore.getAddress());
+ assert (reserveFundScore.getAddress().isContract());
+
+ balnTokenScore.invoke(owner, "mintTo", daofundScore.getAddress(), BigInteger.TEN.pow(23));
+ balnTokenScore.invoke(owner, "mintTo", reserveFundScore.getAddress(), BigInteger.TEN.pow(23));
+
+ for (int i = 0; i < 10; i++) {
+ recipients.add(sm.createAccount());
+ }
+ }
+
+ @Test
+ @DisplayName("Deployment with non contract address")
+ void testDeploy() {
+ Account notContract = sm.createAccount();
+ Executable deploymentWithNonContract = () -> sm.deploy(owner, BatchDisbursement.class,
+ notContract.getAddress());
+
+ String expectedErrorMessage = "BatchDisbursement: Governance address should be a contract";
+ InvocationTargetException e = Assertions.assertThrows(InvocationTargetException.class,
+ deploymentWithNonContract);
+ assertEquals(expectedErrorMessage, e.getCause().getMessage());
+ }
+
+ @Test
+ void name() {
+ String contractName = "Balanced Batch Disbursement";
+ assertEquals(contractName, batchDisbursement.call("name"));
+ }
+
+ @Test
+ void setAndGetGovernance() {
+ assertEquals(governanceScore.getAddress(), batchDisbursement.call("getGovernance"));
+ Address newGovernance = Account.newScoreAccount(2).getAddress();
+ batchDisbursement.invoke(owner, "setGovernance", newGovernance);
+ assertEquals(newGovernance, batchDisbursement.call("getGovernance"));
+ }
+
+ @Test
+ void setAndGetDaofund() {
+ Account nonOwner = sm.createAccount();
+
+ Executable nonOwnerCall = () -> batchDisbursement.invoke(nonOwner, "setDaofund", daofundScore.getAddress());
+ String expectedErrorMessage =
+ "SenderNotScoreOwner: Sender=" + nonOwner.getAddress() + "Owner=" + owner.getAddress();
+ expectErrorMessage(nonOwnerCall, expectedErrorMessage);
+
+ batchDisbursement.invoke(owner, "setDaofund", daofundScore.getAddress());
+ assertEquals(daofundScore.getAddress(), batchDisbursement.call("getDaofund"));
+ }
+
+ @Test
+ void setAndGetReserveFund() {
+ batchDisbursement.invoke(owner, "setReserveFund", reserveFundScore.getAddress());
+ assertEquals(reserveFundScore.getAddress(), batchDisbursement.call("getReserveFund"));
+ }
+
+ @Test
+ void getTokenBalances() {
+ assertEquals(Map.of(), batchDisbursement.call("getTokenBalances"));
+
+ setAndGetDaofund();
+ setAndGetReserveFund();
+
+ batchDisbursement.invoke(governanceScore, "batchDisburse", daofundScore.getAddress());
+ assertEquals(Map.of(balnTokenScore.getAddress().toString(), BigInteger.TEN.pow(22)), batchDisbursement.call(
+ "getTokenBalances"));
+
+ batchDisbursement.invoke(governanceScore, "batchDisburse", reserveFundScore.getAddress());
+ assertEquals(Map.of(balnTokenScore.getAddress().toString(), BigInteger.TEN.pow(22).multiply(BigInteger.TWO)),
+ batchDisbursement.call("getTokenBalances"));
+ }
+
+ @Test
+ void uploadDisbursementData() {
+ getTokenBalances();
+
+ BatchDisbursement.DisbursementRecipient[] batchRecipients = new BatchDisbursement.DisbursementRecipient[10];
+ BatchDisbursement.Disbursement disbursement = new BatchDisbursement.Disbursement();
+ disbursement.tokenAddress = balnTokenScore.getAddress();
+ disbursement.tokenAmount = BigInteger.valueOf(500L);
+
+ for (int j = 0; j < 10; j++) {
+ batchRecipients[j] = new BatchDisbursement.DisbursementRecipient();
+ batchRecipients[j].recipient = recipients.get(j).getAddress();
+ batchRecipients[j].disbursement = new BatchDisbursement.Disbursement[]{disbursement};
+ }
+
+ batchDisbursement.invoke(owner, "uploadDisbursementData", (Object) batchRecipients);
+ for (int i = 0; i < 10; i++) {
+ Address expectedRecipient = batchRecipients[i].recipient;
+ BigInteger expectedAmount = batchRecipients[i].disbursement[0].tokenAmount;
+ Address expectedTokenAddress = batchRecipients[i].disbursement[0].tokenAddress;
+
+ Map disbursementDetail = (Map) batchDisbursement.call(
+ "getDisbursementDetail", expectedRecipient);
+ Address actualRecipient = (Address) disbursementDetail.get("user");
+ Map claimableTokens = (Map) disbursementDetail.get(
+ "claimableTokens");
+ BigInteger actualAmount = claimableTokens.get(expectedTokenAddress.toString());
+
+ assertEquals(expectedRecipient, actualRecipient);
+ assertEquals(expectedAmount, actualAmount);
+ }
+ }
+
+ @Test
+ void claim() {
+ uploadDisbursementData();
+
+ for (int i = 0; i < 10; i++) {
+ Account user = recipients.get(i);
+ assertEquals(BigInteger.ZERO, balnTokenScore.call("balanceOf", user.getAddress()));
+ batchDisbursement.invoke(recipients.get(i), "claim");
+ assertEquals(BigInteger.valueOf(500L), balnTokenScore.call("balanceOf", user.getAddress()));
+
+ // Try claiming once more, should not disburse tokens
+ batchDisbursement.invoke(recipients.get(i), "claim");
+ assertEquals(BigInteger.valueOf(500L), balnTokenScore.call("balanceOf", user.getAddress()));
+ }
+ }
+
+ @Test
+ void tokenFallback() {
+ setAndGetDaofund();
+ setAndGetReserveFund();
+
+ balnTokenScore.invoke(owner, "mint", BigInteger.TEN.pow(25));
+ Executable transferFromInvalidAddress = () -> balnTokenScore.invoke(owner, "transfer",
+ batchDisbursement.getAddress(), BigInteger.TEN.pow(21), new byte[0]);
+ String expectedErrorMessage = "BatchDisbursement: Only receivable from daofund or reserve contract";
+ expectErrorMessage(transferFromInvalidAddress, expectedErrorMessage);
+ }
+}
\ No newline at end of file
diff --git a/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/MockDaofund.java b/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/MockDaofund.java
new file mode 100644
index 000000000..7818902c7
--- /dev/null
+++ b/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/MockDaofund.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2022-2022 Balanced.network.
+ *
+ * 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 network.balanced.score.core.batchDisbursement;
+
+import score.Address;
+import score.Context;
+import score.VarDB;
+import score.annotation.External;
+
+import java.math.BigInteger;
+
+public class MockDaofund {
+
+ private VarDB balnToken = Context.newVarDB("baln_token", Address.class);
+
+ public MockDaofund(Address balnToken) {
+ this.balnToken.set(balnToken);
+ }
+
+ @External
+ public void claim() {
+ Context.call(balnToken.get(), "transfer", Context.getCaller(), BigInteger.TEN.pow(22), new byte[0]);
+ }
+
+ @External
+ public void tokenFallback(Address _from, BigInteger _value, byte[] _data) {
+
+ }
+}
diff --git a/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/MockReserve.java b/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/MockReserve.java
new file mode 100644
index 000000000..337c0afdf
--- /dev/null
+++ b/core-contracts/BatchDisbursement/src/test/java/network/balanced/score/core/batchDisbursement/MockReserve.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2022-2022 Balanced.network.
+ *
+ * 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 network.balanced.score.core.batchDisbursement;
+
+import score.Address;
+import score.Context;
+import score.VarDB;
+import score.annotation.External;
+
+import java.math.BigInteger;
+
+public class MockReserve {
+
+ private VarDB balnToken = Context.newVarDB("baln_token", Address.class);
+
+ public MockReserve(Address balnToken) {
+ this.balnToken.set(balnToken);
+ }
+
+ @External
+ public void claim() {
+ Context.call(balnToken.get(), "transfer", Context.getCaller(), BigInteger.TEN.pow(22), new byte[0]);
+ }
+
+ @External
+ public void tokenFallback(Address _from, BigInteger _value, byte[] _data) {
+
+ }
+}
diff --git a/core-contracts/Reserve/src/test/java/network/balanced/score/core/reserve/utils/Loans.java b/core-contracts/Reserve/src/test/java/network/balanced/score/core/reserve/utils/Loans.java
index a75bfca81..5c1c93f3c 100644
--- a/core-contracts/Reserve/src/test/java/network/balanced/score/core/reserve/utils/Loans.java
+++ b/core-contracts/Reserve/src/test/java/network/balanced/score/core/reserve/utils/Loans.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Balanced.network.
+ * Copyright (c) 2022-2022 Balanced.network.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/settings.gradle b/settings.gradle
index c6e249a35..33c826004 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -26,5 +26,6 @@
rootProject.name = 'balanced-java-contracts'
include(':core-contracts:Multicall',
+ ':core-contracts:BatchDisbursement',
':core-contracts:Reserve',
':token-contracts')