From 1eb4293945085eed1fa6a11c509a1e1dfea29353 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 21 Jan 2024 12:32:35 +0100 Subject: [PATCH] feat: set fly canister address after creation --- .../Form/ChangeOwner/ChangeOwnerForm.tsx | 2 +- .../fly/app/src/js/components/Form/Form.tsx | 4 + .../SetFlyCanisterAddressForm.tsx | 86 +++++++++++++++++++ ethereum/fly/app/src/js/web3/Web3Client.ts | 12 +++ ethereum/fly/app/src/js/web3/contracts/Fly.ts | 18 ++-- ethereum/fly/contracts/Fly.sol | 31 ++++++- ethereum/fly/test/Fly.ts | 47 +++++++--- 7 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 ethereum/fly/app/src/js/components/Form/SetFlyCanisterAddress/SetFlyCanisterAddressForm.tsx 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"