all: consider decimals on wrapped assets, fix VAA posting, fix solana account parsing

This commit is contained in:
Hendrik Hofstadt 2020-08-28 15:10:42 +02:00
parent 8e6dc495dc
commit f6750a3762
34 changed files with 5869 additions and 5426 deletions

View File

@ -48,53 +48,53 @@ func p2p(obsvC chan *gossipv1.LockupObservation, sendC chan []byte) func(ctx con
var idht *dht.IpfsDHT
h, err := libp2p.New(ctx,
// Use the keypair we generated
libp2p.Identity(priv),
h, err := libp2p.New(ctx,
// Use the keypair we generated
libp2p.Identity(priv),
// Multiple listen addresses
libp2p.ListenAddrStrings(
// Listen on QUIC only.
// TODO(leo): is this more or less stable than using both TCP and QUIC transports?
// https://github.com/libp2p/go-libp2p/issues/688
fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", *p2pPort),
fmt.Sprintf("/ip6/::/udp/%d/quic", *p2pPort),
),
// Multiple listen addresses
libp2p.ListenAddrStrings(
// Listen on QUIC only.
// TODO(leo): is this more or less stable than using both TCP and QUIC transports?
// https://github.com/libp2p/go-libp2p/issues/688
fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", *p2pPort),
fmt.Sprintf("/ip6/::/udp/%d/quic", *p2pPort),
),
// Enable TLS security as the only security protocol.
libp2p.Security(libp2ptls.ID, libp2ptls.New),
// Enable TLS security as the only security protocol.
libp2p.Security(libp2ptls.ID, libp2ptls.New),
// Enable QUIC transport as the only transport.
libp2p.Transport(libp2pquic.NewTransport),
// Enable QUIC transport as the only transport.
libp2p.Transport(libp2pquic.NewTransport),
// Let's prevent our peer from having too many
// connections by attaching a connection manager.
libp2p.ConnectionManager(connmgr.NewConnManager(
100, // Lowwater
400, // HighWater,
time.Minute, // GracePeriod
)),
// Let's prevent our peer from having too many
// connections by attaching a connection manager.
libp2p.ConnectionManager(connmgr.NewConnManager(
100, // Lowwater
400, // HighWater,
time.Minute, // GracePeriod
)),
// Let this host use the DHT to find other hosts
libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) {
// TODO(leo): Persistent data store (i.e. address book)
idht, err = dht.New(ctx, h, dht.Mode(dht.ModeServer),
// TODO(leo): This intentionally makes us incompatible with the global IPFS DHT
dht.ProtocolPrefix(protocol.ID("/"+*p2pNetworkID)),
// Let this host use the DHT to find other hosts
libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) {
// TODO(leo): Persistent data store (i.e. address book)
idht, err = dht.New(ctx, h, dht.Mode(dht.ModeServer),
// TODO(leo): This intentionally makes us incompatible with the global IPFS DHT
dht.ProtocolPrefix(protocol.ID("/"+*p2pNetworkID)),
)
return idht, err
}),
)
return idht, err
}),
)
if err != nil {
panic(err)
}
if err != nil {
panic(err)
}
defer func() {
// TODO: libp2p cannot be cleanly restarted (https://github.com/libp2p/go-libp2p/issues/992)
logger.Error("p2p routine has exited, cancelling root context...", zap.Error(re))
rootCtxCancel()
}()
defer func() {
// TODO: libp2p cannot be cleanly restarted (https://github.com/libp2p/go-libp2p/issues/992)
logger.Error("p2p routine has exited, cancelling root context...", zap.Error(re))
rootCtxCancel()
}()
logger.Info("Connecting to bootstrap peers", zap.String("bootstrap_peers", *p2pBootstrap))

View File

@ -130,8 +130,9 @@ func vaaConsensusProcessor(lockC chan *common.ChainLock, setC chan *common.Guard
SourceAddress: k.SourceAddress,
TargetAddress: k.TargetAddress,
Asset: &vaa.AssetMeta{
Chain: k.TokenChain,
Address: k.TokenAddress,
Chain: k.TokenChain,
Address: k.TokenAddress,
Decimals: k.TokenDecimals,
},
Amount: k.Amount,
},
@ -281,10 +282,15 @@ func vaaConsensusProcessor(lockC chan *common.ChainLock, setC chan *common.Guard
zap.Any("vaa", signed),
zap.String("bytes", hex.EncodeToString(vaaBytes)))
if idx == 0 {
if idx == 1 {
vaaC <- signed
}
case t.TargetChain == vaa.ChainIDEthereum:
// cross-submit to Solana for data availability
if idx == 1 {
vaaC <- signed
}
timeout, cancel := context.WithTimeout(ctx, 15*time.Second)
tx, err := devnet.SubmitVAA(timeout, *ethRPC, signed)
cancel()
@ -294,10 +300,6 @@ func vaaConsensusProcessor(lockC chan *common.ChainLock, setC chan *common.Guard
}
logger.Info("lockup submitted to Ethereum", zap.Any("tx", tx))
// cross-submit to Solana for data availability
if idx == 0 {
vaaC <- signed
}
default:
logger.Error("we don't know how to submit this VAA",
zap.String("digest", hash),

View File

@ -20,6 +20,8 @@ type signerInfo struct {
index int
}
var i = 0
func main() {
keys := generateKeys(6)
@ -38,8 +40,9 @@ func main() {
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: padAddress(devnet.GanacheClientDefaultAccountAddress),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
@ -56,8 +59,9 @@ func main() {
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: padAddress(devnet.GanacheClientDefaultAccountAddress),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDEthereum,
Address: hexToAddress("0xd833215cbcc3f914bd1c9ece3ee7bf8b14f841bb"),
Chain: vaa.ChainIDEthereum,
Address: hexToAddress("0xd833215cbcc3f914bd1c9ece3ee7bf8b14f841bb"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
@ -98,8 +102,9 @@ func main() {
SourceAddress: vaa.Address{2, 1, 4},
TargetAddress: padAddress(devnet.GanacheClientDefaultAccountAddress),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
@ -116,8 +121,9 @@ func main() {
SourceAddress: vaa.Address{2, 1, 5},
TargetAddress: padAddress(devnet.GanacheClientDefaultAccountAddress),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
@ -134,13 +140,33 @@ func main() {
SourceAddress: vaa.Address{2, 1, 5},
TargetAddress: padAddress(devnet.GanacheClientDefaultAccountAddress),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[1], 0}})
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 1,
Timestamp: time.Unix(2000, 0),
Payload: &vaa.BodyTransfer{
Nonce: 57,
SourceChain: 1,
TargetChain: 2,
SourceAddress: vaa.Address{2, 1, 5},
TargetAddress: padAddress(devnet.GanacheClientDefaultAccountAddress),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
}, []*signerInfo{{keys[0], 0}})
signAndPrintVAA(&vaa.VAA{
Version: 1,
GuardianSetIndex: 1,
@ -169,8 +195,9 @@ func main() {
SourceAddress: vaa.Address{2, 1, 5},
TargetAddress: padAddress(devnet.GanacheClientDefaultAccountAddress),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
@ -187,8 +214,9 @@ func main() {
SourceAddress: vaa.Address{2, 1, 5},
TargetAddress: padAddress(devnet.GanacheClientDefaultAccountAddress),
Asset: &vaa.AssetMeta{
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Chain: vaa.ChainIDSolana,
Address: hexToAddress("0x347ef34687bdc9f189e87a9200658d9c40e9988"),
Decimals: 8,
},
Amount: big.NewInt(1000000000000000000),
},
@ -203,7 +231,8 @@ func signAndPrintVAA(vaa *vaa.VAA, signers []*signerInfo) {
if err != nil {
panic(err)
}
println(hex.EncodeToString(vData))
println(i, hex.EncodeToString(vData))
i++
}
func generateKeys(n int) (keys []*ecdsa.PrivateKey) {

View File

@ -21,8 +21,9 @@ type ChainLock struct {
SourceChain vaa.ChainID
TargetChain vaa.ChainID
TokenChain vaa.ChainID
TokenAddress vaa.Address
TokenChain vaa.ChainID
TokenAddress vaa.Address
TokenDecimals uint8
Amount *big.Int
}

View File

@ -20,7 +20,6 @@ var (
_ = big.NewInt
_ = strings.NewReader
_ = ethereum.NotFound
_ = abi.U256
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
@ -34,7 +33,7 @@ type WormholeGuardianSet struct {
}
// AbiABI is the input ABI used to generate the binding from.
const AbiABI = "[{\"inputs\":[{\"components\":[{\"internalType\":\"address[]\",\"name\":\"keys\",\"type\":\"address[]\"},{\"internalType\":\"uint32\",\"name\":\"expiration_time\",\"type\":\"uint32\"}],\"internalType\":\"structWormhole.GuardianSet\",\"name\":\"initial_guardian_set\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"wrapped_asset_master\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_guardian_set_expirity\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"oldGuardianIndex\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newGuardianIndex\",\"type\":\"uint32\"}],\"name\":\"LogGuardianSetChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"target_chain\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"token_chain\",\"type\":\"uint8\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"token\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"nonce\",\"type\":\"uint32\"}],\"name\":\"LogTokensLocked\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"guardian_set_expirity\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"guardian_set_index\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"guardian_sets\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"expiration_time\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isWrappedAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wrappedAssetMaster\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"wrappedAssets\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"idx\",\"type\":\"uint32\"}],\"name\":\"getGuardianSet\",\"outputs\":[{\"components\":[{\"internalType\":\"address[]\",\"name\":\"keys\",\"type\":\"address[]\"},{\"internalType\":\"uint32\",\"name\":\"expiration_time\",\"type\":\"uint32\"}],\"internalType\":\"structWormhole.GuardianSet\",\"name\":\"gs\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"vaa\",\"type\":\"bytes\"}],\"name\":\"submitVAA\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"target_chain\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"nonce\",\"type\":\"uint32\"}],\"name\":\"lockAssets\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"target_chain\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"nonce\",\"type\":\"uint32\"}],\"name\":\"lockETH\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}]"
const AbiABI = "[{\"inputs\":[{\"components\":[{\"internalType\":\"address[]\",\"name\":\"keys\",\"type\":\"address[]\"},{\"internalType\":\"uint32\",\"name\":\"expiration_time\",\"type\":\"uint32\"}],\"internalType\":\"structWormhole.GuardianSet\",\"name\":\"initial_guardian_set\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"wrapped_asset_master\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_guardian_set_expirity\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"oldGuardianIndex\",\"type\":\"uint32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newGuardianIndex\",\"type\":\"uint32\"}],\"name\":\"LogGuardianSetChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"target_chain\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"token_chain\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"token_decimals\",\"type\":\"uint8\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"token\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"sender\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"nonce\",\"type\":\"uint32\"}],\"name\":\"LogTokensLocked\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"consumedVAAs\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"guardian_set_expirity\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"guardian_set_index\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"guardian_sets\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"expiration_time\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isWrappedAsset\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wrappedAssetMaster\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"wrappedAssets\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"idx\",\"type\":\"uint32\"}],\"name\":\"getGuardianSet\",\"outputs\":[{\"components\":[{\"internalType\":\"address[]\",\"name\":\"keys\",\"type\":\"address[]\"},{\"internalType\":\"uint32\",\"name\":\"expiration_time\",\"type\":\"uint32\"}],\"internalType\":\"structWormhole.GuardianSet\",\"name\":\"gs\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"vaa\",\"type\":\"bytes\"}],\"name\":\"submitVAA\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"target_chain\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"nonce\",\"type\":\"uint32\"}],\"name\":\"lockAssets\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"recipient\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"target_chain\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"nonce\",\"type\":\"uint32\"}],\"name\":\"lockETH\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}]"
// Abi is an auto generated Go binding around an Ethereum contract.
type Abi struct {
@ -178,9 +177,35 @@ func (_Abi *AbiTransactorRaw) Transact(opts *bind.TransactOpts, method string, p
return _Abi.Contract.contract.Transact(opts, method, params...)
}
// ConsumedVAAs is a free data retrieval call binding the contract method 0xa31fe409.
//
// Solidity: function consumedVAAs(bytes32 ) view returns(bool)
func (_Abi *AbiCaller) ConsumedVAAs(opts *bind.CallOpts, arg0 [32]byte) (bool, error) {
var (
ret0 = new(bool)
)
out := ret0
err := _Abi.contract.Call(opts, out, "consumedVAAs", arg0)
return *ret0, err
}
// ConsumedVAAs is a free data retrieval call binding the contract method 0xa31fe409.
//
// Solidity: function consumedVAAs(bytes32 ) view returns(bool)
func (_Abi *AbiSession) ConsumedVAAs(arg0 [32]byte) (bool, error) {
return _Abi.Contract.ConsumedVAAs(&_Abi.CallOpts, arg0)
}
// ConsumedVAAs is a free data retrieval call binding the contract method 0xa31fe409.
//
// Solidity: function consumedVAAs(bytes32 ) view returns(bool)
func (_Abi *AbiCallerSession) ConsumedVAAs(arg0 [32]byte) (bool, error) {
return _Abi.Contract.ConsumedVAAs(&_Abi.CallOpts, arg0)
}
// GetGuardianSet is a free data retrieval call binding the contract method 0xf951975a.
//
// Solidity: function getGuardianSet(uint32 idx) constant returns(WormholeGuardianSet gs)
// Solidity: function getGuardianSet(uint32 idx) view returns((address[],uint32) gs)
func (_Abi *AbiCaller) GetGuardianSet(opts *bind.CallOpts, idx uint32) (WormholeGuardianSet, error) {
var (
ret0 = new(WormholeGuardianSet)
@ -192,21 +217,21 @@ func (_Abi *AbiCaller) GetGuardianSet(opts *bind.CallOpts, idx uint32) (Wormhole
// GetGuardianSet is a free data retrieval call binding the contract method 0xf951975a.
//
// Solidity: function getGuardianSet(uint32 idx) constant returns(WormholeGuardianSet gs)
// Solidity: function getGuardianSet(uint32 idx) view returns((address[],uint32) gs)
func (_Abi *AbiSession) GetGuardianSet(idx uint32) (WormholeGuardianSet, error) {
return _Abi.Contract.GetGuardianSet(&_Abi.CallOpts, idx)
}
// GetGuardianSet is a free data retrieval call binding the contract method 0xf951975a.
//
// Solidity: function getGuardianSet(uint32 idx) constant returns(WormholeGuardianSet gs)
// Solidity: function getGuardianSet(uint32 idx) view returns((address[],uint32) gs)
func (_Abi *AbiCallerSession) GetGuardianSet(idx uint32) (WormholeGuardianSet, error) {
return _Abi.Contract.GetGuardianSet(&_Abi.CallOpts, idx)
}
// GuardianSetExpirity is a free data retrieval call binding the contract method 0x4db47840.
//
// Solidity: function guardian_set_expirity() constant returns(uint32)
// Solidity: function guardian_set_expirity() view returns(uint32)
func (_Abi *AbiCaller) GuardianSetExpirity(opts *bind.CallOpts) (uint32, error) {
var (
ret0 = new(uint32)
@ -218,21 +243,21 @@ func (_Abi *AbiCaller) GuardianSetExpirity(opts *bind.CallOpts) (uint32, error)
// GuardianSetExpirity is a free data retrieval call binding the contract method 0x4db47840.
//
// Solidity: function guardian_set_expirity() constant returns(uint32)
// Solidity: function guardian_set_expirity() view returns(uint32)
func (_Abi *AbiSession) GuardianSetExpirity() (uint32, error) {
return _Abi.Contract.GuardianSetExpirity(&_Abi.CallOpts)
}
// GuardianSetExpirity is a free data retrieval call binding the contract method 0x4db47840.
//
// Solidity: function guardian_set_expirity() constant returns(uint32)
// Solidity: function guardian_set_expirity() view returns(uint32)
func (_Abi *AbiCallerSession) GuardianSetExpirity() (uint32, error) {
return _Abi.Contract.GuardianSetExpirity(&_Abi.CallOpts)
}
// GuardianSetIndex is a free data retrieval call binding the contract method 0x822d82b3.
//
// Solidity: function guardian_set_index() constant returns(uint32)
// Solidity: function guardian_set_index() view returns(uint32)
func (_Abi *AbiCaller) GuardianSetIndex(opts *bind.CallOpts) (uint32, error) {
var (
ret0 = new(uint32)
@ -244,21 +269,21 @@ func (_Abi *AbiCaller) GuardianSetIndex(opts *bind.CallOpts) (uint32, error) {
// GuardianSetIndex is a free data retrieval call binding the contract method 0x822d82b3.
//
// Solidity: function guardian_set_index() constant returns(uint32)
// Solidity: function guardian_set_index() view returns(uint32)
func (_Abi *AbiSession) GuardianSetIndex() (uint32, error) {
return _Abi.Contract.GuardianSetIndex(&_Abi.CallOpts)
}
// GuardianSetIndex is a free data retrieval call binding the contract method 0x822d82b3.
//
// Solidity: function guardian_set_index() constant returns(uint32)
// Solidity: function guardian_set_index() view returns(uint32)
func (_Abi *AbiCallerSession) GuardianSetIndex() (uint32, error) {
return _Abi.Contract.GuardianSetIndex(&_Abi.CallOpts)
}
// GuardianSets is a free data retrieval call binding the contract method 0x42b0aefa.
//
// Solidity: function guardian_sets(uint32 ) constant returns(uint32 expiration_time)
// Solidity: function guardian_sets(uint32 ) view returns(uint32 expiration_time)
func (_Abi *AbiCaller) GuardianSets(opts *bind.CallOpts, arg0 uint32) (uint32, error) {
var (
ret0 = new(uint32)
@ -270,21 +295,21 @@ func (_Abi *AbiCaller) GuardianSets(opts *bind.CallOpts, arg0 uint32) (uint32, e
// GuardianSets is a free data retrieval call binding the contract method 0x42b0aefa.
//
// Solidity: function guardian_sets(uint32 ) constant returns(uint32 expiration_time)
// Solidity: function guardian_sets(uint32 ) view returns(uint32 expiration_time)
func (_Abi *AbiSession) GuardianSets(arg0 uint32) (uint32, error) {
return _Abi.Contract.GuardianSets(&_Abi.CallOpts, arg0)
}
// GuardianSets is a free data retrieval call binding the contract method 0x42b0aefa.
//
// Solidity: function guardian_sets(uint32 ) constant returns(uint32 expiration_time)
// Solidity: function guardian_sets(uint32 ) view returns(uint32 expiration_time)
func (_Abi *AbiCallerSession) GuardianSets(arg0 uint32) (uint32, error) {
return _Abi.Contract.GuardianSets(&_Abi.CallOpts, arg0)
}
// IsWrappedAsset is a free data retrieval call binding the contract method 0x1a2be4da.
//
// Solidity: function isWrappedAsset(address ) constant returns(bool)
// Solidity: function isWrappedAsset(address ) view returns(bool)
func (_Abi *AbiCaller) IsWrappedAsset(opts *bind.CallOpts, arg0 common.Address) (bool, error) {
var (
ret0 = new(bool)
@ -296,21 +321,21 @@ func (_Abi *AbiCaller) IsWrappedAsset(opts *bind.CallOpts, arg0 common.Address)
// IsWrappedAsset is a free data retrieval call binding the contract method 0x1a2be4da.
//
// Solidity: function isWrappedAsset(address ) constant returns(bool)
// Solidity: function isWrappedAsset(address ) view returns(bool)
func (_Abi *AbiSession) IsWrappedAsset(arg0 common.Address) (bool, error) {
return _Abi.Contract.IsWrappedAsset(&_Abi.CallOpts, arg0)
}
// IsWrappedAsset is a free data retrieval call binding the contract method 0x1a2be4da.
//
// Solidity: function isWrappedAsset(address ) constant returns(bool)
// Solidity: function isWrappedAsset(address ) view returns(bool)
func (_Abi *AbiCallerSession) IsWrappedAsset(arg0 common.Address) (bool, error) {
return _Abi.Contract.IsWrappedAsset(&_Abi.CallOpts, arg0)
}
// WrappedAssetMaster is a free data retrieval call binding the contract method 0x99da1d3c.
//
// Solidity: function wrappedAssetMaster() constant returns(address)
// Solidity: function wrappedAssetMaster() view returns(address)
func (_Abi *AbiCaller) WrappedAssetMaster(opts *bind.CallOpts) (common.Address, error) {
var (
ret0 = new(common.Address)
@ -322,21 +347,21 @@ func (_Abi *AbiCaller) WrappedAssetMaster(opts *bind.CallOpts) (common.Address,
// WrappedAssetMaster is a free data retrieval call binding the contract method 0x99da1d3c.
//
// Solidity: function wrappedAssetMaster() constant returns(address)
// Solidity: function wrappedAssetMaster() view returns(address)
func (_Abi *AbiSession) WrappedAssetMaster() (common.Address, error) {
return _Abi.Contract.WrappedAssetMaster(&_Abi.CallOpts)
}
// WrappedAssetMaster is a free data retrieval call binding the contract method 0x99da1d3c.
//
// Solidity: function wrappedAssetMaster() constant returns(address)
// Solidity: function wrappedAssetMaster() view returns(address)
func (_Abi *AbiCallerSession) WrappedAssetMaster() (common.Address, error) {
return _Abi.Contract.WrappedAssetMaster(&_Abi.CallOpts)
}
// WrappedAssets is a free data retrieval call binding the contract method 0xb6694c2a.
//
// Solidity: function wrappedAssets(bytes32 ) constant returns(address)
// Solidity: function wrappedAssets(bytes32 ) view returns(address)
func (_Abi *AbiCaller) WrappedAssets(opts *bind.CallOpts, arg0 [32]byte) (common.Address, error) {
var (
ret0 = new(common.Address)
@ -348,14 +373,14 @@ func (_Abi *AbiCaller) WrappedAssets(opts *bind.CallOpts, arg0 [32]byte) (common
// WrappedAssets is a free data retrieval call binding the contract method 0xb6694c2a.
//
// Solidity: function wrappedAssets(bytes32 ) constant returns(address)
// Solidity: function wrappedAssets(bytes32 ) view returns(address)
func (_Abi *AbiSession) WrappedAssets(arg0 [32]byte) (common.Address, error) {
return _Abi.Contract.WrappedAssets(&_Abi.CallOpts, arg0)
}
// WrappedAssets is a free data retrieval call binding the contract method 0xb6694c2a.
//
// Solidity: function wrappedAssets(bytes32 ) constant returns(address)
// Solidity: function wrappedAssets(bytes32 ) view returns(address)
func (_Abi *AbiCallerSession) WrappedAssets(arg0 [32]byte) (common.Address, error) {
return _Abi.Contract.WrappedAssets(&_Abi.CallOpts, arg0)
}
@ -383,21 +408,21 @@ func (_Abi *AbiTransactorSession) LockAssets(asset common.Address, amount *big.I
// LockETH is a paid mutator transaction binding the contract method 0x58d62e46.
//
// Solidity: function lockETH(bytes32 recipient, uint8 target_chain, uint32 nonce) returns()
// Solidity: function lockETH(bytes32 recipient, uint8 target_chain, uint32 nonce) payable returns()
func (_Abi *AbiTransactor) LockETH(opts *bind.TransactOpts, recipient [32]byte, target_chain uint8, nonce uint32) (*types.Transaction, error) {
return _Abi.contract.Transact(opts, "lockETH", recipient, target_chain, nonce)
}
// LockETH is a paid mutator transaction binding the contract method 0x58d62e46.
//
// Solidity: function lockETH(bytes32 recipient, uint8 target_chain, uint32 nonce) returns()
// Solidity: function lockETH(bytes32 recipient, uint8 target_chain, uint32 nonce) payable returns()
func (_Abi *AbiSession) LockETH(recipient [32]byte, target_chain uint8, nonce uint32) (*types.Transaction, error) {
return _Abi.Contract.LockETH(&_Abi.TransactOpts, recipient, target_chain, nonce)
}
// LockETH is a paid mutator transaction binding the contract method 0x58d62e46.
//
// Solidity: function lockETH(bytes32 recipient, uint8 target_chain, uint32 nonce) returns()
// Solidity: function lockETH(bytes32 recipient, uint8 target_chain, uint32 nonce) payable returns()
func (_Abi *AbiTransactorSession) LockETH(recipient [32]byte, target_chain uint8, nonce uint32) (*types.Transaction, error) {
return _Abi.Contract.LockETH(&_Abi.TransactOpts, recipient, target_chain, nonce)
}
@ -423,6 +448,48 @@ func (_Abi *AbiTransactorSession) SubmitVAA(vaa []byte) (*types.Transaction, err
return _Abi.Contract.SubmitVAA(&_Abi.TransactOpts, vaa)
}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: fallback() payable returns()
func (_Abi *AbiTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) {
return _Abi.contract.RawTransact(opts, calldata)
}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: fallback() payable returns()
func (_Abi *AbiSession) Fallback(calldata []byte) (*types.Transaction, error) {
return _Abi.Contract.Fallback(&_Abi.TransactOpts, calldata)
}
// Fallback is a paid mutator transaction binding the contract fallback function.
//
// Solidity: fallback() payable returns()
func (_Abi *AbiTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) {
return _Abi.Contract.Fallback(&_Abi.TransactOpts, calldata)
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_Abi *AbiTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
return _Abi.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_Abi *AbiSession) Receive() (*types.Transaction, error) {
return _Abi.Contract.Receive(&_Abi.TransactOpts)
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_Abi *AbiTransactorSession) Receive() (*types.Transaction, error) {
return _Abi.Contract.Receive(&_Abi.TransactOpts)
}
// AbiLogGuardianSetChangedIterator is returned from FilterLogGuardianSetChanged and is used to iterate over the raw logs and unpacked data for LogGuardianSetChanged events raised by the Abi contract.
type AbiLogGuardianSetChangedIterator struct {
Event *AbiLogGuardianSetChanged // Event containing the contract specifics and raw log
@ -626,19 +693,20 @@ func (it *AbiLogTokensLockedIterator) Close() error {
// AbiLogTokensLocked represents a LogTokensLocked event raised by the Abi contract.
type AbiLogTokensLocked struct {
TargetChain uint8
TokenChain uint8
Token [32]byte
Sender [32]byte
Recipient [32]byte
Amount *big.Int
Nonce uint32
Raw types.Log // Blockchain specific contextual infos
TargetChain uint8
TokenChain uint8
TokenDecimals uint8
Token [32]byte
Sender [32]byte
Recipient [32]byte
Amount *big.Int
Nonce uint32
Raw types.Log // Blockchain specific contextual infos
}
// FilterLogTokensLocked is a free log retrieval operation binding the contract event 0x5742f26a345471409566883d5cac5a7d295eee7092e5be3a7d6c60bc2a3e2420.
// FilterLogTokensLocked is a free log retrieval operation binding the contract event 0x6bbd554ad75919f71fd91bf917ca6e4f41c10f03ab25751596a22253bb39aab8.
//
// Solidity: event LogTokensLocked(uint8 target_chain, uint8 token_chain, bytes32 indexed token, bytes32 indexed sender, bytes32 recipient, uint256 amount, uint32 nonce)
// Solidity: event LogTokensLocked(uint8 target_chain, uint8 token_chain, uint8 token_decimals, bytes32 indexed token, bytes32 indexed sender, bytes32 recipient, uint256 amount, uint32 nonce)
func (_Abi *AbiFilterer) FilterLogTokensLocked(opts *bind.FilterOpts, token [][32]byte, sender [][32]byte) (*AbiLogTokensLockedIterator, error) {
var tokenRule []interface{}
@ -657,9 +725,9 @@ func (_Abi *AbiFilterer) FilterLogTokensLocked(opts *bind.FilterOpts, token [][3
return &AbiLogTokensLockedIterator{contract: _Abi.contract, event: "LogTokensLocked", logs: logs, sub: sub}, nil
}
// WatchLogTokensLocked is a free log subscription operation binding the contract event 0x5742f26a345471409566883d5cac5a7d295eee7092e5be3a7d6c60bc2a3e2420.
// WatchLogTokensLocked is a free log subscription operation binding the contract event 0x6bbd554ad75919f71fd91bf917ca6e4f41c10f03ab25751596a22253bb39aab8.
//
// Solidity: event LogTokensLocked(uint8 target_chain, uint8 token_chain, bytes32 indexed token, bytes32 indexed sender, bytes32 recipient, uint256 amount, uint32 nonce)
// Solidity: event LogTokensLocked(uint8 target_chain, uint8 token_chain, uint8 token_decimals, bytes32 indexed token, bytes32 indexed sender, bytes32 recipient, uint256 amount, uint32 nonce)
func (_Abi *AbiFilterer) WatchLogTokensLocked(opts *bind.WatchOpts, sink chan<- *AbiLogTokensLocked, token [][32]byte, sender [][32]byte) (event.Subscription, error) {
var tokenRule []interface{}
@ -703,9 +771,9 @@ func (_Abi *AbiFilterer) WatchLogTokensLocked(opts *bind.WatchOpts, sink chan<-
}), nil
}
// ParseLogTokensLocked is a log parse operation binding the contract event 0x5742f26a345471409566883d5cac5a7d295eee7092e5be3a7d6c60bc2a3e2420.
// ParseLogTokensLocked is a log parse operation binding the contract event 0x6bbd554ad75919f71fd91bf917ca6e4f41c10f03ab25751596a22253bb39aab8.
//
// Solidity: event LogTokensLocked(uint8 target_chain, uint8 token_chain, bytes32 indexed token, bytes32 indexed sender, bytes32 recipient, uint256 amount, uint32 nonce)
// Solidity: event LogTokensLocked(uint8 target_chain, uint8 token_chain, uint8 token_decimals, bytes32 indexed token, bytes32 indexed sender, bytes32 recipient, uint256 amount, uint32 nonce)
func (_Abi *AbiFilterer) ParseLogTokensLocked(log types.Log) (*AbiLogTokensLocked, error) {
event := new(AbiLogTokensLocked)
if err := _Abi.contract.UnpackLog(event, "LogTokensLocked", log); err != nil {

View File

@ -114,6 +114,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
TargetChain: vaa.ChainID(ev.TargetChain),
TokenChain: vaa.ChainID(ev.TokenChain),
TokenAddress: ev.Token,
TokenDecimals: ev.TokenDecimals,
Amount: ev.Amount,
}

View File

@ -32,16 +32,6 @@ func NewSolanaBridgeWatcher(url string, lockEvents chan *common.ChainLock, vaaQu
return &SolanaBridgeWatcher{url: url, lockChan: lockEvents, vaaChan: vaaQueue}
}
// TODO: document/deduplicate
func padAddress(address eth_common.Address) vaa.Address {
paddedAddress := eth_common.LeftPadBytes(address[:], 32)
addr := vaa.Address{}
copy(addr[:], paddedAddress)
return addr
}
func (e *SolanaBridgeWatcher) Run(ctx context.Context) error {
timeout, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
@ -75,20 +65,21 @@ func (e *SolanaBridgeWatcher) Run(ctx context.Context) error {
switch event := ev.Event.(type) {
case *agentv1.LockupEvent_New:
logger.Debug("received lockup event",
zap.Any("event", ev)) // TODO: debug level
zap.Any("event", ev))
lock := &common.ChainLock{
TxHash: eth_common.HexToHash(ev.LockupAddress),
Timestamp: time.Time{}, // FIXME
Timestamp: time.Unix(int64(ev.Time), 0),
Nonce: event.New.Nonce,
SourceAddress: padAddress(eth_common.BytesToAddress(event.New.SourceAddress)),
TargetAddress: padAddress(eth_common.BytesToAddress(event.New.TargetAddress)),
SourceChain: vaa.ChainIDSolana,
TargetChain: vaa.ChainID(event.New.TargetChain),
TokenChain: vaa.ChainID(event.New.TokenChain),
TokenDecimals: uint8(event.New.TokenDecimals),
Amount: new(big.Int).SetBytes(event.New.Amount),
}
copy(lock.TokenAddress[:], event.New.TokenAddress)
copy(lock.SourceAddress[:], event.New.SourceAddress)
copy(lock.TargetAddress[:], event.New.TargetAddress)
e.lockChan <- lock
logger.Info("found new lockup transaction", zap.String("lockup_address", ev.LockupAddress))

View File

@ -44,7 +44,7 @@ type (
// Index of the validator
Index uint8
// Signature data
Signature [65]byte // TODO: hex marshaller
Signature [65]byte // TODO: hex marshaller
}
// AssetMeta describes an asset within the Wormhole protocol
@ -53,6 +53,8 @@ type (
Chain ChainID
// Address is the address of the token contract/mint/equivalent.
Address Address
// Decimals is the number of decimals the token has
Decimals uint8
}
vaaBody interface {
@ -113,8 +115,6 @@ const (
SupportedVAAVersion = 0x01
)
// Unmarshal deserializes the binary representation of a VAA
func Unmarshal(data []byte) (*VAA, error) {
if len(data) < minVAALength {
@ -321,6 +321,9 @@ func parseBodyTransfer(r io.Reader) (*BodyTransfer, error) {
if n, err := r.Read(b.Asset.Address[:]); err != nil || n != 32 {
return nil, fmt.Errorf("failed to read asset address: %w", err)
}
if err := binary.Read(r, binary.BigEndian, &b.Asset.Decimals); err != nil {
return nil, fmt.Errorf("failed to read asset decimals: %w", err)
}
var amountBytes [32]byte
if n, err := r.Read(amountBytes[:]); err != nil || n != 32 {
@ -348,6 +351,7 @@ func (v *BodyTransfer) serialize() ([]byte, error) {
}
MustWrite(buf, binary.BigEndian, v.Asset.Chain)
buf.Write(v.Asset.Address[:])
MustWrite(buf, binary.BigEndian, v.Asset.Decimals)
if v.Amount == nil {
return nil, fmt.Errorf("amount is empty")

View File

@ -125,6 +125,7 @@ followed by:
| 7 | token_program | SplToken | | | | |
| 8 | token | WrappedAsset | | | | ✅ |
| 9 | destination | TokenAccount | | ✅ | | |
| 10 | wrapped_meta | WrappedMeta | | ✅ | opt | ✅ |
##### Transfer: Ethereum (wrapped) -> Solana (native)

View File

@ -6,6 +6,7 @@
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
@ -40,6 +41,7 @@ contract Wormhole is ReentrancyGuard {
event LogTokensLocked(
uint8 target_chain,
uint8 token_chain,
uint8 token_decimals,
bytes32 indexed token,
bytes32 indexed sender,
bytes32 recipient,
@ -56,7 +58,7 @@ contract Wormhole is ReentrancyGuard {
uint32 public guardian_set_expirity;
// Mapping of already consumedVAAs
mapping(bytes32 => bool) consumedVAAs;
mapping(bytes32 => bool) public consumedVAAs;
// Mapping of wrapped asset ERC20 contracts
mapping(bytes32 => address) public wrappedAssets;
@ -88,7 +90,7 @@ contract Wormhole is ReentrancyGuard {
uint offset = 6 + 66 * len_signers;
// Load 4 bytes timestamp
uint32 timestamp = vaa.toUint32(offset);
//uint32 timestamp = vaa.toUint32(offset);
// Hash the body
bytes32 hash = keccak256(vaa.slice(offset, vaa.length - offset));
@ -155,7 +157,7 @@ contract Wormhole is ReentrancyGuard {
uint8 token_chain = data.toUint8(70);
//bytes32 token_address = data.toBytes32(71);
uint256 amount = data.toUint256(103);
uint256 amount = data.toUint256(104);
require(source_chain != target_chain, "same chain transfers are not supported");
require(target_chain == CHAIN_ID, "transfer must be incoming");
@ -168,7 +170,8 @@ contract Wormhole is ReentrancyGuard {
// if no: create and mint
address wrapped_asset = wrappedAssets[asset_id];
if (wrapped_asset == address(0)) {
wrapped_asset = deployWrappedAsset(asset_id, token_chain, token_address);
uint8 asset_decimals = data.toUint8(103);
wrapped_asset = deployWrappedAsset(asset_id, token_chain, token_address, asset_decimals);
}
WrappedAsset(wrapped_asset).mint(target_address, amount);
@ -179,7 +182,7 @@ contract Wormhole is ReentrancyGuard {
}
}
function deployWrappedAsset(bytes32 seed, uint8 token_chain, bytes32 token_address) private returns (address asset){
function deployWrappedAsset(bytes32 seed, uint8 token_chain, bytes32 token_address, uint8 decimals) private returns (address asset){
// Taken from https://github.com/OpenZeppelin/openzeppelin-sdk/blob/master/packages/lib/contracts/upgradeability/ProxyFactory.sol
// Licensed under MIT
bytes20 targetBytes = bytes20(wrappedAssetMaster);
@ -192,7 +195,7 @@ contract Wormhole is ReentrancyGuard {
}
// Call initializer
WrappedAsset(asset).initialize(token_chain, token_address);
WrappedAsset(asset).initialize(token_chain, token_address, decimals);
// Store address
wrappedAssets[seed] = asset;
@ -225,7 +228,7 @@ contract Wormhole is ReentrancyGuard {
asset_address = bytes32(uint256(asset));
}
emit LogTokensLocked(target_chain, asset_chain, asset_address, bytes32(uint256(msg.sender)), recipient, amount, nonce);
emit LogTokensLocked(target_chain, asset_chain, ERC20(asset).decimals(), asset_address, bytes32(uint256(msg.sender)), recipient, amount, nonce);
}
function lockETH(
@ -239,7 +242,7 @@ contract Wormhole is ReentrancyGuard {
WETH(WETHAddress).deposit{value : msg.value}();
// Log deposit of WETH
emit LogTokensLocked(target_chain, CHAIN_ID, bytes32(uint256(WETHAddress)), bytes32(uint256(msg.sender)), recipient, msg.value, nonce);
emit LogTokensLocked(target_chain, CHAIN_ID, 18, bytes32(uint256(WETHAddress)), bytes32(uint256(msg.sender)), recipient, msg.value, nonce);
}

View File

@ -13,7 +13,7 @@ contract WrappedAsset is IERC20, Context {
bool public initialized;
address public bridge;
function initialize(uint8 _assetChain, bytes32 _assetAddress) public {
function initialize(uint8 _assetChain, bytes32 _assetAddress, uint8 decimals) public {
require(!initialized, "already initialized");
// Set local fields
assetChain = _assetChain;
@ -23,7 +23,7 @@ contract WrappedAsset is IERC20, Context {
_name = "Wormhole Wrapped";
_symbol = "WWT";
_decimals = 18;
_decimals = decimals;
}
function mint(address account, uint256 amount) external {

View File

@ -58,12 +58,11 @@ contract("Wormhole", function () {
let bridge = await Wormhole.deployed();
// User locked an asset on the foreign chain and the VAA proving this is transferred in.
await bridge.submitVAA("0x01000000000100eecb367540286d326e333ea06542b82d3feaeb0dc33b1b14bda8cdf8287da2a630a9a6692112bcedee501c0947c607081d51426fa1982ef342c07f4502b584c801000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e99880000000000000000000000000000000000000000000000000de0b6b3a7640000")
await bridge.submitVAA("0x01000000000100454e7de661cd4386b1ce598a505825f8ed66fbc6a608393bae6257fef7370da27a2068240a902470bed6c0b1fa23d38e5d5958e2a422d59a0217fbe155638ed600000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
// Expect user to have a balance of a new wrapped asset
// submitVAA has automatically created a new WrappedAsset for the foreign asset that has been transferred in.
// We know the address because deterministic network. A user would see the address in the submitVAA tx log.
let wa = new WrappedAsset("0x3c63250aFA2470359482d98749f2d60D2971c818");
let wa = new WrappedAsset("0xC3697aaf5B3D354214548248710414812099bc93");
assert.equal(await wa.assetChain(), 1)
// Remote asset's contract address.
assert.equal(await wa.assetAddress(), "0x0000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988")
@ -75,7 +74,7 @@ contract("Wormhole", function () {
it("should not accept the same VAA twice", async function () {
let bridge = await Wormhole.deployed();
try {
await bridge.submitVAA("0x01000000000100eecb367540286d326e333ea06542b82d3feaeb0dc33b1b14bda8cdf8287da2a630a9a6692112bcedee501c0947c607081d51426fa1982ef342c07f4502b584c801000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e99880000000000000000000000000000000000000000000000000de0b6b3a7640000");
await bridge.submitVAA("0x01000000000100454e7de661cd4386b1ce598a505825f8ed66fbc6a608393bae6257fef7370da27a2068240a902470bed6c0b1fa23d38e5d5958e2a422d59a0217fbe155638ed600000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000");
} catch (e) {
assert.equal(e.reason, "VAA was already executed")
return
@ -86,7 +85,7 @@ contract("Wormhole", function () {
it("should burn tokens on lock", async function () {
let bridge = await Wormhole.deployed();
// Expect user to have a balance
let wa = new WrappedAsset("0x3c63250aFA2470359482d98749f2d60D2971c818")
let wa = new WrappedAsset("0xC3697aaf5B3D354214548248710414812099bc93")
await bridge.lockAssets(wa.address, "500000000000000000", "0x0", 2, 2);
let balance = await wa.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1");
@ -113,7 +112,7 @@ contract("Wormhole", function () {
// Transfer of that token out of the contract should not work
let threw = false;
try {
await bridge.submitVAA("0x01000000000100e2d8610a6cf10587ba2e4f43dc639eeacd3fb4297338955b00b7653094278082505de82418b9e0925d9dd889d0850252aa7c613e63c8b2c27ff22c0001c6336600000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c102000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb0000000000000000000000000000000000000000000000000de0b6b3a7640000");
await bridge.submitVAA("0x01000000000100078f0fe9406808b1e5003867ab74aa2085153b7735b329640d275ea943dd115d00e356c6d343142d9190872c11d2de898d075cea7f4e85ff2188af299e26a14200000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c102000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000");
} catch (e) {
threw = true;
}
@ -137,7 +136,7 @@ contract("Wormhole", function () {
assert.equal(await token.balanceOf(bridge.address), "1000000000000000000");
// Transfer this token back
await bridge.submitVAA("0x01000000000100e2d8610a6cf10587ba2e4f43dc639eeacd3fb4297338955b00b7653094278082505de82418b9e0925d9dd889d0850252aa7c613e63c8b2c27ff22c0001c6336600000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c102000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb0000000000000000000000000000000000000000000000000de0b6b3a7640000");
await bridge.submitVAA("0x01000000000100078f0fe9406808b1e5003867ab74aa2085153b7735b329640d275ea943dd115d00e356c6d343142d9190872c11d2de898d075cea7f4e85ff2188af299e26a14200000007d010000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c102000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000");
assert.equal(await token.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"), "1000000000000000000");
assert.equal(await token.balanceOf(bridge.address), "0");
});
@ -173,14 +172,14 @@ contract("Wormhole", function () {
let bridge = await Wormhole.deployed();
// Test VAA from guardian set 0; timestamp 1000
await bridge.submitVAA("0x0100000000010000b61ecc7b9de12de6fc7f01d8a89f8c2911329e44198d0a47768344c69eadd510fd5ab6474a24aa11a6751465fb4e2f8c81a4dbc2fc2427b4c5a981e8e63ed900000003e810000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e99880000000000000000000000000000000000000000000000000de0b6b3a7640000")
await bridge.submitVAA("0x0100000000010034890d1c1aa2455d083602996d924ca9ba2fd9641dcdaa3b0811c9ed37e831a8433b40b0f0779fa16be2daaf53ede378530a135b68ac95814c9d25023a29580e01000003e810000000380102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
await advanceTimeAndBlock(1000);
// Test VAA from guardian set 0; timestamp 2000 - should not work anymore
let threw = false;
try {
await bridge.submitVAA("0x01000000000100b7e82826980fb9f2389a6e22f7db12d5872e7900775c2c5d6ad1c3558ee7a1314f0f7f171cad73caaac0599c8914009ea9d0ef0e416404b141f844b85a5d254701000007d010000000380102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e99880000000000000000000000000000000000000000000000000de0b6b3a7640000")
await bridge.submitVAA("0x010000000001005a55b73ff79bc3cc39bec075ae28ae8351eee1428a7701f0d47fec5736bcfd9e158b49e6282678c425aed8185233ea4ef033af33bd450a77a46ddbadf3ea09ba00000007d010000000380102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
} catch (e) {
threw = true;
assert.equal(e.reason, "guardian set has expired")
@ -188,7 +187,7 @@ contract("Wormhole", function () {
assert.isTrue(threw, "guardian set did not expire")
// Test same transaction with guardian set 1; timestamp 2000
await bridge.submitVAA("0x01000000010100a3f58fb72b3c7e242d6934718eafb3076cb0764e65d8df3e0746b0c72cca791027ac649fa0095a1c3537611f4adc0dc90aaa01fce31fac722eae898cfb06e96d01000007d010000000380102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e99880000000000000000000000000000000000000000000000000de0b6b3a7640000")
await bridge.submitVAA("0x01000000010100958a39752b14ab62a3dcdb37a8642c4ca1085c6ac77205a462ee5bb3650c92407675729615f69255fc150835621e96c917e68929efb975db9647636543c710f200000007d010000000380102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
});
it("mismatching guardian set and signature should not work", async function () {
@ -197,7 +196,7 @@ contract("Wormhole", function () {
// Test VAA signed by guardian set 0 but set guardian set index to 1
let threw = false;
try {
await bridge.submitVAA("0x010000000101006f84df72f3f935543e9bda60d92f77e2e2c073655311f3fc00518bbe7e054ff87e5e6e3c9df9e5bd756ee033253d4513ddebf03ff844fdc0f48f7dcc1b3fd6e10000000fa01087000000370102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000009561c133dd8580860b6b7e504bc5aa500f0f06a70000000000000000000000000000000000000000000000000de0b6b3a7640000")
await bridge.submitVAA("0x01000000010100724a1d2cda45da3cf38f9e0eaef01742210f4deabf9b9d4b20127f6a200a94805928e26ae5f5ab8c3e1cb6d5231d4c48aacae0841513fbd3d9d430be7145db8200000007d010000000390102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
} catch (e) {
threw = true;
assert.equal(e.reason, "VAA signature invalid")
@ -214,7 +213,7 @@ contract("Wormhole", function () {
// Test VAA signed by only 3 signers
let threw = false;
try {
await bridge.submitVAA("0x010000000203001dbccdb06c91929042b20a136d226890e22b07120d2854aa5c17bc1cce934cf66e2f5e31a3d883bc928346c35352a7627fb0aa7e420b73a89dc0c205780f98bc0001eadd27047cb0988ed4a7c681af758e88c628f2a3c424186044e3fd9ad8c3425f401bfc29674db720f62f08a251ff6aa3b982adb57186422cdad03cc4bfc07bb001020193d92acf2ecadad96273f122ada995700225c18d65db636db7f52e2c77906e3e0153a163c4d123b68f78cc1a8c5dbd4bdf1a26718cfc850c8278ec4a39bb470100000fa010000000390102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e99880000000000000000000000000000000000000000000000000de0b6b3a7640000")
await bridge.submitVAA("0x01000000020300e94bec8a17bd313522cdfea30cec5406a41a4cc4b6ec416a633ebe3aca070ae448e370e0a2e7c67fed04a2b825f56cf226c76e6ecd2e71865642393bf729dad80101ccf89506bef58d8cb12baabd60e3304cfb90ef0ef0657caba9c37ffa0d34a54c3aacd1a475ef4c72f24e8d9ce1e2de51e580ce85b18356436b6cda9e2ae9abc0010285f0d3c0d1cd421ce0ae515db1ac3b623c17d4702564971932fb9925c0506fc76e43a7283c712ee680cf058c3c447653c352ca9827b1780e1fc88a61540092d90100000fa010000000390102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
} catch (e) {
threw = true;
assert.equal(e.reason, "no quorum")
@ -222,6 +221,6 @@ contract("Wormhole", function () {
assert.isTrue(threw, "accepted only 3 signatures")
// Test VAA signed by 5 signers (all except i=3)
await bridge.submitVAA("0x010000000205001dbccdb06c91929042b20a136d226890e22b07120d2854aa5c17bc1cce934cf66e2f5e31a3d883bc928346c35352a7627fb0aa7e420b73a89dc0c205780f98bc0001eadd27047cb0988ed4a7c681af758e88c628f2a3c424186044e3fd9ad8c3425f401bfc29674db720f62f08a251ff6aa3b982adb57186422cdad03cc4bfc07bb0010393f4821a0fc8248ad8eccfb6e1b6a1fb70d0294a6a2b53cb6e222205f3d9f960491fdda4e23e2dde46b084f4ac101050deecbe871eeec218217037d7974b41a301049571b8d3fbcebad1e868331570120a27cf122d33f3d5b95355fde3712ecdbd5233888ec51e5d9e960beaa9a0697f5ac69f9deae37782b874fbe8aecf064087e00105ddd37a55e2a654f5898b1863eaf8efa464797bfa602893d0bcbcc06269df6a3b4ba88c01f3ad22d23a02c8dc1cb34d28b6eb4dd3e2030b8b42ff6909537faf430000000fa010000000390102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e99880000000000000000000000000000000000000000000000000de0b6b3a7640000")
await bridge.submitVAA("0x01000000020500e94bec8a17bd313522cdfea30cec5406a41a4cc4b6ec416a633ebe3aca070ae448e370e0a2e7c67fed04a2b825f56cf226c76e6ecd2e71865642393bf729dad80101ccf89506bef58d8cb12baabd60e3304cfb90ef0ef0657caba9c37ffa0d34a54c3aacd1a475ef4c72f24e8d9ce1e2de51e580ce85b18356436b6cda9e2ae9abc001033e9b4ff5fb545e964e907349e3dab0057c408c832bb31fb76fae7f81c3e488ea4897ce14db61c46d1169bd64b449498b1a18dee4de0ef2038b1c7e3a4a0239a0010432eac9532a4c0ce279d6a3018a5ea0d74402eb6969df5d444f20e0cca66d3b4c53e41cb18648f64af100c7410692e83fa16e5696b1f5f0d517653b003e22689800055859330bd1fee76d99728803fa26d739e494e1a232f5658150c2a2c97e1c9722793bdd83bd7cbb4a39b587b45093ee76187c72dfd68d64b7c0abc32bfef5d55c0000000fa010000000390102020105000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000")
});
});

View File

@ -46,7 +46,8 @@ message LockupEventNew {
bytes targetAddress = 5;
uint32 tokenChain = 6;
bytes tokenAddress = 7;
bytes amount = 8;
uint32 tokenDecimals = 8;
bytes amount = 9;
}
// A VAA was posted to Solana for data availability.
@ -58,7 +59,8 @@ message LockupEventVAAPosted {
bytes targetAddress = 5;
uint32 tokenChain = 6;
bytes tokenAddress = 7;
bytes amount = 8;
uint32 tokenDecimals = 8;
bytes amount = 9;
bytes vaa = 9;
bytes vaa = 10;
}

View File

@ -183,6 +183,7 @@ impl Agent for AgentImpl {
target_address: b.foreign_address.to_vec(),
token_chain: b.asset.chain as u32,
token_address: b.asset.address.to_vec(),
token_decimals: b.asset.decimals as u32,
amount: amount_b.to_vec(),
})),
}
@ -200,6 +201,7 @@ impl Agent for AgentImpl {
target_address: b.foreign_address.to_vec(),
token_chain: b.asset.chain as u32,
token_address: b.asset.address.to_vec(),
token_decimals: b.asset.decimals as u32,
amount: amount_b.to_vec(),
vaa: b.vaa.to_vec(),
})),

View File

@ -14,7 +14,7 @@ use solana_sdk::{
use crate::error::Error;
use crate::error::Error::VAATooLong;
use crate::instruction::BridgeInstruction::{CreateWrapped, Initialize, PostVAA, TransferOut};
use crate::instruction::BridgeInstruction::{Initialize, PostVAA, TransferOut};
use crate::state::{AssetMeta, Bridge, BridgeConfig};
use crate::vaa::{VAABody, VAA};
@ -123,9 +123,6 @@ pub enum BridgeInstruction {
/// Deletes a `ExecutedVAA` after the `VAA_EXPIRATION_TIME` is over to free up space on chain.
/// This returns the rent to the sender.
EvictClaimedVAA(),
/// Creates a new wrapped asset
CreateWrapped(AssetMeta),
}
impl BridgeInstruction {
@ -156,11 +153,6 @@ impl BridgeInstruction {
let payload: VAAData = input[1..].to_vec();
PostVAA(payload)
}
5 => {
let payload: &AssetMeta = unpack(input)?;
CreateWrapped(*payload)
}
_ => return Err(ProgramError::InvalidInstructionData),
})
}
@ -209,13 +201,6 @@ impl BridgeInstruction {
Self::EvictClaimedVAA() => {
output[0] = 4;
}
Self::CreateWrapped(meta) => {
output[0] = 5;
#[allow(clippy::cast_ptr_alignment)]
let value =
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut AssetMeta) };
*value = meta;
}
}
Ok(output)
}
@ -261,38 +246,6 @@ pub fn initialize(
})
}
/// Creates a 'CreateWrapped' instruction.
#[cfg(not(target_arch = "bpf"))]
pub fn create_wrapped(
program_id: &Pubkey,
payer: &Pubkey,
meta: AssetMeta,
) -> Result<Instruction, ProgramError> {
let data = BridgeInstruction::CreateWrapped(meta).serialize()?;
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let wrapped_mint_key =
Bridge::derive_wrapped_asset_id(program_id, &bridge_key, meta.chain, meta.address)?;
let wrapped_meta_key =
Bridge::derive_wrapped_meta_id(program_id, &bridge_key, &wrapped_mint_key)?;
let accounts = vec![
AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(*payer, true),
AccountMeta::new(wrapped_mint_key, false),
AccountMeta::new(wrapped_meta_key, false),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates an 'TransferOut' instruction.
#[cfg(not(target_arch = "bpf"))]
pub fn transfer_out(
@ -389,7 +342,6 @@ pub fn post_vaa(
)?;
accounts.push(AccountMeta::new(transfer_key, false))
} else if t.asset.chain == CHAIN_ID_SOLANA {
println!("kat");
// Foreign (wrapped) -> Solana (native)
let mint_key = Pubkey::new(&t.asset.address);
let custody_key = Bridge::derive_custody_id(program_id, &bridge_key, &mint_key)?;
@ -398,7 +350,6 @@ pub fn post_vaa(
accounts.push(AccountMeta::new(Pubkey::new(&t.target_address), false));
accounts.push(AccountMeta::new(custody_key, false));
} else {
println!("kit");
// Foreign (native) -> Solana (wrapped)
let wrapped_key = Bridge::derive_wrapped_asset_id(
program_id,

View File

@ -61,10 +61,6 @@ impl Bridge {
Self::process_vaa(program_id, accounts, vaa_body, &vaa, &hash)
}
CreateWrapped(meta) => {
info!("Instruction: CreateWrapped");
Self::process_create_wrapped(program_id, accounts, &meta)
}
_ => panic!(""),
}
}
@ -158,6 +154,7 @@ impl Bridge {
let sender = Bridge::token_account_deserialize(sender_account_info)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?;
let mint = Bridge::mint_deserialize(mint_info)?;
// Does the token belong to the mint
if sender.mint != *mint_info.key {
@ -196,7 +193,7 @@ impl Bridge {
// Load transfer account
let mut transfer_data = transfer_info.data.borrow_mut();
let mut transfer: &mut TransferOutProposal = Self::unpack(&mut transfer_data)?;
let mut transfer: &mut TransferOutProposal = Self::unpack_unchecked(&mut transfer_data)?;
// Burn tokens
Bridge::wrapped_burn(
@ -214,61 +211,13 @@ impl Bridge {
transfer.foreign_address = t.target;
transfer.amount = t.amount;
transfer.to_chain_id = t.chain_id;
transfer.asset = t.asset;
Ok(())
}
/// Creates a new wrapped asset
pub fn process_create_wrapped(
program_id: &Pubkey,
accounts: &[AccountInfo],
a: &AssetMeta,
) -> ProgramResult {
info!("create wrapped");
let account_info_iter = &mut accounts.iter();
next_account_info(account_info_iter)?; // Bridge program
next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Token program
let bridge_info = next_account_info(account_info_iter)?;
let payer_info = next_account_info(account_info_iter)?;
let mint_info = next_account_info(account_info_iter)?;
let wrapped_meta_info = next_account_info(account_info_iter)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?;
if a.chain == CHAIN_ID_SOLANA {
return Err(Error::CannotWrapNative.into());
}
// Create wrapped mint
Self::create_wrapped_mint(
program_id,
accounts,
&bridge.config.token_program,
mint_info.key,
bridge_info.key,
payer_info.key,
&a,
)?;
// Check and create wrapped asset meta to allow reverse resolution of info
let wrapped_meta_seeds = Bridge::derive_wrapped_meta_seeds(bridge_info.key, mint_info.key);
Bridge::check_and_create_account::<WrappedAssetMeta>(
program_id,
accounts,
wrapped_meta_info.key,
payer_info.key,
program_id,
&wrapped_meta_seeds,
)?;
let mut wrapped_meta_data = wrapped_meta_info.data.borrow_mut();
let wrapped_meta: &mut WrappedAssetMeta = Bridge::unpack_unchecked(&mut wrapped_meta_data)?;
wrapped_meta.is_initialized = true;
wrapped_meta.address = a.address;
wrapped_meta.chain = a.chain;
// Make sure decimals are correct
transfer.asset = AssetMeta {
chain: t.asset.chain, // Chain and address cannot be spoofed because the account is derived from it
address: t.asset.address,
decimals: mint.decimals, // We use the info from mint because it can be spoofed
};
Ok(())
}
@ -292,6 +241,7 @@ impl Bridge {
let custody_info = next_account_info(account_info_iter)?;
let sender = Bridge::token_account_deserialize(sender_account_info)?;
let mint = Bridge::mint_deserialize(mint_info)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?;
// Does the token belong to the mint
@ -374,6 +324,7 @@ impl Bridge {
transfer.asset = AssetMeta {
chain: CHAIN_ID_SOLANA,
address: mint_info.key.to_bytes(),
decimals: mint.decimals,
};
Ok(())
@ -589,15 +540,52 @@ impl Bridge {
b.amount,
)?;
} else {
// Foreign chain asset, mint wrapped asset
let expected_mint_address = Bridge::derive_wrapped_asset_id(
program_id,
bridge_info.key,
b.asset.chain,
b.asset.address,
)?;
if expected_mint_address != *mint_info.key {
return Err(Error::InvalidDerivedAccount.into());
// Create wrapped asset if it does not exist
if mint_info.data_is_empty() {
let meta_info = next_account_info(account_info_iter)?;
// Foreign chain asset, mint wrapped asset
let expected_mint_address = Bridge::derive_wrapped_asset_id(
program_id,
bridge_info.key,
b.asset.chain,
b.asset.address,
)?;
if expected_mint_address != *mint_info.key {
return Err(Error::InvalidDerivedAccount.into());
}
// Create wrapped mint
Self::create_wrapped_mint(
program_id,
accounts,
&bridge.config.token_program,
mint_info.key,
bridge_info.key,
payer_info.key,
&b.asset,
b.asset.decimals,
)?;
// Check and create wrapped asset meta to allow reverse resolution of info
let wrapped_meta_seeds =
Bridge::derive_wrapped_meta_seeds(bridge_info.key, mint_info.key);
Bridge::check_and_create_account::<WrappedAssetMeta>(
program_id,
accounts,
meta_info.key,
payer_info.key,
program_id,
&wrapped_meta_seeds,
)?;
let mut wrapped_meta_data = meta_info.data.borrow_mut();
let wrapped_meta: &mut WrappedAssetMeta =
Bridge::unpack_unchecked(&mut wrapped_meta_data)?;
wrapped_meta.is_initialized = true;
wrapped_meta.address = b.asset.address;
wrapped_meta.chain = b.asset.chain;
}
// This automatically asserts that the mint was created by this account by using
@ -624,6 +612,7 @@ impl Bridge {
b: &BodyTransfer,
vaa_data: VAAData,
) -> ProgramResult {
info!("posting VAA");
let proposal_info = next_account_info(account_info_iter)?;
// Check whether the proposal was derived correctly
@ -784,6 +773,7 @@ impl Bridge {
bridge: &Pubkey,
payer: &Pubkey,
asset: &AssetMeta,
decimals: u8,
) -> Result<(), ProgramError> {
Self::check_and_create_account::<Mint>(
program_id,
@ -799,7 +789,7 @@ impl Bridge {
None,
Some(&Self::derive_bridge_id(program_id)?),
0,
18,
decimals,
)?;
invoke_signed(&ix, accounts, &[])
}

View File

@ -136,6 +136,9 @@ pub struct AssetMeta {
/// Chain of the token
pub chain: u8,
/// Number of decimals of the token
pub decimals: u8,
}
/// Config for a bridge.

View File

@ -262,6 +262,7 @@ impl BodyTransfer {
let token_chain = data.read_u8()?;
let mut token_address: ForeignAddress = ForeignAddress::default();
data.read_exact(&mut token_address)?;
let token_decimals = data.read_u8()?;
let mut am_data: [u8; 32] = [0; 32];
data.read_exact(&mut am_data)?;
@ -276,6 +277,7 @@ impl BodyTransfer {
asset: AssetMeta {
address: token_address,
chain: token_chain,
decimals: token_decimals,
},
amount,
})
@ -290,6 +292,7 @@ impl BodyTransfer {
v.write(&self.target_address)?;
v.write_u8(self.asset.chain)?;
v.write(&self.asset.address)?;
v.write_u8(self.asset.decimals)?;
let mut am_data: [u8; 32] = [0; 32];
self.amount.to_big_endian(&mut am_data);
@ -328,6 +331,7 @@ mod tests {
asset: AssetMeta {
address: [2; 32],
chain: 8,
decimals: 9,
},
amount: U256::from(3),
})),
@ -431,11 +435,12 @@ mod tests {
158, 135, 169, 32, 6, 88, 217, 196, 14, 153, 136,
],
chain: 1,
decimals: 8,
},
amount: U256::from_dec_str("5000000000000000000").unwrap(),
})),
};
let data = hex::decode("0100000000010092737a1504f3b3df8c93cb85c64a4860bb270e26026b6e37f095356a406f6af439c6b2e9775fa1c6669525f06edab033ba5d447308f4e3bdb33c0f361dc32ec3015f37000810000000350102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e99880000000000000000000000000000000000000000000000004563918244f40000").unwrap();
let data = hex::decode("0100000000010092737a1504f3b3df8c93cb85c64a4860bb270e26026b6e37f095356a406f6af439c6b2e9775fa1c6669525f06edab033ba5d447308f4e3bdb33c0f361dc32ec3015f37000810000000350102020104000000000000000000000000000000000000000000000000000000000000000000000000000000000090f8bf6a479f320ead074411a4b0e7944ea8c9c1010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000004563918244f40000").unwrap();
let parsed_vaa = VAA::deserialize(data.as_slice()).unwrap();
assert_eq!(vaa, parsed_vaa);

View File

@ -112,11 +112,13 @@ fn command_lock_tokens(
AssetMeta {
address: wrapped_meta.address,
chain: wrapped_meta.chain,
decimals: 0,
}
}
Err(e) => AssetMeta {
address: token.to_bytes(),
chain: CHAIN_ID_SOLANA,
decimals: 0,
},
};
@ -161,30 +163,6 @@ fn command_lock_tokens(
Ok(Some(transaction))
}
fn command_create_wrapped_asset(
config: &Config,
bridge: &Pubkey,
meta: AssetMeta,
) -> CommmandResult {
println!("Creating wrapped asset");
let minimum_balance_for_rent_exemption = config
.rpc_client
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
let ix = create_wrapped(bridge, &config.owner.pubkey(), meta)?;
let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey()));
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(
config,
minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()),
)?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction))
}
fn command_submit_vaa(config: &Config, bridge: &Pubkey, vaa: &[u8]) -> CommmandResult {
println!("Submitting VAA");
@ -976,40 +954,6 @@ fn main() {
.help("The vaa to be posted"),
)
)
.subcommand(
SubCommand::with_name("create-wrapped")
.about("Create new wrapped asset and token account")
.arg(
Arg::with_name("bridge")
.long("bridge")
.value_name("BRIDGE_KEY")
.validator(is_pubkey_or_keypair)
.takes_value(true)
.index(1)
.required(true)
.help(
"Specify the bridge program public key"
),
)
.arg(
Arg::with_name("chain")
.validator(is_u8)
.value_name("CHAIN")
.takes_value(true)
.index(2)
.required(true)
.help("Chain ID of the asset"),
)
.arg(
Arg::with_name("token")
.validator(is_hex)
.value_name("TOKEN_ADDRESS")
.takes_value(true)
.index(3)
.required(true)
.help("Token address of the asset"),
)
)
.subcommand(
SubCommand::with_name("wrapped-address")
.about("Derive wrapped asset address")
@ -1170,24 +1114,6 @@ fn main() {
let vaa = hex::decode(vaa_string).unwrap();
command_submit_vaa(&config, &bridge, vaa.as_slice())
}
("create-wrapped", Some(arg_matches)) => {
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
let chain = value_t_or_exit!(arg_matches, "chain", u8);
let addr_string: String = value_of(arg_matches, "token").unwrap();
let addr_data = hex::decode(addr_string).unwrap();
let mut token_addr = [0u8; 32];
token_addr.copy_from_slice(addr_data.as_slice());
command_create_wrapped_asset(
&config,
&bridge,
AssetMeta {
chain,
address: token_addr,
},
)
}
("wrapped-address", Some(arg_matches)) => {
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
let chain = value_t_or_exit!(arg_matches, "chain", u8);

View File

@ -734,3 +734,23 @@ Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
echo "${genesis_args[@]}" > spl-genesis-args.sh
echo
Index: account-decoder/src/parse_token.rs
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- account-decoder/src/parse_token.rs (revision 97144cdb8e9cb4d83943b0b5898d08f57844a4dd)
+++ account-decoder/src/parse_token.rs (date 1598606089966)
@@ -30,11 +30,7 @@
if data.len() == size_of::<Account>() {
let account: Account = *unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
- let decimals = mint_decimals.ok_or_else(|| {
- ParseAccountError::AdditionalDataMissing(
- "no mint_decimals provided to parse spl-token account".to_string(),
- )
- })?;
+ let decimals = mint_decimals.or(Some(0)).unwrap();
Ok(TokenAccountType::Account(UiTokenAccount {
mint: account.mint.to_string(),
owner: account.owner.to_string(),

File diff suppressed because one or more lines are too long

8
web/package-lock.json generated
View File

@ -2006,6 +2006,14 @@
"@types/node": "*"
}
},
"@types/bs58": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz",
"integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==",
"requires": {
"base-x": "^3.0.6"
}
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",

View File

@ -14,6 +14,7 @@
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-router-dom": "^5.1.5",
"@types/bs58": "^4.0.1",
"antd": "^4.4.1",
"buffer": "^5.6.0",
"buffer-layout": "^1.2.0",
@ -23,7 +24,8 @@
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"typescript": "~3.7.2",
"web3": "^1.2.9"
"web3": "^1.2.9",
"bs58": "^4.0.1"
},
"devDependencies": {
"npm": "^6.14.6",

View File

@ -3,6 +3,9 @@ import {BalanceInfo, SolanaTokenContext} from "../providers/SolanaTokenContext";
import {Table} from "antd";
import {CHAIN_ID_SOLANA} from "../utils/bridge";
import {BigNumber} from "ethers/utils";
import {PublicKey} from "@solana/web3.js";
import {deriveERC20Address} from "../utils/helpers";
function SplBalances() {
let t = useContext(SolanaTokenContext);
@ -27,7 +30,7 @@ function SplBalances() {
title: 'Wrapped',
key: 'wrapped',
render: (n: any, v: BalanceInfo) => {
return v.assetMeta.chain != CHAIN_ID_SOLANA ? `Wrapped (${v.assetMeta.chain} - 0x${v.assetMeta.address.slice(12).toString("hex")})` : "Native"
return v.assetMeta.chain != CHAIN_ID_SOLANA ? `Wrapped (${v.assetMeta.chain == 2 ? "ETH" : "SOL"} - 0x${v.assetMeta.address.slice(12).toString("hex")})` : `Native (0x${deriveERC20Address(new PublicKey(v.mint))})`
}
},
];

View File

@ -0,0 +1,82 @@
import React, {useContext, useEffect, useState} from "react"
import {SolanaTokenContext} from "../providers/SolanaTokenContext";
import {Table} from "antd";
import {Lockup} from "../utils/bridge";
import {BridgeContext} from "../providers/BridgeContext";
import {SlotContext} from "../providers/SlotContext";
import {ethers} from "ethers";
import {WormholeFactory} from "../contracts/WormholeFactory";
import {BRIDGE_ADDRESS} from "../config";
import {keccak256} from "ethers/utils";
// @ts-ignore
const provider = new ethers.providers.Web3Provider(window.ethereum);
function TransferProposals() {
let s = useContext(SlotContext);
let t = useContext(SolanaTokenContext);
let tokens = useContext(SolanaTokenContext);
let b = useContext(BridgeContext);
let [lockups, setLockups] = useState<Lockup[]>([])
useEffect(() => {
if (s % 10 !== 0) return;
let updateLockups = async () => {
let lockups = [];
for (let account of tokens.balances) {
let accLockups = await b.fetchTransferProposals(account.account)
lockups.push(...accLockups)
}
let wormhole = WormholeFactory.connect(BRIDGE_ADDRESS, provider);
for (let lockup of lockups) {
console.log(lockup)
if (lockup.vaaTime === undefined || lockup.vaaTime === 0) continue;
let signingData = lockup.vaa.slice(lockup.vaa[5] * 66 + 6)
let hash = keccak256(signingData)
let status = await wormhole.consumedVAAs(hash)
lockup.initialized = status;
}
setLockups(lockups);
}
updateLockups()
}, [s])
const columns = [
{
title: 'SourceAccount',
key: 'source',
render: (n: any, v: Lockup) => v.sourceAddress.toString()
},
{
title: 'Mint',
key: 'assetAddress',
render: (n: any, v: Lockup) => v.assetAddress.toString()
},
{
title: 'Amount',
key: 'amount',
render: (n: any, v: Lockup) => v.amount.toString()
},
{
title: 'Status',
key: 'status',
render: (n: any, v: Lockup) => {
return (<>Pending {v.initialized}</>)
}
},
];
return (<>
<h3>Pending transfers</h3>
<Table dataSource={lockups} columns={columns} pagination={false} scroll={{y: 400}}/>
</>
)
}
export default TransferProposals

View File

@ -1,6 +1,8 @@
import {PublicKey} from "@solana/web3.js";
const BRIDGE_ADDRESS = "0x5b1869D9A4C187F2EAa108f3062412ecf0526b24";
const WRAPPED_MASTER = "e78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab"
const SOLANA_BRIDGE_PROGRAM = new PublicKey("Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o");
const TOKEN_PROGRAM = new PublicKey("TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o");
@ -9,5 +11,6 @@ const TOKEN_PROGRAM = new PublicKey("TokenSVp5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
export {
BRIDGE_ADDRESS,
TOKEN_PROGRAM,
WRAPPED_MASTER,
SOLANA_BRIDGE_PROGRAM
}

View File

@ -12,6 +12,10 @@ import {
interface WormholeInterface extends Interface {
functions: {
consumedVAAs: TypedFunctionDescription<{ encode([]: [Arrayish]): string }>;
guardian_set_expirity: TypedFunctionDescription<{ encode([]: []): string }>;
guardian_set_index: TypedFunctionDescription<{ encode([]: []): string }>;
guardian_sets: TypedFunctionDescription<{
@ -20,8 +24,6 @@ interface WormholeInterface extends Interface {
isWrappedAsset: TypedFunctionDescription<{ encode([]: [string]): string }>;
vaa_expiry: TypedFunctionDescription<{ encode([]: []): string }>;
wrappedAssetMaster: TypedFunctionDescription<{ encode([]: []): string }>;
wrappedAssets: TypedFunctionDescription<{ encode([]: [Arrayish]): string }>;
@ -63,12 +65,14 @@ interface WormholeInterface extends Interface {
encodeTopics([
target_chain,
token_chain,
token_decimals,
token,
sender,
recipient,
amount,
nonce
]: [
null,
null,
null,
Arrayish | null,
@ -95,6 +99,22 @@ export class Wormhole extends Contract {
interface: WormholeInterface;
functions: {
consumedVAAs(
arg0: Arrayish,
overrides?: TransactionOverrides
): Promise<boolean>;
"consumedVAAs(bytes32)"(
arg0: Arrayish,
overrides?: TransactionOverrides
): Promise<boolean>;
guardian_set_expirity(overrides?: TransactionOverrides): Promise<number>;
"guardian_set_expirity()"(
overrides?: TransactionOverrides
): Promise<number>;
guardian_set_index(overrides?: TransactionOverrides): Promise<number>;
"guardian_set_index()"(overrides?: TransactionOverrides): Promise<number>;
@ -119,10 +139,6 @@ export class Wormhole extends Contract {
overrides?: TransactionOverrides
): Promise<boolean>;
vaa_expiry(overrides?: TransactionOverrides): Promise<number>;
"vaa_expiry()"(overrides?: TransactionOverrides): Promise<number>;
wrappedAssetMaster(overrides?: TransactionOverrides): Promise<string>;
"wrappedAssetMaster()"(overrides?: TransactionOverrides): Promise<string>;
@ -200,6 +216,20 @@ export class Wormhole extends Contract {
): Promise<ContractTransaction>;
};
consumedVAAs(
arg0: Arrayish,
overrides?: TransactionOverrides
): Promise<boolean>;
"consumedVAAs(bytes32)"(
arg0: Arrayish,
overrides?: TransactionOverrides
): Promise<boolean>;
guardian_set_expirity(overrides?: TransactionOverrides): Promise<number>;
"guardian_set_expirity()"(overrides?: TransactionOverrides): Promise<number>;
guardian_set_index(overrides?: TransactionOverrides): Promise<number>;
"guardian_set_index()"(overrides?: TransactionOverrides): Promise<number>;
@ -224,10 +254,6 @@ export class Wormhole extends Contract {
overrides?: TransactionOverrides
): Promise<boolean>;
vaa_expiry(overrides?: TransactionOverrides): Promise<number>;
"vaa_expiry()"(overrides?: TransactionOverrides): Promise<number>;
wrappedAssetMaster(overrides?: TransactionOverrides): Promise<string>;
"wrappedAssetMaster()"(overrides?: TransactionOverrides): Promise<string>;
@ -313,6 +339,7 @@ export class Wormhole extends Contract {
LogTokensLocked(
target_chain: null,
token_chain: null,
token_decimals: null,
token: Arrayish | null,
sender: Arrayish | null,
recipient: null,
@ -322,6 +349,22 @@ export class Wormhole extends Contract {
};
estimate: {
consumedVAAs(
arg0: Arrayish,
overrides?: TransactionOverrides
): Promise<BigNumber>;
"consumedVAAs(bytes32)"(
arg0: Arrayish,
overrides?: TransactionOverrides
): Promise<BigNumber>;
guardian_set_expirity(overrides?: TransactionOverrides): Promise<BigNumber>;
"guardian_set_expirity()"(
overrides?: TransactionOverrides
): Promise<BigNumber>;
guardian_set_index(overrides?: TransactionOverrides): Promise<BigNumber>;
"guardian_set_index()"(
@ -348,10 +391,6 @@ export class Wormhole extends Contract {
overrides?: TransactionOverrides
): Promise<BigNumber>;
vaa_expiry(overrides?: TransactionOverrides): Promise<BigNumber>;
"vaa_expiry()"(overrides?: TransactionOverrides): Promise<BigNumber>;
wrappedAssetMaster(overrides?: TransactionOverrides): Promise<BigNumber>;
"wrappedAssetMaster()"(

File diff suppressed because one or more lines are too long

View File

@ -15,6 +15,7 @@ import {AssetMeta, SolanaBridge} from "../utils/bridge";
import KeyContext from "../providers/KeyContext";
import {FormInstance} from "antd/lib/form";
import SplBalances from "../components/SplBalances";
import TransferProposals from "../components/TransferProposals";
// @ts-ignore
@ -57,11 +58,6 @@ async function approveAssets(asset: string,
async function createWrapped(c: Connection, b: SolanaBridge, key: Account, meta: AssetMeta, mint: PublicKey) {
try {
let tx = new Transaction();
let mintAccount = await c.getAccountInfo(mint);
if (!mintAccount) {
let ix = await b.createWrappedAssetInstruction(key.publicKey, meta);
tx.add(ix)
}
// @ts-ignore
let [ix_account, newSigner] = await b.createWrappedAssetAndAccountInstructions(key.publicKey, mint);
@ -279,6 +275,11 @@ function Transfer() {
<SplBalances/>
</Col>
</Row>
<Row>
<Col>
<TransferProposals/>
</Col>
</Row>
</>
);
}

View File

@ -74,7 +74,7 @@ function TransferSolana() {
{
chain: coinInfo.chainID,
address: coinInfo.wrappedAddress
}, 2);
}, Math.random()*100000);
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, fromAccount, await bridge.getConfigKey(), k.publicKey, [], transferAmount.toNumber())
let recentHash = await c.getRecentBlockhash();

View File

@ -3,5 +3,5 @@ import * as solanaWeb3 from '@solana/web3.js';
import {Account} from "@solana/web3.js";
const KeyContext = React.createContext<Account>(new Account([97,215,234,123,197,228,56,3,210,182,139,102,127,246,235,213,211,40,93,149,16,226,130,1,29,196,87,105,185,115,179,53,123,232,195,48,5,229,144,176,217,8,1,27,185,162,160,157,137,210,99,173,135,148,20,232,241,43,238,229,1,61,122,183]));
const KeyContext = React.createContext<Account>(new Account([14,173,153,4,176,224,201,111,32,237,183,185,159,247,22,161,89,84,215,209,212,137,10,92,157,49,29,192,101,164,152,70,87,65,8,174,214,157,175,126,98,90,54,24,100,177,247,77,19,112,47,44,165,109,233,102,14,86,109,29,134,145,132,141]));
export default KeyContext

View File

@ -8,6 +8,7 @@ import {TOKEN_PROGRAM} from "../config";
import {BridgeContext} from "./BridgeContext";
import {message} from "antd";
import {AssetMeta} from "../utils/bridge";
import {Buffer} from "buffer";
export interface BalanceInfo {
mint: string,

View File

@ -6,12 +6,26 @@ import assert from "assert";
import * as BufferLayout from 'buffer-layout'
import {Token} from "@solana/spl-token";
import {TOKEN_PROGRAM} from "../config";
import * as bs58 from "bs58";
export interface AssetMeta {
chain: number,
address: Buffer
}
export interface Lockup {
amount: BN,
toChain: number,
sourceAddress: PublicKey,
targetAddress: Uint8Array,
assetAddress: Uint8Array,
assetChain: number,
nonce: number,
vaa: Uint8Array,
vaaTime: number,
initialized: boolean,
}
export const CHAIN_ID_SOLANA = 1;
class SolanaBridge {
@ -25,51 +39,6 @@ class SolanaBridge {
this.connection = connection;
}
async createWrappedAssetInstruction(
payer: PublicKey,
asset: AssetMeta,
): Promise<TransactionInstruction> {
const dataLayout = BufferLayout.struct([
BufferLayout.u8('instruction'),
BufferLayout.blob(32, 'address'),
BufferLayout.u8('chain'),
]);
// @ts-ignore
let configKey = await this.getConfigKey();
let seeds: Array<Buffer> = [Buffer.from("wrapped"), configKey.toBuffer(), Buffer.from([asset.chain]),
padBuffer(asset.address, 32)];
// @ts-ignore
let wrappedKey = (await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID))[0];
// @ts-ignore
let wrappedMetaKey = (await solanaWeb3.PublicKey.findProgramAddress([Buffer.from("meta"), configKey.toBuffer(), wrappedKey.toBuffer()], this.programID))[0];
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 5, // CreateWrapped instruction
address: padBuffer(asset.address, 32),
chain: asset.chain,
},
data,
);
const keys = [
{pubkey: this.programID, isSigner: false, isWritable: false},
{pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false},
{pubkey: this.tokenProgram, isSigner: false, isWritable: false},
{pubkey: configKey, isSigner: false, isWritable: false},
{pubkey: payer, isSigner: true, isWritable: true},
{pubkey: wrappedKey, isSigner: false, isWritable: true},
{pubkey: wrappedMetaKey, isSigner: false, isWritable: true},
];
return new TransactionInstruction({
keys,
programId: this.programID,
data,
});
}
async createLockAssetInstruction(
payer: PublicKey,
tokenAccount: PublicKey,
@ -86,8 +55,9 @@ class SolanaBridge {
BufferLayout.u8('targetChain'),
BufferLayout.blob(32, 'assetAddress'),
BufferLayout.u8('assetChain'),
BufferLayout.u8('assetDecimals'),
BufferLayout.blob(32, 'targetAddress'),
BufferLayout.seq(BufferLayout.u8(), 2),
BufferLayout.seq(BufferLayout.u8(), 1),
BufferLayout.u32('nonce'),
]);
@ -111,6 +81,7 @@ class SolanaBridge {
targetChain: targetChain,
assetAddress: padBuffer(asset.address, 32),
assetChain: asset.chain,
assetDecimals: 0, // This is fetched on chain
targetAddress: padBuffer(targetAddress, 32),
nonce: nonce,
},
@ -172,6 +143,67 @@ class SolanaBridge {
}
}
// fetchAssetMeta fetches the AssetMeta for an SPL token
async fetchTransferProposals(
tokenAccount: PublicKey,
): Promise<Lockup[]> {
let accountRes = await fetch("http://localhost:8899", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"jsonrpc": "2.0",
"id": 1,
"method": "getProgramAccounts",
"params": [this.programID.toString(), {
"filters": [{"dataSize": 1152}, {
"memcmp": {
"offset": 33,
"bytes": tokenAccount.toString()
}
}]
}]
}),
})
let raw_accounts = (await accountRes.json())["result"];
const dataLayout = BufferLayout.struct([
uint256('amount'),
BufferLayout.u8('toChain'),
BufferLayout.blob(32, 'sourceAddress'),
BufferLayout.blob(32, 'targetAddress'),
BufferLayout.blob(32, 'assetAddress'),
BufferLayout.u8('assetChain'),
BufferLayout.u8('assetDecimals'),
BufferLayout.u32('nonce'),
BufferLayout.blob(1001, 'vaa'),
BufferLayout.u32('vaaTime'),
BufferLayout.u8('initialized'),
]);
let accounts: Lockup[] = [];
for (let acc of raw_accounts) {
acc = acc.account;
let parsedAccount = dataLayout.decode(bs58.decode(acc.data))
console.log(parsedAccount);
accounts.push({
amount: new BN(parsedAccount.amount, 2, "le"),
assetAddress: parsedAccount.assetAddress,
assetChain: acc.assetChain,
initialized: acc.initialized == 1,
nonce: acc.nonce,
sourceAddress: new PublicKey(parsedAccount.sourceAddress),
targetAddress: parsedAccount.targetAddress,
toChain: acc.toChain,
vaa: acc.vaa,
vaaTime: acc.vaaTime
})
}
return accounts
}
AccountLayout = BufferLayout.struct([publicKey('mint'), publicKey('owner'), uint64('amount'), BufferLayout.u32('option'), publicKey('delegate'), BufferLayout.u8('is_initialized'), BufferLayout.u8('is_native'), BufferLayout.u16('padding'), uint64('delegatedAmount')]);
async createWrappedAssetAndAccountInstructions(owner: PublicKey, mint: PublicKey): Promise<[TransactionInstruction[], solanaWeb3.Account]> {
@ -221,6 +253,10 @@ class SolanaBridge {
}
async getWrappedAssetMint(asset: AssetMeta): Promise<PublicKey> {
if (asset.chain === 1) {
return new PublicKey(asset.address)
}
let configKey = await this.getConfigKey();
let seeds: Array<Buffer> = [Buffer.from("wrapped"), configKey.toBuffer(), Buffer.of(asset.chain),
padBuffer(asset.address, 32)];

12
web/src/utils/helpers.ts Normal file
View File

@ -0,0 +1,12 @@
import {PublicKey} from "@solana/web3.js";
import {BRIDGE_ADDRESS, WRAPPED_MASTER} from "../config";
import {keccak256} from "ethers/utils";
// derive the ERC20 address of a Solana SPL asset wrapped on ETH.
export function deriveERC20Address(key: PublicKey) {
let hashData = "0xff" + BRIDGE_ADDRESS.slice(2);
hashData += keccak256(Buffer.concat([new Buffer([1]), key.toBuffer()])).slice(2) // asset_id
hashData += keccak256("0x3d602d80600a3d3981f3363d3d373d3d3d363d73" + WRAPPED_MASTER + "5af43d82803e903d91602b57fd5bf3").slice(2) // Bytecode
return keccak256(hashData).slice(26)
}