Evm upgrades and tests (#38)
* Add event testing to icco.js * Add testnet upgrade script * Add wormhole fee to tests * Generate Solana specific VAAs in Conductor.sol * Add solana contract to testnet.json Co-authored-by: Drew <dsterioti@users.noreply.github.com>
This commit is contained in:
parent
9c492e8d3a
commit
19910c7239
|
@ -21,36 +21,25 @@ import "../shared/ICCOStructs.sol";
|
|||
* @notice This contract manages cross-chain token sales. It uses the wormhole
|
||||
* core messaging layer to communicate token sale information to linked Contributor
|
||||
* contracts. For successful sales, it uses the wormhole token bridge to
|
||||
* send the sale token to contributor contracts in exchange for contributed funds.
|
||||
* send the sale token to Contributor contracts in exchange for contributed funds.
|
||||
* For unsuccessful sales, this contract will return the sale tokens to a
|
||||
* specified recipient address.
|
||||
*/
|
||||
contract Conductor is ConductorGovernance, ReentrancyGuard {
|
||||
/**
|
||||
* @dev createSale serves to initialize a cross-chain token sale and disseminate
|
||||
* information about the sale to registered Contributor contracts.
|
||||
* - it validates sale parameters passed in by the client
|
||||
* - it saves a copy of the sale in contract storage
|
||||
* - it encodes and disseminates sale information to contributor contracts via wormhole
|
||||
*/
|
||||
function createSale(
|
||||
ICCOStructs.Raise memory raise,
|
||||
ICCOStructs.Token[] memory acceptedTokens
|
||||
) public payable nonReentrant returns (
|
||||
uint saleId,
|
||||
uint wormholeSequence
|
||||
) {
|
||||
/// validate sale parameters from client
|
||||
require(block.timestamp < raise.saleStart, "sale start must be in the future");
|
||||
require(raise.saleStart < raise.saleEnd, "sale end must be after sale start");
|
||||
/// set timestamp cap for non-evm contributor contracts
|
||||
require(raise.saleStart <= 2**63-1, "saleStart too far in the future");
|
||||
require(raise.tokenAmount > 0, "amount must be > 0");
|
||||
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");
|
||||
/// @dev create dynamic storage for accepted solana tokens
|
||||
ICCOStructs.SolanaToken[] solanaAcceptedTokens;
|
||||
|
||||
/// grab the local token address (address of sale token on conductor chain)
|
||||
/**
|
||||
* @dev receiveSaleToken serves to take custody of the sale token and
|
||||
* returns information about the token on the Conductor chain.
|
||||
* - it transfers the sale tokens to this contract
|
||||
* - it finds the address of the token on the Conductor chain
|
||||
* - it finds the ERC20 token decimals of the token on the Conductor chain
|
||||
*/
|
||||
function receiveSaleToken(
|
||||
ICCOStructs.Raise memory raise
|
||||
) internal returns (address, uint8) {
|
||||
/// @dev grab the local token address (address of sale token on conductor chain)
|
||||
address localTokenAddress;
|
||||
if (raise.tokenChain == chainId()) {
|
||||
localTokenAddress = address(uint160(uint256(raise.token)));
|
||||
|
@ -60,42 +49,68 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
require(localTokenAddress != address(0), "wrapped address not found on this chain");
|
||||
}
|
||||
|
||||
uint8 localTokenDecimals;
|
||||
{ /// avoid stack too deep errors
|
||||
/**
|
||||
* @dev Fetch the sale token decimals and place in the SaleInit struct.
|
||||
* The contributors need to know this to scale allocations on non-evm chains.
|
||||
*/
|
||||
(,bytes memory queriedDecimals) = localTokenAddress.staticcall(
|
||||
abi.encodeWithSignature("decimals()")
|
||||
);
|
||||
localTokenDecimals = abi.decode(queriedDecimals, (uint8));
|
||||
/**
|
||||
* @dev Fetch the sale token decimals and place in the SaleInit struct.
|
||||
* The Contributors need to know this to scale allocations on non-evm chains.
|
||||
*/
|
||||
(,bytes memory queriedDecimals) = localTokenAddress.staticcall(
|
||||
abi.encodeWithSignature("decimals()")
|
||||
);
|
||||
uint8 localTokenDecimals = abi.decode(queriedDecimals, (uint8));
|
||||
|
||||
/// query own token balance before transfer
|
||||
(,bytes memory queriedBalanceBefore) = localTokenAddress.staticcall(
|
||||
abi.encodeWithSelector(IERC20.balanceOf.selector,
|
||||
address(this))
|
||||
);
|
||||
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
|
||||
/// query own token balance before transfer
|
||||
(,bytes memory queriedBalanceBefore) = localTokenAddress.staticcall(
|
||||
abi.encodeWithSelector(IERC20.balanceOf.selector,
|
||||
address(this))
|
||||
);
|
||||
uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));
|
||||
|
||||
/// deposit sale tokens
|
||||
SafeERC20.safeTransferFrom(
|
||||
IERC20(localTokenAddress),
|
||||
msg.sender,
|
||||
address(this),
|
||||
raise.tokenAmount
|
||||
);
|
||||
/// deposit sale tokens
|
||||
SafeERC20.safeTransferFrom(
|
||||
IERC20(localTokenAddress),
|
||||
msg.sender,
|
||||
address(this),
|
||||
raise.tokenAmount
|
||||
);
|
||||
|
||||
/// query own token balance after transfer
|
||||
(,bytes memory queriedBalanceAfter) = localTokenAddress.staticcall(
|
||||
abi.encodeWithSelector(IERC20.balanceOf.selector,
|
||||
address(this))
|
||||
);
|
||||
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
|
||||
/// query own token balance after transfer
|
||||
(,bytes memory queriedBalanceAfter) = localTokenAddress.staticcall(
|
||||
abi.encodeWithSelector(IERC20.balanceOf.selector,
|
||||
address(this))
|
||||
);
|
||||
uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));
|
||||
|
||||
/// revert if token has fee
|
||||
require(raise.tokenAmount == balanceAfter - balanceBefore, "fee-on-transfer tokens are not supported");
|
||||
}
|
||||
/// revert if token has fee
|
||||
require(raise.tokenAmount == balanceAfter - balanceBefore, "fee-on-transfer tokens are not supported");
|
||||
|
||||
return (localTokenAddress, localTokenDecimals);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev createSale serves to initialize a cross-chain token sale and disseminate
|
||||
* information about the sale to registered Contributor contracts.
|
||||
* - it validates sale parameters passed in by the client
|
||||
* - it saves a copy of the sale in contract storage
|
||||
* - it encodes and disseminates sale information to Contributor contracts via wormhole
|
||||
*/
|
||||
function createSale(
|
||||
ICCOStructs.Raise memory raise,
|
||||
ICCOStructs.Token[] memory acceptedTokens
|
||||
) public payable nonReentrant returns (
|
||||
uint256 saleId
|
||||
) {
|
||||
/// validate sale parameters from client
|
||||
require(block.timestamp < raise.saleStart, "sale start must be in the future");
|
||||
require(raise.saleStart < raise.saleEnd, "sale end must be after sale start");
|
||||
/// set timestamp cap for non-evm Contributor contracts
|
||||
require(raise.saleStart <= 2**63-1, "saleStart too far in the future");
|
||||
require(raise.tokenAmount > 0, "amount must be > 0");
|
||||
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");
|
||||
|
||||
/// @dev take custody of sale token and fetch decimal/address info for the sale token
|
||||
(address localTokenAddress, uint8 localTokenDecimals) = receiveSaleToken(raise);
|
||||
|
||||
/// create Sale struct for Conductor's view of the sale
|
||||
saleId = useSaleId();
|
||||
|
@ -116,7 +131,8 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
acceptedTokensChains : new uint16[](acceptedTokens.length),
|
||||
acceptedTokensAddresses : new bytes32[](acceptedTokens.length),
|
||||
acceptedTokensConversionRates : new uint128[](acceptedTokens.length),
|
||||
contributions : new uint[](acceptedTokens.length),
|
||||
solanaAcceptedTokensCount: 0,
|
||||
contributions : new uint256[](acceptedTokens.length),
|
||||
contributionsCollected : new bool[](acceptedTokens.length),
|
||||
/// sale wallet management
|
||||
initiator : msg.sender,
|
||||
|
@ -128,18 +144,37 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
refundIsClaimed : false
|
||||
});
|
||||
|
||||
/// populate the accpeted token arrays
|
||||
for(uint i = 0; i < acceptedTokens.length; i++) {
|
||||
/// populate the accepted token arrays
|
||||
for (uint256 i = 0; i < acceptedTokens.length; i++) {
|
||||
require(acceptedTokens[i].conversionRate > 0, "conversion rate cannot be zero");
|
||||
sale.acceptedTokensChains[i] = acceptedTokens[i].tokenChain;
|
||||
sale.acceptedTokensAddresses[i] = acceptedTokens[i].tokenAddress;
|
||||
sale.acceptedTokensConversionRates[i] = acceptedTokens[i].conversionRate;
|
||||
|
||||
/// store the accepted tokens for the SolanaSaleInit VAA
|
||||
if (acceptedTokens[i].tokenChain == 1) {
|
||||
ICCOStructs.SolanaToken memory solanaToken = ICCOStructs.SolanaToken({
|
||||
tokenIndex: uint8(i),
|
||||
tokenAddress: acceptedTokens[i].tokenAddress
|
||||
});
|
||||
/// only allow 10 accepted tokens for the Solana Contributor
|
||||
require(solanaAcceptedTokens.length < 8, "too many solana tokens");
|
||||
/// save in contract storage
|
||||
solanaAcceptedTokens.push(solanaToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// save number of accepted solana tokens in the sale
|
||||
sale.solanaAcceptedTokensCount = uint8(solanaAcceptedTokens.length);
|
||||
|
||||
/// store sale info
|
||||
setSale(saleId, sale);
|
||||
|
||||
/// create SaleInit struct to disseminate to contributors
|
||||
/// cache wormhole instance
|
||||
IWormhole wormhole = wormhole();
|
||||
uint256 messageFee = wormhole.messageFee();
|
||||
|
||||
/// create SaleInit struct to disseminate to Contributors
|
||||
ICCOStructs.SaleInit memory saleInit = ICCOStructs.SaleInit({
|
||||
payloadID : 1,
|
||||
/// sale ID
|
||||
|
@ -168,15 +203,44 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
recipient : bytes32(uint256(uint160(raise.recipient))),
|
||||
/// refund recipient in case the sale is aborted
|
||||
refundRecipient : bytes32(uint256(uint160(raise.refundRecipient)))
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @dev send encoded SaleInit struct to contributors via wormhole.
|
||||
* The msg.value is the fee collected by wormhole for messages.
|
||||
*/
|
||||
wormholeSequence = wormhole().publishMessage{
|
||||
value : msg.value
|
||||
/// @dev send encoded SaleInit struct to Contributors via wormhole.
|
||||
wormhole.publishMessage{
|
||||
value : messageFee
|
||||
}(0, ICCOStructs.encodeSaleInit(saleInit), consistencyLevel());
|
||||
|
||||
/// see if the sale accepts any Solana tokens
|
||||
if (solanaAcceptedTokens.length > 0) {
|
||||
/// create SolanaSaleInit struct to disseminate to the Solana Contributor
|
||||
ICCOStructs.SolanaSaleInit memory solanaSaleInit = ICCOStructs.SolanaSaleInit({
|
||||
payloadID : 5,
|
||||
/// sale ID
|
||||
saleID : saleId,
|
||||
/// sale token ATA for solana
|
||||
solanaTokenAccount: raise.solanaTokenAccount,
|
||||
/// chain ID of the token
|
||||
tokenChain : raise.tokenChain,
|
||||
/// token decimals
|
||||
tokenDecimals: localTokenDecimals,
|
||||
/// timestamp raise start
|
||||
saleStart : raise.saleStart,
|
||||
/// timestamp raise end
|
||||
saleEnd : raise.saleEnd,
|
||||
/// accepted Tokens
|
||||
acceptedTokens : solanaAcceptedTokens,
|
||||
/// recipient of proceeds
|
||||
recipient : bytes32(uint256(uint160(raise.recipient)))
|
||||
});
|
||||
|
||||
/// @dev send encoded SolanaSaleInit struct to the solana Contributor
|
||||
wormhole.publishMessage{
|
||||
value : messageFee
|
||||
}(0, ICCOStructs.encodeSolanaSaleInit(solanaSaleInit), consistencyLevel());
|
||||
|
||||
/// @dev garbage collection to save on gas fees
|
||||
delete solanaAcceptedTokens;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -186,7 +250,7 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
* - it only allows the sale initiator to invoke the method
|
||||
* - it encodes and disseminates a saleAborted message to the Contributor contracts
|
||||
*/
|
||||
function abortSaleBeforeStartTime(uint saleId) public payable returns (uint wormholeSequence) {
|
||||
function abortSaleBeforeStartTime(uint256 saleId) public payable returns (uint256 wormholeSequence) {
|
||||
require(saleExists(saleId), "sale not initiated");
|
||||
|
||||
ConductorStructs.Sale memory sale = sales(saleId);
|
||||
|
@ -241,7 +305,7 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
);
|
||||
|
||||
/// save the total contribution amount for each accepted token
|
||||
for(uint i = 0; i < conSealed.contributions.length; i++) {
|
||||
for (uint256 i = 0; i < conSealed.contributions.length; i++) {
|
||||
setSaleContribution(
|
||||
conSealed.saleID,
|
||||
conSealed.contributions[i].tokenIndex,
|
||||
|
@ -257,7 +321,7 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
* - it calculates allocations and excess contributions for each accepted token
|
||||
* - it disseminates a saleSealed or saleAborted message to Contributors via wormhole
|
||||
*/
|
||||
function sealSale(uint saleId) public payable returns (uint wormholeSequence) {
|
||||
function sealSale(uint256 saleId) public payable returns (uint256 wormholeSequence) {
|
||||
require(saleExists(saleId), "sale not initiated");
|
||||
|
||||
ConductorStructs.Sale memory sale = sales(saleId);
|
||||
|
@ -267,7 +331,7 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
|
||||
ConductorStructs.InternalAccounting memory accounting;
|
||||
|
||||
for (uint i = 0; i < sale.contributionsCollected.length; i++) {
|
||||
for (uint256 i = 0; i < sale.contributionsCollected.length; i++) {
|
||||
require(saleContributionIsCollected(saleId, i), "missing contribution info");
|
||||
/**
|
||||
* @dev This calculates the total contribution for each accepted token.
|
||||
|
@ -284,7 +348,7 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
|
||||
/// set the messageFee and valueSent values
|
||||
accounting.messageFee = wormhole.messageFee();
|
||||
accounting.valueSent = msg.value;
|
||||
accounting.valueSent = msg.value;
|
||||
|
||||
/**
|
||||
* @dev This determines if contributors qualify for refund payments.
|
||||
|
@ -304,9 +368,9 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
});
|
||||
|
||||
/// calculate allocations and excessContributions for each accepted token
|
||||
for(uint i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
uint allocation = sale.tokenAmount * (sale.contributions[i] * sale.acceptedTokensConversionRates[i] / 1e18) / accounting.totalContribution;
|
||||
uint excessContribution = accounting.totalExcessContribution * sale.contributions[i] / accounting.totalContribution;
|
||||
for (uint256 i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
uint256 allocation = sale.tokenAmount * (sale.contributions[i] * sale.acceptedTokensConversionRates[i] / 1e18) / accounting.totalContribution;
|
||||
uint256 excessContribution = accounting.totalExcessContribution * sale.contributions[i] / accounting.totalContribution;
|
||||
|
||||
if (allocation > 0) {
|
||||
/// send allocations to Contributor contracts
|
||||
|
@ -377,6 +441,30 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
wormholeSequence = wormhole.publishMessage{
|
||||
value : accounting.messageFee
|
||||
}(0, ICCOStructs.encodeSaleSealed(saleSealed), consistencyLevel());
|
||||
|
||||
{ /// scope to make code more readable
|
||||
/// @dev send separate SaleSealed VAA if accepting Solana tokens
|
||||
if (sale.solanaAcceptedTokensCount > 0) {
|
||||
/// create new array to handle solana allocations
|
||||
ICCOStructs.Allocation[] memory solanaAllocations = new ICCOStructs.Allocation[](sale.solanaAcceptedTokensCount);
|
||||
|
||||
/// remove non-solana allocations in SaleSealed VAA
|
||||
uint8 solanaAllocationIndex;
|
||||
for (uint256 i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
if (sale.acceptedTokensChains[i] == 1) {
|
||||
solanaAllocations[solanaAllocationIndex] = saleSealed.allocations[i];
|
||||
solanaAllocationIndex += 1;
|
||||
}
|
||||
}
|
||||
/// @dev replace allocations in the saleSealed struct with Solana only allocations
|
||||
saleSealed.allocations = solanaAllocations;
|
||||
|
||||
/// @dev send encoded SaleSealed message to Solana Contributor
|
||||
wormholeSequence = wormhole.publishMessage{
|
||||
value : accounting.messageFee
|
||||
}(0, ICCOStructs.encodeSaleSealed(saleSealed), consistencyLevel());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/// set saleAborted
|
||||
setSaleAborted(sale.saleID);
|
||||
|
@ -396,7 +484,7 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
* - it confirms that the sale was aborted
|
||||
* - it transfers the sale tokens to the refundRecipient
|
||||
*/
|
||||
function claimRefund(uint saleId) public {
|
||||
function claimRefund(uint256 saleId) public {
|
||||
require(saleExists(saleId), "sale not initiated");
|
||||
|
||||
ConductorStructs.Sale memory sale = sales(saleId);
|
||||
|
@ -430,7 +518,7 @@ contract Conductor is ConductorGovernance, ReentrancyGuard {
|
|||
}
|
||||
|
||||
/// @dev saleExists serves to check if a sale exists
|
||||
function saleExists(uint saleId) public view returns (bool exists) {
|
||||
function saleExists(uint256 saleId) public view returns (bool exists) {
|
||||
exists = (saleId < getNextSaleId());
|
||||
}
|
||||
}
|
|
@ -39,11 +39,11 @@ contract ConductorGetters is ConductorState {
|
|||
return _state.contributorImplementations[chainId_];
|
||||
}
|
||||
|
||||
function solanaWallet(uint saleId_) public view returns (bytes32) {
|
||||
function solanaWallet(uint256 saleId_) public view returns (bytes32) {
|
||||
return _state.sales[saleId_].solanaTokenAccount;
|
||||
}
|
||||
|
||||
function contributorWallets(uint saleId_, uint16 chainId_) public view returns (bytes32) {
|
||||
function contributorWallets(uint256 saleId_, uint16 chainId_) public view returns (bytes32) {
|
||||
/// @dev Solana chainID == 1
|
||||
if (chainId_ == 1) {
|
||||
return solanaWallet(saleId_);
|
||||
|
@ -52,19 +52,19 @@ contract ConductorGetters is ConductorState {
|
|||
}
|
||||
}
|
||||
|
||||
function sales(uint saleId_) public view returns (ConductorStructs.Sale memory sale) {
|
||||
function sales(uint256 saleId_) public view returns (ConductorStructs.Sale memory sale) {
|
||||
return _state.sales[saleId_];
|
||||
}
|
||||
|
||||
function getNextSaleId() public view returns (uint) {
|
||||
function getNextSaleId() public view returns (uint256) {
|
||||
return _state.nextSaleId;
|
||||
}
|
||||
|
||||
function saleContributionIsCollected(uint saleId_, uint tokenIndex) public view returns (bool) {
|
||||
function saleContributionIsCollected(uint256 saleId_, uint256 tokenIndex) public view returns (bool) {
|
||||
return _state.sales[saleId_].contributionsCollected[tokenIndex];
|
||||
}
|
||||
|
||||
function saleContributions(uint saleId_) public view returns (uint[] memory) {
|
||||
function saleContributions(uint256 saleId_) public view returns (uint256[] memory) {
|
||||
return _state.sales[saleId_].contributions;
|
||||
}
|
||||
}
|
|
@ -35,28 +35,28 @@ contract ConductorSetters is ConductorState, Context {
|
|||
_state.consistencyLevel = level;
|
||||
}
|
||||
|
||||
function setSale(uint saleId, ConductorStructs.Sale memory sale) internal {
|
||||
function setSale(uint256 saleId, ConductorStructs.Sale memory sale) internal {
|
||||
_state.sales[saleId] = sale;
|
||||
}
|
||||
|
||||
function setSaleContribution(uint saleId, uint tokenIndex, uint contribution) internal {
|
||||
function setSaleContribution(uint256 saleId, uint256 tokenIndex, uint256 contribution) internal {
|
||||
_state.sales[saleId].contributions[tokenIndex] = contribution;
|
||||
_state.sales[saleId].contributionsCollected[tokenIndex] = true;
|
||||
}
|
||||
|
||||
function setSaleSealed(uint saleId) internal {
|
||||
function setSaleSealed(uint256 saleId) internal {
|
||||
_state.sales[saleId].isSealed = true;
|
||||
}
|
||||
|
||||
function setSaleAborted(uint saleId) internal {
|
||||
function setSaleAborted(uint256 saleId) internal {
|
||||
_state.sales[saleId].isAborted = true;
|
||||
}
|
||||
|
||||
function setRefundClaimed(uint saleId) internal {
|
||||
function setRefundClaimed(uint256 saleId) internal {
|
||||
_state.sales[saleId].refundIsClaimed = true;
|
||||
}
|
||||
|
||||
function setNextSaleId(uint nextSaleId) internal {
|
||||
function setNextSaleId(uint256 nextSaleId) internal {
|
||||
_state.nextSaleId = nextSaleId;
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ contract ConductorStorage {
|
|||
mapping(uint16 => bytes32) contributorImplementations;
|
||||
|
||||
/// mapping of Sales
|
||||
mapping(uint => ConductorStructs.Sale) sales;
|
||||
mapping(uint256 => ConductorStructs.Sale) sales;
|
||||
|
||||
/// next sale id
|
||||
uint256 nextSaleId;
|
||||
|
|
|
@ -31,8 +31,9 @@ contract ConductorStructs {
|
|||
uint16[] acceptedTokensChains;
|
||||
bytes32[] acceptedTokensAddresses;
|
||||
uint128[] acceptedTokensConversionRates;
|
||||
uint8 solanaAcceptedTokensCount;
|
||||
/// contributions
|
||||
uint[] contributions;
|
||||
uint256[] contributions;
|
||||
bool[] contributionsCollected;
|
||||
/// sale initiator - can abort the sale before saleStart
|
||||
address initiator;
|
||||
|
|
|
@ -74,7 +74,7 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
* on this Contributor chain.
|
||||
* - it checks that the token is a valid ERC20 token
|
||||
*/
|
||||
for (uint i = 0; i < saleInit.acceptedTokens.length; i++) {
|
||||
for (uint256 i = 0; i < saleInit.acceptedTokens.length; i++) {
|
||||
if (saleInit.acceptedTokens[i].tokenChain == chainId()) {
|
||||
address tokenAddress = address(uint160(uint256(saleInit.acceptedTokens[i].tokenAddress)));
|
||||
(, bytes memory queriedTotalSupply) = tokenAddress.staticcall(
|
||||
|
@ -132,7 +132,7 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
* - it takes custody of contributed funds
|
||||
* - it stores information about the contribution and contributor
|
||||
*/
|
||||
function contribute(uint saleId, uint tokenIndex, uint amount, bytes memory sig) public nonReentrant {
|
||||
function contribute(uint256 saleId, uint256 tokenIndex, uint256 amount, bytes memory sig) public nonReentrant {
|
||||
require(saleExists(saleId), "sale not initiated");
|
||||
|
||||
{/// bypass stack too deep
|
||||
|
@ -141,7 +141,7 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
|
||||
require(!isAborted, "sale was aborted");
|
||||
|
||||
(uint start, uint end) = getSaleTimeframe(saleId);
|
||||
(uint256 start, uint256 end) = getSaleTimeframe(saleId);
|
||||
|
||||
require(block.timestamp >= start, "sale not yet started");
|
||||
require(block.timestamp <= end, "sale has ended");
|
||||
|
@ -200,7 +200,7 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
* - it calculates the total contributions for each accepted token
|
||||
* - it disseminates a ContributionSealed struct via wormhole
|
||||
*/
|
||||
function attestContributions(uint saleId) public payable returns (uint wormholeSequence) {
|
||||
function attestContributions(uint256 saleId) public payable returns (uint256 wormholeSequence) {
|
||||
require(saleExists(saleId), "sale not initiated");
|
||||
|
||||
/// confirm that the sale period has ended
|
||||
|
@ -210,9 +210,9 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
require(block.timestamp > sale.saleEnd, "sale has not yet ended");
|
||||
|
||||
/// count accepted tokens for this contract to allocate memory in ContributionsSealed struct
|
||||
uint nativeTokens = 0;
|
||||
uint chainId = chainId(); /// cache from storage
|
||||
for (uint i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
uint256 nativeTokens = 0;
|
||||
uint16 chainId = chainId(); /// cache from storage
|
||||
for (uint256 i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
if (sale.acceptedTokensChains[i] == chainId) {
|
||||
nativeTokens++;
|
||||
}
|
||||
|
@ -226,8 +226,8 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
contributions : new ICCOStructs.Contribution[](nativeTokens)
|
||||
});
|
||||
|
||||
uint ci = 0;
|
||||
for (uint i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
uint256 ci = 0;
|
||||
for (uint256 i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
if (sale.acceptedTokensChains[i] == chainId) {
|
||||
consSealed.contributions[ci].tokenIndex = uint8(i);
|
||||
consSealed.contributions[ci].contributed = getSaleTotalContribution(saleId, i);
|
||||
|
@ -278,13 +278,13 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
(, bytes memory queriedTokenBalance) = saleTokenAddress.staticcall(
|
||||
abi.encodeWithSelector(IERC20.balanceOf.selector, address(this))
|
||||
);
|
||||
uint tokenBalance = abi.decode(queriedTokenBalance, (uint256));
|
||||
uint256 tokenBalance = abi.decode(queriedTokenBalance, (uint256));
|
||||
|
||||
require(tokenBalance > 0, "sale token balance must be non-zero");
|
||||
|
||||
/// store the allocated token amounts defined in the SaleSealed message
|
||||
uint tokenAllocation;
|
||||
for (uint i = 0; i < sealedSale.allocations.length; i++) {
|
||||
uint256 tokenAllocation;
|
||||
for (uint256 i = 0; i < sealedSale.allocations.length; i++) {
|
||||
ICCOStructs.Allocation memory allo = sealedSale.allocations[i];
|
||||
if (sale.acceptedTokensChains[allo.tokenIndex] == thisChainId) {
|
||||
tokenAllocation += allo.allocation;
|
||||
|
@ -304,15 +304,15 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
* are being sent to a recipient on a different chain.
|
||||
*/
|
||||
ITokenBridge tknBridge = tokenBridge();
|
||||
uint messageFee = wormhole().messageFee();
|
||||
uint valueSent = msg.value;
|
||||
uint256 messageFee = wormhole().messageFee();
|
||||
uint256 valueSent = msg.value;
|
||||
|
||||
/**
|
||||
* @dev Cache the conductorChainId from storage to save on gas.
|
||||
* We will check each accpetedToken to see if its from this chain.
|
||||
*/
|
||||
uint16 conductorChainId = conductorChainId();
|
||||
for (uint i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
for (uint256 i = 0; i < sale.acceptedTokensAddresses.length; i++) {
|
||||
if (sale.acceptedTokensChains[i] == thisChainId) {
|
||||
/// compute the total contributions to send to the recipient
|
||||
uint256 totalContributionsLessExcess = getSaleTotalContribution(sale.saleID, i) - getSaleExcessContribution(sale.saleID, i);
|
||||
|
@ -396,7 +396,7 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
* - it transfer any excessContributions to the contributors wallet
|
||||
* - it marks the allocation as claimed to prevent multiple claims for the same allocation
|
||||
*/
|
||||
function claimAllocation(uint saleId, uint tokenIndex) public {
|
||||
function claimAllocation(uint256 saleId, uint256 tokenIndex) public {
|
||||
require(saleExists(saleId), "sale not initiated");
|
||||
|
||||
/// make sure the sale is sealed and not aborted
|
||||
|
@ -457,7 +457,7 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
* - it confirms that the sale was aborted
|
||||
* - it transfers the contributed funds back to the contributor's wallet
|
||||
*/
|
||||
function claimRefund(uint saleId, uint tokenIndex) public {
|
||||
function claimRefund(uint256 saleId, uint256 tokenIndex) public {
|
||||
require(saleExists(saleId), "sale not initiated");
|
||||
|
||||
(, bool isAborted) = getSaleStatus(saleId);
|
||||
|
@ -490,7 +490,7 @@ contract Contributor is ContributorGovernance, ReentrancyGuard {
|
|||
}
|
||||
|
||||
/// @dev saleExists serves to check if a sale exists
|
||||
function saleExists(uint saleId) public view returns (bool exists) {
|
||||
function saleExists(uint256 saleId) public view returns (bool exists) {
|
||||
exists = (getSaleTokenAddress(saleId) != bytes32(0));
|
||||
}
|
||||
}
|
|
@ -47,11 +47,11 @@ contract ContributorGetters is ContributorState {
|
|||
return _state.provider.conductorContract;
|
||||
}
|
||||
|
||||
function sales(uint saleId_) public view returns (ContributorStructs.Sale memory sale){
|
||||
function sales(uint256 saleId_) public view returns (ContributorStructs.Sale memory sale){
|
||||
return _state.sales[saleId_];
|
||||
}
|
||||
|
||||
function getSaleAcceptedTokenInfo(uint saleId_, uint tokenIndex) public view returns (uint16 tokenChainId, bytes32 tokenAddress, uint128 conversionRate){
|
||||
function getSaleAcceptedTokenInfo(uint256 saleId_, uint256 tokenIndex) public view returns (uint16 tokenChainId, bytes32 tokenAddress, uint128 conversionRate){
|
||||
return (
|
||||
_state.sales[saleId_].acceptedTokensChains[tokenIndex],
|
||||
_state.sales[saleId_].acceptedTokensAddresses[tokenIndex],
|
||||
|
@ -59,45 +59,45 @@ contract ContributorGetters is ContributorState {
|
|||
);
|
||||
}
|
||||
|
||||
function getSaleTimeframe(uint saleId_) public view returns (uint256 start, uint256 end){
|
||||
function getSaleTimeframe(uint256 saleId_) public view returns (uint256 start, uint256 end){
|
||||
return (
|
||||
_state.sales[saleId_].saleStart,
|
||||
_state.sales[saleId_].saleEnd
|
||||
);
|
||||
}
|
||||
|
||||
function getSaleStatus(uint saleId_) public view returns (bool isSealed, bool isAborted){
|
||||
function getSaleStatus(uint256 saleId_) public view returns (bool isSealed, bool isAborted){
|
||||
return (
|
||||
_state.sales[saleId_].isSealed,
|
||||
_state.sales[saleId_].isAborted
|
||||
);
|
||||
}
|
||||
|
||||
function getSaleTokenAddress(uint saleId_) public view returns (bytes32 tokenAddress){
|
||||
function getSaleTokenAddress(uint256 saleId_) public view returns (bytes32 tokenAddress){
|
||||
tokenAddress = _state.sales[saleId_].tokenAddress;
|
||||
}
|
||||
|
||||
function getSaleAllocation(uint saleId, uint tokenIndex) public view returns (uint256 allocation){
|
||||
function getSaleAllocation(uint256 saleId, uint256 tokenIndex) public view returns (uint256 allocation){
|
||||
return _state.sales[saleId].allocations[tokenIndex];
|
||||
}
|
||||
|
||||
function getSaleExcessContribution(uint saleId, uint tokenIndex) public view returns (uint256 allocation){
|
||||
function getSaleExcessContribution(uint256 saleId, uint256 tokenIndex) public view returns (uint256 allocation){
|
||||
return _state.sales[saleId].excessContributions[tokenIndex];
|
||||
}
|
||||
|
||||
function getSaleTotalContribution(uint saleId, uint tokenIndex) public view returns (uint256 contributed){
|
||||
function getSaleTotalContribution(uint256 saleId, uint256 tokenIndex) public view returns (uint256 contributed){
|
||||
return _state.totalContributions[saleId][tokenIndex];
|
||||
}
|
||||
|
||||
function getSaleContribution(uint saleId, uint tokenIndex, address contributor) public view returns (uint256 contributed){
|
||||
function getSaleContribution(uint256 saleId, uint256 tokenIndex, address contributor) public view returns (uint256 contributed){
|
||||
return _state.contributions[saleId][tokenIndex][contributor];
|
||||
}
|
||||
|
||||
function refundIsClaimed(uint saleId, uint tokenIndex, address contributor) public view returns (bool){
|
||||
function refundIsClaimed(uint256 saleId, uint256 tokenIndex, address contributor) public view returns (bool){
|
||||
return _state.refundIsClaimed[saleId][tokenIndex][contributor];
|
||||
}
|
||||
|
||||
function allocationIsClaimed(uint saleId, uint tokenIndex, address contributor) public view returns (bool){
|
||||
function allocationIsClaimed(uint256 saleId, uint256 tokenIndex, address contributor) public view returns (bool){
|
||||
return _state.allocationIsClaimed[saleId][tokenIndex][contributor];
|
||||
}
|
||||
}
|
|
@ -43,36 +43,36 @@ contract ContributorSetters is ContributorState, Context {
|
|||
_state.consistencyLevel = level;
|
||||
}
|
||||
|
||||
function setSale(uint saleId, ContributorStructs.Sale memory sale) internal {
|
||||
function setSale(uint256 saleId, ContributorStructs.Sale memory sale) internal {
|
||||
_state.sales[saleId] = sale;
|
||||
}
|
||||
|
||||
function setSaleContribution(uint saleId, address contributor, uint tokenIndex, uint contribution) internal {
|
||||
function setSaleContribution(uint256 saleId, address contributor, uint256 tokenIndex, uint256 contribution) internal {
|
||||
_state.contributions[saleId][tokenIndex][contributor] += contribution;
|
||||
_state.totalContributions[saleId][tokenIndex] += contribution;
|
||||
}
|
||||
|
||||
function setSaleSealed(uint saleId) internal {
|
||||
function setSaleSealed(uint256 saleId) internal {
|
||||
_state.sales[saleId].isSealed = true;
|
||||
}
|
||||
|
||||
function setSaleAborted(uint saleId) internal {
|
||||
function setSaleAborted(uint256 saleId) internal {
|
||||
_state.sales[saleId].isAborted = true;
|
||||
}
|
||||
|
||||
function setRefundClaimed(uint saleId, uint tokenIndex, address contributor) internal {
|
||||
function setRefundClaimed(uint256 saleId, uint256 tokenIndex, address contributor) internal {
|
||||
_state.refundIsClaimed[saleId][tokenIndex][contributor] = true;
|
||||
}
|
||||
|
||||
function setAllocationClaimed(uint saleId, uint tokenIndex, address contributor) internal {
|
||||
function setAllocationClaimed(uint256 saleId, uint256 tokenIndex, address contributor) internal {
|
||||
_state.allocationIsClaimed[saleId][tokenIndex][contributor] = true;
|
||||
}
|
||||
|
||||
function setSaleAllocation(uint saleId, uint tokenIndex, uint allocation) internal {
|
||||
function setSaleAllocation(uint256 saleId, uint256 tokenIndex, uint256 allocation) internal {
|
||||
_state.sales[saleId].allocations[tokenIndex] = allocation;
|
||||
}
|
||||
|
||||
function setExcessContribution(uint saleId, uint tokenIndex, uint excessContribution) internal {
|
||||
function setExcessContribution(uint256 saleId, uint256 tokenIndex, uint256 excessContribution) internal {
|
||||
_state.sales[saleId].excessContributions[tokenIndex] = excessContribution;
|
||||
}
|
||||
}
|
|
@ -28,19 +28,19 @@ contract ContributorStorage {
|
|||
mapping(address => bool) initializedImplementations;
|
||||
|
||||
/// mapping of Sales
|
||||
mapping(uint => ContributorStructs.Sale) sales;
|
||||
mapping(uint256 => ContributorStructs.Sale) sales;
|
||||
|
||||
/// sale id > token id > contributor > contribution
|
||||
mapping(uint => mapping(uint => mapping(address => uint))) contributions;
|
||||
mapping(uint256 => mapping(uint256 => mapping(address => uint256))) contributions;
|
||||
|
||||
/// sale id > token id > contribution
|
||||
mapping(uint => mapping(uint => uint)) totalContributions;
|
||||
mapping(uint256 => mapping(uint256 => uint256)) totalContributions;
|
||||
|
||||
/// sale id > token id > contributor > isClaimed
|
||||
mapping(uint => mapping(uint => mapping(address => bool))) allocationIsClaimed;
|
||||
mapping(uint256 => mapping(uint256 => mapping(address => bool))) allocationIsClaimed;
|
||||
|
||||
/// sale id > [token id > contributor > isClaimed
|
||||
mapping(uint => mapping(uint => mapping(address => bool))) refundIsClaimed;
|
||||
mapping(uint256 => mapping(uint256 => mapping(address => bool))) refundIsClaimed;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@ library ICCOStructs {
|
|||
uint128 conversionRate;
|
||||
}
|
||||
|
||||
struct SolanaToken {
|
||||
uint8 tokenIndex;
|
||||
bytes32 tokenAddress;
|
||||
}
|
||||
|
||||
struct Contribution {
|
||||
/// index in acceptedTokens array
|
||||
uint8 tokenIndex;
|
||||
|
@ -83,6 +88,27 @@ library ICCOStructs {
|
|||
bytes32 refundRecipient;
|
||||
}
|
||||
|
||||
struct SolanaSaleInit {
|
||||
/// payloadID uint8 = 5
|
||||
uint8 payloadID;
|
||||
/// sale ID
|
||||
uint256 saleID;
|
||||
/// sale token ATA for solana
|
||||
bytes32 solanaTokenAccount;
|
||||
/// chain ID of the token
|
||||
uint16 tokenChain;
|
||||
/// token decimals
|
||||
uint8 tokenDecimals;
|
||||
/// timestamp raise start
|
||||
uint256 saleStart;
|
||||
/// timestamp raise end
|
||||
uint256 saleEnd;
|
||||
/// accepted Tokens
|
||||
SolanaToken[] acceptedTokens;
|
||||
/// recipient of proceeds
|
||||
bytes32 recipient;
|
||||
}
|
||||
|
||||
struct ContributionsSealed {
|
||||
/// payloadID uint8 = 2
|
||||
uint8 payloadID;
|
||||
|
@ -143,8 +169,22 @@ library ICCOStructs {
|
|||
);
|
||||
}
|
||||
|
||||
function encodeSolanaSaleInit(SolanaSaleInit memory solanaSaleInit) public pure returns (bytes memory encoded) {
|
||||
return abi.encodePacked(
|
||||
uint8(5),
|
||||
solanaSaleInit.saleID,
|
||||
solanaSaleInit.solanaTokenAccount,
|
||||
solanaSaleInit.tokenChain,
|
||||
solanaSaleInit.tokenDecimals,
|
||||
solanaSaleInit.saleStart,
|
||||
solanaSaleInit.saleEnd,
|
||||
encodeSolanaTokens(solanaSaleInit.acceptedTokens),
|
||||
solanaSaleInit.recipient
|
||||
);
|
||||
}
|
||||
|
||||
function parseSaleInit(bytes memory encoded) public pure returns (SaleInit memory saleInit) {
|
||||
uint index = 0;
|
||||
uint256 index = 0;
|
||||
|
||||
saleInit.payloadID = encoded.toUint8(index);
|
||||
index += 1;
|
||||
|
@ -178,7 +218,7 @@ library ICCOStructs {
|
|||
saleInit.saleEnd = encoded.toUint256(index);
|
||||
index += 32;
|
||||
|
||||
uint len = 1 + 50 * uint256(uint8(encoded[index]));
|
||||
uint256 len = 1 + 50 * uint256(uint8(encoded[index]));
|
||||
saleInit.acceptedTokens = parseTokens(encoded.slice(index, len));
|
||||
index += len;
|
||||
|
||||
|
@ -196,7 +236,7 @@ library ICCOStructs {
|
|||
|
||||
function encodeTokens(Token[] memory tokens) public pure returns (bytes memory encoded) {
|
||||
encoded = abi.encodePacked(uint8(tokens.length));
|
||||
for (uint i = 0; i < tokens.length; i++) {
|
||||
for (uint256 i = 0; i < tokens.length; i++) {
|
||||
encoded = abi.encodePacked(
|
||||
encoded,
|
||||
tokens[i].tokenAddress,
|
||||
|
@ -206,6 +246,17 @@ library ICCOStructs {
|
|||
}
|
||||
}
|
||||
|
||||
function encodeSolanaTokens(SolanaToken[] memory tokens) public pure returns (bytes memory encoded) {
|
||||
encoded = abi.encodePacked(uint8(tokens.length));
|
||||
for (uint256 i = 0; i < tokens.length; i++) {
|
||||
encoded = abi.encodePacked(
|
||||
encoded,
|
||||
tokens[i].tokenIndex,
|
||||
tokens[i].tokenAddress
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function parseTokens(bytes memory encoded) public pure returns (Token[] memory tokens) {
|
||||
require(encoded.length % 50 == 1, "invalid Token[]");
|
||||
|
||||
|
@ -213,7 +264,7 @@ library ICCOStructs {
|
|||
|
||||
tokens = new Token[](len);
|
||||
|
||||
for (uint i = 0; i < len; i++) {
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
tokens[i].tokenAddress = encoded.toBytes32( 1 + i * 50);
|
||||
tokens[i].tokenChain = encoded.toUint16( 33 + i * 50);
|
||||
tokens[i].conversionRate = encoded.toUint128(35 + i * 50);
|
||||
|
@ -230,7 +281,7 @@ library ICCOStructs {
|
|||
}
|
||||
|
||||
function parseContributionsSealed(bytes memory encoded) public pure returns (ContributionsSealed memory consSealed) {
|
||||
uint index = 0;
|
||||
uint256 index = 0;
|
||||
|
||||
consSealed.payloadID = encoded.toUint8(index);
|
||||
index += 1;
|
||||
|
@ -243,7 +294,7 @@ library ICCOStructs {
|
|||
consSealed.chainID = encoded.toUint16(index);
|
||||
index += 2;
|
||||
|
||||
uint len = 1 + 33 * uint256(uint8(encoded[index]));
|
||||
uint256 len = 1 + 33 * uint256(uint8(encoded[index]));
|
||||
consSealed.contributions = parseContributions(encoded.slice(index, len));
|
||||
index += len;
|
||||
|
||||
|
@ -252,7 +303,7 @@ library ICCOStructs {
|
|||
|
||||
function encodeContributions(Contribution[] memory contributions) public pure returns (bytes memory encoded) {
|
||||
encoded = abi.encodePacked(uint8(contributions.length));
|
||||
for (uint i = 0; i < contributions.length; i++) {
|
||||
for (uint256 i = 0; i < contributions.length; i++) {
|
||||
encoded = abi.encodePacked(
|
||||
encoded,
|
||||
contributions[i].tokenIndex,
|
||||
|
@ -268,7 +319,7 @@ library ICCOStructs {
|
|||
|
||||
cons = new Contribution[](len);
|
||||
|
||||
for (uint i = 0; i < len; i++) {
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
cons[i].tokenIndex = encoded.toUint8(1 + i * 33);
|
||||
cons[i].contributed = encoded.toUint256(2 + i * 33);
|
||||
}
|
||||
|
@ -283,7 +334,7 @@ library ICCOStructs {
|
|||
}
|
||||
|
||||
function parseSaleSealed(bytes memory encoded) public pure returns (SaleSealed memory ss) {
|
||||
uint index = 0;
|
||||
uint256 index = 0;
|
||||
ss.payloadID = encoded.toUint8(index);
|
||||
index += 1;
|
||||
|
||||
|
@ -292,7 +343,7 @@ library ICCOStructs {
|
|||
ss.saleID = encoded.toUint256(index);
|
||||
index += 32;
|
||||
|
||||
uint len = 1 + 65 * uint256(uint8(encoded[index]));
|
||||
uint256 len = 1 + 65 * uint256(uint8(encoded[index]));
|
||||
ss.allocations = parseAllocations(encoded.slice(index, len));
|
||||
index += len;
|
||||
|
||||
|
@ -301,7 +352,7 @@ library ICCOStructs {
|
|||
|
||||
function encodeAllocations(Allocation[] memory allocations) public pure returns (bytes memory encoded) {
|
||||
encoded = abi.encodePacked(uint8(allocations.length));
|
||||
for (uint i = 0; i < allocations.length; i++) {
|
||||
for (uint256 i = 0; i < allocations.length; i++) {
|
||||
encoded = abi.encodePacked(
|
||||
encoded,
|
||||
allocations[i].tokenIndex,
|
||||
|
@ -318,7 +369,7 @@ library ICCOStructs {
|
|||
|
||||
allos = new Allocation[](len);
|
||||
|
||||
for (uint i = 0; i < len; i++) {
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
allos[i].tokenIndex = encoded.toUint8(1 + i * 65);
|
||||
allos[i].allocation = encoded.toUint256(2 + i * 65);
|
||||
allos[i].excessContribution = encoded.toUint256(34 + i * 65);
|
||||
|
@ -330,7 +381,7 @@ library ICCOStructs {
|
|||
}
|
||||
|
||||
function parseSaleAborted(bytes memory encoded) public pure returns (SaleAborted memory sa) {
|
||||
uint index = 0;
|
||||
uint256 index = 0;
|
||||
sa.payloadID = encoded.toUint8(index);
|
||||
index += 1;
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ module.exports = {
|
|||
tokenBridge: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
binance: {
|
||||
conductorChainId: 2,
|
||||
|
@ -22,6 +23,7 @@ module.exports = {
|
|||
tokenBridge: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
polygon: {
|
||||
conductorChainId: 2,
|
||||
|
@ -32,6 +34,7 @@ module.exports = {
|
|||
tokenBridge: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
avalanche: {
|
||||
conductorChainId: 2,
|
||||
|
@ -42,6 +45,7 @@ module.exports = {
|
|||
tokenBridge: "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
development: {
|
||||
conductorChainId: 2,
|
||||
|
@ -76,6 +80,7 @@ module.exports = {
|
|||
tokenBridge: "0xF890982f9310df57d00f659cf4fd87e65adEd8d7",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
fuji: {
|
||||
conductorChainId: 2,
|
||||
|
@ -86,6 +91,7 @@ module.exports = {
|
|||
tokenBridge: "0x61E44E506Ca5659E6c0bba9b678586fA2d729756",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
binance_testnet: {
|
||||
conductorChainId: 2,
|
||||
|
@ -96,6 +102,7 @@ module.exports = {
|
|||
tokenBridge: "0x9dcF9D205C9De35334D646BeE44b2D2859712A09",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
mumbai: {
|
||||
conductorChainId: 2,
|
||||
|
@ -106,6 +113,7 @@ module.exports = {
|
|||
tokenBridge: "0x377D55a7928c046E18eEbb61977e714d2a76472a",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
fantom_testnet: {
|
||||
conductorChainId: 2,
|
||||
|
@ -116,5 +124,6 @@ module.exports = {
|
|||
tokenBridge: "0x599CEa2204B4FaECd584Ab1F2b6aCA137a0afbE8",
|
||||
mnemonic: "",
|
||||
rpc: "",
|
||||
deployImplementationOnly: false,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,7 +9,6 @@ const DeploymentConfig = require(`${ethereumRootPath}/icco_deployment_config.js`
|
|||
const fs = require("fs");
|
||||
|
||||
module.exports = async function(deployer, network) {
|
||||
console.log(network);
|
||||
const config = DeploymentConfig[network];
|
||||
if (!config) {
|
||||
throw Error("deployment config undefined");
|
||||
|
@ -22,30 +21,32 @@ module.exports = async function(deployer, network) {
|
|||
// deploy conductor implementation
|
||||
await deployer.deploy(ConductorImplementation);
|
||||
|
||||
// deploy conductor setup
|
||||
await deployer.deploy(ConductorSetup);
|
||||
if (!config.deployImplementationOnly) {
|
||||
// deploy conductor setup
|
||||
await deployer.deploy(ConductorSetup);
|
||||
|
||||
// encode initialisation data
|
||||
const conductorSetup = new web3.eth.Contract(
|
||||
ConductorSetup.abi,
|
||||
ConductorSetup.address
|
||||
);
|
||||
const conductorInitData = conductorSetup.methods
|
||||
.setup(
|
||||
ConductorImplementation.address,
|
||||
config.conductorChainId,
|
||||
config.wormhole,
|
||||
config.tokenBridge,
|
||||
config.consistencyLevel
|
||||
)
|
||||
.encodeABI();
|
||||
// encode initialisation data
|
||||
const conductorSetup = new web3.eth.Contract(
|
||||
ConductorSetup.abi,
|
||||
ConductorSetup.address
|
||||
);
|
||||
const conductorInitData = conductorSetup.methods
|
||||
.setup(
|
||||
ConductorImplementation.address,
|
||||
config.conductorChainId,
|
||||
config.wormhole,
|
||||
config.tokenBridge,
|
||||
config.consistencyLevel
|
||||
)
|
||||
.encodeABI();
|
||||
|
||||
// deploy conductor proxy
|
||||
await deployer.deploy(
|
||||
TokenSaleConductor,
|
||||
ConductorSetup.address,
|
||||
conductorInitData
|
||||
);
|
||||
// deploy conductor proxy
|
||||
await deployer.deploy(
|
||||
TokenSaleConductor,
|
||||
ConductorSetup.address,
|
||||
conductorInitData
|
||||
);
|
||||
}
|
||||
|
||||
// cache address depending on whether contract
|
||||
// has been deployed to mainnet, testnet or devnet
|
||||
|
@ -73,12 +74,14 @@ module.exports = async function(deployer, network) {
|
|||
const contents = fs.existsSync(fp)
|
||||
? JSON.parse(fs.readFileSync(fp, "utf8"))
|
||||
: {};
|
||||
contents.conductorAddress = TokenSaleConductor.address;
|
||||
contents.conductorChain = parseInt(config.conductorChainId);
|
||||
if (!config.deployImplementationOnly) {
|
||||
contents.conductorAddress = TokenSaleConductor.address;
|
||||
contents.conductorChain = parseInt(config.conductorChainId);
|
||||
} else {
|
||||
const implementationString = network.concat("ConductorImplementation");
|
||||
contents[implementationString] = ConductorImplementation.address;
|
||||
}
|
||||
|
||||
fs.writeFileSync(fp, JSON.stringify(contents, null, 2), "utf8");
|
||||
}
|
||||
|
||||
// TODO: mainnet
|
||||
if (network == "mainnet") {
|
||||
}
|
||||
};
|
||||
|
|
|
@ -23,15 +23,6 @@ module.exports = async function(deployer, network) {
|
|||
// deploy contributor implementation
|
||||
await deployer.deploy(ContributorImplementation);
|
||||
|
||||
// deploy contributor setup
|
||||
await deployer.deploy(ContributorSetup);
|
||||
|
||||
// encode initialisation data
|
||||
const contributorSetup = new web3.eth.Contract(
|
||||
ContributorSetup.abi,
|
||||
ContributorSetup.address
|
||||
);
|
||||
|
||||
// figure out which conductor address to use
|
||||
let conductorAddr = undefined;
|
||||
if (network == "development") {
|
||||
|
@ -61,25 +52,36 @@ module.exports = async function(deployer, network) {
|
|||
throw Error("conductorAddr is undefined");
|
||||
}
|
||||
|
||||
const contributorInitData = contributorSetup.methods
|
||||
.setup(
|
||||
ContributorImplementation.address,
|
||||
config.contributorChainId,
|
||||
config.conductorChainId,
|
||||
conductorAddr,
|
||||
config.authority,
|
||||
config.wormhole,
|
||||
config.tokenBridge,
|
||||
config.consistencyLevel
|
||||
)
|
||||
.encodeABI();
|
||||
if (!config.deployImplementationOnly) {
|
||||
// deploy contributor setup
|
||||
await deployer.deploy(ContributorSetup);
|
||||
|
||||
// deploy conductor proxy
|
||||
await deployer.deploy(
|
||||
TokenSaleContributor,
|
||||
ContributorSetup.address,
|
||||
contributorInitData
|
||||
);
|
||||
// encode initialisation data
|
||||
const contributorSetup = new web3.eth.Contract(
|
||||
ContributorSetup.abi,
|
||||
ContributorSetup.address
|
||||
);
|
||||
|
||||
const contributorInitData = contributorSetup.methods
|
||||
.setup(
|
||||
ContributorImplementation.address,
|
||||
config.contributorChainId,
|
||||
config.conductorChainId,
|
||||
conductorAddr,
|
||||
config.authority,
|
||||
config.wormhole,
|
||||
config.tokenBridge,
|
||||
config.consistencyLevel
|
||||
)
|
||||
.encodeABI();
|
||||
|
||||
// deploy conductor proxy
|
||||
await deployer.deploy(
|
||||
TokenSaleContributor,
|
||||
ContributorSetup.address,
|
||||
contributorInitData
|
||||
);
|
||||
}
|
||||
|
||||
// cache address for registration purposes
|
||||
{
|
||||
|
@ -108,7 +110,14 @@ module.exports = async function(deployer, network) {
|
|||
if (network == "eth_devnet" || network == "eth_devnet2") {
|
||||
contents[addrName] = TokenSaleContributor.address;
|
||||
} else {
|
||||
contents[network] = TokenSaleContributor.address;
|
||||
if (!config.deployImplementationOnly) {
|
||||
contents[network] = TokenSaleContributor.address;
|
||||
} else {
|
||||
const implementationString = network.concat(
|
||||
"ContributorImplementation"
|
||||
);
|
||||
contents[implementationString] = ContributorImplementation.address;
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(fp, JSON.stringify(contents, null, 2), "utf8");
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,6 +32,7 @@ export async function getSaleFromConductorOnEth(
|
|||
acceptedTokensChains: sale.acceptedTokensChains,
|
||||
acceptedTokensAddresses: sale.acceptedTokensAddresses,
|
||||
acceptedTokensConversionRates: sale.acceptedTokensConversionRates,
|
||||
solanaAcceptedTokensCount: sale.solanaAcceptedTokensCount,
|
||||
contributions: sale.contributions,
|
||||
contributionsCollected: sale.contributionsCollected,
|
||||
isSealed: sale.isSealed,
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
nativeToHexString,
|
||||
getForeignAssetEth,
|
||||
getOriginalAssetEth,
|
||||
hexToNativeString,
|
||||
uint8ArrayToNative,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { parseUnits } from "ethers/lib/utils";
|
||||
|
|
|
@ -42,6 +42,7 @@ export interface ConductorSale extends Sale {
|
|||
localTokenDecimals: number;
|
||||
localTokenAddress: string;
|
||||
solanaTokenAccount: ethers.BytesLike;
|
||||
solanaAcceptedTokensCount: number;
|
||||
contributions: ethers.BigNumberish[];
|
||||
contributionsCollected: boolean[];
|
||||
refundIsClaimed: boolean;
|
||||
|
|
|
@ -12,6 +12,9 @@ export const CONTRIBUTOR_INFO = JSON.parse(
|
|||
fs.readFileSync(`${__dirname}/../cfg/contributors.json`, "utf8")
|
||||
);
|
||||
|
||||
// VAA fetching params
|
||||
export const RETRY_TIMEOUT_SECONDS = 180;
|
||||
|
||||
// deployment info for the sale
|
||||
export const CONDUCTOR_ADDRESS = TESTNET_ADDRESSES.conductorAddress;
|
||||
export const CONDUCTOR_CHAIN_ID = TESTNET_ADDRESSES.conductorChain;
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
abortSaleEarlyAtConductor,
|
||||
abortSaleEarlyAtContributor,
|
||||
testProvider,
|
||||
abortSaleAtContributors,
|
||||
} from "./utils";
|
||||
import {
|
||||
SALE_CONFIG,
|
||||
|
@ -21,10 +22,14 @@ import {
|
|||
CONDUCTOR_NETWORK,
|
||||
CONTRIBUTOR_INFO,
|
||||
CONTRIBUTOR_NETWORKS,
|
||||
CONDUCTOR_ADDRESS,
|
||||
} from "./consts";
|
||||
import { Contribution, saleParams, SealSaleResult } from "./structs";
|
||||
import { setDefaultWasm } from "@certusone/wormhole-sdk";
|
||||
import { getSaleFromContributorOnEth } from "wormhole-icco-sdk";
|
||||
import {
|
||||
getSaleFromConductorOnEth,
|
||||
getSaleFromContributorOnEth,
|
||||
} from "wormhole-icco-sdk";
|
||||
|
||||
setDefaultWasm("node");
|
||||
|
||||
|
@ -49,6 +54,8 @@ async function main() {
|
|||
console.info("Sale", saleInit.saleId, "has been initialized.");
|
||||
|
||||
// test aborting the sale early
|
||||
let saleTerminatedEarly = false;
|
||||
|
||||
if (SALE_CONFIG["testParams"].abortSaleEarly) {
|
||||
console.log("Aborting sale early on the Conductor.");
|
||||
// abort the sale early in the conductor
|
||||
|
@ -57,6 +64,61 @@ async function main() {
|
|||
console.log("Aborting sale early on the Contributors.");
|
||||
await abortSaleEarlyAtContributor(saleInit, abortEarlyReceipt);
|
||||
|
||||
saleTerminatedEarly = true;
|
||||
}
|
||||
|
||||
// continue with the sale if it wasn't aborted early
|
||||
let saleResult: SealSaleResult;
|
||||
let successfulContributions: Contribution[] = [];
|
||||
|
||||
if (!saleTerminatedEarly) {
|
||||
// wait for the sale to start before contributing
|
||||
console.info("Waiting for the sale to start...");
|
||||
const extraTime: number = 5; // wait an extra 5 seconds
|
||||
await waitForSaleToStart(saleInit, extraTime);
|
||||
|
||||
// loop through contributors and safe contribute one by one
|
||||
const contributions: Contribution[] = CONTRIBUTOR_INFO["contributions"];
|
||||
for (let i = 0; i < contributions.length; i++) {
|
||||
const successful = await prepareAndExecuteContribution(
|
||||
saleInit.saleId,
|
||||
raiseParams.token,
|
||||
contributions[i]
|
||||
);
|
||||
if (successful) {
|
||||
console.info("Contribution successful for contribution:", i);
|
||||
successfulContributions.push(contributions[i]);
|
||||
} else {
|
||||
console.log("Contribution failed for contribution:", i);
|
||||
}
|
||||
}
|
||||
|
||||
// wait for sale to end
|
||||
console.log("Waiting for the sale to end...");
|
||||
await waitForSaleToEnd(saleInit, 10);
|
||||
|
||||
// attest contributions on each contributor and collect contributions in conductor
|
||||
await attestAndCollectContributions(saleInit);
|
||||
|
||||
// seal the sale on the conductor contract
|
||||
saleResult = await sealOrAbortSaleOnEth(saleInit);
|
||||
console.log("Sale results have been finalized.");
|
||||
} else {
|
||||
console.log("Skipping contributions, the sale was aborted early!");
|
||||
}
|
||||
|
||||
// check to see if the sale failed, abort and refund folks if so
|
||||
const conductorSale = await getSaleFromConductorOnEth(
|
||||
CONDUCTOR_ADDRESS,
|
||||
testProvider(CONDUCTOR_NETWORK),
|
||||
saleInit.saleId
|
||||
);
|
||||
|
||||
if (conductorSale.isAborted || saleTerminatedEarly) {
|
||||
// abort on the contributors if not saleTerminatedEarly
|
||||
if (!saleTerminatedEarly) {
|
||||
await abortSaleAtContributors(saleResult);
|
||||
}
|
||||
// confirm that the sale was aborted on each contributor
|
||||
for (let i = 0; i < CONTRIBUTOR_NETWORKS.length; i++) {
|
||||
let network = CONTRIBUTOR_NETWORKS[i];
|
||||
|
@ -67,45 +129,13 @@ async function main() {
|
|||
);
|
||||
if (contributorSale.isAborted) {
|
||||
console.log("Successfully aborted sale on contributor:", network);
|
||||
} else {
|
||||
console.log("Failed to abort the sale on contributor:", network);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// wait for the sale to start before contributing
|
||||
console.info("Waiting for the sale to start...");
|
||||
const extraTime: number = 5; // wait an extra 5 seconds
|
||||
await waitForSaleToStart(saleInit, extraTime);
|
||||
|
||||
// loop through contributors and safe contribute one by one
|
||||
const successfulContributions: Contribution[] = [];
|
||||
|
||||
const contributions: Contribution[] = CONTRIBUTOR_INFO["contributions"];
|
||||
for (let i = 0; i < contributions.length; i++) {
|
||||
const successful = await prepareAndExecuteContribution(
|
||||
saleInit.saleId,
|
||||
raiseParams.token,
|
||||
contributions[i]
|
||||
);
|
||||
if (successful) {
|
||||
console.info("Contribution successful for contribution:", i);
|
||||
successfulContributions.push(contributions[i]);
|
||||
} else {
|
||||
console.log("Contribution failed for contribution:", i);
|
||||
}
|
||||
}
|
||||
|
||||
// wait for sale to end
|
||||
console.log("Waiting for the sale to end...");
|
||||
await waitForSaleToEnd(saleInit, 10);
|
||||
|
||||
// attest contributions on each contributor and collect contributions in conductor
|
||||
await attestAndCollectContributions(saleInit);
|
||||
|
||||
// seal the sale on the Conductor contract
|
||||
const saleResult: SealSaleResult = await sealOrAbortSaleOnEth(saleInit);
|
||||
console.log("Sale results have been finalized.");
|
||||
|
||||
// redeem the transfer VAAs on all chains
|
||||
await redeemCrossChainAllocations(saleResult);
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
nativeToUint8Array,
|
||||
abortSaleBeforeStartOnEth,
|
||||
saleAbortedOnEth,
|
||||
getSaleIdFromIccoVaa,
|
||||
} from "wormhole-icco-sdk";
|
||||
import {
|
||||
WORMHOLE_ADDRESSES,
|
||||
|
@ -48,6 +49,7 @@ import {
|
|||
KYC_AUTHORITY_KEY,
|
||||
CHAIN_ID_TO_NETWORK,
|
||||
CONDUCTOR_CHAIN_ID,
|
||||
RETRY_TIMEOUT_SECONDS,
|
||||
} from "./consts";
|
||||
import {
|
||||
TokenConfig,
|
||||
|
@ -57,6 +59,7 @@ import {
|
|||
SaleSealed,
|
||||
} from "./structs";
|
||||
import { signContribution } from "./kyc";
|
||||
import { assert } from "console";
|
||||
|
||||
export async function extractVaaPayload(
|
||||
signedVaa: Uint8Array
|
||||
|
@ -152,6 +155,7 @@ export async function getSignedVaaFromSequence(
|
|||
emitterAddress: string,
|
||||
sequence: string
|
||||
): Promise<Uint8Array> {
|
||||
console.log("Searching for VAA with sequence:", sequence);
|
||||
const result = await getSignedVAAWithRetry(
|
||||
WORMHOLE_ADDRESSES.guardianRpc,
|
||||
chainId,
|
||||
|
@ -159,8 +163,10 @@ export async function getSignedVaaFromSequence(
|
|||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
},
|
||||
RETRY_TIMEOUT_SECONDS
|
||||
);
|
||||
console.log("Found VAA for sequence:", sequence);
|
||||
return result.vaaBytes;
|
||||
}
|
||||
|
||||
|
@ -469,6 +475,7 @@ export async function attestAndCollectContributions(
|
|||
signedVaas,
|
||||
initiatorWallet(CONDUCTOR_NETWORK)
|
||||
);
|
||||
assert(receipts.length == signedVaas.length);
|
||||
}
|
||||
console.info("Finished collecting contributions.");
|
||||
|
||||
|
@ -496,11 +503,10 @@ export async function sealSaleAndParseReceiptOnEth(
|
|||
saleId: ethers.BigNumberish,
|
||||
coreBridgeAddress: string,
|
||||
tokenBridgeAddress: string,
|
||||
wormholeHosts: string[],
|
||||
extraGrpcOpts: any = {},
|
||||
wallet: ethers.Wallet
|
||||
): Promise<SealSaleResult> {
|
||||
const receipt = await sealSaleOnEth(conductorAddress, saleId, wallet);
|
||||
console.log("Finished sealing the sale on the Conductor.");
|
||||
|
||||
const sale = await getSaleFromConductorOnEth(
|
||||
conductorAddress,
|
||||
|
@ -515,37 +521,35 @@ export async function sealSaleAndParseReceiptOnEth(
|
|||
throw Error("no vaa sequences found");
|
||||
}
|
||||
|
||||
const result = await getSignedVAAWithRetry(
|
||||
wormholeHosts,
|
||||
// fetch the VAA
|
||||
const sealSaleVaa = await getSignedVaaFromSequence(
|
||||
emitterChain,
|
||||
getEmitterAddressEth(conductorAddress),
|
||||
sealSaleSequence,
|
||||
extraGrpcOpts
|
||||
sealSaleSequence
|
||||
);
|
||||
const sealSaleVaa = result.vaaBytes;
|
||||
console.log("Found the sealSale VAA emitted from the Conductor.");
|
||||
|
||||
// search for allocations
|
||||
// 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);
|
||||
if (sale.isSealed) {
|
||||
for (const sequence of sequences) {
|
||||
const signedVaa = await getSignedVaaFromSequence(
|
||||
emitterChain,
|
||||
getEmitterAddressEth(tokenBridgeAddress),
|
||||
sequence
|
||||
);
|
||||
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);
|
||||
const signedVaas = mapped.get(chainId);
|
||||
if (signedVaas === undefined) {
|
||||
mapped.set(chainId, [signedVaa]);
|
||||
} else {
|
||||
signedVaas.push(signedVaa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sale: sale,
|
||||
transferVaas: mapped,
|
||||
|
@ -563,10 +567,6 @@ export async function sealOrAbortSaleOnEth(
|
|||
saleId,
|
||||
WORMHOLE_ADDRESSES[CONDUCTOR_NETWORK].wormhole,
|
||||
WORMHOLE_ADDRESSES[CONDUCTOR_NETWORK].tokenBridge,
|
||||
WORMHOLE_ADDRESSES.guardianRpc,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
},
|
||||
initiatorWallet(CONDUCTOR_NETWORK)
|
||||
);
|
||||
}
|
||||
|
@ -687,18 +687,13 @@ export async function redeemCrossChainContributions(
|
|||
}
|
||||
|
||||
for (const sequence of sequences) {
|
||||
const result = await getSignedVAAWithRetry(
|
||||
WORMHOLE_ADDRESSES.guardianRpc,
|
||||
const signedVaa = await getSignedVaaFromSequence(
|
||||
emitterChain,
|
||||
getEmitterAddressEth(
|
||||
WORMHOLE_ADDRESSES[CHAIN_ID_TO_NETWORK.get(emitterChain)].tokenBridge
|
||||
),
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
sequence
|
||||
);
|
||||
const signedVaa = result.vaaBytes;
|
||||
const vaaPayload = await extractVaaPayload(signedVaa);
|
||||
const chainId = await getTargetChainIdFromTransferVaa(vaaPayload);
|
||||
const targetNetwork = CHAIN_ID_TO_NETWORK.get(chainId);
|
||||
|
@ -758,3 +753,26 @@ export async function abortSaleEarlyAtContributor(
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
export async function abortSaleAtContributors(saleResult: SealSaleResult) {
|
||||
const signedVaa = saleResult.sealSaleVaa;
|
||||
const vaaPayload = await extractVaaPayload(signedVaa);
|
||||
const saleId = await getSaleIdFromIccoVaa(vaaPayload);
|
||||
|
||||
{
|
||||
const receipts = await Promise.all(
|
||||
CONTRIBUTOR_NETWORKS.map(
|
||||
async (network): Promise<ethers.ContractReceipt> => {
|
||||
return saleAbortedOnEth(
|
||||
TESTNET_ADDRESSES[network],
|
||||
signedVaa,
|
||||
initiatorWallet(network),
|
||||
saleId
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"gaurdianRpc": "https://wormhole-v2-testnet-api.certus.one",
|
||||
"conductorAddress": "0x5c49f34D92316A2ac68d10A1e2168e16610e84f9",
|
||||
"conductorAddress": "0xce121EA9c289390df7d812F83Ed6bE79A167DfE4",
|
||||
"conductorChain": 2,
|
||||
"goerli": "0xB6AF16A9B216c9eEd2AbA4452088FE28cc22d5Ff",
|
||||
"fuji": "0xE60C9105BF114f198CE93F3A1aDf0FB09427C674"
|
||||
}
|
||||
"goerli": "0xFee98e512e4Ab409294a080b3C2B19748Fd54A6d",
|
||||
"fuji": "0x72df3672A0E889ca8875080d414056fD4f7Aa9fA",
|
||||
"solana_testnet": "8WCGVzrvGoLRrWY5V4L7iDCJym3wu6SiL3DCjGsSrP4k"
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
},
|
||||
"include": [
|
||||
"register_tilt_contributors.ts",
|
||||
"register_testnet_contributors.ts"
|
||||
"register_testnet_contributors.ts",
|
||||
"upgrade_testnet_contracts.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import yargs from "yargs";
|
||||
import { ethers } from "ethers";
|
||||
import { Conductor__factory, Contributor__factory } from "wormhole-icco-sdk";
|
||||
|
||||
const fs = require("fs");
|
||||
const DeploymentConfig = require("../../ethereum/icco_deployment_config.js");
|
||||
|
||||
function parseArgs(): string[] {
|
||||
const parsed = yargs(process.argv.slice(2))
|
||||
.options("contractType", {
|
||||
type: "string",
|
||||
description: "Type of contract (e.g. conductor)",
|
||||
require: true,
|
||||
})
|
||||
.options("network", {
|
||||
type: "string",
|
||||
description: "Network to deploy to (e.g. goerli)",
|
||||
require: true,
|
||||
})
|
||||
.help("h")
|
||||
.alias("h", "help").argv;
|
||||
|
||||
const args = [parsed.contractType, parsed.network];
|
||||
return args;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs();
|
||||
|
||||
// create checksum address
|
||||
const contractType = args[0];
|
||||
const network = args[1];
|
||||
|
||||
const config = DeploymentConfig[network];
|
||||
if (!config) {
|
||||
throw Error("deployment config undefined");
|
||||
}
|
||||
|
||||
const testnet = JSON.parse(
|
||||
fs.readFileSync(`${__dirname}/../../testnet.json`, "utf8")
|
||||
);
|
||||
|
||||
// create wallet to call sdk method with
|
||||
const provider = new ethers.providers.JsonRpcProvider(config.rpc);
|
||||
const wallet: ethers.Wallet = new ethers.Wallet(config.mnemonic, provider);
|
||||
|
||||
// create the factory and grab the implementation address
|
||||
let contractFactory;
|
||||
let chainId;
|
||||
let newImplementation;
|
||||
|
||||
if (contractType == "conductor") {
|
||||
contractFactory = Conductor__factory.connect(
|
||||
testnet["conductorAddress"],
|
||||
wallet
|
||||
);
|
||||
chainId = config.conductorChainId;
|
||||
newImplementation = testnet[network.concat("ConductorImplementation")];
|
||||
} else {
|
||||
contractFactory = Contributor__factory.connect(testnet[network], wallet);
|
||||
chainId = config.contributorChainId;
|
||||
newImplementation = testnet[network.concat("ContributorImplementation")];
|
||||
}
|
||||
|
||||
// run the upgrade
|
||||
const tx = await contractFactory.upgrade(chainId, newImplementation);
|
||||
const receipt = await tx.wait();
|
||||
|
||||
console.log(
|
||||
"transction:",
|
||||
receipt.transactionHash,
|
||||
", newImplementation:",
|
||||
newImplementation
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
main();
|
Loading…
Reference in New Issue