diff --git a/ethereum/fly/app/src/js/components/Form/ChangeOwner/ChangeOwnerForm.tsx b/ethereum/fly/app/src/js/components/Form/ChangeOwner/ChangeOwnerForm.tsx
index ae4cf7f..5d23d97 100644
--- a/ethereum/fly/app/src/js/components/Form/ChangeOwner/ChangeOwnerForm.tsx
+++ b/ethereum/fly/app/src/js/components/Form/ChangeOwner/ChangeOwnerForm.tsx
@@ -34,7 +34,7 @@ const ChangeOwnerForm = () => {
});
};
- const btnDisabled = newOwner.length == 0 || pendingTx;
+ const btnDisabled = newOwner.length != 42 || pendingTx;
return (
diff --git a/ethereum/fly/app/src/js/components/Form/Form.tsx b/ethereum/fly/app/src/js/components/Form/Form.tsx
index b4b0fbc..5e66c54 100644
--- a/ethereum/fly/app/src/js/components/Form/Form.tsx
+++ b/ethereum/fly/app/src/js/components/Form/Form.tsx
@@ -10,6 +10,7 @@ import TransferForm from './Transfer/TransferForm';
import RenounceOwnershipForm from './RenounceOwnership/RenounceOwnershipForm';
import SwappedSupply from './SwappedSupply';
import MintTestnetTokens from './MintTestnetTokens/MintTestnetTokens';
+import SetFlyCanisterAddressForm from './SetFlyCanisterAddress/SetFlyCanisterAddressForm';
const Form = () => {
const { status } = useMetaMask();
@@ -31,6 +32,9 @@ const Form = () => {
+
+
+
) : (
diff --git a/ethereum/fly/app/src/js/components/Form/SetFlyCanisterAddress/SetFlyCanisterAddressForm.tsx b/ethereum/fly/app/src/js/components/Form/SetFlyCanisterAddress/SetFlyCanisterAddressForm.tsx
new file mode 100644
index 0000000..908d7c2
--- /dev/null
+++ b/ethereum/fly/app/src/js/components/Form/SetFlyCanisterAddress/SetFlyCanisterAddressForm.tsx
@@ -0,0 +1,86 @@
+import * as React from 'react';
+import { useConnectedMetaMask } from 'metamask-react';
+
+import Container from '../../reusable/Container';
+import Heading from '../../reusable/Heading';
+import Input from '../../reusable/Input';
+import Button from '../../reusable/Button';
+import Web3Client from '../../../web3/Web3Client';
+import Alerts from '../../reusable/Alerts';
+import { ChainId } from '../../MetamaskConnect';
+
+const setFlyCanisterAddressForm = () => {
+ const { account, ethereum, chainId } = useConnectedMetaMask();
+ const [address, setAddress] = React.useState('');
+ const [addressSet, setAddressSet] = React.useState(null);
+ const [pendingTx, setPendingTx] = React.useState(false);
+ const [error, setError] = React.useState();
+
+ const onAddressChange = (event: React.ChangeEvent) => {
+ setAddress(event.target.value);
+ };
+
+ const onChangeAddress = () => {
+ setPendingTx(true);
+ const client = new Web3Client(account, ethereum, chainId as ChainId);
+ client
+ .setFlyCanisterAddress(address)
+ .then(() => {
+ setPendingTx(false);
+ setError(undefined);
+ })
+ .catch((e) => {
+ setError(e.message);
+ setPendingTx(false);
+ });
+ };
+
+ React.useEffect(() => {
+ if (account && ethereum && chainId) {
+ const client = new Web3Client(account, ethereum, chainId as ChainId);
+ client
+ .getFlyCanisterAddress()
+ .then((address) => {
+ setAddressSet(address);
+ })
+ .catch((e) => {
+ console.log(e);
+ setAddressSet(null);
+ });
+ }
+ }, [account, ethereum, chainId]);
+
+ const btnDisabled = address.length != 42 || pendingTx;
+
+ return (
+
+ {addressSet ? (
+ Fly canister address: {addressSet}
+ ) : (
+ <>
+ Set Fly canister address
+
+
+ Set fly canister address
+
+ {error && (
+
+ {error}
+
+ )}
+ >
+ )}
+
+ );
+};
+
+export default setFlyCanisterAddressForm;
diff --git a/ethereum/fly/app/src/js/web3/Web3Client.ts b/ethereum/fly/app/src/js/web3/Web3Client.ts
index cc14a5a..42ad119 100644
--- a/ethereum/fly/app/src/js/web3/Web3Client.ts
+++ b/ethereum/fly/app/src/js/web3/Web3Client.ts
@@ -21,6 +21,18 @@ export default class Web3Client {
.send({ from: this.address });
}
+ async setFlyCanisterAddress(newAddress: string) {
+ const contract = this.getContract();
+ return contract.methods
+ .setFlyCanisterAddress(newAddress)
+ .send({ from: this.address });
+ }
+
+ async getFlyCanisterAddress(): Promise {
+ const contract = this.getContract();
+ return contract.methods.getFlyCanisterAddress().call();
+ }
+
async renounceOwnership() {
const contract = this.getContract();
return contract.methods.renounceOwnership().send({ from: this.address });
diff --git a/ethereum/fly/app/src/js/web3/contracts/Fly.ts b/ethereum/fly/app/src/js/web3/contracts/Fly.ts
index ebcb4ba..d4f0d74 100644
--- a/ethereum/fly/app/src/js/web3/contracts/Fly.ts
+++ b/ethereum/fly/app/src/js/web3/contracts/Fly.ts
@@ -8,11 +8,6 @@ export const ABI = [
name: '_initialOwner',
type: 'address',
},
- {
- internalType: 'address',
- name: '_fly_canister_address',
- type: 'address',
- },
{
internalType: 'uint256',
name: '_swapFee',
@@ -368,6 +363,19 @@ export const ABI = [
stateMutability: 'nonpayable',
type: 'function',
},
+ {
+ inputs: [
+ {
+ internalType: 'address',
+ name: '_fly_canister_address',
+ type: 'address',
+ },
+ ],
+ name: 'setFlyCanisterAddress',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
{
inputs: [
{
diff --git a/ethereum/fly/contracts/Fly.sol b/ethereum/fly/contracts/Fly.sol
index f0174ac..cc2bb9b 100644
--- a/ethereum/fly/contracts/Fly.sol
+++ b/ethereum/fly/contracts/Fly.sol
@@ -8,7 +8,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
// import "hardhat/console.sol";
contract Fly is ERC20, Ownable {
- address private immutable fly_canister_address;
+ address private fly_canister_address;
uint8 private _decimals;
uint256 public swapFee;
@@ -23,17 +23,17 @@ contract Fly is ERC20, Ownable {
constructor(
address _initialOwner,
- address _fly_canister_address,
uint256 _swapFee
) ERC20("Fly", "FLY") Ownable(_initialOwner) {
_decimals = 12;
- fly_canister_address = _fly_canister_address;
swapFee = _swapFee;
+ fly_canister_address = address(0);
}
modifier onlyFlyCanister() {
require(
- msg.sender == fly_canister_address,
+ msg.sender == fly_canister_address &&
+ fly_canister_address != address(0),
"Fly: caller is not the fly canister"
);
_;
@@ -69,9 +69,27 @@ contract Fly is ERC20, Ownable {
* @return The address of the fly canister.
*/
function getFlyCanisterAddress() public view returns (address) {
+ require(
+ fly_canister_address != address(0),
+ "Fly: fly canister address not set"
+ );
return fly_canister_address;
}
+ /**
+ * @dev Sets the address of the fly canister. The address can only be set once.
+ * @param _fly_canister_address The new address of the fly canister.
+ */
+ function setFlyCanisterAddress(
+ address _fly_canister_address
+ ) public onlyOwner {
+ require(
+ fly_canister_address == address(0),
+ "Fly: fly canister address already set"
+ );
+ fly_canister_address = _fly_canister_address;
+ }
+
/**
* @dev Sets the swap fee.
* @param _swapFee The new swap fee.
@@ -86,6 +104,11 @@ contract Fly is ERC20, Ownable {
* @param _amount The amount of tokens to swap.
*/
function swap(bytes32 _recipient, uint256 _amount) public payable {
+ // check if the fly canister address is set
+ require(
+ fly_canister_address != address(0),
+ "Fly: fly canister address not set"
+ );
// check if the caller has enough tokens to swap
require(
balanceOf(msg.sender) >= _amount,
diff --git a/ethereum/fly/test/Fly.ts b/ethereum/fly/test/Fly.ts
index bae7aa1..3ed31da 100644
--- a/ethereum/fly/test/Fly.ts
+++ b/ethereum/fly/test/Fly.ts
@@ -27,11 +27,7 @@ describe("Fly", () => {
const signer = await ethers.provider.getSigner(owner.address);
const Contract = await ethers.getContractFactory("Fly");
- const contract = await Contract.deploy(
- owner.address,
- owner.address,
- INITIAL_FEE
- );
+ const contract = await Contract.deploy(owner.address, INITIAL_FEE);
await contract.waitForDeployment();
const address = await contract.getAddress();
@@ -52,19 +48,35 @@ describe("Fly", () => {
expect(await token.swapFee()).to.equal(INITIAL_FEE);
// check balance
expect(await token.balanceOf(owner.address)).to.equal(0);
+ // check fly canister is unset
+ expect(token.getFlyCanisterAddress()).to.be.revertedWith(
+ "Fly: fly canister address not set"
+ );
+ });
+
+ it("Should set fly canister address just once", async () => {
+ const { token, flyCanister } = deploy;
+ await token.setFlyCanisterAddress(flyCanister.address);
+ expect(await token.getFlyCanisterAddress()).to.equal(flyCanister.address);
+ expect(token.setFlyCanisterAddress(flyCanister.address)).to.be.revertedWith(
+ "Fly: fly canister address already set"
+ );
});
it("Should transcribe swap", async () => {
- const { token, owner, flyCanister } = deploy;
+ const { token, owner } = deploy;
+ await token.setFlyCanisterAddress(owner.address);
await token.transcribeSwap(owner.address, 100);
expect(await token.balanceOf(owner.address)).to.equal(100);
});
it("Should swap 100 tokens", async () => {
- const { token, owner } = deploy;
+ const { token, owner, flyCanister } = deploy;
await token.mintTestnetTokens(owner.address, 100);
const fee = await token.swapFee();
+ await token.setFlyCanisterAddress(flyCanister.address);
+
const initialBalance = await ethers.provider.getBalance(owner.address);
// swap and check event is emitted
@@ -85,10 +97,24 @@ describe("Fly", () => {
expect(finalBalance).to.greaterThan(fee);
});
- it("should fail swap if fee is not paid", async () => {
+ it("should fail swap if fly canister address is not set", async () => {
const { token, owner } = deploy;
await token.mintTestnetTokens(owner.address, 100);
+ const fee = await token.swapFee();
+
+ expect(
+ token.swap(DUMMY_PRINCIPAL, 75, {
+ value: fee,
+ })
+ ).to.be.revertedWith("Fly: fly canister address not set");
+ });
+
+ it("should fail swap if fee is not paid", async () => {
+ const { token, owner, flyCanister } = deploy;
+ await token.setFlyCanisterAddress(flyCanister.address);
+ await token.mintTestnetTokens(owner.address, 100);
+
await expect(
token.swap(DUMMY_PRINCIPAL, 75, {
value: 10,
@@ -99,7 +125,8 @@ describe("Fly", () => {
});
it("should fail swap if has not enough tokens", async () => {
- const { token, owner } = deploy;
+ const { token, owner, flyCanister } = deploy;
+ await token.setFlyCanisterAddress(flyCanister.address);
await token.mintTestnetTokens(owner.address, 100);
const fee = await token.swapFee();
@@ -134,7 +161,7 @@ describe("Fly", () => {
});
it("should renounce ownership", async () => {
- const { owner, token } = deploy;
+ const { token } = deploy;
await token.renounceOwnership();
expect(await token.owner()).to.equal(
"0x0000000000000000000000000000000000000000"