Update contracts and sdk to fix bugs
This commit is contained in:
parent
b7db58a6ab
commit
1868071afb
|
@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
|
|||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
|
||||
import "../../libraries/external/BytesLib.sol";
|
||||
|
||||
|
@ -15,11 +16,11 @@ import "./ConductorGovernance.sol";
|
|||
|
||||
import "../shared/ICCOStructs.sol";
|
||||
|
||||
contract Conductor is ConductorGovernance {
|
||||
contract Conductor is ConductorGovernance, ReentrancyGuard {
|
||||
function createSale(
|
||||
ICCOStructs.Raise memory raise,
|
||||
ICCOStructs.Token[] memory acceptedTokens
|
||||
) public payable returns (
|
||||
) public payable nonReentrant returns (
|
||||
uint saleId,
|
||||
uint wormholeSequence
|
||||
) {
|
||||
|
@ -31,18 +32,28 @@ contract Conductor is ConductorGovernance {
|
|||
require(acceptedTokens.length > 0, "must accept at least one token");
|
||||
require(acceptedTokens.length < 255, "too many tokens");
|
||||
require(raise.maxRaise > raise.minRaise, "maxRaise must be > minRaise");
|
||||
|
||||
// grab the local token address (address for the conductor chain)
|
||||
address localTokenAddress;
|
||||
if (raise.tokenChain == chainId()) {
|
||||
localTokenAddress = address(uint160(uint256(raise.token)));
|
||||
} else {
|
||||
// identify wormhole token bridge wrapper
|
||||
localTokenAddress = tokenBridge().wrappedAsset(raise.tokenChain, raise.token);
|
||||
require(localTokenAddress != address(0), "wrapped address not found on this chain");
|
||||
}
|
||||
|
||||
{ // token deposit context to avoid stack too deep errors
|
||||
|
||||
// query own token balance before transfer
|
||||
(,bytes memory queriedBalanceBefore) = raise.token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)));
|
||||
(,bytes memory queriedBalanceBefore) = localTokenAddress.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)));
|
||||
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
|
||||
|
||||
// deposit tokens
|
||||
SafeERC20.safeTransferFrom(IERC20(raise.token), msg.sender, address(this), raise.tokenAmount);
|
||||
SafeERC20.safeTransferFrom(IERC20(localTokenAddress), msg.sender, address(this), raise.tokenAmount);
|
||||
|
||||
// query own token balance after transfer
|
||||
(,bytes memory queriedBalanceAfter) = raise.token.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)));
|
||||
(,bytes memory queriedBalanceAfter) = localTokenAddress.staticcall(abi.encodeWithSelector(IERC20.balanceOf.selector, address(this)));
|
||||
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
|
||||
|
||||
// revert if token has fee
|
||||
|
@ -54,8 +65,9 @@ contract Conductor is ConductorGovernance {
|
|||
saleId = useSaleId();
|
||||
ConductorStructs.Sale memory sale = ConductorStructs.Sale({
|
||||
saleID : saleId,
|
||||
tokenAddress : bytes32(uint256(uint160(raise.token))),
|
||||
tokenChain : chainId(),
|
||||
tokenAddress : raise.token,
|
||||
tokenChain : raise.tokenChain,
|
||||
localTokenAddress: localTokenAddress,
|
||||
tokenAmount : raise.tokenAmount,
|
||||
minRaise: raise.minRaise,
|
||||
maxRaise: raise.maxRaise,
|
||||
|
@ -96,9 +108,9 @@ contract Conductor is ConductorGovernance {
|
|||
// Sale ID
|
||||
saleID : saleId,
|
||||
// Address of the token. Left-zero-padded if shorter than 32 bytes
|
||||
tokenAddress : bytes32(uint256(uint160(raise.token))),
|
||||
tokenAddress : raise.token,
|
||||
// Chain ID of the token
|
||||
tokenChain : chainId(),
|
||||
tokenChain : raise.tokenChain,
|
||||
// token amount being sold
|
||||
tokenAmount : raise.tokenAmount,
|
||||
// min raise amount
|
||||
|
@ -118,7 +130,7 @@ contract Conductor is ConductorGovernance {
|
|||
});
|
||||
wormholeSequence = wormhole().publishMessage{
|
||||
value : msg.value
|
||||
}(0, ICCOStructs.encodeSaleInit(saleInit), consistencyLevel());
|
||||
}(0, ICCOStructs.encodeSaleInit(saleInit), consistencyLevel());
|
||||
}
|
||||
|
||||
function abortSaleBeforeStartTime(uint saleId) public payable returns (uint wormholeSequence) {
|
||||
|
@ -178,6 +190,14 @@ contract Conductor is ConductorGovernance {
|
|||
|
||||
require(!sale.isSealed && !sale.isAborted, "already sealed / aborted");
|
||||
|
||||
// query sale tokens decimals
|
||||
// bypass stack too deep
|
||||
uint8 saleTokenDecimals;
|
||||
{
|
||||
(,bytes memory queriedDecimals) = sale.localTokenAddress.staticcall(abi.encodeWithSignature("decimals()"));
|
||||
saleTokenDecimals = abi.decode(queriedDecimals, (uint8));
|
||||
}
|
||||
|
||||
ConductorStructs.InternalAccounting memory accounting;
|
||||
|
||||
for (uint i = 0; i < sale.contributionsCollected.length; i++) {
|
||||
|
@ -209,18 +229,18 @@ contract Conductor is ConductorGovernance {
|
|||
uint allocation = sale.tokenAmount * (sale.contributions[i] * sale.acceptedTokensConversionRates[i] / 1e18) / accounting.totalContribution;
|
||||
uint excessContribution = accounting.totalExcessContribution * sale.contributions[i] / accounting.totalContribution;
|
||||
|
||||
if(allocation > 0) {
|
||||
if (allocation > 0) {
|
||||
|
||||
// send allocations to contributor contracts
|
||||
if (sale.acceptedTokensChains[i] == chainId()) {
|
||||
// simple transfer on same chain
|
||||
SafeERC20.safeTransfer(IERC20(address(uint160(uint256(sale.tokenAddress)))), address(uint160(uint256(contributorContracts(sale.acceptedTokensChains[i])))), allocation);
|
||||
SafeERC20.safeTransfer(IERC20(sale.localTokenAddress), address(uint160(uint256(contributorCustody(sale.acceptedTokensChains[i])))), allocation);
|
||||
} else {
|
||||
// adjust allocation for dust after token bridge transfer
|
||||
allocation = (allocation / 1e10) * 1e10;
|
||||
allocation = ICCOStructs.deNormalizeAmount(ICCOStructs.normalizeAmount(allocation, saleTokenDecimals), saleTokenDecimals);
|
||||
|
||||
// transfer over wormhole token bridge
|
||||
SafeERC20.safeApprove(IERC20(address(uint160(uint256(sale.tokenAddress)))), address(tknBridge), allocation);
|
||||
SafeERC20.safeApprove(IERC20(sale.localTokenAddress), address(tknBridge), allocation);
|
||||
|
||||
require(accounting.valueSent >= accounting.messageFee, "insufficient wormhole messaging fees");
|
||||
accounting.valueSent -= accounting.messageFee;
|
||||
|
@ -228,10 +248,10 @@ contract Conductor is ConductorGovernance {
|
|||
tknBridge.transferTokens{
|
||||
value : accounting.messageFee
|
||||
}(
|
||||
address(uint160(uint256(sale.tokenAddress))),
|
||||
sale.localTokenAddress,
|
||||
allocation,
|
||||
sale.acceptedTokensChains[i],
|
||||
contributorContracts(sale.acceptedTokensChains[i]),
|
||||
contributorCustody(sale.acceptedTokensChains[i]),
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
@ -248,7 +268,7 @@ contract Conductor is ConductorGovernance {
|
|||
// transfer dust back to refund recipient
|
||||
accounting.dust = sale.tokenAmount - accounting.totalAllocated;
|
||||
if (accounting.dust > 0) {
|
||||
SafeERC20.safeTransfer(IERC20(address(uint160(uint256(sale.tokenAddress)))), address(uint160(uint256(sale.refundRecipient))), accounting.dust);
|
||||
SafeERC20.safeTransfer(IERC20(sale.localTokenAddress), address(uint160(uint256(sale.refundRecipient))), accounting.dust);
|
||||
}
|
||||
|
||||
require(accounting.valueSent >= accounting.messageFee, "insufficient wormhole messaging fees");
|
||||
|
@ -284,7 +304,7 @@ contract Conductor is ConductorGovernance {
|
|||
|
||||
setRefundClaimed(saleId);
|
||||
|
||||
SafeERC20.safeTransfer(IERC20(address(uint160(uint256(sale.tokenAddress)))), address(uint160(uint256(sale.refundRecipient))), sale.tokenAmount);
|
||||
SafeERC20.safeTransfer(IERC20(sale.localTokenAddress), address(uint160(uint256(sale.refundRecipient))), sale.tokenAmount);
|
||||
}
|
||||
|
||||
function useSaleId() internal returns(uint256 saleId) {
|
||||
|
|
|
@ -39,6 +39,10 @@ contract ConductorGetters is ConductorState {
|
|||
return _state.contributorImplementations[chainId_];
|
||||
}
|
||||
|
||||
function contributorCustody(uint16 chainId_) public view returns (bytes32){
|
||||
return _state.contributorCustody[chainId_];
|
||||
}
|
||||
|
||||
function sales(uint saleId_) public view returns (ConductorStructs.Sale memory sale){
|
||||
return _state.sales[saleId_];
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ contract ConductorGovernance is ConductorGetters, ConductorSetters, ERC1967Upgra
|
|||
event OwnershipTransfered(address indexed oldOwner, address indexed newOwner);
|
||||
|
||||
// register contributor contract
|
||||
function registerChain(uint16 contributorChainId, bytes32 contributorAddress) public onlyOwner {
|
||||
function registerChain(uint16 contributorChainId, bytes32 contributorAddress, bytes32 custodyAddress) public onlyOwner {
|
||||
require(contributorContracts(contributorChainId) == bytes32(0), "chain already registered");
|
||||
setContributor(contributorChainId, contributorAddress);
|
||||
setContributor(contributorChainId, contributorAddress, custodyAddress);
|
||||
}
|
||||
|
||||
function upgrade(uint16 conductorChainId, address newImplementation) public onlyOwner {
|
||||
|
|
|
@ -11,8 +11,9 @@ contract ConductorSetters is ConductorState, Context {
|
|||
_state.owner = owner_;
|
||||
}
|
||||
|
||||
function setContributor(uint16 chainId, bytes32 emitter) internal {
|
||||
function setContributor(uint16 chainId, bytes32 emitter, bytes32 custody) internal {
|
||||
_state.contributorImplementations[chainId] = emitter;
|
||||
_state.contributorCustody[chainId] = custody;
|
||||
}
|
||||
|
||||
function setInitialized(address implementatiom) internal {
|
||||
|
|
|
@ -33,6 +33,7 @@ contract ConductorStorage {
|
|||
|
||||
// Mapping of Conductor contracts on other chains
|
||||
mapping(uint16 => bytes32) contributorImplementations;
|
||||
mapping(uint16 => bytes32) contributorCustody;
|
||||
|
||||
// Mapping of Sales
|
||||
mapping(uint => ConductorStructs.Sale) sales;
|
||||
|
|
|
@ -7,10 +7,12 @@ contract ConductorStructs {
|
|||
struct Sale {
|
||||
// Sale ID
|
||||
uint256 saleID;
|
||||
// Address of the token. Left-zero-padded if shorter than 32 bytes
|
||||
// Native address of the token. Left-zero-padded if shorter than 32 bytes
|
||||
bytes32 tokenAddress;
|
||||
// Chain ID of the token
|
||||
// Native chain ID of the token
|
||||
uint16 tokenChain;
|
||||
// address of token on conductor chain, will be different if selling a wrapped token
|
||||
address localTokenAddress;
|
||||
// token amount being sold
|
||||
uint256 tokenAmount;
|
||||
// min raise amount
|
||||
|
|
|
@ -209,19 +209,21 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
require(tokenBalance >= tokenAllocation, "insufficient sale token balance");
|
||||
setSaleSealed(sealedSale.saleID);
|
||||
}
|
||||
|
||||
uint16 conductorChainId = conductorChainId();
|
||||
if (conductorChainId == thisChainId) {
|
||||
// REVIEW: need to refactor this code
|
||||
uint16 conductorChainId = conductorChainId(); // REVIEW: can the recipient be a cross-chain wallet?
|
||||
if (conductorChainId == thisChainId) { // REVIEW: change the logic to make it more readible
|
||||
// raised funds are payed out on this chain
|
||||
for (uint i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
if (sale.acceptedTokensChains[i] == thisChainId) {
|
||||
// send contributions less excess owed to contributors
|
||||
uint256 totalContributionsLessExcess = getSaleTotalContribution(sale.saleID, i) - getSaleExcessContribution(sale.saleID, i);
|
||||
SafeERC20.safeTransfer(
|
||||
IERC20(address(uint160(uint256(sale.acceptedTokensAddresses[i])))),
|
||||
address(uint160(uint256(sale.recipient))),
|
||||
totalContributionsLessExcess
|
||||
);
|
||||
if (totalContributionsLessExcess > 0) {
|
||||
SafeERC20.safeTransfer(
|
||||
IERC20(address(uint160(uint256(sale.acceptedTokensAddresses[i])))),
|
||||
address(uint160(uint256(sale.recipient))),
|
||||
totalContributionsLessExcess
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -231,26 +233,43 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
uint valueSent = msg.value;
|
||||
|
||||
for (uint i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
if (sale.acceptedTokensChains[i] == thisChainId) {
|
||||
if (sale.acceptedTokensChains[i] == thisChainId) {
|
||||
address acceptedTokenAddress = address(uint160(uint256(sale.acceptedTokensAddresses[i])));
|
||||
|
||||
// get token decimals for normalization of token amount
|
||||
uint8 acceptedTokenDecimals;
|
||||
{// bypass stack too deep
|
||||
(,bytes memory queriedDecimals) = acceptedTokenAddress.staticcall(abi.encodeWithSignature("decimals()"));
|
||||
acceptedTokenDecimals = abi.decode(queriedDecimals, (uint8));
|
||||
}
|
||||
|
||||
// send contributions less excess owed to contributors
|
||||
uint totalContributionsLessExcess = ((getSaleTotalContribution(sale.saleID, i) - getSaleExcessContribution(sale.saleID, i)) / 1e10) * 1e10;
|
||||
|
||||
// transfer over wormhole token bridge
|
||||
SafeERC20.safeApprove(IERC20(address(uint160(uint256(sale.acceptedTokensAddresses[i])))), address(tknBridge), totalContributionsLessExcess);
|
||||
|
||||
require(valueSent >= messageFee, "insufficient wormhole messaging fees");
|
||||
valueSent -= messageFee;
|
||||
|
||||
tknBridge.transferTokens{
|
||||
value : messageFee
|
||||
}(
|
||||
address(uint160(uint256(sale.acceptedTokensAddresses[i]))),
|
||||
totalContributionsLessExcess,
|
||||
conductorChainId,
|
||||
sale.recipient,
|
||||
0,
|
||||
0
|
||||
uint totalContributionsLessExcess = ICCOStructs.deNormalizeAmount(
|
||||
ICCOStructs.normalizeAmount(
|
||||
getSaleTotalContribution(sale.saleID, i) - getSaleExcessContribution(sale.saleID, i),
|
||||
acceptedTokenDecimals
|
||||
),
|
||||
acceptedTokenDecimals
|
||||
);
|
||||
|
||||
if (totalContributionsLessExcess > 0) {
|
||||
// transfer over wormhole token bridge
|
||||
SafeERC20.safeApprove(IERC20(acceptedTokenAddress), address(tknBridge), totalContributionsLessExcess);
|
||||
|
||||
require(valueSent >= messageFee, "insufficient wormhole messaging fees");
|
||||
valueSent -= messageFee;
|
||||
|
||||
tknBridge.transferTokens{
|
||||
value : messageFee
|
||||
}(
|
||||
acceptedTokenAddress,
|
||||
totalContributionsLessExcess,
|
||||
conductorChainId,
|
||||
sale.recipient,
|
||||
0,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,9 @@ library ICCOStructs {
|
|||
|
||||
struct Raise {
|
||||
// sale token address
|
||||
address token;
|
||||
bytes32 token;
|
||||
// sale token chainId
|
||||
uint16 tokenChain;
|
||||
// token amount being sold
|
||||
uint256 tokenAmount;
|
||||
// min raise amount
|
||||
|
@ -102,6 +104,20 @@ library ICCOStructs {
|
|||
uint256 saleID;
|
||||
}
|
||||
|
||||
function normalizeAmount(uint256 amount, uint8 decimals) public pure returns(uint256){
|
||||
if (decimals > 8) {
|
||||
amount /= 10 ** (decimals - 8);
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
function deNormalizeAmount(uint256 amount, uint8 decimals) public pure returns(uint256){
|
||||
if (decimals > 8) {
|
||||
amount *= 10 ** (decimals - 8);
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
function encodeSaleInit(SaleInit memory saleInit) public pure returns (bytes memory encoded) {
|
||||
return abi.encodePacked(
|
||||
uint8(1),
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
module.exports = {
|
||||
development: {
|
||||
wormhole: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
|
||||
tokenBridge: "0x0290FB167208Af455bB137780163b7B7a9a10C16",
|
||||
},
|
||||
eth_devnet: {
|
||||
wormhole: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
|
||||
tokenBridge: "0x0290FB167208Af455bB137780163b7B7a9a10C16",
|
||||
},
|
||||
eth_devnet2: {
|
||||
wormhole: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550",
|
||||
tokenBridge: "0x0290FB167208Af455bB137780163b7B7a9a10C16",
|
||||
},
|
||||
// add more based on truffle-config.js
|
||||
};
|
|
@ -27,8 +27,12 @@ export const ETH_CORE_BRIDGE_ADDRESS =
|
|||
export const ETH_TOKEN_BRIDGE_ADDRESS =
|
||||
"0x0290FB167208Af455bB137780163b7B7a9a10C16";
|
||||
|
||||
// decimals for min/max raise denomination
|
||||
export const DENOMINATION_DECIMALS = 18;
|
||||
|
||||
// contributors only registered with conductor on CHAIN_ID_ETH
|
||||
export const ETH_TOKEN_SALE_CONDUCTOR_ADDRESS = Tilt.conductorAddress;
|
||||
export const ETH_TOKEN_SALE_CONDUCTOR_CHAIN_ID = Tilt.conductorChain;
|
||||
export const TOKEN_SALE_CONTRIBUTOR_ADDRESSES = new Map<ChainId, string>();
|
||||
TOKEN_SALE_CONTRIBUTOR_ADDRESSES.set(CHAIN_ID_ETH, Tilt.ethContributorAddress);
|
||||
TOKEN_SALE_CONTRIBUTOR_ADDRESSES.set(CHAIN_ID_BSC, Tilt.bscContributorAddress);
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
uint8ArrayToHex,
|
||||
hexToNativeString,
|
||||
uint8ArrayToNative,
|
||||
importCoreWasm,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import { Contributor__factory } from "../../ethers-contracts";
|
||||
|
@ -37,16 +38,13 @@ import {
|
|||
createSaleOnEth,
|
||||
contributeOnEth,
|
||||
secureContributeOnEth,
|
||||
extractVaaPayload,
|
||||
getAllocationIsClaimedOnEth,
|
||||
getCurrentBlock,
|
||||
getErc20Balance,
|
||||
getRefundIsClaimedOnEth,
|
||||
getSaleContributionOnEth,
|
||||
getSaleFromConductorOnEth,
|
||||
getSaleFromContributorOnEth,
|
||||
makeAcceptedToken,
|
||||
makeAcceptedWrappedTokenEth,
|
||||
nativeToUint8Array,
|
||||
parseSaleInit,
|
||||
parseSaleSealed,
|
||||
|
@ -55,9 +53,9 @@ import {
|
|||
sleepFor,
|
||||
wrapEth,
|
||||
saleAbortedOnEth,
|
||||
sealSaleAndParseReceiptOnEth,
|
||||
SealSaleResult,
|
||||
getSaleWalletAllocationOnEth,
|
||||
ConductorSale,
|
||||
getTargetChainIdFromTransferVaa,
|
||||
} from "..";
|
||||
import {
|
||||
ETH_CORE_BRIDGE_ADDRESS,
|
||||
|
@ -66,7 +64,13 @@ import {
|
|||
TOKEN_SALE_CONTRIBUTOR_ADDRESSES,
|
||||
KYC_PRIVATE_KEYS,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
DENOMINATION_DECIMALS,
|
||||
ETH_TOKEN_SALE_CONDUCTOR_CHAIN_ID,
|
||||
ETH_NODE_URL,
|
||||
} from "./consts";
|
||||
import { getSaleIdFromIccoVaa } from "../signedVaa";
|
||||
import { normalizeConversionRate } from "../..";
|
||||
import { getAcceptedTokenDecimalsOnConductor } from "../misc";
|
||||
|
||||
const ERC20 = require("@openzeppelin/contracts/build/contracts/ERC20PresetMinterPauser.json");
|
||||
|
||||
|
@ -93,6 +97,14 @@ enum BalanceChange {
|
|||
Decrease,
|
||||
}
|
||||
|
||||
export async function extractVaaPayload(
|
||||
signedVaa: Uint8Array
|
||||
): Promise<Uint8Array> {
|
||||
const { parse_vaa } = await importCoreWasm();
|
||||
const { payload: payload } = parse_vaa(signedVaa);
|
||||
return payload;
|
||||
}
|
||||
|
||||
export async function deployTokenOnEth(
|
||||
rpc: string,
|
||||
name: string,
|
||||
|
@ -124,13 +136,17 @@ export async function deployTokenOnEth(
|
|||
// TODO: add terra and solana handling to this (doing it serially here to make it easier to adapt)
|
||||
export async function makeAcceptedTokensFromConfigs(
|
||||
configs: EthContributorConfig[],
|
||||
potentialBuyers: EthBuyerConfig[]
|
||||
potentialBuyers: EthBuyerConfig[],
|
||||
denominationDecimals: number
|
||||
): Promise<AcceptedToken[]> {
|
||||
const acceptedTokens: AcceptedToken[] = [];
|
||||
|
||||
// create map to record which accepted tokens have been created
|
||||
const tokenMap: Map<number, string[]> = new Map<number, string[]>();
|
||||
|
||||
// eth conductor provider
|
||||
const ethProvider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
|
||||
|
||||
for (const buyer of potentialBuyers) {
|
||||
const info = await getOriginalAssetEth(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
|
@ -163,11 +179,37 @@ export async function makeAcceptedTokensFromConfigs(
|
|||
}
|
||||
}
|
||||
|
||||
// normalize the conversion rates here
|
||||
const token = ERC20__factory.connect(
|
||||
contributor.collateralAddress,
|
||||
contributor.wallet
|
||||
);
|
||||
const acceptedTokenDecimals = await token.decimals();
|
||||
|
||||
// compute the normalized conversionRate
|
||||
const acceptedTokenDecimalsOnConductor =
|
||||
await getAcceptedTokenDecimalsOnConductor(
|
||||
buyer.chainId,
|
||||
ETH_TOKEN_SALE_CONDUCTOR_CHAIN_ID as ChainId,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
buyer.wallet.provider,
|
||||
ethProvider,
|
||||
buyer.collateralAddress,
|
||||
acceptedTokenDecimals
|
||||
);
|
||||
const normalizedConversionRate = await normalizeConversionRate(
|
||||
denominationDecimals,
|
||||
acceptedTokenDecimals,
|
||||
contributor.conversionRate,
|
||||
acceptedTokenDecimalsOnConductor
|
||||
);
|
||||
|
||||
acceptedTokens.push(
|
||||
makeAcceptedToken(
|
||||
buyer.chainId,
|
||||
buyer.collateralAddress,
|
||||
contributor.conversionRate
|
||||
normalizedConversionRate
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -396,7 +438,9 @@ export async function transferFromEthNativeAndRedeemOnEth(
|
|||
export async function createSaleOnEthAndGetVaa(
|
||||
seller: ethers.Wallet,
|
||||
chainId: ChainId,
|
||||
localTokenAddress: string,
|
||||
tokenAddress: string,
|
||||
tokenChain: ChainId,
|
||||
amount: ethers.BigNumberish,
|
||||
minRaise: ethers.BigNumberish,
|
||||
maxRaise: ethers.BigNumberish,
|
||||
|
@ -407,7 +451,9 @@ export async function createSaleOnEthAndGetVaa(
|
|||
// create
|
||||
const receipt = await createSaleOnEth(
|
||||
ETH_TOKEN_SALE_CONDUCTOR_ADDRESS,
|
||||
localTokenAddress,
|
||||
tokenAddress,
|
||||
tokenChain,
|
||||
amount,
|
||||
minRaise,
|
||||
maxRaise,
|
||||
|
@ -590,7 +636,9 @@ export async function makeSaleStartFromLastBlock(
|
|||
export async function createSaleOnEthAndInit(
|
||||
conductorConfig: EthContributorConfig,
|
||||
contributorConfigs: EthContributorConfig[],
|
||||
localTokenAddress: string,
|
||||
saleTokenAddress: string,
|
||||
saleTokenChain: ChainId,
|
||||
tokenAmount: string,
|
||||
minRaise: string,
|
||||
maxRaise: string,
|
||||
|
@ -609,10 +657,12 @@ export async function createSaleOnEthAndInit(
|
|||
const saleInitVaa = await createSaleOnEthAndGetVaa(
|
||||
conductorConfig.wallet,
|
||||
conductorConfig.chainId,
|
||||
localTokenAddress,
|
||||
saleTokenAddress,
|
||||
saleTokenChain,
|
||||
ethers.utils.parseUnits(tokenAmount, decimals),
|
||||
ethers.utils.parseUnits(minRaise),
|
||||
ethers.utils.parseUnits(maxRaise),
|
||||
ethers.utils.parseUnits(minRaise, DENOMINATION_DECIMALS),
|
||||
ethers.utils.parseUnits(maxRaise, DENOMINATION_DECIMALS),
|
||||
saleStart,
|
||||
saleEnd,
|
||||
acceptedTokens
|
||||
|
@ -620,7 +670,10 @@ export async function createSaleOnEthAndInit(
|
|||
|
||||
console.info("Sale Init VAA:", Buffer.from(saleInitVaa).toString("hex"));
|
||||
|
||||
const saleInit = await parseSaleInit(saleInitVaa);
|
||||
const saleInitPayload = await extractVaaPayload(saleInitVaa);
|
||||
const saleInit = await parseSaleInit(saleInitPayload);
|
||||
|
||||
console.log(saleInit);
|
||||
|
||||
{
|
||||
const receipts = await Promise.all(
|
||||
|
@ -715,6 +768,76 @@ export async function attestAndCollectContributions(
|
|||
return;
|
||||
}
|
||||
|
||||
export interface SealSaleResult {
|
||||
sale: ConductorSale;
|
||||
transferVaas: Map<ChainId, Uint8Array[]>;
|
||||
sealSaleVaa: Uint8Array;
|
||||
}
|
||||
|
||||
export async function sealSaleAndParseReceiptOnEth(
|
||||
conductorAddress: string,
|
||||
saleId: ethers.BigNumberish,
|
||||
coreBridgeAddress: string,
|
||||
tokenBridgeAddress: string,
|
||||
wormholeHosts: string[],
|
||||
extraGrpcOpts: any = {},
|
||||
wallet: ethers.Wallet
|
||||
): Promise<SealSaleResult> {
|
||||
const receipt = await sealSaleOnEth(conductorAddress, saleId, wallet);
|
||||
|
||||
const sale = await getSaleFromConductorOnEth(
|
||||
conductorAddress,
|
||||
wallet.provider,
|
||||
saleId
|
||||
);
|
||||
const emitterChain = sale.tokenChain as ChainId;
|
||||
|
||||
const sequences = parseSequencesFromLogEth(receipt, coreBridgeAddress);
|
||||
const sealSaleSequence = sequences.pop();
|
||||
if (sealSaleSequence === undefined) {
|
||||
throw Error("no vaa sequences found");
|
||||
}
|
||||
|
||||
const result = await getSignedVAAWithRetry(
|
||||
wormholeHosts,
|
||||
emitterChain,
|
||||
getEmitterAddressEth(conductorAddress),
|
||||
sealSaleSequence,
|
||||
extraGrpcOpts
|
||||
);
|
||||
const sealSaleVaa = result.vaaBytes;
|
||||
|
||||
console.info("Seal Sale VAA:", Buffer.from(sealSaleVaa).toString("hex"));
|
||||
|
||||
// doing it serially for ease of putting into the map
|
||||
const mapped = new Map<ChainId, Uint8Array[]>();
|
||||
for (const sequence of sequences) {
|
||||
const result = await getSignedVAAWithRetry(
|
||||
wormholeHosts,
|
||||
emitterChain,
|
||||
getEmitterAddressEth(tokenBridgeAddress),
|
||||
sequence,
|
||||
extraGrpcOpts
|
||||
);
|
||||
const signedVaa = result.vaaBytes;
|
||||
const vaaPayload = await extractVaaPayload(signedVaa);
|
||||
const chainId = await getTargetChainIdFromTransferVaa(vaaPayload);
|
||||
|
||||
const signedVaas = mapped.get(chainId);
|
||||
if (signedVaas === undefined) {
|
||||
mapped.set(chainId, [signedVaa]);
|
||||
} else {
|
||||
signedVaas.push(signedVaa);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sale: sale,
|
||||
transferVaas: mapped,
|
||||
sealSaleVaa: sealSaleVaa,
|
||||
};
|
||||
}
|
||||
|
||||
async function _sealOrAbortSaleOnEth(
|
||||
saleInit: SaleInit,
|
||||
conductorConfig: EthContributorConfig,
|
||||
|
@ -822,8 +945,8 @@ export async function sealSaleAtContributors(
|
|||
}
|
||||
|
||||
const signedVaa = saleResult.sealSaleVaa;
|
||||
|
||||
const saleSealed = await parseSaleSealed(signedVaa);
|
||||
const vaaPayload = await extractVaaPayload(signedVaa);
|
||||
const saleSealed = await parseSaleSealed(vaaPayload);
|
||||
|
||||
// first check if the sale token has been attested
|
||||
{
|
||||
|
@ -841,7 +964,8 @@ export async function sealSaleAtContributors(
|
|||
return saleSealedOnEth(
|
||||
TOKEN_SALE_CONTRIBUTOR_ADDRESSES.get(config.chainId)!,
|
||||
signedVaa,
|
||||
config.wallet
|
||||
config.wallet,
|
||||
saleInit.saleId
|
||||
);
|
||||
}
|
||||
)
|
||||
|
@ -856,6 +980,8 @@ export async function abortSaleAtContributors(
|
|||
contributorConfigs: EthContributorConfig[]
|
||||
) {
|
||||
const signedVaa = saleResult.sealSaleVaa;
|
||||
const vaaPayload = await extractVaaPayload(signedVaa);
|
||||
const saleId = await getSaleIdFromIccoVaa(vaaPayload);
|
||||
|
||||
{
|
||||
const receipts = await Promise.all(
|
||||
|
@ -864,7 +990,8 @@ export async function abortSaleAtContributors(
|
|||
return saleAbortedOnEth(
|
||||
TOKEN_SALE_CONTRIBUTOR_ADDRESSES.get(config.chainId)!,
|
||||
signedVaa,
|
||||
config.wallet
|
||||
config.wallet,
|
||||
saleId
|
||||
);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -13,10 +13,7 @@ import {
|
|||
getContributorContractOnEth,
|
||||
getSaleFromConductorOnEth,
|
||||
getSaleFromContributorOnEth,
|
||||
registerChainOnEth,
|
||||
sealSaleOnEth,
|
||||
ConductorSale,
|
||||
makeAcceptedToken,
|
||||
} from "../..";
|
||||
import {
|
||||
BSC_NODE_URL,
|
||||
|
@ -29,9 +26,9 @@ import {
|
|||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
ETH_TOKEN_SALE_CONDUCTOR_ADDRESS,
|
||||
TOKEN_SALE_CONTRIBUTOR_ADDRESSES,
|
||||
KYC_PRIVATE_KEYS,
|
||||
WBNB_ADDRESS,
|
||||
WETH_ADDRESS,
|
||||
DENOMINATION_DECIMALS,
|
||||
} from "./consts";
|
||||
import {
|
||||
EthBuyerConfig,
|
||||
|
@ -41,7 +38,6 @@ import {
|
|||
waitForSaleToStart,
|
||||
makeAcceptedTokensFromConfigs,
|
||||
sealOrAbortSaleOnEth,
|
||||
contributeAllTokensOnEth,
|
||||
secureContributeAllTokensOnEth,
|
||||
getCollateralBalancesOnEth,
|
||||
claimAllAllocationsOnEth,
|
||||
|
@ -63,7 +59,6 @@ import {
|
|||
abortSaleEarlyAtContributors,
|
||||
abortSaleEarlyAtConductor,
|
||||
deployTokenOnEth,
|
||||
signContribution,
|
||||
} from "./helpers";
|
||||
|
||||
// ten minutes? nobody got time for that
|
||||
|
@ -228,7 +223,8 @@ describe("Integration Tests", () => {
|
|||
// we need to set up all of the accepted tokens (natives plus their wrapped versions)
|
||||
const acceptedTokens = await makeAcceptedTokensFromConfigs(
|
||||
contributorConfigs,
|
||||
buyers
|
||||
buyers,
|
||||
DENOMINATION_DECIMALS
|
||||
);
|
||||
|
||||
// add fake terra and solana tokens to acceptedTokens
|
||||
|
@ -255,6 +251,7 @@ describe("Integration Tests", () => {
|
|||
conductorConfig.wallet
|
||||
);
|
||||
|
||||
const tokenChain = CHAIN_ID_ETH; // needed to check if token is native or not
|
||||
const tokenAmount = "1";
|
||||
const minRaise = "10"; // eth units
|
||||
const maxRaise = "14";
|
||||
|
@ -265,10 +262,16 @@ describe("Integration Tests", () => {
|
|||
contributorConfigs
|
||||
);
|
||||
|
||||
// the token being sold is on eth
|
||||
// which means it has the same local token address
|
||||
const localTokenAddress = tokenAddress;
|
||||
|
||||
const saleInit = await createSaleOnEthAndInit(
|
||||
conductorConfig,
|
||||
contributorConfigs,
|
||||
localTokenAddress,
|
||||
tokenAddress,
|
||||
tokenChain,
|
||||
tokenAmount,
|
||||
minRaise,
|
||||
maxRaise,
|
||||
|
@ -278,8 +281,6 @@ describe("Integration Tests", () => {
|
|||
);
|
||||
console.log("Parsed Sale Init:", saleInit);
|
||||
|
||||
console.log("Parsed Sale Init:", saleInit);
|
||||
|
||||
// balance check
|
||||
{
|
||||
const buyerBalancesBefore = await getCollateralBalancesOnEth(
|
||||
|
@ -687,7 +688,8 @@ describe("Integration Tests", () => {
|
|||
// we need to set up all of the accepted tokens (natives plus their wrapped versions)
|
||||
const acceptedTokens = await makeAcceptedTokensFromConfigs(
|
||||
contributorConfigs,
|
||||
buyers
|
||||
buyers,
|
||||
DENOMINATION_DECIMALS
|
||||
);
|
||||
|
||||
// conductor lives in CHAIN_ID_ETH
|
||||
|
@ -702,6 +704,7 @@ describe("Integration Tests", () => {
|
|||
conductorConfig.wallet
|
||||
);
|
||||
|
||||
const tokenChain = CHAIN_ID_ETH; // needed to check if token is native or not
|
||||
const tokenAmount = "1";
|
||||
const minRaise = "10"; // eth units
|
||||
const maxRaise = "100";
|
||||
|
@ -712,10 +715,16 @@ describe("Integration Tests", () => {
|
|||
contributorConfigs
|
||||
);
|
||||
|
||||
// the token being sold is on eth
|
||||
// which means it has the same local token address
|
||||
const localTokenAddress = tokenAddress;
|
||||
|
||||
const saleInit = await createSaleOnEthAndInit(
|
||||
conductorConfig,
|
||||
contributorConfigs,
|
||||
localTokenAddress,
|
||||
tokenAddress,
|
||||
tokenChain,
|
||||
tokenAmount,
|
||||
minRaise,
|
||||
maxRaise,
|
||||
|
@ -924,7 +933,8 @@ describe("Integration Tests", () => {
|
|||
// we need to set up all of the accepted tokens
|
||||
const acceptedTokens = await makeAcceptedTokensFromConfigs(
|
||||
contributorConfigs,
|
||||
buyers
|
||||
buyers,
|
||||
DENOMINATION_DECIMALS
|
||||
);
|
||||
|
||||
// conductor lives in CHAIN_ID_ETH
|
||||
|
@ -939,6 +949,7 @@ describe("Integration Tests", () => {
|
|||
conductorConfig.wallet
|
||||
);
|
||||
|
||||
const tokenChain = CHAIN_ID_ETH; // needed to check if token is native or not
|
||||
const tokenAmount = "1";
|
||||
const minRaise = "10"; // eth units
|
||||
const maxRaise = "100";
|
||||
|
@ -956,10 +967,16 @@ describe("Integration Tests", () => {
|
|||
contributorConfigs
|
||||
);
|
||||
|
||||
// the token being sold is on eth
|
||||
// which means it has the same local token address
|
||||
const localTokenAddress = tokenAddress;
|
||||
|
||||
const saleInit = await createSaleOnEthAndInit(
|
||||
conductorConfig,
|
||||
contributorConfigs,
|
||||
localTokenAddress,
|
||||
tokenAddress,
|
||||
tokenChain,
|
||||
tokenAmount,
|
||||
minRaise,
|
||||
maxRaise,
|
||||
|
|
|
@ -47,7 +47,12 @@ export async function contributeOnEth(
|
|||
const receipt = await tx.wait();
|
||||
}
|
||||
|
||||
const tx = await contributor.contribute(saleId, tokenIndex, amount, signature);
|
||||
const tx = await contributor.contribute(
|
||||
saleId,
|
||||
tokenIndex,
|
||||
amount,
|
||||
signature
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,8 @@ import {
|
|||
ERC20__factory,
|
||||
getForeignAssetEth,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import { Conductor__factory } from "../ethers-contracts";
|
||||
import { nativeToUint8Array } from "./misc";
|
||||
import { Conductor__factory } from "../ethers-contracts";
|
||||
import { AcceptedToken, SaleInit, makeAcceptedToken, Raise } from "./structs";
|
||||
|
||||
export { AcceptedToken, SaleInit };
|
||||
|
@ -39,7 +38,9 @@ export async function makeAcceptedWrappedTokenEth(
|
|||
|
||||
export async function createSaleOnEth(
|
||||
conductorAddress: string,
|
||||
localTokenAddress: string,
|
||||
tokenAddress: string,
|
||||
tokenChain: ChainId,
|
||||
amount: ethers.BigNumberish,
|
||||
minRaise: ethers.BigNumberish,
|
||||
maxRaise: ethers.BigNumberish,
|
||||
|
@ -52,14 +53,18 @@ export async function createSaleOnEth(
|
|||
): Promise<ethers.ContractReceipt> {
|
||||
// approve first
|
||||
{
|
||||
const token = ERC20__factory.connect(tokenAddress, wallet);
|
||||
const token = ERC20__factory.connect(localTokenAddress, wallet);
|
||||
const tx = await token.approve(conductorAddress, amount);
|
||||
const receipt = await tx.wait();
|
||||
}
|
||||
|
||||
// convert address string to bytes32
|
||||
const tokenAddressBytes32 = nativeToUint8Array(tokenAddress, tokenChain);
|
||||
|
||||
// create a struct to pass to createSale
|
||||
const raise: Raise = {
|
||||
token: tokenAddress,
|
||||
token: tokenAddressBytes32,
|
||||
tokenChain: tokenChain,
|
||||
tokenAmount: amount,
|
||||
minRaise: minRaise,
|
||||
maxRaise: maxRaise,
|
||||
|
|
|
@ -18,6 +18,7 @@ export async function getSaleFromConductorOnEth(
|
|||
saleId: sale.saleID,
|
||||
tokenAddress: sale.tokenAddress,
|
||||
tokenChain: sale.tokenChain,
|
||||
localTokenAddress: sale.localTokenAddress,
|
||||
tokenAmount: sale.tokenAmount,
|
||||
minRaise: sale.minRaise,
|
||||
maxRaise: sale.maxRaise,
|
||||
|
|
|
@ -1,26 +1,12 @@
|
|||
import { ethers } from "ethers";
|
||||
|
||||
import { Contributor__factory } from "../ethers-contracts";
|
||||
import { getSaleFromContributorOnEth } from "./getters";
|
||||
import { parseSaleInit } from "./signedVaa";
|
||||
|
||||
export async function initSaleOnEth(
|
||||
contributorAddress: string,
|
||||
signedVaa: Uint8Array,
|
||||
wallet: ethers.Wallet
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
const saleInit = await parseSaleInit(signedVaa);
|
||||
|
||||
// check if sale exists already
|
||||
const sale = await getSaleFromContributorOnEth(
|
||||
contributorAddress,
|
||||
wallet.provider,
|
||||
saleInit.saleId
|
||||
);
|
||||
if (!ethers.BigNumber.from(sale.saleId).eq("0")) {
|
||||
throw Error("sale already exists");
|
||||
}
|
||||
|
||||
const contributor = Contributor__factory.connect(contributorAddress, wallet);
|
||||
const tx = await contributor.initSale(signedVaa);
|
||||
return tx.wait();
|
||||
|
|
|
@ -5,7 +5,12 @@ import {
|
|||
IWETH__factory,
|
||||
hexToUint8Array,
|
||||
nativeToHexString,
|
||||
getForeignAssetEth,
|
||||
getOriginalAssetEth,
|
||||
hexToNativeString,
|
||||
uint8ArrayToNative,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { parseUnits } from "ethers/lib/utils";
|
||||
|
||||
export function nativeToUint8Array(
|
||||
address: string,
|
||||
|
@ -44,3 +49,94 @@ export async function getErc20Balance(
|
|||
const token = ERC20__factory.connect(tokenAddress, provider);
|
||||
return token.balanceOf(walletAddress);
|
||||
}
|
||||
|
||||
export async function getErc20Decimals(
|
||||
provider: ethers.providers.Provider,
|
||||
tokenAddress: string
|
||||
): Promise<number> {
|
||||
const token = ERC20__factory.connect(tokenAddress, provider);
|
||||
return token.decimals();
|
||||
}
|
||||
|
||||
export async function getAcceptedTokenDecimalsOnConductor(
|
||||
contributorChain: ChainId,
|
||||
conductorChain: ChainId,
|
||||
contributorTokenBridgeAddress: string,
|
||||
conductorTokenBridgeAddress: string,
|
||||
contributorProvider: ethers.providers.Provider,
|
||||
conductorProvider: ethers.providers.Provider,
|
||||
contributedTokenAddress: string,
|
||||
condtributedTokenDecimals: number
|
||||
): Promise<number> {
|
||||
if (contributorChain !== conductorChain) {
|
||||
// fetch the original token address for contributed token
|
||||
const originalToken = await getOriginalAssetEth(
|
||||
contributorTokenBridgeAddress,
|
||||
contributorProvider,
|
||||
contributedTokenAddress,
|
||||
contributorChain
|
||||
);
|
||||
let tokenDecimalsOnConductor;
|
||||
if (originalToken.chainId === conductorChain) {
|
||||
// get the original decimals
|
||||
const nativeConductorAddress = uint8ArrayToNative(
|
||||
originalToken.assetAddress,
|
||||
originalToken.chainId
|
||||
);
|
||||
|
||||
if (nativeConductorAddress !== undefined) {
|
||||
// fetch the token decimals on the conductor chain
|
||||
tokenDecimalsOnConductor = await getErc20Decimals(
|
||||
conductorProvider,
|
||||
nativeConductorAddress
|
||||
);
|
||||
} else {
|
||||
throw Error("Native conductor address is undefined");
|
||||
}
|
||||
} else {
|
||||
// get the wrapped versionals decimals on eth
|
||||
const conductorWrappedToken = await getForeignAssetEth(
|
||||
conductorTokenBridgeAddress,
|
||||
conductorProvider,
|
||||
originalToken.chainId,
|
||||
originalToken.assetAddress
|
||||
);
|
||||
|
||||
if (conductorWrappedToken !== null) {
|
||||
// fetch the token decimals on the conductor chain
|
||||
tokenDecimalsOnConductor = await getErc20Decimals(
|
||||
conductorProvider,
|
||||
conductorWrappedToken
|
||||
);
|
||||
} else {
|
||||
throw Error("Wrapped conductor address is null");
|
||||
}
|
||||
}
|
||||
return tokenDecimalsOnConductor;
|
||||
} else {
|
||||
return condtributedTokenDecimals;
|
||||
}
|
||||
}
|
||||
|
||||
export async function normalizeConversionRate(
|
||||
denominationDecimals: number,
|
||||
acceptedTokenDecimals: number,
|
||||
conversionRate: string,
|
||||
conductorDecimals: number
|
||||
): Promise<ethers.BigNumberish> {
|
||||
const precision = 18;
|
||||
const normDecimals = denominationDecimals + precision - acceptedTokenDecimals;
|
||||
let normalizedConversionRate = parseUnits(conversionRate, normDecimals);
|
||||
|
||||
if (acceptedTokenDecimals === conductorDecimals) {
|
||||
return normalizedConversionRate;
|
||||
} else if (acceptedTokenDecimals > conductorDecimals) {
|
||||
return normalizedConversionRate.div(
|
||||
parseUnits("1", acceptedTokenDecimals - conductorDecimals)
|
||||
);
|
||||
} else {
|
||||
return normalizedConversionRate.mul(
|
||||
parseUnits("1", conductorDecimals - acceptedTokenDecimals)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@ export async function registerChainOnEth(
|
|||
conductorAddress: string,
|
||||
contributorChain: ChainId,
|
||||
contributorAddress: Uint8Array,
|
||||
contributorCustodyAddress: Uint8Array,
|
||||
wallet: ethers.Wallet
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
const contributor = Conductor__factory.connect(conductorAddress, wallet);
|
||||
const tx = await contributor.registerChain(
|
||||
contributorChain,
|
||||
contributorAddress
|
||||
contributorAddress,
|
||||
contributorCustodyAddress
|
||||
);
|
||||
return tx.wait();
|
||||
}
|
||||
|
|
|
@ -2,15 +2,13 @@ import { ethers } from "ethers";
|
|||
|
||||
import { Contributor__factory } from "../ethers-contracts";
|
||||
import { getSaleFromContributorOnEth } from "./getters";
|
||||
import { getSaleIdFromIccoVaa } from "./signedVaa";
|
||||
|
||||
export async function saleAbortedOnEth(
|
||||
contributorAddress: string,
|
||||
signedVaa: Uint8Array,
|
||||
wallet: ethers.Wallet
|
||||
wallet: ethers.Wallet,
|
||||
saleId: ethers.BigNumberish
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
const saleId = await getSaleIdFromIccoVaa(signedVaa);
|
||||
|
||||
// save on gas by checking the state of the sale
|
||||
const sale = await getSaleFromContributorOnEth(
|
||||
contributorAddress,
|
||||
|
|
|
@ -2,15 +2,13 @@ import { ethers } from "ethers";
|
|||
|
||||
import { Contributor__factory } from "../ethers-contracts";
|
||||
import { getSaleFromContributorOnEth } from "./getters";
|
||||
import { getSaleIdFromIccoVaa } from "./signedVaa";
|
||||
|
||||
export async function saleSealedOnEth(
|
||||
contributorAddress: string,
|
||||
signedVaa: Uint8Array,
|
||||
wallet: ethers.Wallet
|
||||
wallet: ethers.Wallet,
|
||||
saleId: ethers.BigNumberish
|
||||
): Promise<ethers.ContractReceipt> {
|
||||
const saleId = await getSaleIdFromIccoVaa(signedVaa);
|
||||
|
||||
// save on gas by checking the state of the sale
|
||||
const sale = await getSaleFromContributorOnEth(
|
||||
contributorAddress,
|
||||
|
|
|
@ -1,13 +1,4 @@
|
|||
import { ethers } from "ethers";
|
||||
import {
|
||||
ChainId,
|
||||
getEmitterAddressEth,
|
||||
getSignedVAA,
|
||||
getSignedVAAWithRetry,
|
||||
parseSequencesFromLogEth,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
|
||||
import { ConductorSale, getTargetChainIdFromTransferVaa } from ".";
|
||||
import { Conductor__factory } from "../ethers-contracts";
|
||||
import { getSaleFromConductorOnEth } from "./getters";
|
||||
|
||||
|
@ -33,72 +24,3 @@ export async function sealSaleOnEth(
|
|||
const tx = await conductor.sealSale(saleId);
|
||||
return tx.wait();
|
||||
}
|
||||
|
||||
export interface SealSaleResult {
|
||||
sale: ConductorSale;
|
||||
transferVaas: Map<ChainId, Uint8Array[]>;
|
||||
sealSaleVaa: Uint8Array;
|
||||
}
|
||||
|
||||
export async function sealSaleAndParseReceiptOnEth(
|
||||
conductorAddress: string,
|
||||
saleId: ethers.BigNumberish,
|
||||
coreBridgeAddress: string,
|
||||
tokenBridgeAddress: string,
|
||||
wormholeHosts: string[],
|
||||
extraGrpcOpts: any = {},
|
||||
wallet: ethers.Wallet
|
||||
): Promise<SealSaleResult> {
|
||||
const receipt = await sealSaleOnEth(conductorAddress, saleId, wallet);
|
||||
|
||||
const sale = await getSaleFromConductorOnEth(
|
||||
conductorAddress,
|
||||
wallet.provider,
|
||||
saleId
|
||||
);
|
||||
const emitterChain = sale.tokenChain as ChainId;
|
||||
|
||||
const sequences = parseSequencesFromLogEth(receipt, coreBridgeAddress);
|
||||
const sealSaleSequence = sequences.pop();
|
||||
if (sealSaleSequence === undefined) {
|
||||
throw Error("no vaa sequences found");
|
||||
}
|
||||
|
||||
const result = await getSignedVAAWithRetry(
|
||||
wormholeHosts,
|
||||
emitterChain,
|
||||
getEmitterAddressEth(conductorAddress),
|
||||
sealSaleSequence,
|
||||
extraGrpcOpts
|
||||
);
|
||||
const sealSaleVaa = result.vaaBytes;
|
||||
|
||||
console.info("Seal Sale VAA:", Buffer.from(sealSaleVaa).toString("hex"));
|
||||
|
||||
// doing it serially for ease of putting into the map
|
||||
const mapped = new Map<ChainId, Uint8Array[]>();
|
||||
for (const sequence of sequences) {
|
||||
const result = await getSignedVAAWithRetry(
|
||||
wormholeHosts,
|
||||
emitterChain,
|
||||
getEmitterAddressEth(tokenBridgeAddress),
|
||||
sequence,
|
||||
extraGrpcOpts
|
||||
);
|
||||
const signedVaa = result.vaaBytes;
|
||||
const chainId = await getTargetChainIdFromTransferVaa(signedVaa);
|
||||
|
||||
const signedVaas = mapped.get(chainId);
|
||||
if (signedVaas === undefined) {
|
||||
mapped.set(chainId, [signedVaa]);
|
||||
} else {
|
||||
signedVaas.push(signedVaa);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sale: sale,
|
||||
transferVaas: mapped,
|
||||
sealSaleVaa: sealSaleVaa,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,31 +10,19 @@ import { AcceptedToken, Allocation, SaleInit, SaleSealed } from "./structs";
|
|||
const VAA_PAYLOAD_NUM_ACCEPTED_TOKENS = 227;
|
||||
const VAA_PAYLOAD_ACCEPTED_TOKEN_BYTES_LENGTH = 50;
|
||||
|
||||
export async function extractVaaPayload(
|
||||
signedVaa: Uint8Array
|
||||
): Promise<Uint8Array> {
|
||||
const { parse_vaa } = await importCoreWasm();
|
||||
const { payload: payload } = parse_vaa(signedVaa);
|
||||
return payload;
|
||||
}
|
||||
|
||||
export async function getSaleIdFromIccoVaa(
|
||||
signedVaa: Uint8Array
|
||||
payload: Uint8Array
|
||||
): Promise<ethers.BigNumberish> {
|
||||
const payload = await extractVaaPayload(signedVaa);
|
||||
return ethers.BigNumber.from(payload.slice(1, 33)).toString();
|
||||
}
|
||||
|
||||
export async function getTargetChainIdFromTransferVaa(
|
||||
signedVaa: Uint8Array
|
||||
payload: Uint8Array
|
||||
): Promise<ChainId> {
|
||||
const payload = await extractVaaPayload(signedVaa);
|
||||
return Buffer.from(payload).readUInt16BE(99) as ChainId;
|
||||
}
|
||||
|
||||
export async function parseSaleInit(signedVaa: Uint8Array): Promise<SaleInit> {
|
||||
const payload = await extractVaaPayload(signedVaa);
|
||||
|
||||
export async function parseSaleInit(payload: Uint8Array): Promise<SaleInit> {
|
||||
const buffer = Buffer.from(payload);
|
||||
|
||||
const numAcceptedTokens = buffer.readUInt8(VAA_PAYLOAD_NUM_ACCEPTED_TOKENS);
|
||||
|
@ -93,10 +81,8 @@ const VAA_PAYLOAD_NUM_ALLOCATIONS = 33;
|
|||
const VAA_PAYLOAD_ALLOCATION_BYTES_LENGTH = 65;
|
||||
|
||||
export async function parseSaleSealed(
|
||||
signedVaa: Uint8Array
|
||||
payload: Uint8Array
|
||||
): Promise<SaleSealed> {
|
||||
const payload = await extractVaaPayload(signedVaa);
|
||||
|
||||
const buffer = Buffer.from(payload);
|
||||
|
||||
const numAllocations = buffer.readUInt8(VAA_PAYLOAD_NUM_ALLOCATIONS);
|
||||
|
|
|
@ -4,7 +4,8 @@ import { ChainId } from "@certusone/wormhole-sdk";
|
|||
import { nativeToUint8Array } from "./misc";
|
||||
|
||||
export interface Raise {
|
||||
token: string;
|
||||
token: ethers.BytesLike;
|
||||
tokenChain: ChainId;
|
||||
tokenAmount: ethers.BigNumberish;
|
||||
minRaise: ethers.BigNumberish;
|
||||
maxRaise: ethers.BigNumberish;
|
||||
|
@ -37,6 +38,7 @@ export interface Sale {
|
|||
|
||||
export interface ConductorSale extends Sale {
|
||||
initiator: string;
|
||||
localTokenAddress: string;
|
||||
contributions: ethers.BigNumberish[];
|
||||
contributionsCollected: boolean[];
|
||||
refundIsClaimed: boolean;
|
||||
|
@ -83,11 +85,11 @@ export interface SaleSealed {
|
|||
export function makeAcceptedToken(
|
||||
chainId: ChainId,
|
||||
address: string,
|
||||
conversion: string
|
||||
conversion: ethers.BigNumberish
|
||||
): AcceptedToken {
|
||||
return {
|
||||
tokenChain: chainId,
|
||||
tokenAddress: nativeToUint8Array(address, chainId),
|
||||
conversionRate: ethers.utils.parseUnits(conversion), // always 1e18
|
||||
conversionRate: conversion,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue